From 502e56e391db28d8cb7009490ebabdeadaf1757a Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Thu, 15 Jan 2026 19:22:16 +0100 Subject: [PATCH 01/43] Add multi-package proposal and fingerprint script --- docs/multi-module.md | 247 ++++++++++++++++++++++++++++++++++++ scripts/fingerprint-pack.js | 89 +++++++++++++ 2 files changed, 336 insertions(+) create mode 100644 docs/multi-module.md create mode 100644 scripts/fingerprint-pack.js diff --git a/docs/multi-module.md b/docs/multi-module.md new file mode 100644 index 00000000..b37bd2f2 --- /dev/null +++ b/docs/multi-module.md @@ -0,0 +1,247 @@ +# Proposal: multi-package repo with @transloadit/types, legacy transloadit, and @transloadit/node + +## Summary + +Split the current node-sdk repo into a Yarn 4 workspace monorepo that publishes: + +- `@transloadit/node` (canonical Node runtime, current source lives here) +- `transloadit` (legacy wrapper, byte-for-byte compatible with current npm package) +- `@transloadit/types` (shared types for assemblies, robots, API responses) +- `@transloadit/schemas` (JSON Schema for runtime validation, no runtime deps) +- `@transloadit/zod` (optional Zod schemas, `zod` as a dependency) + +This keeps backwards compatibility, adds a canonical types package for reuse across Convex, browser, and future SDKs, and sets the stage for a future repo rename to `typescript-sdk` with additional packages like `@transloadit/browser` and `@transloadit/deno`. + +## Why a dedicated @transloadit/types + +### 1) One source of truth for the public contract +The repo already has structured types under `src/alphalib/types` (templates, assemblies, robots, bills, etc). These are synced across internal repos and should remain the single source of truth. A types package should re-export from alphalib without copying the files, so there is one canonical contract. + +A `@transloadit/types` package would provide: + +- Strongly typed assembly instruction shapes (robot params, steps, template payloads) +- Shared response types (assembly status, templates, bills) +- Schema output for runtime validation without pulling in Node APIs + +This reduces duplication and drift between SDKs, Convex integrations, and docs examples. + +### 2) Better DX without runtime coupling +Consumers like browser SDKs (uppy) and Convex components often need types but not Node runtime code. A types-only package keeps bundles smaller and avoids Node-only dependencies (fs, streams, crypto). JSON Schema can be published alongside types without any runtime dependency. + +### 3) Future-proof for multi-platform SDKs +If we eventually publish `@transloadit/browser`, `@transloadit/deno`, or a shared `@transloadit/core`, they can all depend on `@transloadit/types` for the contract while keeping platform-specific runtime. Zod schemas can live in `@transloadit/zod` for projects that want runtime parsing with Zod. + +## Goals + +- Preserve `transloadit` package identity and output (byte-for-byte where possible). +- Make `@transloadit/node` the canonical source for the existing Node SDK runtime. +- Introduce `@transloadit/types` with stable, documented export surface. +- Publish schemas without forcing a runtime dependency choice. +- Enable future modularization without breaking existing users. +- Keep publish and versioning consistent (changesets, Yarn 4). + +## Proposed workspace layout + +``` +node-sdk/ + packages/ + node/ + package.json (name: "@transloadit/node") + src/ (current src moved here) + dist/ + transloadit/ + package.json (name: "transloadit") + src/ (thin wrapper or re-export) + dist/ + types/ + package.json (name: "@transloadit/types") + src/ + index.ts + robots/ + assemblies/ + templates/ + schemas/ + dist/ + schemas/ + package.json (name: "@transloadit/schemas") + src/ (json schema export surface) + dist/ + zod/ + package.json (name: "@transloadit/zod") + src/ (zod schema export surface) + dist/ + package.json (workspace root) + tsconfig.base.json + biome.json + .changeset/ +``` + +### Notes +- `packages/node` is the canonical source for the existing Node SDK runtime. +- `packages/transloadit` is a wrapper package that publishes the same artifacts under the legacy name. +- `packages/types` re-exports from `src/alphalib/types` so alphalib remains the single source of truth. +- `packages/schemas` exports JSON Schema built from alphalib types; no runtime dependencies. +- `packages/zod` exports Zod schemas and depends on `zod` explicitly. + +## Alphalib strategy (synced source of truth) + +`src/alphalib` is already synced across internal Transloadit repos. We should keep it as the single +source of truth for types and schemas, and avoid copying files into packages. Two viable approaches: + +1) **Keep alphalib at repo root and re-export it** + - `packages/types` uses TS path mapping or barrel re-exports that point to `src/alphalib/types`. + - `packages/schemas` and `packages/zod` import from `src/alphalib/types` or from generated schema + artifacts stored alongside alphalib. + - This keeps the sync workflow intact and avoids duplication. + +2) **Promote alphalib to a workspace package** + - Move `src/alphalib` to `packages/alphalib` and keep the sync tool aligned. + - `@transloadit/types` depends on `@transloadit/alphalib` internally. + - This is cleaner structurally but changes the sync story. + +Given the current sync mechanism, option 1 is safer and lower risk. + +## Publishing strategy + +### transloadit and @transloadit/node + +Two options that both preserve compatibility: + +1) **Source duplication with shared build inputs** + - Build `packages/node` as the canonical runtime. + - Build `packages/transloadit` from the same source using a small wrapper that re-exports the runtime, or by pointing its build output to the same compiled artifacts. + +2) **Single build output, dual packaging** + - Build once in `packages/node/dist`. + - For `transloadit`, publish a package that contains the same files as `@transloadit/node`, generated during `prepack` (copy/rsync dist + package.json transforms). + +Option 2 is stricter for byte-for-byte output parity but requires packaging logic. Option 1 is easier but can drift if any build settings differ. I recommend option 2 and automated fingerprinting (see below). + +### @transloadit/types / @transloadit/schemas / @transloadit/zod + +- `@transloadit/types`: types only (`.d.ts`), no runtime dependencies. +- `@transloadit/schemas`: JSON Schema output derived from alphalib types (no runtime deps). +- `@transloadit/zod`: Zod schemas derived from alphalib types (depends on `zod`). +- Export stable API surfaces: + +``` +@transloadit/types + /assemblies + /templates + /robots + /webhooks +@transloadit/schemas + /assemblies + /templates + /robots + /webhooks +@transloadit/zod + /assemblies + /templates + /robots + /webhooks +``` + +Zod schemas cannot be shipped without a runtime dependency; separating them avoids forcing Zod on all consumers. If we want to keep the number of packages smaller, `@transloadit/zod` can list `zod` as a peer dependency and mark it optional, but a dedicated package is clearer. + +## Compatibility verification plan (byte-for-byte) + +We can treat compatibility as a deterministic refactor target using a pre/post fingerprint. + +### Proposed fingerprint process + +1) **Baseline (current repo)** + - `npm pack` or `yarn pack` from current `transloadit` package. + - Capture: + - tarball SHA256 + - file list, sizes, file SHA256 inside tarball + - `package.json` fields that affect install (name, version, main, types, exports, files) + +2) **After refactor** + - `yarn workspace transloadit pack` + - Compute the same fingerprint. + - Assert byte-for-byte identity. + +### Suggested script + +A small Node script can be added under `scripts/fingerprint-pack.js` (uses `npm pack` + `tar`): + +- `npm pack --json` to get the tarball name +- `sha256` the tarball +- `tar -tf` + `tar -xOf` to hash each file +- output JSON artifact for comparison + +Example usage: + +```bash +node scripts/fingerprint-pack.js . +node scripts/fingerprint-pack.js packages/transloadit +``` + +This becomes a refactor gate: only proceed if fingerprints match. + +## Versioning and releases + +Use Changesets in the workspace root (similar to `~/code/monolib`): + +- `yarn changeset` for changes +- `yarn changeset version` to bump versions +- `yarn changeset publish` to publish all packages + +We can keep versions synchronized across `transloadit` and `@transloadit/node`. `@transloadit/types` can either track the same version (simpler) or diverge if needed later. + +## Suggested implementation phases + +### Phase 1: Prepare monorepo scaffolding +- Add root workspace config, changesets, base tsconfig. +- Move current package into `packages/node` with minimal changes. +- Ensure existing build/test scripts still pass. + +### Phase 2: Add @transloadit/types (re-export alphalib) +- Keep alphalib as a synced directory (single source of truth). +- In `packages/types`, re-export from `src/alphalib/types` via TS path mapping or barrel files. +- Build `.d.ts` only, no runtime JS. + +### Phase 3: Add @transloadit/schemas and @transloadit/zod +- Generate JSON Schema in `packages/schemas` from alphalib types. +- Generate Zod schemas in `packages/zod` from alphalib types (requires `zod`). + +### Phase 4: Add transloadit wrapper +- Package identical output to `@transloadit/node` (copy dist + package.json transform). +- Confirm both packages are publishable via Changesets. + +### Phase 5: Byte-for-byte compatibility validation +- Record baseline fingerprint from current `transloadit`. +- Validate refactor output is identical (except for `package.json` name where applicable). + +## Risks and mitigations + +- **Risk: Build output drift** + - Mitigation: fingerprint test gate; lock tsconfig, build tools, and emitted files. + +- **Risk: Consumers depend on side-effects or file paths** + - Mitigation: keep `transloadit` package output identical and preserve exports layout. + +- **Risk: Types package diverges from runtime API** + - Mitigation: generate or validate types against runtime tests; add type-level tests. + +## Long-term direction + +A rename from `node-sdk` to `typescript-sdk` makes sense if this becomes the home for: + +- `@transloadit/node` (Node runtime) +- `@transloadit/browser` (browser runtime) +- `@transloadit/deno` (Deno runtime) +- `@transloadit/types` (shared contract) + +All can share `@transloadit/types` for schema and type fidelity, with platform-specific transport in each runtime package. + +## Open questions + +- Should we make `@transloadit/zod` a dedicated package or make Zod a peer dep of `@transloadit/types`? I recommend a dedicated package to keep types-only consumers clean. +- Do we keep versions in lock-step across all packages or allow independent versioning? +- Should the root package publish anything, or remain private-only? + +## Recommendation + +Proceed with a workspace refactor using a fingerprint gate to keep `transloadit` byte-for-byte compatible, and introduce `@transloadit/types` as the contract layer for all SDKs. This unlocks future multi-platform SDKs while preserving existing consumers. diff --git a/scripts/fingerprint-pack.js b/scripts/fingerprint-pack.js new file mode 100644 index 00000000..24997807 --- /dev/null +++ b/scripts/fingerprint-pack.js @@ -0,0 +1,89 @@ +import { execFile } from 'node:child_process' +import { createHash } from 'node:crypto' +import { readFile } from 'node:fs/promises' +import { resolve } from 'node:path' +import { promisify } from 'node:util' + +const execFileAsync = promisify(execFile) + +const cwd = process.argv[2] ? resolve(process.argv[2]) : process.cwd() + +/** @param {Uint8Array} buffer */ +const sha256 = (buffer) => createHash('sha256').update(buffer).digest('hex') + +const pack = async () => { + const { stdout } = await execFileAsync('npm', ['pack', '--json'], { + cwd, + encoding: 'utf8', + maxBuffer: 10 * 1024 * 1024, + }) + + const parsed = JSON.parse(stdout) + if (!Array.isArray(parsed) || !parsed[0] || !parsed[0].filename) { + throw new Error('Unexpected npm pack output') + } + + return parsed[0] +} + +/** @param {string} tarballPath */ +const listTarFiles = async (tarballPath) => { + const { stdout } = await execFileAsync('tar', ['-tf', tarballPath], { + cwd, + encoding: 'utf8', + maxBuffer: 50 * 1024 * 1024, + }) + + return stdout + .split('\n') + .map((entry) => entry.trim()) + .filter(Boolean) + .filter((entry) => !entry.endsWith('/')) +} + +/** + * @param {string} tarballPath + * @param {string} entry + * @returns {Promise} + */ +const readTarFile = async (tarballPath, entry) => { + const { stdout } = await execFileAsync('tar', ['-xOf', tarballPath, entry], { + cwd, + encoding: 'buffer', + maxBuffer: 200 * 1024 * 1024, + }) + + return stdout +} + +const main = async () => { + const packInfo = await pack() + const tarballPath = resolve(cwd, packInfo.filename) + const tarballBuffer = await readFile(tarballPath) + + const files = await listTarFiles(tarballPath) + /** @type {Record} */ + const fileFingerprints = {} + + for (const entry of files) { + const contents = await readTarFile(tarballPath, entry) + const buffer = Buffer.isBuffer(contents) ? contents : Buffer.from(contents) + fileFingerprints[entry] = { + sha256: sha256(buffer), + size: buffer.length, + } + } + + const fingerprint = { + tarball: { + file: packInfo.filename, + sha256: sha256(tarballBuffer), + size: tarballBuffer.length, + }, + files: fileFingerprints, + } + + process.stdout.write(`${JSON.stringify(fingerprint, null, 2)}\n`) +} + +await main() From 130eacd5e662aa8ae88e69d751881582012d07ae Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Thu, 15 Jan 2026 19:28:56 +0100 Subject: [PATCH 02/43] Update proposal for zod3 and types strategy --- docs/multi-module.md | 59 ++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/docs/multi-module.md b/docs/multi-module.md index b37bd2f2..1a79517b 100644 --- a/docs/multi-module.md +++ b/docs/multi-module.md @@ -7,8 +7,7 @@ Split the current node-sdk repo into a Yarn 4 workspace monorepo that publishes: - `@transloadit/node` (canonical Node runtime, current source lives here) - `transloadit` (legacy wrapper, byte-for-byte compatible with current npm package) - `@transloadit/types` (shared types for assemblies, robots, API responses) -- `@transloadit/schemas` (JSON Schema for runtime validation, no runtime deps) -- `@transloadit/zod` (optional Zod schemas, `zod` as a dependency) +- `@transloadit/zod3` (Zod v3 schemas, `zod` as a dependency) This keeps backwards compatibility, adds a canonical types package for reuse across Convex, browser, and future SDKs, and sets the stage for a future repo rename to `typescript-sdk` with additional packages like `@transloadit/browser` and `@transloadit/deno`. @@ -26,17 +25,17 @@ A `@transloadit/types` package would provide: This reduces duplication and drift between SDKs, Convex integrations, and docs examples. ### 2) Better DX without runtime coupling -Consumers like browser SDKs (uppy) and Convex components often need types but not Node runtime code. A types-only package keeps bundles smaller and avoids Node-only dependencies (fs, streams, crypto). JSON Schema can be published alongside types without any runtime dependency. +Consumers like browser SDKs (uppy) and Convex components often need types but not Node runtime code. A types-only package keeps bundles smaller and avoids Node-only dependencies (fs, streams, crypto). ### 3) Future-proof for multi-platform SDKs -If we eventually publish `@transloadit/browser`, `@transloadit/deno`, or a shared `@transloadit/core`, they can all depend on `@transloadit/types` for the contract while keeping platform-specific runtime. Zod schemas can live in `@transloadit/zod` for projects that want runtime parsing with Zod. +If we eventually publish `@transloadit/browser`, `@transloadit/deno`, or a shared `@transloadit/core`, they can all depend on `@transloadit/types` for the contract while keeping platform-specific runtime. Zod schemas live in `@transloadit/zod3`, and we can later add `@transloadit/zod4` without breaking the v3 consumers. ## Goals - Preserve `transloadit` package identity and output (byte-for-byte where possible). - Make `@transloadit/node` the canonical source for the existing Node SDK runtime. - Introduce `@transloadit/types` with stable, documented export surface. -- Publish schemas without forcing a runtime dependency choice. +- Publish Zod schemas without forcing a runtime dependency choice for types consumers. - Enable future modularization without breaking existing users. - Keep publish and versioning consistent (changesets, Yarn 4). @@ -62,12 +61,8 @@ node-sdk/ templates/ schemas/ dist/ - schemas/ - package.json (name: "@transloadit/schemas") - src/ (json schema export surface) - dist/ - zod/ - package.json (name: "@transloadit/zod") + zod3/ + package.json (name: "@transloadit/zod3") src/ (zod schema export surface) dist/ package.json (workspace root) @@ -80,8 +75,7 @@ node-sdk/ - `packages/node` is the canonical source for the existing Node SDK runtime. - `packages/transloadit` is a wrapper package that publishes the same artifacts under the legacy name. - `packages/types` re-exports from `src/alphalib/types` so alphalib remains the single source of truth. -- `packages/schemas` exports JSON Schema built from alphalib types; no runtime dependencies. -- `packages/zod` exports Zod schemas and depends on `zod` explicitly. +- `packages/zod3` exports Zod schemas and depends on `zod` explicitly. ## Alphalib strategy (synced source of truth) @@ -101,6 +95,24 @@ source of truth for types and schemas, and avoid copying files into packages. Tw Given the current sync mechanism, option 1 is safer and lower risk. +## Single source of truth for types vs Zod schemas + +If we want Zod to be the canonical source, we should generate the types at build time and publish +only plain `.d.ts` files in `@transloadit/types` that do **not** reference Zod. Two patterns: + +1) **Build-time type expansion (recommended)** + - `@transloadit/zod3` owns the schemas. + - A build step in `@transloadit/types` imports the schemas and emits `.d.ts` with concrete + structural types (no `z.infer<>` left in the output). + - `zod` is a devDependency of `@transloadit/types` only, so consumers do not need it. + +2) **Type-only imports from Zod (not recommended)** + - `@transloadit/types` would export `z.infer` types. + - This leaks a dependency on `zod` into downstream typechecking. + +Recommendation: keep Zod as the source of truth, but generate `.d.ts` for `@transloadit/types` +so consumers do not need `zod` at runtime or for typechecking. + ## Publishing strategy ### transloadit and @transloadit/node @@ -117,11 +129,10 @@ Two options that both preserve compatibility: Option 2 is stricter for byte-for-byte output parity but requires packaging logic. Option 1 is easier but can drift if any build settings differ. I recommend option 2 and automated fingerprinting (see below). -### @transloadit/types / @transloadit/schemas / @transloadit/zod +### @transloadit/types / @transloadit/zod3 - `@transloadit/types`: types only (`.d.ts`), no runtime dependencies. -- `@transloadit/schemas`: JSON Schema output derived from alphalib types (no runtime deps). -- `@transloadit/zod`: Zod schemas derived from alphalib types (depends on `zod`). +- `@transloadit/zod3`: Zod schemas derived from alphalib types (depends on `zod`). - Export stable API surfaces: ``` @@ -130,19 +141,14 @@ Option 2 is stricter for byte-for-byte output parity but requires packaging logi /templates /robots /webhooks -@transloadit/schemas - /assemblies - /templates - /robots - /webhooks -@transloadit/zod +@transloadit/zod3 /assemblies /templates /robots /webhooks ``` -Zod schemas cannot be shipped without a runtime dependency; separating them avoids forcing Zod on all consumers. If we want to keep the number of packages smaller, `@transloadit/zod` can list `zod` as a peer dependency and mark it optional, but a dedicated package is clearer. +Zod schemas cannot be shipped without a runtime dependency; separating them avoids forcing Zod on all consumers. A dedicated `@transloadit/zod3` package also leaves room for a future `@transloadit/zod4`. ## Compatibility verification plan (byte-for-byte) @@ -202,9 +208,8 @@ We can keep versions synchronized across `transloadit` and `@transloadit/node`. - In `packages/types`, re-export from `src/alphalib/types` via TS path mapping or barrel files. - Build `.d.ts` only, no runtime JS. -### Phase 3: Add @transloadit/schemas and @transloadit/zod -- Generate JSON Schema in `packages/schemas` from alphalib types. -- Generate Zod schemas in `packages/zod` from alphalib types (requires `zod`). +### Phase 3: Add @transloadit/zod3 +- Generate Zod schemas in `packages/zod3` from alphalib types (requires `zod`). ### Phase 4: Add transloadit wrapper - Package identical output to `@transloadit/node` (copy dist + package.json transform). @@ -238,7 +243,7 @@ All can share `@transloadit/types` for schema and type fidelity, with platform-s ## Open questions -- Should we make `@transloadit/zod` a dedicated package or make Zod a peer dep of `@transloadit/types`? I recommend a dedicated package to keep types-only consumers clean. +- Should we make `@transloadit/zod3` a dedicated package or make Zod a peer dep of `@transloadit/types`? I recommend a dedicated package to keep types-only consumers clean. - Do we keep versions in lock-step across all packages or allow independent versioning? - Should the root package publish anything, or remain private-only? From bfe042f2b9c8943b6414866e5c6ac9d611462677 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Thu, 15 Jan 2026 19:40:47 +0100 Subject: [PATCH 03/43] Refine proposal for zod versioning and type generation --- docs/multi-module.md | 89 +++++++++++++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 25 deletions(-) diff --git a/docs/multi-module.md b/docs/multi-module.md index 1a79517b..ff20de7e 100644 --- a/docs/multi-module.md +++ b/docs/multi-module.md @@ -7,7 +7,7 @@ Split the current node-sdk repo into a Yarn 4 workspace monorepo that publishes: - `@transloadit/node` (canonical Node runtime, current source lives here) - `transloadit` (legacy wrapper, byte-for-byte compatible with current npm package) - `@transloadit/types` (shared types for assemblies, robots, API responses) -- `@transloadit/zod3` (Zod v3 schemas, `zod` as a dependency) +- `@transloadit/zod` (Zod schemas with versioned subpaths like `@transloadit/zod/v3`) This keeps backwards compatibility, adds a canonical types package for reuse across Convex, browser, and future SDKs, and sets the stage for a future repo rename to `typescript-sdk` with additional packages like `@transloadit/browser` and `@transloadit/deno`. @@ -28,7 +28,7 @@ This reduces duplication and drift between SDKs, Convex integrations, and docs e Consumers like browser SDKs (uppy) and Convex components often need types but not Node runtime code. A types-only package keeps bundles smaller and avoids Node-only dependencies (fs, streams, crypto). ### 3) Future-proof for multi-platform SDKs -If we eventually publish `@transloadit/browser`, `@transloadit/deno`, or a shared `@transloadit/core`, they can all depend on `@transloadit/types` for the contract while keeping platform-specific runtime. Zod schemas live in `@transloadit/zod3`, and we can later add `@transloadit/zod4` without breaking the v3 consumers. +If we eventually publish `@transloadit/browser`, `@transloadit/deno`, or a shared `@transloadit/core`, they can all depend on `@transloadit/types` for the contract while keeping platform-specific runtime. Zod schemas live in `@transloadit/zod` with versioned subpaths (`/v3`, later `/v4`) so we can add a new major without breaking existing imports. ## Goals @@ -61,9 +61,10 @@ node-sdk/ templates/ schemas/ dist/ - zod3/ - package.json (name: "@transloadit/zod3") - src/ (zod schema export surface) + zod/ + package.json (name: "@transloadit/zod") + src/v3/ (zod v3 schema export surface) + src/v4/ (future) dist/ package.json (workspace root) tsconfig.base.json @@ -75,7 +76,7 @@ node-sdk/ - `packages/node` is the canonical source for the existing Node SDK runtime. - `packages/transloadit` is a wrapper package that publishes the same artifacts under the legacy name. - `packages/types` re-exports from `src/alphalib/types` so alphalib remains the single source of truth. -- `packages/zod3` exports Zod schemas and depends on `zod` explicitly. +- `packages/zod` exports Zod schemas and depends on `zod` explicitly, with `exports` for `./v3` (and later `./v4`). ## Alphalib strategy (synced source of truth) @@ -84,7 +85,7 @@ source of truth for types and schemas, and avoid copying files into packages. Tw 1) **Keep alphalib at repo root and re-export it** - `packages/types` uses TS path mapping or barrel re-exports that point to `src/alphalib/types`. - - `packages/schemas` and `packages/zod` import from `src/alphalib/types` or from generated schema + - `packages/zod` imports from `src/alphalib/types` or from generated schema artifacts stored alongside alphalib. - This keeps the sync workflow intact and avoids duplication. @@ -98,20 +99,58 @@ Given the current sync mechanism, option 1 is safer and lower risk. ## Single source of truth for types vs Zod schemas If we want Zod to be the canonical source, we should generate the types at build time and publish -only plain `.d.ts` files in `@transloadit/types` that do **not** reference Zod. Two patterns: +only plain `.d.ts` files in `@transloadit/types` that do **not** reference Zod. -1) **Build-time type expansion (recommended)** - - `@transloadit/zod3` owns the schemas. - - A build step in `@transloadit/types` imports the schemas and emits `.d.ts` with concrete - structural types (no `z.infer<>` left in the output). - - `zod` is a devDependency of `@transloadit/types` only, so consumers do not need it. +### Recommended pipeline (guaranteed compatibility) -2) **Type-only imports from Zod (not recommended)** - - `@transloadit/types` would export `z.infer` types. - - This leaks a dependency on `zod` into downstream typechecking. +1) **Zod schemas live in `@transloadit/zod/v3`.** +2) **Generate structural types** into `@transloadit/types` (no `z.infer`, no `zod` imports). +3) **Enforce type equality in CI** between generated types and `z.infer` from the schemas. -Recommendation: keep Zod as the source of truth, but generate `.d.ts` for `@transloadit/types` -so consumers do not need `zod` at runtime or for typechecking. +This gives a compile-time guarantee that the generated types are 100% compatible with Zod inference. + +### Build script sketch + +``` +packages/ + zod/ + src/v3/*.ts # Zod schemas + test/type-equality.ts # Assert z.infer === generated types + types/ + scripts/emit-types.ts # Generates .ts from zod schemas + src/index.ts # Re-exports generated types +``` + +`emit-types.ts` can use the TS compiler API or ts-morph to: +- import `@transloadit/zod/v3` schemas, +- create `type Foo = z.infer` aliases, +- emit a temporary `.ts`, +- run `tsc --emitDeclarationOnly` to produce `.d.ts`, +- then strip the intermediate `.ts` from the published package. + +`type-equality.ts` then asserts: + +```ts +type Equal = + (() => T extends A ? 1 : 2) extends + (() => T extends B ? 1 : 2) ? true : false; +type Assert = T; + +type _Check = Assert>>; +``` + +If anything drifts, the build fails. This is the compatibility guarantee. + +### Why not export `z.infer` directly? + +That would force all `@transloadit/types` consumers to install `zod` just to typecheck. Generating +`.d.ts` avoids runtime and typechecking dependencies downstream. + +### Future: JSON Schema from Zod v4 + +When we move to Zod v4 (outside this scope), we can add `@transloadit/zod/v4` and use the native +JSON Schema exporter to generate a separate `@transloadit/jsonschema` package (or subpath) without +changing the `@transloadit/types` contract. ## Publishing strategy @@ -129,10 +168,10 @@ Two options that both preserve compatibility: Option 2 is stricter for byte-for-byte output parity but requires packaging logic. Option 1 is easier but can drift if any build settings differ. I recommend option 2 and automated fingerprinting (see below). -### @transloadit/types / @transloadit/zod3 +### @transloadit/types / @transloadit/zod - `@transloadit/types`: types only (`.d.ts`), no runtime dependencies. -- `@transloadit/zod3`: Zod schemas derived from alphalib types (depends on `zod`). +- `@transloadit/zod`: Zod schemas derived from alphalib types (depends on `zod`). - Export stable API surfaces: ``` @@ -141,14 +180,14 @@ Option 2 is stricter for byte-for-byte output parity but requires packaging logi /templates /robots /webhooks -@transloadit/zod3 +@transloadit/zod/v3 /assemblies /templates /robots /webhooks ``` -Zod schemas cannot be shipped without a runtime dependency; separating them avoids forcing Zod on all consumers. A dedicated `@transloadit/zod3` package also leaves room for a future `@transloadit/zod4`. +Zod schemas cannot be shipped without a runtime dependency; separating them avoids forcing Zod on all consumers. A single `@transloadit/zod` package with `./v3` and `./v4` subpath exports keeps upgrade paths explicit. ## Compatibility verification plan (byte-for-byte) @@ -208,8 +247,8 @@ We can keep versions synchronized across `transloadit` and `@transloadit/node`. - In `packages/types`, re-export from `src/alphalib/types` via TS path mapping or barrel files. - Build `.d.ts` only, no runtime JS. -### Phase 3: Add @transloadit/zod3 -- Generate Zod schemas in `packages/zod3` from alphalib types (requires `zod`). +### Phase 3: Add @transloadit/zod +- Generate Zod schemas in `packages/zod` from alphalib types (requires `zod`). ### Phase 4: Add transloadit wrapper - Package identical output to `@transloadit/node` (copy dist + package.json transform). @@ -243,7 +282,7 @@ All can share `@transloadit/types` for schema and type fidelity, with platform-s ## Open questions -- Should we make `@transloadit/zod3` a dedicated package or make Zod a peer dep of `@transloadit/types`? I recommend a dedicated package to keep types-only consumers clean. +- Should we make `@transloadit/zod` a dedicated package or make Zod a peer dep of `@transloadit/types`? I recommend a dedicated package to keep types-only consumers clean. - Do we keep versions in lock-step across all packages or allow independent versioning? - Should the root package publish anything, or remain private-only? From 7ad5884ea1bda71ab30f1f2ae3d395402575538a Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Thu, 15 Jan 2026 19:46:29 +0100 Subject: [PATCH 04/43] Add type generation scaffold and equality check --- packages/types/scripts/emit-types.ts | 15 +++++++++++++++ packages/zod/test/type-equality.ts | 20 ++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 packages/types/scripts/emit-types.ts create mode 100644 packages/zod/test/type-equality.ts diff --git a/packages/types/scripts/emit-types.ts b/packages/types/scripts/emit-types.ts new file mode 100644 index 00000000..56a88326 --- /dev/null +++ b/packages/types/scripts/emit-types.ts @@ -0,0 +1,15 @@ +import { fileURLToPath } from 'node:url' + +const steps = [ + 'emit-types pipeline (outline):', + '1) import schemas from @transloadit/zod/v3', + '2) generate a temp .ts file exporting z.infer aliases', + '3) run tsc --emitDeclarationOnly against that temp file', + '4) write .d.ts output into packages/types/src/generated', + '5) keep generated .d.ts free of zod imports (structural types only)', + '6) leave CI type-equality tests in @transloadit/zod to ensure parity', +] + +if (process.argv[1] && fileURLToPath(import.meta.url) === fileURLToPath(process.argv[1])) { + console.log(steps.join('\n')) +} diff --git a/packages/zod/test/type-equality.ts b/packages/zod/test/type-equality.ts new file mode 100644 index 00000000..361d66ba --- /dev/null +++ b/packages/zod/test/type-equality.ts @@ -0,0 +1,20 @@ +import { z } from 'zod' + +const exampleSchema = z.object({ + // TODO: replace with a real schema export once @transloadit/zod/v3 exists. +}) + +type Equal = (() => T extends A ? 1 : 2) extends () => T extends B ? 1 : 2 + ? true + : false + +type Assert = T + +// TODO: replace GeneratedExample with a real type from @transloadit/types. +// import type * as Types from '@transloadit/types' +// type GeneratedExample = Types.Example +type GeneratedExample = z.infer + +export type _ExampleCheck = Assert>> + +export { exampleSchema } From cb65d1ae10c9e9e056b4fbc4f6e706d75fbe3249 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Thu, 15 Jan 2026 22:32:45 +0100 Subject: [PATCH 05/43] chore: monorepo structure and fingerprinting --- .changeset/README.md | 3 + .changeset/config.json | 21 + .github/workflows/ci.yml | 10 +- .gitignore | 3 + biome.json | 5 +- docs/fingerprint/transloadit-after.json | 2988 +++++++++++++++++ docs/fingerprint/transloadit-baseline.json | 2988 +++++++++++++++++ docs/todo.md | 53 + package.json | 109 +- packages/node/LICENSE | 21 + packages/node/README.md | 698 ++++ .../node/examples}/convert_to_webp.ts | 0 .../node/examples}/credentials.ts | 0 .../node/examples}/face_detect_download.ts | 0 ...ch_costs_of_all_assemblies_in_timeframe.ts | 0 .../node/examples}/fixtures/berkley.jpg | Bin .../node/examples}/fixtures/circle.svg | 0 .../node/examples}/rasterize_svg_to_png.ts | 0 .../node/examples}/resize_an_image.ts | 0 {examples => packages/node/examples}/retry.ts | 0 .../node/examples}/template_api.ts | 0 knip.ts => packages/node/knip.ts | 5 + packages/node/package.json | 85 + {src => packages/node/src}/ApiError.ts | 0 .../node/src}/InconsistentResponseError.ts | 0 .../node/src}/PaginationStream.ts | 0 .../node/src}/PollingTimeoutError.ts | 0 {src => packages/node/src}/Transloadit.ts | 0 .../node/src}/alphalib/lib/nativeGlobby.ts | 0 {src => packages/node/src}/alphalib/mcache.ts | 0 .../node/src}/alphalib/tryCatch.ts | 0 .../node/src}/alphalib/types/assembliesGet.ts | 0 .../src}/alphalib/types/assemblyReplay.ts | 0 .../types/assemblyReplayNotification.ts | 0 .../src}/alphalib/types/assemblyStatus.ts | 0 .../node/src}/alphalib/types/bill.ts | 0 .../node/src}/alphalib/types/robots/_index.ts | 0 .../types/robots/_instructions-primitives.ts | 0 .../src}/alphalib/types/robots/ai-chat.ts | 0 .../types/robots/assembly-savejson.ts | 0 .../alphalib/types/robots/audio-artwork.ts | 0 .../alphalib/types/robots/audio-concat.ts | 0 .../alphalib/types/robots/audio-encode.ts | 0 .../src}/alphalib/types/robots/audio-loop.ts | 0 .../src}/alphalib/types/robots/audio-merge.ts | 0 .../alphalib/types/robots/audio-waveform.ts | 0 .../alphalib/types/robots/azure-import.ts | 0 .../src}/alphalib/types/robots/azure-store.ts | 0 .../alphalib/types/robots/backblaze-import.ts | 0 .../alphalib/types/robots/backblaze-store.ts | 0 .../types/robots/cloudfiles-import.ts | 0 .../alphalib/types/robots/cloudfiles-store.ts | 0 .../types/robots/cloudflare-import.ts | 0 .../alphalib/types/robots/cloudflare-store.ts | 0 .../types/robots/digitalocean-import.ts | 0 .../types/robots/digitalocean-store.ts | 0 .../types/robots/document-autorotate.ts | 0 .../alphalib/types/robots/document-convert.ts | 0 .../alphalib/types/robots/document-merge.ts | 0 .../alphalib/types/robots/document-ocr.ts | 0 .../alphalib/types/robots/document-split.ts | 0 .../alphalib/types/robots/document-thumbs.ts | 0 .../alphalib/types/robots/dropbox-import.ts | 0 .../alphalib/types/robots/dropbox-store.ts | 0 .../alphalib/types/robots/edgly-deliver.ts | 0 .../alphalib/types/robots/file-compress.ts | 0 .../alphalib/types/robots/file-decompress.ts | 0 .../src}/alphalib/types/robots/file-filter.ts | 0 .../src}/alphalib/types/robots/file-hash.ts | 0 .../alphalib/types/robots/file-preview.ts | 0 .../src}/alphalib/types/robots/file-read.ts | 0 .../src}/alphalib/types/robots/file-serve.ts | 0 .../src}/alphalib/types/robots/file-verify.ts | 0 .../alphalib/types/robots/file-virusscan.ts | 0 .../alphalib/types/robots/file-watermark.ts | 0 .../src}/alphalib/types/robots/ftp-import.ts | 0 .../src}/alphalib/types/robots/ftp-store.ts | 0 .../alphalib/types/robots/google-import.ts | 0 .../alphalib/types/robots/google-store.ts | 0 .../alphalib/types/robots/html-convert.ts | 0 .../src}/alphalib/types/robots/http-import.ts | 0 .../alphalib/types/robots/image-bgremove.ts | 0 .../alphalib/types/robots/image-describe.ts | 0 .../alphalib/types/robots/image-facedetect.ts | 0 .../alphalib/types/robots/image-generate.ts | 0 .../src}/alphalib/types/robots/image-merge.ts | 0 .../src}/alphalib/types/robots/image-ocr.ts | 0 .../alphalib/types/robots/image-optimize.ts | 0 .../alphalib/types/robots/image-resize.ts | 0 .../src}/alphalib/types/robots/meta-read.ts | 0 .../src}/alphalib/types/robots/meta-write.ts | 3 +- .../alphalib/types/robots/minio-import.ts | 0 .../src}/alphalib/types/robots/minio-store.ts | 0 .../types/robots/progress-simulate.ts | 0 .../src}/alphalib/types/robots/s3-import.ts | 0 .../src}/alphalib/types/robots/s3-store.ts | 0 .../src}/alphalib/types/robots/script-run.ts | 0 .../src}/alphalib/types/robots/sftp-import.ts | 0 .../src}/alphalib/types/robots/sftp-store.ts | 0 .../types/robots/speech-transcribe.ts | 0 .../alphalib/types/robots/supabase-import.ts | 0 .../alphalib/types/robots/supabase-store.ts | 0 .../alphalib/types/robots/swift-import.ts | 0 .../src}/alphalib/types/robots/swift-store.ts | 0 .../src}/alphalib/types/robots/text-speak.ts | 0 .../alphalib/types/robots/text-translate.ts | 0 .../alphalib/types/robots/tigris-import.ts | 0 .../alphalib/types/robots/tigris-store.ts | 0 .../alphalib/types/robots/tlcdn-deliver.ts | 0 .../src}/alphalib/types/robots/tus-store.ts | 0 .../alphalib/types/robots/upload-handle.ts | 0 .../alphalib/types/robots/video-adaptive.ts | 0 .../alphalib/types/robots/video-concat.ts | 0 .../alphalib/types/robots/video-encode.ts | 0 .../src}/alphalib/types/robots/video-merge.ts | 0 .../alphalib/types/robots/video-ondemand.ts | 0 .../alphalib/types/robots/video-subtitle.ts | 0 .../alphalib/types/robots/video-thumbs.ts | 0 .../alphalib/types/robots/vimeo-import.ts | 0 .../src}/alphalib/types/robots/vimeo-store.ts | 0 .../alphalib/types/robots/wasabi-import.ts | 0 .../alphalib/types/robots/wasabi-store.ts | 0 .../alphalib/types/robots/youtube-store.ts | 0 .../node/src}/alphalib/types/stackVersions.ts | 0 .../node/src}/alphalib/types/template.ts | 0 .../src}/alphalib/types/templateCredential.ts | 0 .../node/src}/alphalib/zodParseWithContext.ts | 0 {src => packages/node/src}/apiTypes.ts | 0 {src => packages/node/src}/cli.ts | 0 {src => packages/node/src}/cli/OutputCtl.ts | 0 .../node/src}/cli/commands/BaseCommand.ts | 0 .../node/src}/cli/commands/assemblies.ts | 0 .../node/src}/cli/commands/auth.ts | 0 .../node/src}/cli/commands/bills.ts | 0 .../node/src}/cli/commands/index.ts | 0 .../node/src}/cli/commands/notifications.ts | 0 .../node/src}/cli/commands/templates.ts | 0 {src => packages/node/src}/cli/helpers.ts | 0 .../node/src}/cli/template-last-modified.ts | 0 {src => packages/node/src}/cli/types.ts | 0 {src => packages/node/src}/tus.ts | 0 .../node/test}/e2e/cli/OutputCtl.ts | 0 .../test}/e2e/cli/assemblies-create.test.ts | 0 .../test}/e2e/cli/assemblies-list.test.ts | 0 .../node/test}/e2e/cli/assemblies.test.ts | 0 .../node/test}/e2e/cli/bills.test.ts | 0 .../node/test}/e2e/cli/cli.test.ts | 0 .../node/test}/e2e/cli/templates.test.ts | 0 .../node/test}/e2e/cli/test-utils.ts | 0 .../node/test}/e2e/fixtures/zerobytes.jpg | 0 .../node/test}/e2e/live-api.test.ts | 0 .../node/test}/generate-coverage-badge.ts | 0 {test => packages/node/test}/testserver.ts | 0 {test => packages/node/test}/tunnel.ts | 0 .../node/test}/unit/cli/test-cli.test.ts | 0 .../node/test}/unit/mock-http.test.ts | 0 .../test}/unit/test-pagination-stream.test.ts | 0 .../unit/test-transloadit-client.test.ts | 0 .../test}/unit/transloadit-advanced.test.ts | 0 {test => packages/node/test}/unit/tus.test.ts | 0 {test => packages/node/test}/util.ts | 0 .../node/tsconfig.build.json | 0 packages/node/tsconfig.json | 16 + .../node/vitest.config.ts | 0 packages/transloadit/LICENSE | 21 + packages/transloadit/README.md | 698 ++++ packages/transloadit/package.json | 95 + packages/transloadit/src/ApiError.ts | 49 + .../src/InconsistentResponseError.ts | 3 + packages/transloadit/src/PaginationStream.ts | 54 + .../transloadit/src/PollingTimeoutError.ts | 5 + packages/transloadit/src/Transloadit.ts | 980 ++++++ .../src/alphalib/lib/nativeGlobby.ts | 244 ++ packages/transloadit/src/alphalib/mcache.ts | 184 + packages/transloadit/src/alphalib/tryCatch.ts | 30 + .../src/alphalib/types/assembliesGet.ts | 42 + .../src/alphalib/types/assemblyReplay.ts | 24 + .../types/assemblyReplayNotification.ts | 16 + .../src/alphalib/types/assemblyStatus.ts | 794 +++++ .../transloadit/src/alphalib/types/bill.ts | 9 + .../src/alphalib/types/robots/_index.ts | 1201 +++++++ .../types/robots/_instructions-primitives.ts | 1770 ++++++++++ .../src/alphalib/types/robots/ai-chat.ts | 278 ++ .../types/robots/assembly-savejson.ts | 37 + .../alphalib/types/robots/audio-artwork.ts | 107 + .../src/alphalib/types/robots/audio-concat.ts | 142 + .../src/alphalib/types/robots/audio-encode.ts | 103 + .../src/alphalib/types/robots/audio-loop.ts | 102 + .../src/alphalib/types/robots/audio-merge.ts | 137 + .../alphalib/types/robots/audio-waveform.ts | 280 ++ .../src/alphalib/types/robots/azure-import.ts | 111 + .../src/alphalib/types/robots/azure-store.ts | 143 + .../alphalib/types/robots/backblaze-import.ts | 119 + .../alphalib/types/robots/backblaze-store.ts | 104 + .../types/robots/cloudfiles-import.ts | 118 + .../alphalib/types/robots/cloudfiles-store.ts | 104 + .../types/robots/cloudflare-import.ts | 121 + .../alphalib/types/robots/cloudflare-store.ts | 120 + .../types/robots/digitalocean-import.ts | 118 + .../types/robots/digitalocean-store.ts | 125 + .../types/robots/document-autorotate.ts | 74 + .../alphalib/types/robots/document-convert.ts | 302 ++ .../alphalib/types/robots/document-merge.ts | 114 + .../src/alphalib/types/robots/document-ocr.ts | 120 + .../alphalib/types/robots/document-split.ts | 78 + .../alphalib/types/robots/document-thumbs.ts | 231 ++ .../alphalib/types/robots/dropbox-import.ts | 100 + .../alphalib/types/robots/dropbox-store.ts | 97 + .../alphalib/types/robots/edgly-deliver.ts | 73 + .../alphalib/types/robots/file-compress.ts | 167 + .../alphalib/types/robots/file-decompress.ts | 125 + .../src/alphalib/types/robots/file-filter.ts | 173 + .../src/alphalib/types/robots/file-hash.ts | 86 + .../src/alphalib/types/robots/file-preview.ts | 260 ++ .../src/alphalib/types/robots/file-read.ts | 71 + .../src/alphalib/types/robots/file-serve.ts | 128 + .../src/alphalib/types/robots/file-verify.ts | 102 + .../alphalib/types/robots/file-virusscan.ts | 113 + .../alphalib/types/robots/file-watermark.ts | 56 + .../src/alphalib/types/robots/ftp-import.ts | 95 + .../src/alphalib/types/robots/ftp-store.ts | 119 + .../alphalib/types/robots/google-import.ts | 115 + .../src/alphalib/types/robots/google-store.ts | 139 + .../src/alphalib/types/robots/html-convert.ts | 165 + .../src/alphalib/types/robots/http-import.ts | 168 + .../alphalib/types/robots/image-bgremove.ts | 95 + .../alphalib/types/robots/image-describe.ts | 121 + .../alphalib/types/robots/image-facedetect.ts | 187 ++ .../alphalib/types/robots/image-generate.ts | 92 + .../src/alphalib/types/robots/image-merge.ts | 127 + .../src/alphalib/types/robots/image-ocr.ts | 112 + .../alphalib/types/robots/image-optimize.ts | 114 + .../src/alphalib/types/robots/image-resize.ts | 653 ++++ .../src/alphalib/types/robots/meta-read.ts | 44 + .../src/alphalib/types/robots/meta-write.ts | 93 + .../src/alphalib/types/robots/minio-import.ts | 120 + .../src/alphalib/types/robots/minio-store.ts | 115 + .../types/robots/progress-simulate.ts | 40 + .../src/alphalib/types/robots/s3-import.ts | 175 + .../src/alphalib/types/robots/s3-store.ts | 198 ++ .../src/alphalib/types/robots/script-run.ts | 122 + .../src/alphalib/types/robots/sftp-import.ts | 92 + .../src/alphalib/types/robots/sftp-store.ts | 110 + .../types/robots/speech-transcribe.ts | 139 + .../alphalib/types/robots/supabase-import.ts | 122 + .../alphalib/types/robots/supabase-store.ts | 105 + .../src/alphalib/types/robots/swift-import.ts | 120 + .../src/alphalib/types/robots/swift-store.ts | 112 + .../src/alphalib/types/robots/text-speak.ts | 152 + .../alphalib/types/robots/text-translate.ts | 245 ++ .../alphalib/types/robots/tigris-import.ts | 124 + .../src/alphalib/types/robots/tigris-store.ts | 119 + .../alphalib/types/robots/tlcdn-deliver.ts | 73 + .../src/alphalib/types/robots/tus-store.ts | 129 + .../alphalib/types/robots/upload-handle.ts | 95 + .../alphalib/types/robots/video-adaptive.ts | 179 + .../src/alphalib/types/robots/video-concat.ts | 130 + .../src/alphalib/types/robots/video-encode.ts | 141 + .../src/alphalib/types/robots/video-merge.ts | 138 + .../alphalib/types/robots/video-ondemand.ts | 161 + .../alphalib/types/robots/video-subtitle.ts | 159 + .../src/alphalib/types/robots/video-thumbs.ts | 158 + .../src/alphalib/types/robots/vimeo-import.ts | 126 + .../src/alphalib/types/robots/vimeo-store.ts | 143 + .../alphalib/types/robots/wasabi-import.ts | 124 + .../src/alphalib/types/robots/wasabi-store.ts | 113 + .../alphalib/types/robots/youtube-store.ts | 153 + .../src/alphalib/types/stackVersions.ts | 12 + .../src/alphalib/types/template.ts | 277 ++ .../src/alphalib/types/templateCredential.ts | 61 + .../src/alphalib/zodParseWithContext.ts | 306 ++ packages/transloadit/src/apiTypes.ts | 154 + packages/transloadit/src/cli.ts | 44 + packages/transloadit/src/cli/OutputCtl.ts | 115 + .../src/cli/commands/BaseCommand.ts | 71 + .../src/cli/commands/assemblies.ts | 1373 ++++++++ packages/transloadit/src/cli/commands/auth.ts | 354 ++ .../transloadit/src/cli/commands/bills.ts | 91 + .../transloadit/src/cli/commands/index.ts | 65 + .../src/cli/commands/notifications.ts | 63 + .../transloadit/src/cli/commands/templates.ts | 556 +++ packages/transloadit/src/cli/helpers.ts | 50 + .../src/cli/template-last-modified.ts | 156 + packages/transloadit/src/cli/types.ts | 70 + packages/transloadit/src/tus.ts | 168 + packages/types/package.json | 34 + packages/types/scripts/emit-types.ts | 436 ++- packages/types/src/index.ts | 1 + packages/types/tsconfig.build.json | 21 + packages/types/tsconfig.json | 15 + packages/zod/package.json | 35 + packages/zod/scripts/sync-v3.js | 30 + packages/zod/test/type-equality.ts | 24 +- packages/zod/tsconfig.build.json | 20 + packages/zod/tsconfig.json | 15 + packages/zod/tsconfig.test.json | 12 + scripts/fingerprint-pack.js | 194 +- scripts/prepare-transloadit.js | 31 + tsconfig.json | 20 +- yarn.lock | 750 ++++- 300 files changed, 30159 insertions(+), 202 deletions(-) create mode 100644 .changeset/README.md create mode 100644 .changeset/config.json create mode 100644 docs/fingerprint/transloadit-after.json create mode 100644 docs/fingerprint/transloadit-baseline.json create mode 100644 docs/todo.md create mode 100644 packages/node/LICENSE create mode 100644 packages/node/README.md rename {examples => packages/node/examples}/convert_to_webp.ts (100%) rename {examples => packages/node/examples}/credentials.ts (100%) rename {examples => packages/node/examples}/face_detect_download.ts (100%) rename {examples => packages/node/examples}/fetch_costs_of_all_assemblies_in_timeframe.ts (100%) rename {examples => packages/node/examples}/fixtures/berkley.jpg (100%) rename {examples => packages/node/examples}/fixtures/circle.svg (100%) rename {examples => packages/node/examples}/rasterize_svg_to_png.ts (100%) rename {examples => packages/node/examples}/resize_an_image.ts (100%) rename {examples => packages/node/examples}/retry.ts (100%) rename {examples => packages/node/examples}/template_api.ts (100%) rename knip.ts => packages/node/knip.ts (83%) create mode 100644 packages/node/package.json rename {src => packages/node/src}/ApiError.ts (100%) rename {src => packages/node/src}/InconsistentResponseError.ts (100%) rename {src => packages/node/src}/PaginationStream.ts (100%) rename {src => packages/node/src}/PollingTimeoutError.ts (100%) rename {src => packages/node/src}/Transloadit.ts (100%) rename {src => packages/node/src}/alphalib/lib/nativeGlobby.ts (100%) rename {src => packages/node/src}/alphalib/mcache.ts (100%) rename {src => packages/node/src}/alphalib/tryCatch.ts (100%) rename {src => packages/node/src}/alphalib/types/assembliesGet.ts (100%) rename {src => packages/node/src}/alphalib/types/assemblyReplay.ts (100%) rename {src => packages/node/src}/alphalib/types/assemblyReplayNotification.ts (100%) rename {src => packages/node/src}/alphalib/types/assemblyStatus.ts (100%) rename {src => packages/node/src}/alphalib/types/bill.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/_index.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/_instructions-primitives.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/ai-chat.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/assembly-savejson.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/audio-artwork.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/audio-concat.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/audio-encode.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/audio-loop.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/audio-merge.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/audio-waveform.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/azure-import.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/azure-store.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/backblaze-import.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/backblaze-store.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/cloudfiles-import.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/cloudfiles-store.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/cloudflare-import.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/cloudflare-store.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/digitalocean-import.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/digitalocean-store.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/document-autorotate.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/document-convert.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/document-merge.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/document-ocr.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/document-split.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/document-thumbs.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/dropbox-import.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/dropbox-store.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/edgly-deliver.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/file-compress.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/file-decompress.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/file-filter.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/file-hash.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/file-preview.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/file-read.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/file-serve.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/file-verify.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/file-virusscan.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/file-watermark.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/ftp-import.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/ftp-store.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/google-import.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/google-store.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/html-convert.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/http-import.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/image-bgremove.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/image-describe.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/image-facedetect.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/image-generate.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/image-merge.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/image-ocr.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/image-optimize.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/image-resize.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/meta-read.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/meta-write.ts (98%) rename {src => packages/node/src}/alphalib/types/robots/minio-import.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/minio-store.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/progress-simulate.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/s3-import.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/s3-store.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/script-run.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/sftp-import.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/sftp-store.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/speech-transcribe.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/supabase-import.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/supabase-store.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/swift-import.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/swift-store.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/text-speak.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/text-translate.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/tigris-import.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/tigris-store.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/tlcdn-deliver.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/tus-store.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/upload-handle.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/video-adaptive.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/video-concat.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/video-encode.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/video-merge.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/video-ondemand.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/video-subtitle.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/video-thumbs.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/vimeo-import.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/vimeo-store.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/wasabi-import.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/wasabi-store.ts (100%) rename {src => packages/node/src}/alphalib/types/robots/youtube-store.ts (100%) rename {src => packages/node/src}/alphalib/types/stackVersions.ts (100%) rename {src => packages/node/src}/alphalib/types/template.ts (100%) rename {src => packages/node/src}/alphalib/types/templateCredential.ts (100%) rename {src => packages/node/src}/alphalib/zodParseWithContext.ts (100%) rename {src => packages/node/src}/apiTypes.ts (100%) rename {src => packages/node/src}/cli.ts (100%) rename {src => packages/node/src}/cli/OutputCtl.ts (100%) rename {src => packages/node/src}/cli/commands/BaseCommand.ts (100%) rename {src => packages/node/src}/cli/commands/assemblies.ts (100%) rename {src => packages/node/src}/cli/commands/auth.ts (100%) rename {src => packages/node/src}/cli/commands/bills.ts (100%) rename {src => packages/node/src}/cli/commands/index.ts (100%) rename {src => packages/node/src}/cli/commands/notifications.ts (100%) rename {src => packages/node/src}/cli/commands/templates.ts (100%) rename {src => packages/node/src}/cli/helpers.ts (100%) rename {src => packages/node/src}/cli/template-last-modified.ts (100%) rename {src => packages/node/src}/cli/types.ts (100%) rename {src => packages/node/src}/tus.ts (100%) rename {test => packages/node/test}/e2e/cli/OutputCtl.ts (100%) rename {test => packages/node/test}/e2e/cli/assemblies-create.test.ts (100%) rename {test => packages/node/test}/e2e/cli/assemblies-list.test.ts (100%) rename {test => packages/node/test}/e2e/cli/assemblies.test.ts (100%) rename {test => packages/node/test}/e2e/cli/bills.test.ts (100%) rename {test => packages/node/test}/e2e/cli/cli.test.ts (100%) rename {test => packages/node/test}/e2e/cli/templates.test.ts (100%) rename {test => packages/node/test}/e2e/cli/test-utils.ts (100%) rename {test => packages/node/test}/e2e/fixtures/zerobytes.jpg (100%) rename {test => packages/node/test}/e2e/live-api.test.ts (100%) rename {test => packages/node/test}/generate-coverage-badge.ts (100%) rename {test => packages/node/test}/testserver.ts (100%) rename {test => packages/node/test}/tunnel.ts (100%) rename {test => packages/node/test}/unit/cli/test-cli.test.ts (100%) rename {test => packages/node/test}/unit/mock-http.test.ts (100%) rename {test => packages/node/test}/unit/test-pagination-stream.test.ts (100%) rename {test => packages/node/test}/unit/test-transloadit-client.test.ts (100%) rename {test => packages/node/test}/unit/transloadit-advanced.test.ts (100%) rename {test => packages/node/test}/unit/tus.test.ts (100%) rename {test => packages/node/test}/util.ts (100%) rename tsconfig.build.json => packages/node/tsconfig.build.json (100%) create mode 100644 packages/node/tsconfig.json rename vitest.config.ts => packages/node/vitest.config.ts (100%) create mode 100644 packages/transloadit/LICENSE create mode 100644 packages/transloadit/README.md create mode 100644 packages/transloadit/package.json create mode 100644 packages/transloadit/src/ApiError.ts create mode 100644 packages/transloadit/src/InconsistentResponseError.ts create mode 100644 packages/transloadit/src/PaginationStream.ts create mode 100644 packages/transloadit/src/PollingTimeoutError.ts create mode 100644 packages/transloadit/src/Transloadit.ts create mode 100644 packages/transloadit/src/alphalib/lib/nativeGlobby.ts create mode 100644 packages/transloadit/src/alphalib/mcache.ts create mode 100644 packages/transloadit/src/alphalib/tryCatch.ts create mode 100644 packages/transloadit/src/alphalib/types/assembliesGet.ts create mode 100644 packages/transloadit/src/alphalib/types/assemblyReplay.ts create mode 100644 packages/transloadit/src/alphalib/types/assemblyReplayNotification.ts create mode 100644 packages/transloadit/src/alphalib/types/assemblyStatus.ts create mode 100644 packages/transloadit/src/alphalib/types/bill.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/_index.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/_instructions-primitives.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/ai-chat.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/assembly-savejson.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/audio-artwork.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/audio-concat.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/audio-encode.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/audio-loop.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/audio-merge.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/audio-waveform.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/azure-import.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/azure-store.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/backblaze-import.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/backblaze-store.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/cloudfiles-import.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/cloudfiles-store.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/cloudflare-import.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/cloudflare-store.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/digitalocean-import.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/digitalocean-store.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/document-autorotate.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/document-convert.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/document-merge.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/document-ocr.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/document-split.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/document-thumbs.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/dropbox-import.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/dropbox-store.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/edgly-deliver.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/file-compress.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/file-decompress.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/file-filter.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/file-hash.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/file-preview.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/file-read.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/file-serve.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/file-verify.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/file-virusscan.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/file-watermark.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/ftp-import.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/ftp-store.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/google-import.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/google-store.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/html-convert.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/http-import.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/image-bgremove.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/image-describe.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/image-facedetect.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/image-generate.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/image-merge.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/image-ocr.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/image-optimize.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/image-resize.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/meta-read.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/meta-write.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/minio-import.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/minio-store.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/progress-simulate.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/s3-import.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/s3-store.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/script-run.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/sftp-import.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/sftp-store.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/speech-transcribe.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/supabase-import.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/supabase-store.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/swift-import.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/swift-store.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/text-speak.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/text-translate.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/tigris-import.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/tigris-store.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/tlcdn-deliver.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/tus-store.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/upload-handle.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/video-adaptive.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/video-concat.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/video-encode.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/video-merge.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/video-ondemand.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/video-subtitle.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/video-thumbs.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/vimeo-import.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/vimeo-store.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/wasabi-import.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/wasabi-store.ts create mode 100644 packages/transloadit/src/alphalib/types/robots/youtube-store.ts create mode 100644 packages/transloadit/src/alphalib/types/stackVersions.ts create mode 100644 packages/transloadit/src/alphalib/types/template.ts create mode 100644 packages/transloadit/src/alphalib/types/templateCredential.ts create mode 100644 packages/transloadit/src/alphalib/zodParseWithContext.ts create mode 100644 packages/transloadit/src/apiTypes.ts create mode 100644 packages/transloadit/src/cli.ts create mode 100644 packages/transloadit/src/cli/OutputCtl.ts create mode 100644 packages/transloadit/src/cli/commands/BaseCommand.ts create mode 100644 packages/transloadit/src/cli/commands/assemblies.ts create mode 100644 packages/transloadit/src/cli/commands/auth.ts create mode 100644 packages/transloadit/src/cli/commands/bills.ts create mode 100644 packages/transloadit/src/cli/commands/index.ts create mode 100644 packages/transloadit/src/cli/commands/notifications.ts create mode 100644 packages/transloadit/src/cli/commands/templates.ts create mode 100644 packages/transloadit/src/cli/helpers.ts create mode 100644 packages/transloadit/src/cli/template-last-modified.ts create mode 100644 packages/transloadit/src/cli/types.ts create mode 100644 packages/transloadit/src/tus.ts create mode 100644 packages/types/package.json create mode 100644 packages/types/src/index.ts create mode 100644 packages/types/tsconfig.build.json create mode 100644 packages/types/tsconfig.json create mode 100644 packages/zod/package.json create mode 100644 packages/zod/scripts/sync-v3.js create mode 100644 packages/zod/tsconfig.build.json create mode 100644 packages/zod/tsconfig.json create mode 100644 packages/zod/tsconfig.test.json create mode 100644 scripts/prepare-transloadit.js diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 00000000..e2bd8c23 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,3 @@ +# Changesets + +Run `yarn changeset` to add a release note for your change. diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 00000000..9658be22 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json", + "changelog": [ + "@changesets/cli/changelog", + { + "repo": "transloadit/node-sdk" + } + ], + "commit": false, + "fixed": [ + [ + "@transloadit/node", + "transloadit" + ] + ], + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8fba4932..e8542016 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,7 +81,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: coverage-reports - path: coverage/ + path: packages/node/coverage/ e2e: name: E2E tests @@ -111,14 +111,14 @@ jobs: TRANSLOADIT_KEY: ${{ secrets.TRANSLOADIT_KEY }} TRANSLOADIT_SECRET: ${{ secrets.TRANSLOADIT_SECRET }} NODE_OPTIONS: --trace-deprecation --trace-warnings - CLOUDFLARED_PATH: ./cloudflared-linux-amd64 + CLOUDFLARED_PATH: ../cloudflared-linux-amd64 DEBUG: 'transloadit:*' - name: Generate the badge from the json-summary - run: node --experimental-strip-types test/generate-coverage-badge.ts coverage/coverage-summary.json + run: node --experimental-strip-types packages/node/test/generate-coverage-badge.ts packages/node/coverage/coverage-summary.json - name: Move HTML report and badge to the correct location run: | - mv coverage/lcov-report static-build + mv packages/node/coverage/lcov-report static-build mv coverage-badge.svg static-build/ # *** BEGIN PUBLISH STATIC SITE STEPS *** # Use the standard checkout action to check out the destination repo to a separate directory @@ -158,7 +158,7 @@ jobs: uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage/lcov.info + files: ./packages/node/coverage/lcov.info flags: unittests name: node-sdk fail_ci_if_error: true diff --git a/.gitignore b/.gitignore index e1e043e0..d4f73c52 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ sample.js npm-debug.log env.sh /coverage +packages/node/coverage .pnp.* .yarn/* @@ -19,3 +20,5 @@ env.sh .aider* .DS_Store .env +packages/types/src/generated +packages/zod/src/v3 diff --git a/biome.json b/biome.json index aef24a02..c1fea328 100644 --- a/biome.json +++ b/biome.json @@ -7,10 +7,13 @@ "**", "!package.json", "!coverage", + "!**/coverage/**", "!dist", "!fixture", "!.vscode", - "!src/alphalib" + "!packages/**/dist", + "!packages/**/node_modules", + "!packages/node/src/alphalib" ] }, "formatter": { diff --git a/docs/fingerprint/transloadit-after.json b/docs/fingerprint/transloadit-after.json new file mode 100644 index 00000000..7ce4a3ef --- /dev/null +++ b/docs/fingerprint/transloadit-after.json @@ -0,0 +1,2988 @@ +{ + "packageDir": "/home/kvz/code/node-sdk/packages/transloadit", + "tarball": { + "filename": "transloadit-4.1.2.tgz", + "sizeBytes": 1110479, + "sha256": "1f9d45a0d0055c488da28eac563ad79073cedfab16ccf7bd4e5ea9bb50cf655a" + }, + "packageJson": { + "name": "transloadit", + "version": "4.1.2", + "main": "./dist/Transloadit.js", + "exports": { + ".": "./dist/Transloadit.js", + "./package.json": "./package.json" + }, + "files": [ + "dist", + "src" + ] + }, + "files": [ + { + "path": "LICENSE", + "sizeBytes": 1081, + "sha256": "f2ef2628f6aafeca9a1b1230f13c21670af294088cb42d39fb764f8e5d569146" + }, + { + "path": "dist/alphalib/types/robots/_index.js", + "sizeBytes": 27976, + "sha256": "a78ae27fbae9d86e5b49d218a3685fac031cf2f6a6ccae372176f74e73d4af93" + }, + { + "path": "dist/alphalib/types/robots/_instructions-primitives.js", + "sizeBytes": 59552, + "sha256": "a9cb9b01c80fdb77fe5e8874ff58ade6447a725f2012c825ca87d2aaef5a339f" + }, + { + "path": "dist/alphalib/types/robots/ai-chat.js", + "sizeBytes": 9161, + "sha256": "3b750f7fa1d29200895f5b5b789326570fb85c1b65e51bc49bdeee73707dd3e0" + }, + { + "path": "dist/ApiError.js", + "sizeBytes": 1142, + "sha256": "04195425a4e243b510c7532ab81715fab616b626792c78d34cdacfd01b5e001e" + }, + { + "path": "dist/apiTypes.js", + "sizeBytes": 212, + "sha256": "b4f636535a1697010c34d7c2eca37ee2a0646441e3482174a83076a230031f47" + }, + { + "path": "dist/cli/commands/assemblies.js", + "sizeBytes": 41382, + "sha256": "5cf7b811de94982c89bcd5746dd2a25bf3fde22c78a2afb0c738b28de6a42031" + }, + { + "path": "dist/alphalib/types/assembliesGet.js", + "sizeBytes": 1454, + "sha256": "9e597e5a7b157453d64e4076d5eaa3e14af04e0a527faa7fee82e66252524d57" + }, + { + "path": "dist/alphalib/types/robots/assembly-savejson.js", + "sizeBytes": 829, + "sha256": "d5c6fae674126937d25d9f52ec84045c6a90c7c6f33a5b6a0adc96dfa8c94f83" + }, + { + "path": "dist/alphalib/types/assemblyReplay.js", + "sizeBytes": 708, + "sha256": "678ac3ccb4b3d4cfc239bc82315fecf3b6c0ab9830c4262af53094521efe49cd" + }, + { + "path": "dist/alphalib/types/assemblyReplayNotification.js", + "sizeBytes": 648, + "sha256": "99fc21decd44147f9800078b5f2030a5f4572eb939ad0d130e3d1b59aa919e74" + }, + { + "path": "dist/alphalib/types/assemblyStatus.js", + "sizeBytes": 28396, + "sha256": "4466ef1c9ed54e8955cae08480cd7f69d257b92965fa25a32402f1838929e075" + }, + { + "path": "dist/alphalib/types/robots/audio-artwork.js", + "sizeBytes": 3103, + "sha256": "76757ca647e388f1f25b9958d90eee491329ac055e69cc0d44f34f5588e01c32" + }, + { + "path": "dist/alphalib/types/robots/audio-concat.js", + "sizeBytes": 5014, + "sha256": "115fd94cccdb0a79e92b9c6bd0876c8c744d4c9abeba370cf2be6d9ca2e8ac1a" + }, + { + "path": "dist/alphalib/types/robots/audio-encode.js", + "sizeBytes": 2726, + "sha256": "c140e1b162b20e637ea71b3b6448dd4cd12ac7f1f1eb77d9d362458aabda4890" + }, + { + "path": "dist/alphalib/types/robots/audio-loop.js", + "sizeBytes": 2965, + "sha256": "dbe253c901dc7237b4641e5a915a4762b8e06406a5f1472d824ae3aa127e7cdc" + }, + { + "path": "dist/alphalib/types/robots/audio-merge.js", + "sizeBytes": 4445, + "sha256": "afe42b1aeeb4ae4521bdae2f9b66389c3e51a3be31ed6fef5444bf63e661094f" + }, + { + "path": "dist/alphalib/types/robots/audio-waveform.js", + "sizeBytes": 8845, + "sha256": "7303f23c4b4076e6c4df63b591ee5cd3eeef2eb704e6f5f5b09650bacf111c5e" + }, + { + "path": "dist/cli/commands/auth.js", + "sizeBytes": 10038, + "sha256": "58f10fbe648d8a655ececd76da07bfed67b9d737e879ca412f167988a1c164f2" + }, + { + "path": "dist/alphalib/types/robots/azure-import.js", + "sizeBytes": 3151, + "sha256": "6910a81f6a49a8d066f115fe5c01ca858544f1be5e873d69b4005eed8ff149dd" + }, + { + "path": "dist/alphalib/types/robots/azure-store.js", + "sizeBytes": 4221, + "sha256": "f172a4b516cee0ffa08ae7be418062269e1acb38a897758eb53af0ca6d75e6b7" + }, + { + "path": "dist/alphalib/types/robots/backblaze-import.js", + "sizeBytes": 3793, + "sha256": "d485074d56b7ad715a6bafcb24d325ddd82161375777624c2a051605dd08edbc" + }, + { + "path": "dist/alphalib/types/robots/backblaze-store.js", + "sizeBytes": 3067, + "sha256": "894476eb386dab764c1bcf6b432da7fa193181f4206523b1cefac17676fbef1c" + }, + { + "path": "dist/cli/commands/BaseCommand.js", + "sizeBytes": 1895, + "sha256": "1141a59a8ec2f47f6e5d4257b81e44fad8f50d693d1d22d6a0d6a2a08b5f8792" + }, + { + "path": "dist/alphalib/types/bill.js", + "sizeBytes": 223, + "sha256": "3dc47a7b3e5c570bf7ace002fe9438174b553118eee9ff95b5a9170d4c5d904f" + }, + { + "path": "dist/cli/commands/bills.js", + "sizeBytes": 2328, + "sha256": "fee6e43bf67ac5c5b2187300fde17c075e0ffc83a13b04d6485649ce23bd61e3" + }, + { + "path": "dist/cli.js", + "sizeBytes": 1147, + "sha256": "4d52d0cea6f64abe67fd99d9bdf14dee38a51ee9c366eb45f110f38ab008f4dd" + }, + { + "path": "dist/alphalib/types/robots/cloudfiles-import.js", + "sizeBytes": 3721, + "sha256": "e567b21d596f55897ac665f9381eeaae446bbd8b8d0194fe78eced661f90ee7d" + }, + { + "path": "dist/alphalib/types/robots/cloudfiles-store.js", + "sizeBytes": 2774, + "sha256": "4e62566a3bd5f3c721530756d69ffbdebe0f8068c481adc0cab0f89e045bfc98" + }, + { + "path": "dist/alphalib/types/robots/cloudflare-import.js", + "sizeBytes": 4080, + "sha256": "e9c54853a785a5c00c8f505e22f3f945cd916e4cc2ed86fa48df5a7615ceb6ee" + }, + { + "path": "dist/alphalib/types/robots/cloudflare-store.js", + "sizeBytes": 3732, + "sha256": "20620755e2983692110364ca87c5c2a33c682cf5bd0c4f2605fb7778aba0c269" + }, + { + "path": "dist/alphalib/types/robots/digitalocean-import.js", + "sizeBytes": 3732, + "sha256": "27446736ec4af2ed0cbc591d06c54d5ea267463dfdc966293412260fc9d209bc" + }, + { + "path": "dist/alphalib/types/robots/digitalocean-store.js", + "sizeBytes": 3911, + "sha256": "614596c4ce9011844b648692fe95579a630b81c47cf153a35bf5895daddb6930" + }, + { + "path": "dist/alphalib/types/robots/document-autorotate.js", + "sizeBytes": 1855, + "sha256": "f6b173cb57244a55a8b8664e995c4a6ed4fb24496acbbd88b72e236f935747aa" + }, + { + "path": "dist/alphalib/types/robots/document-convert.js", + "sizeBytes": 9264, + "sha256": "48fa4fcf88072fd2b0d5f77efe762fee2a1592696c16b715876856480dffb205" + }, + { + "path": "dist/alphalib/types/robots/document-merge.js", + "sizeBytes": 3113, + "sha256": "48c65c895d8d70c624a1a0485ed2d1330970644ffc253968fbeb1aa7e452dea3" + }, + { + "path": "dist/alphalib/types/robots/document-ocr.js", + "sizeBytes": 4580, + "sha256": "b9210e80eeebdcec9637eabe101d0bf60705b52c62241ad5b8363fd270ace5a3" + }, + { + "path": "dist/alphalib/types/robots/document-split.js", + "sizeBytes": 2052, + "sha256": "1cc694f4eb281b7ea81b3bd13e503d082d510cd7a709d312cd5e6345094e1e1b" + }, + { + "path": "dist/alphalib/types/robots/document-thumbs.js", + "sizeBytes": 9164, + "sha256": "b9b3bee876448a5994fa07b72c503c265ec0c424b720366c3e3f1f458e51106a" + }, + { + "path": "dist/alphalib/types/robots/dropbox-import.js", + "sizeBytes": 2739, + "sha256": "601c199b71199df3b6555751beb56a360601d811a8a101482abeb132dde7ce62" + }, + { + "path": "dist/alphalib/types/robots/dropbox-store.js", + "sizeBytes": 2641, + "sha256": "6cdd30c2c056d0088bada2d9291f161f4586098acdbc1f0b281a122190360287" + }, + { + "path": "dist/alphalib/types/robots/edgly-deliver.js", + "sizeBytes": 1993, + "sha256": "268fedb80d76c3a8406f6ea79ee453ce1b821dc9bdc2bc92977e429aeca9bcc2" + }, + { + "path": "dist/alphalib/types/robots/file-compress.js", + "sizeBytes": 6014, + "sha256": "2ae785374900612d9812ff336cb5de575dd9a3cfe7c07c79c92397f5b58ad8db" + }, + { + "path": "dist/alphalib/types/robots/file-decompress.js", + "sizeBytes": 3815, + "sha256": "ffb6ea41fcb598c2d9ac0931f0eb3a3327ceb78cd76b7975ecb61a797ed48726" + }, + { + "path": "dist/alphalib/types/robots/file-filter.js", + "sizeBytes": 6060, + "sha256": "7a2e6c361c4e23a1357d9771b0b4c953ccebcc0d1ead5f7783afa3042cf2f919" + }, + { + "path": "dist/alphalib/types/robots/file-hash.js", + "sizeBytes": 2160, + "sha256": "da50b20968ed9a1382b6798674d0d09170ddd27ecafba053c8f0cebd3959968c" + }, + { + "path": "dist/alphalib/types/robots/file-preview.js", + "sizeBytes": 12783, + "sha256": "167e45afe25e777baedeeff632cf90d394529a8c6fec6a891619b56257c26333" + }, + { + "path": "dist/alphalib/types/robots/file-read.js", + "sizeBytes": 1794, + "sha256": "990503f1b1c09b3de0220ed00539cc8b3bf8d01bb7b8a8f2da83c3d2f0d20981" + }, + { + "path": "dist/alphalib/types/robots/file-serve.js", + "sizeBytes": 5843, + "sha256": "1da5e06b7a0df96856d6e4f5eee1738f22d9df80a1f8634e0ddf5b0146e6a7ac" + }, + { + "path": "dist/alphalib/types/robots/file-verify.js", + "sizeBytes": 3481, + "sha256": "7400f95c002a9709a3815d1bd6ce7fe929821eb97178e4a2b9ddf66f6ebe827b" + }, + { + "path": "dist/alphalib/types/robots/file-virusscan.js", + "sizeBytes": 3859, + "sha256": "d47518d1d345ace571c9eaaad3e15b49ab4267fd7fb7dfed3d3c9539a4cffca9" + }, + { + "path": "dist/alphalib/types/robots/file-watermark.js", + "sizeBytes": 1228, + "sha256": "474e8f93000f842761a1cebe9282c17eeba8c809f1d8ef25db026796edacbf89" + }, + { + "path": "dist/alphalib/types/robots/ftp-import.js", + "sizeBytes": 2406, + "sha256": "ed845f664e1ae7ab2cf8bf3c061f294f0926bb8889935afefa38e056f98e6526" + }, + { + "path": "dist/alphalib/types/robots/ftp-store.js", + "sizeBytes": 3534, + "sha256": "c4bd648bb097acadbc349406192105367b9d94c516700b99c9f4d7a4b6c7a6f0" + }, + { + "path": "dist/alphalib/types/robots/google-import.js", + "sizeBytes": 3748, + "sha256": "0688e2f84f217ae26b187916b93c6e4f32539c0fc84b6cb162ea2230cd81ae27" + }, + { + "path": "dist/alphalib/types/robots/google-store.js", + "sizeBytes": 5495, + "sha256": "c35a94120a06d17559df3ddf18a18a7d6a89a858ff4cdff4c12a6a3d1dec17ed" + }, + { + "path": "dist/cli/helpers.js", + "sizeBytes": 1239, + "sha256": "3d5c40d5c39207a606a545b1492ab53ebdfbe1592cb66e0b1493de870bbdb6af" + }, + { + "path": "dist/alphalib/types/robots/html-convert.js", + "sizeBytes": 5294, + "sha256": "17c47da30d42ce1209bcb0c53dddc14c119a6c202ae8f0f27afaaf0927a2c72c" + }, + { + "path": "dist/alphalib/types/robots/http-import.js", + "sizeBytes": 5758, + "sha256": "6257ae9fa7e6c9ef61cfd9be9b9f807da82caeaabf7bfb381da61948376a36ee" + }, + { + "path": "dist/alphalib/types/robots/image-bgremove.js", + "sizeBytes": 2515, + "sha256": "0016427ea42441dfba53c65ece6a30586456b14818dafc48d8f025ff910697c2" + }, + { + "path": "dist/alphalib/types/robots/image-describe.js", + "sizeBytes": 4568, + "sha256": "8f1f5d50e461b9ec9f223fa894e278a2fb8198823fbdd8a8cae6ef7bfd50ff5d" + }, + { + "path": "dist/alphalib/types/robots/image-facedetect.js", + "sizeBytes": 6408, + "sha256": "b8b19422756cfe35d038bccd7167fcaee1935932df441de68d9d16598a6c3141" + }, + { + "path": "dist/alphalib/types/robots/image-generate.js", + "sizeBytes": 2681, + "sha256": "85713e4db98b326fca7530d3f50f8fea2acc3bb56c10582f63cb85bc3ceaadf6" + }, + { + "path": "dist/alphalib/types/robots/image-merge.js", + "sizeBytes": 3712, + "sha256": "23c06084dfe66c3ebdbf11f7c93de2e928112814507987902520e0695d2fe6a5" + }, + { + "path": "dist/alphalib/types/robots/image-ocr.js", + "sizeBytes": 4316, + "sha256": "fe040da6c69082e831c8897389f2c32b13f488714ae0d9e97685865176c6ddce" + }, + { + "path": "dist/alphalib/types/robots/image-optimize.js", + "sizeBytes": 4187, + "sha256": "d9d455acad58e028da5c948b735025943ee07b112338c3b66933ed0ff4db9e54" + }, + { + "path": "dist/alphalib/types/robots/image-resize.js", + "sizeBytes": 27934, + "sha256": "7683dca61e77618aad347431b7693fac282d208526dde351ba86387a53c962f4" + }, + { + "path": "dist/InconsistentResponseError.js", + "sizeBytes": 158, + "sha256": "ed9fa27d9022fa08f620bb0f94cc17222c35301dd9bda8bfc59db669c9262ada" + }, + { + "path": "dist/cli/commands/index.js", + "sizeBytes": 1730, + "sha256": "f9b0afff030bd07795df879b5bf78d75ede571973a6a671803c7551b6c3e87c9" + }, + { + "path": "dist/alphalib/mcache.js", + "sizeBytes": 4515, + "sha256": "abcc5fb21d05f7c04bb7c454bdbce15c25f7d4fec03b901291bc8b2925e95d16" + }, + { + "path": "dist/alphalib/types/robots/meta-read.js", + "sizeBytes": 1119, + "sha256": "94bd6bb2e45f20009fffbf2d0395ac702ac4295c1b7e5088ae7cb12e1bdaeb5e" + }, + { + "path": "dist/alphalib/types/robots/meta-write.js", + "sizeBytes": 2446, + "sha256": "5bb3b6372ca87e8a18aa0a3c41a50f7cee87cafdca8e95a885b4de83dbf4611a" + }, + { + "path": "dist/alphalib/types/robots/minio-import.js", + "sizeBytes": 3985, + "sha256": "053ed2a56f66ceb50e780cf4478a6a66c1e2503d0f0f295252beeaaa8a06e1b1" + }, + { + "path": "dist/alphalib/types/robots/minio-store.js", + "sizeBytes": 3504, + "sha256": "6194fc2e78f65aa48c1d5c85d7c9a488e501436f8ef6758077aaa0da8bc44185" + }, + { + "path": "dist/alphalib/lib/nativeGlobby.js", + "sizeBytes": 6315, + "sha256": "33e19cc01c00058e2c46866a7dddc29769edffaab132f412d671f6f52a4be380" + }, + { + "path": "dist/cli/commands/notifications.js", + "sizeBytes": 1640, + "sha256": "5a419d28577d2952f32282ff0227ffa2cfbec6f4c3a48a6974b5485453297e44" + }, + { + "path": "dist/cli/OutputCtl.js", + "sizeBytes": 2838, + "sha256": "ccd55e24ca9b05134aeb5051b2b17a9b1dc181bff73b875f63ccde8071564630" + }, + { + "path": "dist/PaginationStream.js", + "sizeBytes": 1049, + "sha256": "955003e8e346cb275a55989da117fc99d7fb17ad2f2282e02b1940c6b3fceb85" + }, + { + "path": "dist/PollingTimeoutError.js", + "sizeBytes": 172, + "sha256": "bef858dea74a3ac0a03e6d595e6276d63fe272f9cb601d3101eeec8723ebb48a" + }, + { + "path": "dist/alphalib/types/robots/progress-simulate.js", + "sizeBytes": 935, + "sha256": "e01935073eab55214d9e37fa2d25e5615368efb8e9e2aedfa7a765e0d6e2bd84" + }, + { + "path": "dist/alphalib/types/robots/s3-import.js", + "sizeBytes": 8446, + "sha256": "d2abd1d554916505892242fb68b64bcc29350963f97808ccd57e047f487bb00a" + }, + { + "path": "dist/alphalib/types/robots/s3-store.js", + "sizeBytes": 9698, + "sha256": "7ac1cebb40a5959581740147f7ef1e3d680199a9f9e39a3562e6f818fbc5a0cb" + }, + { + "path": "dist/alphalib/types/robots/script-run.js", + "sizeBytes": 3740, + "sha256": "94f608e168909cf2dbb588d4c9c591921fbc2c13d68e11c51784e08b1588649c" + }, + { + "path": "dist/alphalib/types/robots/sftp-import.js", + "sizeBytes": 2340, + "sha256": "453005b909a864b3a6abf2714232e45aa5f7c08303579454301d3e80f2ad8e97" + }, + { + "path": "dist/alphalib/types/robots/sftp-store.js", + "sizeBytes": 3455, + "sha256": "4c023f0931db25b7d99da7f56828d5d7d2132c6e467ceda0bb764a0ec21d2555" + }, + { + "path": "dist/alphalib/types/robots/speech-transcribe.js", + "sizeBytes": 5236, + "sha256": "e60ffe357e734f4031d64f6574d885f009eedd62b77ab4052318f23a35ef8a4e" + }, + { + "path": "dist/alphalib/types/stackVersions.js", + "sizeBytes": 359, + "sha256": "44173300fc46f06c80f670d5a3a72e403cfcaddd7eb60703bc057ece42292ece" + }, + { + "path": "dist/alphalib/types/robots/supabase-import.js", + "sizeBytes": 4131, + "sha256": "774aacd11972e02c5f07217c43dc5a5c553c28191c36ea35e1f276019cbe395b" + }, + { + "path": "dist/alphalib/types/robots/supabase-store.js", + "sizeBytes": 3326, + "sha256": "8d0a8f42a2b26ff4db7a4ff9053bebd4ca9c772df64ffff60b8a433e055ffcea" + }, + { + "path": "dist/alphalib/types/robots/swift-import.js", + "sizeBytes": 4025, + "sha256": "96c59605b04963ad242f8f689b20149d07fd9216d936f097e0ee9646f52411f3" + }, + { + "path": "dist/alphalib/types/robots/swift-store.js", + "sizeBytes": 3574, + "sha256": "0d8af15a6f01c57651930e010d9190341d5ca41be81565b5c10549e21406c3c7" + }, + { + "path": "dist/cli/template-last-modified.js", + "sizeBytes": 4183, + "sha256": "26fb01b74d324df74e120cd7640a64d221214879e44dd695903602afbc01ee58" + }, + { + "path": "dist/alphalib/types/template.js", + "sizeBytes": 10452, + "sha256": "b48fbb82af77032c3076c5016410f251759ad295344557215753d931679c0679" + }, + { + "path": "dist/alphalib/types/templateCredential.js", + "sizeBytes": 1423, + "sha256": "02c74c8b94d3514c65c86af727ccf69acf6f3ef1cac184eea35aab75bd0b554f" + }, + { + "path": "dist/cli/commands/templates.js", + "sizeBytes": 15694, + "sha256": "fc2e8b636bf2f3d6c61bca5d11cf54acdf9f2e23d57d608cf282c94e2f9ea984" + }, + { + "path": "dist/alphalib/types/robots/text-speak.js", + "sizeBytes": 4762, + "sha256": "9021afe8eee26c0a33cbaf894e6151bce60073c8d22a40dab0ef8db8ae37223d" + }, + { + "path": "dist/alphalib/types/robots/text-translate.js", + "sizeBytes": 5413, + "sha256": "e6619ba063df5a848d3f80193b7f3fd427d223d8ea6863f144fdf7ffa2cd6643" + }, + { + "path": "dist/alphalib/types/robots/tigris-import.js", + "sizeBytes": 4164, + "sha256": "1329c999c4e31bf4f91aa76b4376b642367e91cbb2da69914166a83cfe05a899" + }, + { + "path": "dist/alphalib/types/robots/tigris-store.js", + "sizeBytes": 3678, + "sha256": "ebba4a6bfdf08283da83254b89baed774aa8080f40f97e93b90c2831b58461dc" + }, + { + "path": "dist/alphalib/types/robots/tlcdn-deliver.js", + "sizeBytes": 1988, + "sha256": "84f79f7a6d04e6330a7c032d609db16748ece8d750ca3f3023eed43a440f4b15" + }, + { + "path": "dist/Transloadit.js", + "sizeBytes": 28131, + "sha256": "8a9f68704b6dfaef019f10a79fbcc967034e6be6787e3f112c2ee480ae1ba5d3" + }, + { + "path": "dist/alphalib/tryCatch.js", + "sizeBytes": 447, + "sha256": "822422b495de06b013adca2c952371b85c5ce27f05058112384eec0781d7b80b" + }, + { + "path": "dist/alphalib/types/robots/tus-store.js", + "sizeBytes": 4596, + "sha256": "b845028fc26a96ba0509b0f8dc2444bed647144ad2415d167bd495df84d3217b" + }, + { + "path": "dist/tus.js", + "sizeBytes": 5058, + "sha256": "1f287a9083e9264f509833a1193f8cf4fae161cfa6d126ac3f81849f05ca3bc9" + }, + { + "path": "dist/cli/types.js", + "sizeBytes": 1433, + "sha256": "26181f39ef63756230a5ce4a3746b745e07949246d1ec72306f9960207f8bdeb" + }, + { + "path": "dist/alphalib/types/robots/upload-handle.js", + "sizeBytes": 2710, + "sha256": "0da0cf7c28a54af82ac125af0129f885b111b9e48cd64c477865d5438e29974d" + }, + { + "path": "dist/alphalib/types/robots/video-adaptive.js", + "sizeBytes": 6059, + "sha256": "6f2630b0d877d9c3ec398535231ef25cb31d3916e4613eb4b406cba8bc334613" + }, + { + "path": "dist/alphalib/types/robots/video-concat.js", + "sizeBytes": 4707, + "sha256": "fa977c68900d0417506fabc3a2b15dd96571374da707e0f79cfd48e9612e82b9" + }, + { + "path": "dist/alphalib/types/robots/video-encode.js", + "sizeBytes": 4357, + "sha256": "39dc586629c3715b6b9d8534477b79f82d790b052979d09cdd942ea9767f93a2" + }, + { + "path": "dist/alphalib/types/robots/video-merge.js", + "sizeBytes": 5568, + "sha256": "eb7b72fe71fa99aabcd6d83b24f836f1642447d6b8f2d42126698a1b3ee22669" + }, + { + "path": "dist/alphalib/types/robots/video-ondemand.js", + "sizeBytes": 4856, + "sha256": "b3a449cba726f0256be971a52f445f4319b6fe98300782bfae82417f4c910701" + }, + { + "path": "dist/alphalib/types/robots/video-subtitle.js", + "sizeBytes": 4789, + "sha256": "154d14545e5ea067928b4310e904f4c3ebab0be18f2e57fa4afbe252df679063" + }, + { + "path": "dist/alphalib/types/robots/video-thumbs.js", + "sizeBytes": 5469, + "sha256": "f25a86957a6a6a8a0eadd4e5db493405528c0d3aedb4576ddef9d0b0bc1394d3" + }, + { + "path": "dist/alphalib/types/robots/vimeo-import.js", + "sizeBytes": 3486, + "sha256": "7877ea6f6f225d50cb3767c5dd9efa67a08f867ca13ea534939682fc1a65c109" + }, + { + "path": "dist/alphalib/types/robots/vimeo-store.js", + "sizeBytes": 4410, + "sha256": "68f6b9618c990bc00f822a0e61f87ac8972ea0ebb2f795a13f2dd0f5b9e39a4d" + }, + { + "path": "dist/alphalib/types/robots/wasabi-import.js", + "sizeBytes": 4149, + "sha256": "f0886f787e0b56a07bed3abdb6e9e1b142bbc9f6d422ad060426e7f343329cb8" + }, + { + "path": "dist/alphalib/types/robots/wasabi-store.js", + "sizeBytes": 3524, + "sha256": "59f53885f81e1ca96733076d59492b4ea16e090185f98a02f1182d57ceb71930" + }, + { + "path": "dist/alphalib/types/robots/youtube-store.js", + "sizeBytes": 4838, + "sha256": "cc711980ff82e0050c63b28e04714c82b38856d785ac5f9b08eefa89f4a1bd15" + }, + { + "path": "dist/alphalib/zodParseWithContext.js", + "sizeBytes": 13900, + "sha256": "95686fd259cf628f479d483dff11edf5ec4bceb75f9780e079fa2444948260fe" + }, + { + "path": "package.json", + "sizeBytes": 2648, + "sha256": "a2fd83a1adc245ade4d1a376c3d8c92ba286e73412afb89fa5d8ef31212f560d" + }, + { + "path": "dist/alphalib/types/robots/_index.d.ts.map", + "sizeBytes": 83925, + "sha256": "4a58b2d2526d61cf04de1f64cd577281c16010c6c209bf03e2c9472d78e17ca1" + }, + { + "path": "dist/alphalib/types/robots/_index.js.map", + "sizeBytes": 10505, + "sha256": "0a08c1185a523b944b5beed2f8d4148ead599a31bae1233e7a4fe38f9678fc84" + }, + { + "path": "dist/alphalib/types/robots/_instructions-primitives.d.ts.map", + "sizeBytes": 10500, + "sha256": "ca5baf2a027f31493bb771f8bbdd9e7986ef3b5ad28013316c2fa0fedcce1eca" + }, + { + "path": "dist/alphalib/types/robots/_instructions-primitives.js.map", + "sizeBytes": 36172, + "sha256": "3436569bb5194b67b6f7606f4c7225aae7abf35285d4beab3196ed2ff7e30790" + }, + { + "path": "dist/alphalib/types/robots/ai-chat.d.ts.map", + "sizeBytes": 3190, + "sha256": "6921ce50d888101af8bd473fa0c52780df529fba30531295810ec191267a47f9" + }, + { + "path": "dist/alphalib/types/robots/ai-chat.js.map", + "sizeBytes": 7431, + "sha256": "38d154af5baea41967ac69fa5fa725189006ff7afcaaf098cde2178fb340e9c2" + }, + { + "path": "dist/ApiError.d.ts.map", + "sizeBytes": 669, + "sha256": "0f8015ffaa115fe02d3877267c7e236c3ad8b98c2e7bf9f5a66a389464ecdc62" + }, + { + "path": "dist/ApiError.js.map", + "sizeBytes": 1182, + "sha256": "b14ea886615cd781bf6d852ad60154339e06aa8541d70c1a352115807ea8af52" + }, + { + "path": "dist/apiTypes.d.ts.map", + "sizeBytes": 3499, + "sha256": "afccc6e3b08e1a87a42a680ffa1439d4cf0e883a1ebc3b19832b2f25db939987" + }, + { + "path": "dist/apiTypes.js.map", + "sizeBytes": 210, + "sha256": "74dee10f68f3060119affdbe0db6d1ad4cdc8a75b9345bc7107e071da1d69010" + }, + { + "path": "dist/cli/commands/assemblies.d.ts.map", + "sizeBytes": 3081, + "sha256": "3fbad0cc1518bfc5daa6aa1ac0ae071d9ee6d7559b655f1fdc03af4a6fc2b459" + }, + { + "path": "dist/cli/commands/assemblies.js.map", + "sizeBytes": 37213, + "sha256": "81900715bbe8def1da2bbaec42f51b311a083e7382daab88a3f4a899998cf4e9" + }, + { + "path": "dist/alphalib/types/assembliesGet.d.ts.map", + "sizeBytes": 263, + "sha256": "df2b0c48f851d217f2c2d31c8eb287c5eef4864eacaa0850fd3ac340a632487b" + }, + { + "path": "dist/alphalib/types/assembliesGet.js.map", + "sizeBytes": 961, + "sha256": "5295095d35108254c8b002333cf3d4d82d7d64d19be93fad049358ab1e78f217" + }, + { + "path": "dist/alphalib/types/robots/assembly-savejson.d.ts.map", + "sizeBytes": 573, + "sha256": "11ae78232682c6feb764982cb9c5166dd9660bad8f1fbfa26e9884a54daa2a66" + }, + { + "path": "dist/alphalib/types/robots/assembly-savejson.js.map", + "sizeBytes": 701, + "sha256": "1410553d8a5f26d188bb58de38ce04123e63f2d5d749bbfd0fe6495d84dd0078" + }, + { + "path": "dist/alphalib/types/assemblyReplay.d.ts.map", + "sizeBytes": 8129, + "sha256": "ac663fe8af49ddf31ac35347b4f1052c5234b98fb206fc7314cb18669fa4e0d5" + }, + { + "path": "dist/alphalib/types/assemblyReplay.js.map", + "sizeBytes": 612, + "sha256": "e3515c0e8c7af60e5655c8e8befc9d9456f0951c5db08b9fdbdc2753198a0719" + }, + { + "path": "dist/alphalib/types/assemblyReplayNotification.d.ts.map", + "sizeBytes": 8144, + "sha256": "aafb363bad7c9ae2ec4a94a79073be7b80f4e81ef7efe73e0e5b1ed11e853399" + }, + { + "path": "dist/alphalib/types/assemblyReplayNotification.js.map", + "sizeBytes": 477, + "sha256": "912872cd7b257168bb487687c7ca898633269f43849a8c36ac25629002604452" + }, + { + "path": "dist/alphalib/types/assemblyStatus.d.ts.map", + "sizeBytes": 73453, + "sha256": "51782d698296dcc8a71e651ab5c562b5a6939fc83fadf70538ddf0a6fda22532" + }, + { + "path": "dist/alphalib/types/assemblyStatus.js.map", + "sizeBytes": 31739, + "sha256": "d915945fb5cdcae71448b0baa4ad7a6ebd22848f5112858bb326920d8794be42" + }, + { + "path": "dist/alphalib/types/robots/audio-artwork.d.ts.map", + "sizeBytes": 3653, + "sha256": "353a21c0c3103024565abd36aa4767d7b67e04c55916ec2f34993c7aefd01118" + }, + { + "path": "dist/alphalib/types/robots/audio-artwork.js.map", + "sizeBytes": 1835, + "sha256": "8b45f9bcc11be303e408e50c56932d3747bc80c515eaad50fe9b9fc9de32b137" + }, + { + "path": "dist/alphalib/types/robots/audio-concat.d.ts.map", + "sizeBytes": 3700, + "sha256": "11a6b27a252899506dd9a75539c82cc65a94ad95057187cb7d60fd27dcbfd1b8" + }, + { + "path": "dist/alphalib/types/robots/audio-concat.js.map", + "sizeBytes": 2368, + "sha256": "cf43600aa6b1131695f23728291f4550688701f5aad2430631004c4b982a0df0" + }, + { + "path": "dist/alphalib/types/robots/audio-encode.d.ts.map", + "sizeBytes": 3648, + "sha256": "00bcc0475f9cd159b74fabff5ef5a9ec8659929d49dc01f660aa2214e6980c81" + }, + { + "path": "dist/alphalib/types/robots/audio-encode.js.map", + "sizeBytes": 1853, + "sha256": "170083f1b2e97951fe027d364d0508fb9be83f199e755f6c2dba565575b85866" + }, + { + "path": "dist/alphalib/types/robots/audio-loop.d.ts.map", + "sizeBytes": 3658, + "sha256": "506a308f01cfd07b72266b26c4ca70d439135e5333368273612fff8501061237" + }, + { + "path": "dist/alphalib/types/robots/audio-loop.js.map", + "sizeBytes": 1859, + "sha256": "e3c8723e8e8a6456269c5aa19f041beb95eecfb7077bebab05228fa42a0b01ef" + }, + { + "path": "dist/alphalib/types/robots/audio-merge.d.ts.map", + "sizeBytes": 3713, + "sha256": "200ce1e8d4be67a04522707ccb648b918f465f47cea81d253e282a3959d51bb4" + }, + { + "path": "dist/alphalib/types/robots/audio-merge.js.map", + "sizeBytes": 2434, + "sha256": "72aa8bf105e43533deb5a5f3aa3e7865120d9e1b0b92b1f0e7f1f5733be2b0bd" + }, + { + "path": "dist/alphalib/types/robots/audio-waveform.d.ts.map", + "sizeBytes": 3950, + "sha256": "8d1bc9e02a68d4d18d07a8fe5d55c9d1f8bcb5c61978b677deb0fb32f6ddd562" + }, + { + "path": "dist/alphalib/types/robots/audio-waveform.js.map", + "sizeBytes": 4691, + "sha256": "ef7f6f12ae396db830f8c92314712cfd958697a711a26725f6b2281019b7e170" + }, + { + "path": "dist/cli/commands/auth.d.ts.map", + "sizeBytes": 749, + "sha256": "a8fc5875ca97a6dbdb65b7830479c4c4cd9f5949fe4a8c95106700705439e966" + }, + { + "path": "dist/cli/commands/auth.js.map", + "sizeBytes": 9049, + "sha256": "2b239bbfbe26870ca1fb542220842283a0b391267cedbecf298fff39bc8e0da1" + }, + { + "path": "dist/alphalib/types/robots/azure-import.d.ts.map", + "sizeBytes": 994, + "sha256": "f176be84e91088b839e5769a7a249eb0f25ebcb67fde7db712997f46c2c32bd5" + }, + { + "path": "dist/alphalib/types/robots/azure-import.js.map", + "sizeBytes": 1772, + "sha256": "1a699e6491c85a6cea6a6dfd2de985fa5a5b2aef4fb99f92093dab8085c193eb" + }, + { + "path": "dist/alphalib/types/robots/azure-store.d.ts.map", + "sizeBytes": 1344, + "sha256": "24846cd482aa230e9c961d7032bdf5e3e02c21139ad6d4454c46d1dfa9acc94b" + }, + { + "path": "dist/alphalib/types/robots/azure-store.js.map", + "sizeBytes": 2407, + "sha256": "58b0f3158aca067ae6e398a5f4a37935907ce2916ac15e172dcef0ead8d9afcb" + }, + { + "path": "dist/alphalib/types/robots/backblaze-import.d.ts.map", + "sizeBytes": 1003, + "sha256": "c22ab6d436286cb3e0ba6fc739efe5e9a4c9fc073f3d7dc483c8cf2876f5e819" + }, + { + "path": "dist/alphalib/types/robots/backblaze-import.js.map", + "sizeBytes": 1838, + "sha256": "5328598e0fdf883487259800873e9155ebbe3458b5e13eb35247519a9f258e95" + }, + { + "path": "dist/alphalib/types/robots/backblaze-store.d.ts.map", + "sizeBytes": 1268, + "sha256": "b16532e497d1282929b3d743db600f4463052fdafc7b0c953371c86295efcda8" + }, + { + "path": "dist/alphalib/types/robots/backblaze-store.js.map", + "sizeBytes": 1779, + "sha256": "18a983d205a1a89ba39f80ed3f2f9c0af3c5b0bfbda262b09b0fa5510a82495b" + }, + { + "path": "dist/cli/commands/BaseCommand.d.ts.map", + "sizeBytes": 853, + "sha256": "22f956bf0d909d109be49926c456031c783bafccc688056d1911b7440cdf77c3" + }, + { + "path": "dist/cli/commands/BaseCommand.js.map", + "sizeBytes": 1740, + "sha256": "14ec91bb62f94bc02328d085cf1d6b7541f3e17d10703c39f85df40969cd84eb" + }, + { + "path": "dist/alphalib/types/bill.d.ts.map", + "sizeBytes": 233, + "sha256": "100418a9cbbb497bd3deb4fba29c4bff7cd202c869085f2ac1cd1293d403548a" + }, + { + "path": "dist/alphalib/types/bill.js.map", + "sizeBytes": 308, + "sha256": "6e2138660b44631f62207483c146991bd6e0e010067d85af9109d72f89224953" + }, + { + "path": "dist/cli/commands/bills.d.ts.map", + "sizeBytes": 585, + "sha256": "54258ccf4730a4b0989883ab5a4b67b5deb7e7fba3a25581743a20a9fc8bfa82" + }, + { + "path": "dist/cli/commands/bills.js.map", + "sizeBytes": 2277, + "sha256": "15f2a633092558d16a9026ac829a4458e6ff4e3f4b51f5277c40f9785df82cc0" + }, + { + "path": "dist/cli.d.ts.map", + "sizeBytes": 278, + "sha256": "5e6f1a916256a81fdc3e6678644c191a87bf1bbcb273e0e256a1e04533c045cd" + }, + { + "path": "dist/cli.js.map", + "sizeBytes": 1335, + "sha256": "aa838fe53a894d7c2eca041e15963a3ddb50e3bf127911f472da237aec07ae56" + }, + { + "path": "dist/alphalib/types/robots/cloudfiles-import.d.ts.map", + "sizeBytes": 1029, + "sha256": "0869484ee48ae682859788c6ccad011fcc16dab0854a6d6a727cd13a0cfb0902" + }, + { + "path": "dist/alphalib/types/robots/cloudfiles-import.js.map", + "sizeBytes": 1821, + "sha256": "067afacca012fb1d96267aa96370f459b1d85e75727922874910a5edcc07bc50" + }, + { + "path": "dist/alphalib/types/robots/cloudfiles-store.d.ts.map", + "sizeBytes": 1282, + "sha256": "71887d8900734a6c6bf3964018d0143135d016740daf537ddd2d6546a70dd3d4" + }, + { + "path": "dist/alphalib/types/robots/cloudfiles-store.js.map", + "sizeBytes": 1651, + "sha256": "7631f3c8361b3ed5315a26310954e49fd81db2c5a091954087006ba3efb9053b" + }, + { + "path": "dist/alphalib/types/robots/cloudflare-import.d.ts.map", + "sizeBytes": 1029, + "sha256": "ce4aab1f1c2d1be7f7fadf750582c137edd423c1839fb18581539b548184795f" + }, + { + "path": "dist/alphalib/types/robots/cloudflare-import.js.map", + "sizeBytes": 1839, + "sha256": "cb3a961fbf7e141e32692dcce6f94a5c37cba356f0f27e92b97d5bb4eca04da9" + }, + { + "path": "dist/alphalib/types/robots/cloudflare-store.d.ts.map", + "sizeBytes": 1307, + "sha256": "32f6c9187a32831b74c67f07d415f8e5132f7d7f5be73e3e42985f6bfa9ba7dd" + }, + { + "path": "dist/alphalib/types/robots/cloudflare-store.js.map", + "sizeBytes": 1990, + "sha256": "c62aed0ef67118db1bdf93e63a6159295e2dd818c1ee912a25ce54be09089361" + }, + { + "path": "dist/alphalib/types/robots/digitalocean-import.d.ts.map", + "sizeBytes": 1033, + "sha256": "b4ca8b8b29285f0ded9587aa9f66146d2feef4aa22f4fb04dc505e51074d0989" + }, + { + "path": "dist/alphalib/types/robots/digitalocean-import.js.map", + "sizeBytes": 1825, + "sha256": "a79d68e6246b971ce82c154c3f9d4922d8dc887a2387f369ae46dbc5ba0ce531" + }, + { + "path": "dist/alphalib/types/robots/digitalocean-store.d.ts.map", + "sizeBytes": 1323, + "sha256": "5516c371bf38b3d1e4ea87ee409602c49848c3f9891fb2a67648d61fa83a4829" + }, + { + "path": "dist/alphalib/types/robots/digitalocean-store.js.map", + "sizeBytes": 2110, + "sha256": "03bf0e17cc0b4752df73ccdf82aa4ca27e6f5455448d3b5469fb440649eb88dd" + }, + { + "path": "dist/alphalib/types/robots/document-autorotate.d.ts.map", + "sizeBytes": 1203, + "sha256": "2b806b760baa93da32c9ed78d7bf3ff51a2044d08e80e450a366912d7ad7bba2" + }, + { + "path": "dist/alphalib/types/robots/document-autorotate.js.map", + "sizeBytes": 1362, + "sha256": "1e7adcd6fc7ca833092b64c568d60775e939b70b5382c8fc20ae2dd4bcdc2cfe" + }, + { + "path": "dist/alphalib/types/robots/document-convert.d.ts.map", + "sizeBytes": 1306, + "sha256": "ce5ed754a73b39ad8143b4faef3d4132418bf8ffe09e0a294802834c31ac4b72" + }, + { + "path": "dist/alphalib/types/robots/document-convert.js.map", + "sizeBytes": 3091, + "sha256": "35966b6a021abdebdd4d232b6163c4fc9f951eaf7d5c57c3892da71a0cd26736" + }, + { + "path": "dist/alphalib/types/robots/document-merge.d.ts.map", + "sizeBytes": 1217, + "sha256": "c395a64c42eeae620a4522bb6ecce832f885485fee9ac7210bb7ce6c89be8b28" + }, + { + "path": "dist/alphalib/types/robots/document-merge.js.map", + "sizeBytes": 1738, + "sha256": "a058ab91732d822f8044c61040d69d12bc3903e354f34793b91bd72e6695549c" + }, + { + "path": "dist/alphalib/types/robots/document-ocr.d.ts.map", + "sizeBytes": 1225, + "sha256": "18c3a595151d7ede4e0a308bc61cb17c7f7fb5156ae93992868aea16b0d4f307" + }, + { + "path": "dist/alphalib/types/robots/document-ocr.js.map", + "sizeBytes": 1829, + "sha256": "a7b6169af7fb9b26d919cdab1b4b557f0134c5e6a15c53065e8168fd9dfd37e3" + }, + { + "path": "dist/alphalib/types/robots/document-split.d.ts.map", + "sizeBytes": 1204, + "sha256": "c19d3a5ce5d0d7876c1fdb06ae523cc8544c005f2299e0e38a78edea1148eaca" + }, + { + "path": "dist/alphalib/types/robots/document-split.js.map", + "sizeBytes": 1516, + "sha256": "0755d47cbbf71c6a78d9a858ff7e6602f6da2bf6482bf7e6b0adb6a43c779b05" + }, + { + "path": "dist/alphalib/types/robots/document-thumbs.d.ts.map", + "sizeBytes": 1383, + "sha256": "2243134191a54be4ff5f64897a9c50b6bff40a6d8539ab37e641dee1e78adfd2" + }, + { + "path": "dist/alphalib/types/robots/document-thumbs.js.map", + "sizeBytes": 3582, + "sha256": "18c7fccfe0abf172d679805454b84ef121c88c7daffb0172f6d4a2cf04a728a1" + }, + { + "path": "dist/alphalib/types/robots/dropbox-import.d.ts.map", + "sizeBytes": 931, + "sha256": "0f1b530229aad380249ed2cdced0fc3ba4dea4bb25bbdeba5196d1b25b83ca1f" + }, + { + "path": "dist/alphalib/types/robots/dropbox-import.js.map", + "sizeBytes": 1672, + "sha256": "acd1432e1b99bf224913f5cd9e827ace7949a2fc2f12118a8cd7ab5d635f22c1" + }, + { + "path": "dist/alphalib/types/robots/dropbox-store.d.ts.map", + "sizeBytes": 1304, + "sha256": "2929109a87204cf98b4c5bfd411ac7a764028314d02bbb290d2e4dfe79bf812c" + }, + { + "path": "dist/alphalib/types/robots/dropbox-store.js.map", + "sizeBytes": 1785, + "sha256": "d0f7854dfb91540f64c238ca9bee2a6100b84b1239ea6dfa4601e6308ff7ca7b" + }, + { + "path": "dist/alphalib/types/robots/edgly-deliver.d.ts.map", + "sizeBytes": 885, + "sha256": "8f9200e8e330adeea424d735f663ad757326e3662fdb166a44bad09b3443ee8f" + }, + { + "path": "dist/alphalib/types/robots/edgly-deliver.js.map", + "sizeBytes": 1366, + "sha256": "bf5b2ae88cc181f02afaf1ed026a327b8f1d3b4cec26ee2b87cb63d7dee4f17d" + }, + { + "path": "dist/alphalib/types/robots/file-compress.d.ts.map", + "sizeBytes": 1263, + "sha256": "67e26c961677ec63ae7b838ac54ce4ef92897bb844e32ce28980ba28e1a81593" + }, + { + "path": "dist/alphalib/types/robots/file-compress.js.map", + "sizeBytes": 2246, + "sha256": "6db06343a6a9606037cdd68c8aeec52f9db1d6abf621dac94acfd13186309c71" + }, + { + "path": "dist/alphalib/types/robots/file-decompress.d.ts.map", + "sizeBytes": 1194, + "sha256": "e3167146144669a49864af77a2844095e7a704fe8d6c3d138da2e2482fce4329" + }, + { + "path": "dist/alphalib/types/robots/file-decompress.js.map", + "sizeBytes": 1875, + "sha256": "8b480292b5374d7c2eed61ecb7ba958742b710b466a6ca6f8b3ad00ebb318792" + }, + { + "path": "dist/alphalib/types/robots/file-filter.d.ts.map", + "sizeBytes": 1247, + "sha256": "fbed0076ff28a613a9c4f8b6e5941058729b3aa39c196dccc8af789625d2eea3" + }, + { + "path": "dist/alphalib/types/robots/file-filter.js.map", + "sizeBytes": 2086, + "sha256": "a9bb479e9cc2816253fe182250b0b4fbc337301f361b13b8d632286501c2f377" + }, + { + "path": "dist/alphalib/types/robots/file-hash.d.ts.map", + "sizeBytes": 1193, + "sha256": "e32c94f338be64db833c3a52edebc813aa6cd92d1f03fffb463e0d72bbde941d" + }, + { + "path": "dist/alphalib/types/robots/file-hash.js.map", + "sizeBytes": 1611, + "sha256": "f589e306ee342792017d634feadfeb2b9854286960b0fc328ed8c68f2bf139f5" + }, + { + "path": "dist/alphalib/types/robots/file-preview.d.ts.map", + "sizeBytes": 1707, + "sha256": "bbd206d8489b7015987c19cc9a12a831855192f2f41dc718ef037d3cef144d6c" + }, + { + "path": "dist/alphalib/types/robots/file-preview.js.map", + "sizeBytes": 4716, + "sha256": "739b27deb2eba0c132a99420187415b97d3c5728f2d1d6ef57c934a557c41444" + }, + { + "path": "dist/alphalib/types/robots/file-read.d.ts.map", + "sizeBytes": 1181, + "sha256": "ced5f8c632d33c3f195178a40396cdb52f6ecb7b6bde3ceb4f78f97768727c8b" + }, + { + "path": "dist/alphalib/types/robots/file-read.js.map", + "sizeBytes": 1317, + "sha256": "e5dc39318ed77a5b044fa2d0957065adfb39b6badd98e78906a36dec52b78dd6" + }, + { + "path": "dist/alphalib/types/robots/file-serve.d.ts.map", + "sizeBytes": 1266, + "sha256": "2acd526a0502c1d9c0b8039cfadc6ddf84ca19127ad078898b9886fa53f30aa0" + }, + { + "path": "dist/alphalib/types/robots/file-serve.js.map", + "sizeBytes": 1687, + "sha256": "e553a47bd75cf6b95f70cfa50c64f99b7cfa4da788d0a84040adaf63b80eaa5f" + }, + { + "path": "dist/alphalib/types/robots/file-verify.d.ts.map", + "sizeBytes": 1223, + "sha256": "8e27540fb398c334b003fa1226cc4cca7f276841ab9c09d456effc04dc4f8e60" + }, + { + "path": "dist/alphalib/types/robots/file-verify.js.map", + "sizeBytes": 1788, + "sha256": "3e2b26391ed81f2083247cfc101ef82aeeeb02f69665ce999fddc6bab255d6d7" + }, + { + "path": "dist/alphalib/types/robots/file-virusscan.d.ts.map", + "sizeBytes": 1223, + "sha256": "84e84f34c14bced953ced1d8fd1f10987be49fcf7c6b28f3b0d815ea6d01ba56" + }, + { + "path": "dist/alphalib/types/robots/file-virusscan.js.map", + "sizeBytes": 1915, + "sha256": "220df6a5d0b0e7efcdbde6c62ac1354d81e61a377ce800ced9a33300098d818f" + }, + { + "path": "dist/alphalib/types/robots/file-watermark.d.ts.map", + "sizeBytes": 1203, + "sha256": "abf5b2f9284b433b8925e7f3a99e77781b2959dacdebf6dce4048ea00575e6fb" + }, + { + "path": "dist/alphalib/types/robots/file-watermark.js.map", + "sizeBytes": 1017, + "sha256": "6583f0e6b3a04b39758bc60bbd77383f00715365ac714be95b871ba6797050b9" + }, + { + "path": "dist/alphalib/types/robots/ftp-import.d.ts.map", + "sizeBytes": 976, + "sha256": "921de9063ecf8dfe8054bf4ce597658aefd55b28da6935b1dc22b9de4ab50534" + }, + { + "path": "dist/alphalib/types/robots/ftp-import.js.map", + "sizeBytes": 1632, + "sha256": "717f12442854a116abeceb646f0a0b0d43bddf7d355b1261603e365ee2f75954" + }, + { + "path": "dist/alphalib/types/robots/ftp-store.d.ts.map", + "sizeBytes": 1310, + "sha256": "a74b1c73d55dd9d4c277e173246d268c85f28b3c7ea85fb5b5fa202895affa13" + }, + { + "path": "dist/alphalib/types/robots/ftp-store.js.map", + "sizeBytes": 2145, + "sha256": "ce1bf48c1cc713ae843061cba3c3b119475baa5cb6b62ac4b575e50b297bcf71" + }, + { + "path": "dist/alphalib/types/robots/google-import.d.ts.map", + "sizeBytes": 960, + "sha256": "464452457441bede584276700d4aa2905186df95d7b0e8f853fd276556a3ca73" + }, + { + "path": "dist/alphalib/types/robots/google-import.js.map", + "sizeBytes": 1783, + "sha256": "25f2633e57de13845396a75c6f3e7dd4b59cfd8b7587300b5041dcd3970768f4" + }, + { + "path": "dist/alphalib/types/robots/google-store.d.ts.map", + "sizeBytes": 1260, + "sha256": "05d35ccb12ce96d6b2e5372640a02a2c9f8ff34b9ca0da87197e7174d3402429" + }, + { + "path": "dist/alphalib/types/robots/google-store.js.map", + "sizeBytes": 2233, + "sha256": "40ad8416070e56ba35c23114eea8e5503953f1e00248ecd75796be6bab4404de" + }, + { + "path": "dist/cli/helpers.d.ts.map", + "sizeBytes": 697, + "sha256": "7741da5e1fdb8a1fac23517391b55ef32185134622a06f5b044a31f87424de46" + }, + { + "path": "dist/cli/helpers.js.map", + "sizeBytes": 1656, + "sha256": "061d1985ffefb16813c96520199edb566e65fe0b2113dd8c83fe36116a80a9d0" + }, + { + "path": "dist/alphalib/types/robots/html-convert.d.ts.map", + "sizeBytes": 1315, + "sha256": "16452c1d458edb1c509b118e7de72886a1547100c74d636509b904cea851fb7e" + }, + { + "path": "dist/alphalib/types/robots/html-convert.js.map", + "sizeBytes": 2766, + "sha256": "eacdd8d74271b8273f8356dd343de5953253860859932e8370fec23d99c589b0" + }, + { + "path": "dist/alphalib/types/robots/http-import.d.ts.map", + "sizeBytes": 998, + "sha256": "ef3b63e0a2c104c1c3631d430ad09514953fb3555daddb69d0d0e5b5306752ed" + }, + { + "path": "dist/alphalib/types/robots/http-import.js.map", + "sizeBytes": 2893, + "sha256": "d90362259ac8cdcf6e4d304042836bb172bf9e37552f4381d793afb57eff1768" + }, + { + "path": "dist/alphalib/types/robots/image-bgremove.d.ts.map", + "sizeBytes": 1241, + "sha256": "1f2d8c7319395b5228a586231c110b98758a4a11c8a6b91d1b406a085aae7440" + }, + { + "path": "dist/alphalib/types/robots/image-bgremove.js.map", + "sizeBytes": 1885, + "sha256": "a5fbb151ec01dc82e30ab793f405a6b3a1146805511669748079d4a5b54de6a5" + }, + { + "path": "dist/alphalib/types/robots/image-describe.d.ts.map", + "sizeBytes": 1241, + "sha256": "44776af23c24f582ff0f6552d49e01155de4589ed09e52012ec1321a6ca698c8" + }, + { + "path": "dist/alphalib/types/robots/image-describe.js.map", + "sizeBytes": 1899, + "sha256": "10dfe79760d33f0efa5a600b2060b04350fc733bf0e70f1c557e9ba33c83c601" + }, + { + "path": "dist/alphalib/types/robots/image-facedetect.d.ts.map", + "sizeBytes": 1270, + "sha256": "bb9528a90de57e383b99dfdcf5196462ecc5528c101d6a98402b7eb12b8de284" + }, + { + "path": "dist/alphalib/types/robots/image-facedetect.js.map", + "sizeBytes": 2389, + "sha256": "538eabf594bb90fe2cd54eeb27f097b9b42b0ffff95d6ed3f442b47d694ec599" + }, + { + "path": "dist/alphalib/types/robots/image-generate.d.ts.map", + "sizeBytes": 1310, + "sha256": "25b14e8c4cd9e295533e60c401e2fc9a8822a075e2ca2703b8a179fdac957bdf" + }, + { + "path": "dist/alphalib/types/robots/image-generate.js.map", + "sizeBytes": 2205, + "sha256": "ebb90ad613707be35e72b368ef80323c368a9d7751daf0a54a9588223dafe355" + }, + { + "path": "dist/alphalib/types/robots/image-merge.d.ts.map", + "sizeBytes": 1259, + "sha256": "2880a90a84df8f77f0a2c30c1835fda589c87f52ef21f077729ee23dd145b8bc" + }, + { + "path": "dist/alphalib/types/robots/image-merge.js.map", + "sizeBytes": 2080, + "sha256": "564ae537d3189541c9c0b645ff56919c86df8440fac89685b4180b851d862f2a" + }, + { + "path": "dist/alphalib/types/robots/image-ocr.d.ts.map", + "sizeBytes": 1218, + "sha256": "afbf395a44bc6f1a0ef163d6ff6b45dacac15b9b1e01045365f88f1fe4ce403f" + }, + { + "path": "dist/alphalib/types/robots/image-ocr.js.map", + "sizeBytes": 1798, + "sha256": "135f86e2ad0c6690838ce611f6e48375642dbdc9857c19723040c8860d07c620" + }, + { + "path": "dist/alphalib/types/robots/image-optimize.d.ts.map", + "sizeBytes": 1241, + "sha256": "eb4d9d068c6538732c9ccf8d1f335a16444fd10a7f5f838529a81d81fade03aa" + }, + { + "path": "dist/alphalib/types/robots/image-optimize.js.map", + "sizeBytes": 1795, + "sha256": "aae465a013cc6e22dd12b7e44986a7be746a70563c3b7aff861d8229888dac39" + }, + { + "path": "dist/alphalib/types/robots/image-resize.d.ts.map", + "sizeBytes": 2575, + "sha256": "4039c44fe629044c78562654dea361edb3d5fc31f6cbcbaff8e8ef3b3f2c8362" + }, + { + "path": "dist/alphalib/types/robots/image-resize.js.map", + "sizeBytes": 9404, + "sha256": "655db1c155f512b6b8b9fa21e8a6150ecab07e3b366e186694ef2b289f04b688" + }, + { + "path": "dist/InconsistentResponseError.d.ts.map", + "sizeBytes": 208, + "sha256": "5333a56696e4ed761ee350c4a342fd2d90e4b4445c80606ea8ebc7f19210276f" + }, + { + "path": "dist/InconsistentResponseError.js.map", + "sizeBytes": 217, + "sha256": "b1145dc4071d1b9fe9b438dc6cacfad7aa127329c06ed2cfa8c4495d4db289a1" + }, + { + "path": "dist/cli/commands/index.d.ts.map", + "sizeBytes": 198, + "sha256": "02d46596bc9ebce98e9668a9ec4fd017aa2e553b828dfc47dcbcff96bff71f3e" + }, + { + "path": "dist/cli/commands/index.js.map", + "sizeBytes": 1584, + "sha256": "d44b8dc7479c999f6f694cdb90628b32087b5a7a13758f727b5174fdf291fea6" + }, + { + "path": "dist/alphalib/mcache.d.ts.map", + "sizeBytes": 968, + "sha256": "7e45e93e39d2031b2a197c7f145f7a535b83af9435129b21cdbc6577da7fa6f8" + }, + { + "path": "dist/alphalib/mcache.js.map", + "sizeBytes": 4326, + "sha256": "718daef94e4679b5326fda25a3d6060a0bbcf86895e3b6d4865a310767584aa3" + }, + { + "path": "dist/alphalib/types/robots/meta-read.d.ts.map", + "sizeBytes": 733, + "sha256": "74696fc88caf7dab3962245213e1553190b7daa91ce6c98d8842a26ba7aa78e7" + }, + { + "path": "dist/alphalib/types/robots/meta-read.js.map", + "sizeBytes": 944, + "sha256": "bebdb932e6d46cd2075e908e77d6c013127a2aa21bf63f1cd52ee53445431159" + }, + { + "path": "dist/alphalib/types/robots/meta-write.d.ts.map", + "sizeBytes": 3621, + "sha256": "2aa5bcd142a9675827964aaf5ff8faebe2a46b65fbd4f05e393d9ddd683dc312" + }, + { + "path": "dist/alphalib/types/robots/meta-write.js.map", + "sizeBytes": 1763, + "sha256": "0ee3d8d54a5d87e087020ac29cb96997d5ddc154be11f4506d69be0458c2b66e" + }, + { + "path": "dist/alphalib/types/robots/minio-import.d.ts.map", + "sizeBytes": 1018, + "sha256": "daa3c35498566645725c26eaf6cac16cccc7591ad52ec1102fb5568f8a67707e" + }, + { + "path": "dist/alphalib/types/robots/minio-import.js.map", + "sizeBytes": 1826, + "sha256": "54f186aa5939c1a4ee41dec57a40aa2e4e67adc694bff3394ceecef4d5c1e50a" + }, + { + "path": "dist/alphalib/types/robots/minio-store.d.ts.map", + "sizeBytes": 1296, + "sha256": "5cab9c663094f8ed691d3726e183c8205b8dcafcbecadb1209c1aee4a667022e" + }, + { + "path": "dist/alphalib/types/robots/minio-store.js.map", + "sizeBytes": 2022, + "sha256": "09880706215eb25ad7b6c8a07976fcd329853e016d6d680ee71c8657e234ab34" + }, + { + "path": "dist/alphalib/lib/nativeGlobby.d.ts.map", + "sizeBytes": 583, + "sha256": "8f3c30e2d30cfdfb4b7fda43590e36d0cc5f340b8dc75a69314602e19b566047" + }, + { + "path": "dist/alphalib/lib/nativeGlobby.js.map", + "sizeBytes": 7177, + "sha256": "faa6544a8c107851651831356371f77c7a2fbae9810951ad2b18f4968bf7580c" + }, + { + "path": "dist/cli/commands/notifications.d.ts.map", + "sizeBytes": 344, + "sha256": "b6d7ff0ac754f7977f133a78361ca7d469f23319dc9fd8a5a8c9093dac428eb6" + }, + { + "path": "dist/cli/commands/notifications.js.map", + "sizeBytes": 1500, + "sha256": "ba8bc70ab727c78e8641b541d992bb5487b9f8c6af9a329ed527a21d38cf3f45" + }, + { + "path": "dist/cli/OutputCtl.d.ts.map", + "sizeBytes": 1377, + "sha256": "d49a26f7287d6e2dbee8de015c9409ad54fa2a2ed5dfe44e99b2510f95ed93a1" + }, + { + "path": "dist/cli/OutputCtl.js.map", + "sizeBytes": 2947, + "sha256": "4922203562a927063b145504529b677a8205047aace3b4b86cbe8b8f75ef5b70" + }, + { + "path": "dist/PaginationStream.d.ts.map", + "sizeBytes": 664, + "sha256": "9bdfee8e9abe338b8b6deb1b484e54b8c1b20134dc2721ceddd42ad512b70d3c" + }, + { + "path": "dist/PaginationStream.js.map", + "sizeBytes": 1303, + "sha256": "861ba3671e06da0092b9f5d41d687441c16f98fb34c69332bbe4005f993ee12f" + }, + { + "path": "dist/PollingTimeoutError.d.ts.map", + "sizeBytes": 213, + "sha256": "e728bb40a887365822da6380ce35a2d5de504fa4c24e4a61a03a14d365f0b96d" + }, + { + "path": "dist/PollingTimeoutError.js.map", + "sizeBytes": 233, + "sha256": "1bf6de3e9b8f0e62b6900be45f7db6c9dad37510a1ee9dc05c7883319ac15a48" + }, + { + "path": "dist/alphalib/types/robots/progress-simulate.d.ts.map", + "sizeBytes": 750, + "sha256": "aec55a688c0da530314297cf6652d456b032580f848e84cfbb00054b3bc9ab91" + }, + { + "path": "dist/alphalib/types/robots/progress-simulate.js.map", + "sizeBytes": 854, + "sha256": "c743fb4ea5217d34ff665926bd14ecbb259dec99c2de862abfe787ece58817a0" + }, + { + "path": "dist/alphalib/types/robots/s3-import.d.ts.map", + "sizeBytes": 1023, + "sha256": "2fa93c5f1aacff33c82795f1f6f4b7bc533fa4f00dabb0da996021cfb3a36b58" + }, + { + "path": "dist/alphalib/types/robots/s3-import.js.map", + "sizeBytes": 2086, + "sha256": "2a512555a098d4da6785a118506d5992ced9c7f9ad6fadb139e68161425dc5a3" + }, + { + "path": "dist/alphalib/types/robots/s3-store.d.ts.map", + "sizeBytes": 1437, + "sha256": "77b3508baff7e474df2bd8bb4dca53f8fb09ffcd229cd16aa09a35e2d12cd608" + }, + { + "path": "dist/alphalib/types/robots/s3-store.js.map", + "sizeBytes": 2689, + "sha256": "354bf2bfe15c57ef1054242d90e09fc69a50fac4a57f07510b81ab91cefa73df" + }, + { + "path": "dist/alphalib/types/robots/script-run.d.ts.map", + "sizeBytes": 1202, + "sha256": "d73d6e081a159f0446e83051828a2b4cd919e0439ec4c79423fbf74ce938633a" + }, + { + "path": "dist/alphalib/types/robots/script-run.js.map", + "sizeBytes": 1502, + "sha256": "6e61b686b0b26ba5d90c62cdb94d6611e09e57081efe1a7042dda77d127fd51f" + }, + { + "path": "dist/alphalib/types/robots/sftp-import.d.ts.map", + "sizeBytes": 973, + "sha256": "dad41e054b1f3f496000c2fe3eba6222f59230b6af63c38fa75cc3ce37ea862f" + }, + { + "path": "dist/alphalib/types/robots/sftp-import.js.map", + "sizeBytes": 1628, + "sha256": "f903dbbee86ee2801eb24d091c17d85686090035b263a27965987e4766587d75" + }, + { + "path": "dist/alphalib/types/robots/sftp-store.d.ts.map", + "sizeBytes": 1299, + "sha256": "b37328dfadccc7c6d482a2d44e79ddea777c2a4ef8944d91f0039a88beae661d" + }, + { + "path": "dist/alphalib/types/robots/sftp-store.js.map", + "sizeBytes": 1972, + "sha256": "9e1dd16cb07a33783ce79a07599589d90c6991eb4d17f042e07bd41b04be52be" + }, + { + "path": "dist/alphalib/types/robots/speech-transcribe.d.ts.map", + "sizeBytes": 1260, + "sha256": "a0c4d3e87110af87e7d44e6b16edb861a9c8b60b5916f8a084701fb0889928a1" + }, + { + "path": "dist/alphalib/types/robots/speech-transcribe.js.map", + "sizeBytes": 2118, + "sha256": "062dce9e6e903b2013b9ae939e54b1eb63594bf2905bcd49981870b5ab18be67" + }, + { + "path": "dist/alphalib/types/stackVersions.d.ts.map", + "sizeBytes": 181, + "sha256": "8801a952778d7c9ff6ec5da9ab04f67cb8912592a64219bee73f89f9647e0311" + }, + { + "path": "dist/alphalib/types/stackVersions.js.map", + "sizeBytes": 403, + "sha256": "b502d17102001adede09a03d6bc1b8d21c857709ca59699c5771eba6ff187768" + }, + { + "path": "dist/alphalib/types/robots/supabase-import.d.ts.map", + "sizeBytes": 1036, + "sha256": "591b8a2e1f9a882c9a800101271bc944ac6a7720022d634a9f6a52b273d09b12" + }, + { + "path": "dist/alphalib/types/robots/supabase-import.js.map", + "sizeBytes": 1862, + "sha256": "fb87b0dd2b73a22dcb20c1103f640e4c59de2f5cfae91bcaa8bcbacf33646872" + }, + { + "path": "dist/alphalib/types/robots/supabase-store.d.ts.map", + "sizeBytes": 1302, + "sha256": "26b0c17d6bb419f009a55c63720a5a6e01315eee5e47639f827968677dbcbcc7" + }, + { + "path": "dist/alphalib/types/robots/supabase-store.js.map", + "sizeBytes": 1886, + "sha256": "51670b0c87beb48d14581510971e8f6702d6f7536fb19e9779e0dca22261c996" + }, + { + "path": "dist/alphalib/types/robots/swift-import.d.ts.map", + "sizeBytes": 1030, + "sha256": "62f71493d289ab513756c9a0801fea959d1dbfebac53a937d0ce3e40d8412f43" + }, + { + "path": "dist/alphalib/types/robots/swift-import.js.map", + "sizeBytes": 1828, + "sha256": "8266677cc62c1cbf8acbe28d70e38b73ead4da441c24da57549b8d5401ff09a6" + }, + { + "path": "dist/alphalib/types/robots/swift-store.d.ts.map", + "sizeBytes": 1308, + "sha256": "d7861db0ea1879346faf51aa54feea5889457c72f9b890e996a5db6dce5c27a9" + }, + { + "path": "dist/alphalib/types/robots/swift-store.js.map", + "sizeBytes": 2023, + "sha256": "a1051b980e394da608e7b95994ca490f6f9d6dbe5fb4d89707266c495175a347" + }, + { + "path": "dist/cli/template-last-modified.d.ts.map", + "sizeBytes": 492, + "sha256": "927f06c6c3ac447c7459f881ad4150ed0cbec8c199b114452590e25df57d6f44" + }, + { + "path": "dist/cli/template-last-modified.js.map", + "sizeBytes": 4406, + "sha256": "ecf48762d1c4275b90bab7da82b44a9e0c31b2cf8bb62a2f1381c34ceced91c9" + }, + { + "path": "dist/alphalib/types/template.d.ts.map", + "sizeBytes": 133490, + "sha256": "d2e7aac5bedd1f24066164e27d0b68f2737998853c38cc33234379b3c61a9681" + }, + { + "path": "dist/alphalib/types/template.js.map", + "sizeBytes": 5214, + "sha256": "df5e14bc81fcb702b288bb964fe05ac0f6976a0b4061e8dd6cc8586c19d18b9d" + }, + { + "path": "dist/alphalib/types/templateCredential.d.ts.map", + "sizeBytes": 347, + "sha256": "b47bd53ceca8cec91455635a28942287286b87d962de0b32bbf4afd785df1822" + }, + { + "path": "dist/alphalib/types/templateCredential.js.map", + "sizeBytes": 912, + "sha256": "125372bf800fcb2b436edf9b7ac47aeea00a7ae8bc6cd0592b5a1fc4419e1cd1" + }, + { + "path": "dist/cli/commands/templates.d.ts.map", + "sizeBytes": 2355, + "sha256": "dde861e027753cac8ae7255ac4cc9b31d40e86b341ac52d467696294df1b32b3" + }, + { + "path": "dist/cli/commands/templates.js.map", + "sizeBytes": 14657, + "sha256": "d8af75491dfa21dd4c3f6587ef199a28f14450a1533bf78f8437e8581a358fd6" + }, + { + "path": "dist/alphalib/types/robots/text-speak.d.ts.map", + "sizeBytes": 1244, + "sha256": "f6c1fbc4234dc0e9b2d9c57790fcc5678aea31e4aa528703f3ddcb2b31c2d6e3" + }, + { + "path": "dist/alphalib/types/robots/text-speak.js.map", + "sizeBytes": 2081, + "sha256": "152ee18242f273409266c8a95fb15b965069ac9646aebf6e61ddb4a02453146d" + }, + { + "path": "dist/alphalib/types/robots/text-translate.d.ts.map", + "sizeBytes": 1230, + "sha256": "b7b203cfdd08f1e497abc8614b4b2a4d3ce74e15c5c056eaa29d187d94f9927e" + }, + { + "path": "dist/alphalib/types/robots/text-translate.js.map", + "sizeBytes": 2951, + "sha256": "4c09df3519ed7bc416814b2541d8ee0f5b4b77e8dfb72d6d1a57e45ccdcccb32" + }, + { + "path": "dist/alphalib/types/robots/tigris-import.d.ts.map", + "sizeBytes": 1030, + "sha256": "324f86249a446619ec7c815c0a39ac79bf8f543a849a7ae92aabc9e304694c6e" + }, + { + "path": "dist/alphalib/types/robots/tigris-import.js.map", + "sizeBytes": 1910, + "sha256": "817e599e2f948bf8d2a84eaf23ab95a4cc45bf83a1ca126ab359d729378daaba" + }, + { + "path": "dist/alphalib/types/robots/tigris-store.d.ts.map", + "sizeBytes": 1308, + "sha256": "543391776a31c889d13fca5749fbc029c329c3d6ac78b79b4561ebcb07fe9b2d" + }, + { + "path": "dist/alphalib/types/robots/tigris-store.js.map", + "sizeBytes": 2102, + "sha256": "7dfa4c4a70c85be45c9185428fd349ba0c788132a886f71eabb9fff85563de09" + }, + { + "path": "dist/alphalib/types/robots/tlcdn-deliver.d.ts.map", + "sizeBytes": 885, + "sha256": "554ee0091f06fb0600daf9bfb8ec6211d5e5b125e23a4c4807381909df171ee3" + }, + { + "path": "dist/alphalib/types/robots/tlcdn-deliver.js.map", + "sizeBytes": 1365, + "sha256": "2cc54073267b8bcdebf87bfe2b94b78bef94cf5bce3a55c574adb033261a7faf" + }, + { + "path": "dist/Transloadit.d.ts.map", + "sizeBytes": 4955, + "sha256": "3933904963537b1d3c1a33218f154e2c0620fb7e6048135b5291fe6d4affcb84" + }, + { + "path": "dist/Transloadit.js.map", + "sizeBytes": 19217, + "sha256": "b07b0ec19dff6764da41b0dfe6978243370f2d7af425844ff79c95c439f5b1c7" + }, + { + "path": "dist/alphalib/tryCatch.d.ts.map", + "sizeBytes": 555, + "sha256": "5fa048d8181c4b97c76d21af2b1c7353cdd0c0ad7b5f7c0209c1a685ac13b1d8" + }, + { + "path": "dist/alphalib/tryCatch.js.map", + "sizeBytes": 364, + "sha256": "d8b6506cd4a2b2aef2c10d358a01858ee7795ab47ba68dc483cfe05bd8bcd304" + }, + { + "path": "dist/alphalib/types/robots/tus-store.d.ts.map", + "sizeBytes": 1254, + "sha256": "c018e01926cb975e5991815d950d23b794e8da606204d1012afdde5cd40be48c" + }, + { + "path": "dist/alphalib/types/robots/tus-store.js.map", + "sizeBytes": 2130, + "sha256": "e332628b81b5a2f1db21e2163f3e82b1e2d1aee3bdc5da31641f56ac8f8c84cd" + }, + { + "path": "dist/tus.d.ts.map", + "sizeBytes": 675, + "sha256": "4174cce4740dbfaa616f6ebfd69e7d8a67f43e74d7b6e72f783fd5d7aada6b56" + }, + { + "path": "dist/tus.js.map", + "sizeBytes": 4004, + "sha256": "c61b325945542f75b7e227f99388b3da266126a431ad115dbb1da82f0253a73c" + }, + { + "path": "dist/cli/types.d.ts.map", + "sizeBytes": 988, + "sha256": "9ec4dae6c1187072ce760a518f8e2b659b2df95173ece898c6a3e458b48dfe5e" + }, + { + "path": "dist/cli/types.js.map", + "sizeBytes": 1487, + "sha256": "ba68dc25f9bfc67b80d7d0a06927d55d59f1dd722cff7c7b99115fc6d8212d7a" + }, + { + "path": "dist/alphalib/types/robots/upload-handle.d.ts.map", + "sizeBytes": 885, + "sha256": "3cb0f33183cfbcb8cf5cd93dea02062c80415b5a778f4f01e59c203907712338" + }, + { + "path": "dist/alphalib/types/robots/upload-handle.js.map", + "sizeBytes": 1547, + "sha256": "5565507cacaccfbae0ed8af22fa55d0f2e1764db6d5b512ca275d2dc0baa52c7" + }, + { + "path": "dist/alphalib/types/robots/video-adaptive.d.ts.map", + "sizeBytes": 3703, + "sha256": "ad17ce7383971d02115b646735b2e887b02e8cdd431fd8870be7dd5432c0d968" + }, + { + "path": "dist/alphalib/types/robots/video-adaptive.js.map", + "sizeBytes": 2536, + "sha256": "935700eac713fc175e93597246e0f8a3430641e6c014d322fc133ca7a856ea47" + }, + { + "path": "dist/alphalib/types/robots/video-concat.d.ts.map", + "sizeBytes": 3675, + "sha256": "e02b704959be22d2a2b3bf2e085233d2caaff4a30b237ecb84ce9946bacced1b" + }, + { + "path": "dist/alphalib/types/robots/video-concat.js.map", + "sizeBytes": 1976, + "sha256": "1663bd7c7b505072d3f559221615e39f28dd990108def6f05ec2cb6e504fc607" + }, + { + "path": "dist/alphalib/types/robots/video-encode.d.ts.map", + "sizeBytes": 4064, + "sha256": "500f47f6364a47894b08b0fb4907679310c8be7389a46fdf622d454339fd0050" + }, + { + "path": "dist/alphalib/types/robots/video-encode.js.map", + "sizeBytes": 1856, + "sha256": "6461a5e4016592c7e9ea4390b88c1b7b1d614ad81296803c066c7d1946766e2e" + }, + { + "path": "dist/alphalib/types/robots/video-merge.d.ts.map", + "sizeBytes": 3769, + "sha256": "e19e2f55fd7d80116f97b13808d73ea3b213268d3fa8ace990b2e3b22eebcfb5" + }, + { + "path": "dist/alphalib/types/robots/video-merge.js.map", + "sizeBytes": 2366, + "sha256": "5aa6fd3a2e9b7531f1f8abc63fb3d508ad98224f7c9e508f36df4f41f05768d1" + }, + { + "path": "dist/alphalib/types/robots/video-ondemand.d.ts.map", + "sizeBytes": 5342, + "sha256": "c2115e7d16dc0802c7ce4b1df6658f1c78cc53f9b2a9b045c084615901243c40" + }, + { + "path": "dist/alphalib/types/robots/video-ondemand.js.map", + "sizeBytes": 2724, + "sha256": "c20260c3defe1778351ba4119259fb43252554db5467d44676136189ed4deb00" + }, + { + "path": "dist/alphalib/types/robots/video-subtitle.d.ts.map", + "sizeBytes": 3763, + "sha256": "97a1c9a22930643d78fd48ce5749bcfceb81253442f9f2029ed0204de19073ba" + }, + { + "path": "dist/alphalib/types/robots/video-subtitle.js.map", + "sizeBytes": 2827, + "sha256": "af93d44416d43e48085ec00d7be8248967807f6bd3323a927a47beaab122b4a4" + }, + { + "path": "dist/alphalib/types/robots/video-thumbs.d.ts.map", + "sizeBytes": 3722, + "sha256": "f594f39d5f0f72e59ff3d9fa5a9639f8e533c2e553a1c5fedb09b3c6242e30c1" + }, + { + "path": "dist/alphalib/types/robots/video-thumbs.js.map", + "sizeBytes": 2886, + "sha256": "02b98cbff11b63daaf474f4784d1105b4dd56e57d1082e8677cd7d80690cff51" + }, + { + "path": "dist/alphalib/types/robots/vimeo-import.d.ts.map", + "sizeBytes": 972, + "sha256": "9f97e32a87f8e40a48b2669b712cf2d810b7180e2f98c034a1e66b987a786867" + }, + { + "path": "dist/alphalib/types/robots/vimeo-import.js.map", + "sizeBytes": 2142, + "sha256": "912a3b1308b6b9f97052d14bb022f66b12058c4c9f180795b2d6e6d79a26c808" + }, + { + "path": "dist/alphalib/types/robots/vimeo-store.d.ts.map", + "sizeBytes": 1302, + "sha256": "c670114a11cd43867aa15bacace8e1e65a9a9aa83d6f5b42b3f5ae05938d405f" + }, + { + "path": "dist/alphalib/types/robots/vimeo-store.js.map", + "sizeBytes": 2393, + "sha256": "29227154e1ea921a08f345cbb76aa1fe2ca895b597df7bbf95e62d09fb56268e" + }, + { + "path": "dist/alphalib/types/robots/wasabi-import.d.ts.map", + "sizeBytes": 1030, + "sha256": "b7e6ada139a658355a85cef4b883a42c15515c1cc1a89c741e0eeb9f8fa19191" + }, + { + "path": "dist/alphalib/types/robots/wasabi-import.js.map", + "sizeBytes": 1910, + "sha256": "348c647bde3a01cf71187ed1d440fb0fa875ff93e535b8e7024e5f47a30824f4" + }, + { + "path": "dist/alphalib/types/robots/wasabi-store.d.ts.map", + "sizeBytes": 1310, + "sha256": "18968bde05eb5699ce98695f16f65333077ae83af2850fcc65fdc0b9662d0d0d" + }, + { + "path": "dist/alphalib/types/robots/wasabi-store.js.map", + "sizeBytes": 2022, + "sha256": "238f78cc0876c72bea35d35b9ea4dbb3d033dfe35e416007e405d26bd7ca2a45" + }, + { + "path": "dist/alphalib/types/robots/youtube-store.d.ts.map", + "sizeBytes": 1263, + "sha256": "13df33bee9919599cc7854debac40597a412d4ad3673122257fe343928f985ff" + }, + { + "path": "dist/alphalib/types/robots/youtube-store.js.map", + "sizeBytes": 2380, + "sha256": "b59ed4420731f9f161d7de7a4c889eead8868c089b4507e5ce810de59fe0bf17" + }, + { + "path": "dist/alphalib/zodParseWithContext.d.ts.map", + "sizeBytes": 711, + "sha256": "7422db554332a0a7a4e2d58e402c5714a1b67db6a1b2ea98f4709b3c319d3041" + }, + { + "path": "dist/alphalib/zodParseWithContext.js.map", + "sizeBytes": 9563, + "sha256": "c3ee1daecdb3fb7a6fb1ab0d5b5cb327e1620c94b3c16ab522b9b599a04c468a" + }, + { + "path": "README.md", + "sizeBytes": 30169, + "sha256": "47d191fb87e0add731c826a7553a26e2572f34ad6c8506e00b025a21ff322580" + }, + { + "path": "dist/alphalib/types/robots/_index.d.ts", + "sizeBytes": 4680970, + "sha256": "8ffd4631bfe520f6a594365f95301e832c560840456d7c2c086b40f83ed1d566" + }, + { + "path": "src/alphalib/types/robots/_index.ts", + "sizeBytes": 50795, + "sha256": "ae25dd93eac765b3a4f5a3d8a28e49c8e2b88b122f6d1534e5d2779d1186a302" + }, + { + "path": "dist/alphalib/types/robots/_instructions-primitives.d.ts", + "sizeBytes": 207294, + "sha256": "ec04807c7f1ee31c94b2242f05936ccac7a6f4791353fd29f43a0e8860499d3b" + }, + { + "path": "src/alphalib/types/robots/_instructions-primitives.ts", + "sizeBytes": 63648, + "sha256": "3bf5225f4881aff338cbdd430f5eb22e58b5ef793392bcc8c0b6f52507f6b7b3" + }, + { + "path": "dist/alphalib/types/robots/ai-chat.d.ts", + "sizeBytes": 90303, + "sha256": "a6fbd4c6b3d58a37a87ffd6226db7136ddd3d206d7de818c816b48003ab12bad" + }, + { + "path": "src/alphalib/types/robots/ai-chat.ts", + "sizeBytes": 10035, + "sha256": "05f5c0a9aba087f5fb2a83cec845c3bc462d1ada916abe01c6510eecfd99b392" + }, + { + "path": "dist/ApiError.d.ts", + "sizeBytes": 592, + "sha256": "dde26d7ee9dea3fa185dbc490915a99c6785cc1462f96138867f33a75f5f5dee" + }, + { + "path": "src/ApiError.ts", + "sizeBytes": 1335, + "sha256": "12b90cef5bbf9760c478a3882cc85162368b492aed39d1b5e2915df927db8a05" + }, + { + "path": "dist/apiTypes.d.ts", + "sizeBytes": 3730, + "sha256": "5c69b795640fa02650cf0733d262b28488a049848863392bf33a3992eb0049eb" + }, + { + "path": "src/apiTypes.ts", + "sizeBytes": 4042, + "sha256": "6be2494c40b5cee641b33329b6e2bc495c0357ffa3509228c4c531d59dbc0c5a" + }, + { + "path": "dist/cli/commands/assemblies.d.ts", + "sizeBytes": 3569, + "sha256": "cac7dbbcb9f413eb9b391eec586b905675793ab2497beb361d04a1f6f64a89e6" + }, + { + "path": "src/cli/commands/assemblies.ts", + "sizeBytes": 41625, + "sha256": "9989a3f1070e07117fe57371b61118421a6ac626c84836e647d677e4c5babe08" + }, + { + "path": "dist/alphalib/types/assembliesGet.d.ts", + "sizeBytes": 2112, + "sha256": "9708f78e367a541625e567768f5d2bc8339ec49057dd2fa454d5c7215216f7be" + }, + { + "path": "src/alphalib/types/assembliesGet.ts", + "sizeBytes": 1440, + "sha256": "acf47bb92b25c5552a8b19419be3c14323f158726b7ee16e462a248ff190a347" + }, + { + "path": "dist/alphalib/types/robots/assembly-savejson.d.ts", + "sizeBytes": 3053, + "sha256": "8c30087c4cebb948e6373c79e83e5df104c2dc7b669e862f5ffc2ffd2b07c2ed" + }, + { + "path": "src/alphalib/types/robots/assembly-savejson.ts", + "sizeBytes": 1221, + "sha256": "05973f206156273ed5fb7f26a24e34479844fef3012f7086e3ab2fb8e12eee50" + }, + { + "path": "dist/alphalib/types/assemblyReplay.d.ts", + "sizeBytes": 414604, + "sha256": "db12e59f0737b3aab4c748c45905f95ab335157af57cd7de252f1b8fd76eeacb" + }, + { + "path": "src/alphalib/types/assemblyReplay.ts", + "sizeBytes": 716, + "sha256": "dadae8a1525f6d8313d65d52810fdd40ac14f1d2c52aa3fbd9e227ea13482aa8" + }, + { + "path": "dist/alphalib/types/assemblyReplayNotification.d.ts", + "sizeBytes": 414166, + "sha256": "bf372161e78fdbd03487c0967307015b263445be87601c2266637a831e91ac7e" + }, + { + "path": "src/alphalib/types/assemblyReplayNotification.ts", + "sizeBytes": 632, + "sha256": "c6ddb9cc02103ad5f791d25bcfe6b899fa86500b2c457dd2a698db5ec0ebee25" + }, + { + "path": "dist/alphalib/types/assemblyStatus.d.ts", + "sizeBytes": 4247003, + "sha256": "224358eca0d6e38f26d82b64cd10ebc3d2b609824e46a5691d7f8162e21d2218" + }, + { + "path": "src/alphalib/types/assemblyStatus.ts", + "sizeBytes": 29485, + "sha256": "0ef4a2377e4c67c4de4dfd0d2f6475c47e046d4e2df232f3407acf7a53946f4c" + }, + { + "path": "dist/alphalib/types/robots/audio-artwork.d.ts", + "sizeBytes": 167655, + "sha256": "56670af4f3daa10c5d495be972649563a4b912828c1e9988b2b7d8119493af88" + }, + { + "path": "src/alphalib/types/robots/audio-artwork.ts", + "sizeBytes": 3845, + "sha256": "82df928d54f70ea3d34a6602cdfe8ffdb43a8de10a36656263661e4309a54026" + }, + { + "path": "dist/alphalib/types/robots/audio-concat.d.ts", + "sizeBytes": 169966, + "sha256": "9c4150072f77917ca68e63e7862e0c9fee3e7d9df890d54b78f5a734079937e7" + }, + { + "path": "src/alphalib/types/robots/audio-concat.ts", + "sizeBytes": 5511, + "sha256": "a4e0146b728bf265969dad46f8a7536638efee63875906c544d4860e1ed4557a" + }, + { + "path": "dist/alphalib/types/robots/audio-encode.d.ts", + "sizeBytes": 167466, + "sha256": "922043005e80121472fc0ff17a50c49ec9fa4173130e1563adab679cd279fa0f" + }, + { + "path": "src/alphalib/types/robots/audio-encode.ts", + "sizeBytes": 3451, + "sha256": "be951d6318aab6473087dbea86193b69837a58796aae68761b3d31ec88efc975" + }, + { + "path": "dist/alphalib/types/robots/audio-loop.d.ts", + "sizeBytes": 167878, + "sha256": "751a9f62bae4eeb980ac67ed6646fb9522e1328abc3d40bc913309a5c8fc1420" + }, + { + "path": "src/alphalib/types/robots/audio-loop.ts", + "sizeBytes": 3676, + "sha256": "d91da780538c130608a56936f148a9537b2cedde0e233e3872b7ec70b678ef83" + }, + { + "path": "dist/alphalib/types/robots/audio-merge.d.ts", + "sizeBytes": 170479, + "sha256": "7ff8b6a67c07fd16d9c2323da40deb65d7f193cdeb277182adef3b4a8a661ff7" + }, + { + "path": "src/alphalib/types/robots/audio-merge.ts", + "sizeBytes": 4923, + "sha256": "fa524f4d4f7186dc7b001d27bba6b8397edf746d2853a3e280aa28a60aab990a" + }, + { + "path": "dist/alphalib/types/robots/audio-waveform.d.ts", + "sizeBytes": 168087, + "sha256": "e6778bd4b1c71a3b87bbdaa097b8efc97efd29962279fafe39cf75d802cdce4c" + }, + { + "path": "src/alphalib/types/robots/audio-waveform.ts", + "sizeBytes": 9454, + "sha256": "4b61b0599fade66e3a0d954eea55f980e9745eff3f19341ce8f6786a80469d3f" + }, + { + "path": "dist/cli/commands/auth.d.ts", + "sizeBytes": 936, + "sha256": "20a1d35fb55fad8af33fb6decede3cbf2cd621007fe443d2866e9975bbe23b20" + }, + { + "path": "src/cli/commands/auth.ts", + "sizeBytes": 10502, + "sha256": "0dea0295f4f5798dd619860d01fe6f50f59efd99a83c36c1e35803d584f158aa" + }, + { + "path": "dist/alphalib/types/robots/azure-import.d.ts", + "sizeBytes": 11036, + "sha256": "fb0980e7bd0fcca08b195cc79b5f168e874a25673c97f9340ad89449d2a9c25b" + }, + { + "path": "src/alphalib/types/robots/azure-import.ts", + "sizeBytes": 3901, + "sha256": "162715ca46c65fce521ceefa5141d96ae5a8d51ca8ed8d045af6cd55f27282eb" + }, + { + "path": "dist/alphalib/types/robots/azure-store.d.ts", + "sizeBytes": 23317, + "sha256": "7bd4f1c2d33efe2a62e8dc2efd851f61785960145c1954058eeb8882daf7177f" + }, + { + "path": "src/alphalib/types/robots/azure-store.ts", + "sizeBytes": 4870, + "sha256": "98ece7cd3cd7575ffa57cc301aec88a2465851addda92a43d6f914e74fe44cb1" + }, + { + "path": "dist/alphalib/types/robots/backblaze-import.d.ts", + "sizeBytes": 11200, + "sha256": "656737162a0c93544d22a8a6d5c640892644a5363ef91fcdaa26d7e9f5fe2664" + }, + { + "path": "src/alphalib/types/robots/backblaze-import.ts", + "sizeBytes": 4572, + "sha256": "143f75fd564c8ab4b580eaec2ca3599df03738e93b6d265de650903f32c708f4" + }, + { + "path": "dist/alphalib/types/robots/backblaze-store.d.ts", + "sizeBytes": 19655, + "sha256": "728cd867f57b1ff2a919ef4f226e2a0ca9513813f92165a6d25bb51735cc22d9" + }, + { + "path": "src/alphalib/types/robots/backblaze-store.ts", + "sizeBytes": 3810, + "sha256": "60a83e189004145990e911398693103f92826456698c7b1e77f1951f832d8367" + }, + { + "path": "dist/cli/commands/BaseCommand.d.ts", + "sizeBytes": 944, + "sha256": "9f132cb73d644f225f0efa48ab02f0edc6a82c9db7db080e84ae2281d9cf845c" + }, + { + "path": "src/cli/commands/BaseCommand.ts", + "sizeBytes": 2146, + "sha256": "d0cab4ebb72ce5d555be82bf3de4ba1f09dd223b71702bc53527928cf1c7ac91" + }, + { + "path": "dist/alphalib/types/bill.d.ts", + "sizeBytes": 1340, + "sha256": "addda1ad6e3507c37fa8eaa835c6ed57ede1206a732d0237e40f109d329549db" + }, + { + "path": "src/alphalib/types/bill.ts", + "sizeBytes": 188, + "sha256": "2ec2d5c86e068794e85195e417a515e383a70e2741926c3a949d10002a1ad13b" + }, + { + "path": "dist/cli/commands/bills.d.ts", + "sizeBytes": 590, + "sha256": "08945c847c67df1e376230d66034497ac77cf548e33354b595d304b47ae86203" + }, + { + "path": "src/cli/commands/bills.ts", + "sizeBytes": 2402, + "sha256": "c3272b2808ff8ff1a2924aaea1431e01fb0bd46205861d5621d28ac08ef4f5f9" + }, + { + "path": "dist/cli.d.ts", + "sizeBytes": 256, + "sha256": "c0b85d46fb05f111ab4b71bf0adc491e71b78efd5b5344b74599e4126477979b" + }, + { + "path": "src/cli.ts", + "sizeBytes": 1101, + "sha256": "9f7fa1f5565e87ffdf37abd416e6e77661d3cdba15513ae37fc9a5952a24abc0" + }, + { + "path": "dist/alphalib/types/robots/cloudfiles-import.d.ts", + "sizeBytes": 12289, + "sha256": "420c6b08193c73d5acb5b533160f437847cac7707dbfb0d19772bcd3e1a6d56f" + }, + { + "path": "src/alphalib/types/robots/cloudfiles-import.ts", + "sizeBytes": 4513, + "sha256": "39f9b5ef411d7ebf410fd2d14dd47c5332ace0deda99b02a0bec775b3f1e6248" + }, + { + "path": "dist/alphalib/types/robots/cloudfiles-store.d.ts", + "sizeBytes": 20090, + "sha256": "7e427617b89defb34f177c39ed4cfc57e9546e81569054be66c3a3fdcc1d004e" + }, + { + "path": "src/alphalib/types/robots/cloudfiles-store.ts", + "sizeBytes": 3545, + "sha256": "e9e41caeb42b5518893e7abe897975e133854c98df0cae60a48d51cb0f835d7d" + }, + { + "path": "dist/alphalib/types/robots/cloudflare-import.d.ts", + "sizeBytes": 12177, + "sha256": "6b349b174338e3814f7afe52af2e53271f0357845dea3fb33ee2ba663f545302" + }, + { + "path": "src/alphalib/types/robots/cloudflare-import.ts", + "sizeBytes": 4880, + "sha256": "3f2055ece87c5d7e583d6237395a4da4fda556fc01505254c3d0a6a126939142" + }, + { + "path": "dist/alphalib/types/robots/cloudflare-store.d.ts", + "sizeBytes": 21058, + "sha256": "06a0b0724e064c0f8cc75210d7630e3147b5a086a7f82eaff618701e772fe934" + }, + { + "path": "src/alphalib/types/robots/cloudflare-store.ts", + "sizeBytes": 4479, + "sha256": "be419be86bd4a2bd1d54cad4c0a0670d52e8bff6b7a51a7d31eaeecb6c64f932" + }, + { + "path": "dist/alphalib/types/robots/digitalocean-import.d.ts", + "sizeBytes": 12247, + "sha256": "1e2ea0768661803a5f478255a60e075031eacc6755f198d24e3e19579b471ab2" + }, + { + "path": "src/alphalib/types/robots/digitalocean-import.ts", + "sizeBytes": 4556, + "sha256": "f48028a8d933f4521ef27b82848c82ec3d139c3baff7f7f46942e2b4b76e58e6" + }, + { + "path": "dist/alphalib/types/robots/digitalocean-store.d.ts", + "sizeBytes": 21638, + "sha256": "86c25da875cdf43b1adebe42e50b0125df3fb570950d7818a94908c942efd785" + }, + { + "path": "src/alphalib/types/robots/digitalocean-store.ts", + "sizeBytes": 4676, + "sha256": "1f6cb42c4dea85cf1919b8d535247ef07c4d989e7d497e10a98fe5190c8e5083" + }, + { + "path": "dist/alphalib/types/robots/document-autorotate.d.ts", + "sizeBytes": 16993, + "sha256": "ee70d548c8ca611c2cc14f313bf245c3134cf3dcf04750aac29d05a5ad0975ba" + }, + { + "path": "src/alphalib/types/robots/document-autorotate.ts", + "sizeBytes": 2719, + "sha256": "174985d88d0e927eb16a23d831c23fa924ffd0527a436692acaad3c609b8b2fb" + }, + { + "path": "dist/alphalib/types/robots/document-convert.d.ts", + "sizeBytes": 24638, + "sha256": "6a9a0d28e5e25d40de359d63d524519c4c24904b6eb332b940a8c4dbbe0d61dc" + }, + { + "path": "src/alphalib/types/robots/document-convert.ts", + "sizeBytes": 9975, + "sha256": "b5f73d19418a5bb67b1895d647b0f3f4328130540e476620b0ca1fecf3e084a1" + }, + { + "path": "dist/alphalib/types/robots/document-merge.d.ts", + "sizeBytes": 18042, + "sha256": "7f60c8960caade2fae1a9478c4075c120eb4858dec888e5060a95756c4e4ac5d" + }, + { + "path": "src/alphalib/types/robots/document-merge.ts", + "sizeBytes": 3835, + "sha256": "265357067cf08c107cec5fdab7951810832559e4902e7eff0ae3474b1c3bceb2" + }, + { + "path": "dist/alphalib/types/robots/document-ocr.d.ts", + "sizeBytes": 18676, + "sha256": "de5e1f257cf9c796390a18365d2883cb9e43b1465d2259fd967d3d3370217908" + }, + { + "path": "src/alphalib/types/robots/document-ocr.ts", + "sizeBytes": 5306, + "sha256": "3b47017593ee1014cbb6c320a9ef5b04a6962958cb0006d269144972cb89f9ed" + }, + { + "path": "dist/alphalib/types/robots/document-split.d.ts", + "sizeBytes": 17616, + "sha256": "99431a94cba0901a94742e7bcad58745a74f388ad3a0a42a944afe69065bd543" + }, + { + "path": "src/alphalib/types/robots/document-split.ts", + "sizeBytes": 2867, + "sha256": "a8965112f587ff50452a72e3448e9802de1850f63a300f2643ba841c8414cceb" + }, + { + "path": "dist/alphalib/types/robots/document-thumbs.d.ts", + "sizeBytes": 28207, + "sha256": "cf8bbcd2f6c105baf34678306b32ab0bce1f1b7b409173f08c52646f806b2588" + }, + { + "path": "src/alphalib/types/robots/document-thumbs.ts", + "sizeBytes": 9807, + "sha256": "3a2d6c877178fb51a0d77d44ff0e11fdf59af26cd3839818b5a3a74665961ab3" + }, + { + "path": "dist/alphalib/types/robots/dropbox-import.d.ts", + "sizeBytes": 8522, + "sha256": "24118bc0f2c3a8daa16eec2d2d9bfe74e4a39776fa7936316161bc446c9af408" + }, + { + "path": "src/alphalib/types/robots/dropbox-import.ts", + "sizeBytes": 3505, + "sha256": "80d05dfafc8be76e46712016da81a25fff3e1e9edff7787124b4d417a2003103" + }, + { + "path": "dist/alphalib/types/robots/dropbox-store.d.ts", + "sizeBytes": 18629, + "sha256": "56fbd6ad497fc0e28d1956afecfad9aa77dfecdd7bef23d29595c3224171dd97" + }, + { + "path": "src/alphalib/types/robots/dropbox-store.ts", + "sizeBytes": 3465, + "sha256": "5f0ecce1e742d401864a14c9599b618b7c27743926b9c4452b6f56d9491e4866" + }, + { + "path": "dist/alphalib/types/robots/edgly-deliver.d.ts", + "sizeBytes": 6141, + "sha256": "153eb50b9d1ee94be2e1ec67179e70a1568ff0b1eac63ef8cc5ba66325b9b867" + }, + { + "path": "src/alphalib/types/robots/edgly-deliver.ts", + "sizeBytes": 2785, + "sha256": "40d7a8b6567fd80057781d7a24f093e9e1918ee45c3191085ee1bc256f9eeb44" + }, + { + "path": "dist/alphalib/types/robots/file-compress.d.ts", + "sizeBytes": 20135, + "sha256": "70a145f269bdd19a0dfede425e33849d3b4ba56631f11739d58d648f8d0b3089" + }, + { + "path": "src/alphalib/types/robots/file-compress.ts", + "sizeBytes": 6687, + "sha256": "b28ad0566636340b965e4fba2f5b9c7da277cbba7bba75237cca5e4d07affd27" + }, + { + "path": "dist/alphalib/types/robots/file-decompress.d.ts", + "sizeBytes": 16983, + "sha256": "5579a9407db5af3e825f06bc34d2a5f11b0d6b87e1bcff6ddd3a15dac8a2cd9c" + }, + { + "path": "src/alphalib/types/robots/file-decompress.ts", + "sizeBytes": 4628, + "sha256": "8c460d61060e013707889172190ed615775467d3d69ee7850efd977e86bc58ff" + }, + { + "path": "dist/alphalib/types/robots/file-filter.d.ts", + "sizeBytes": 30301, + "sha256": "477f0767ef827af10ab26e016e308bff23f2989e868f2c815f4f23cd94b3313f" + }, + { + "path": "src/alphalib/types/robots/file-filter.ts", + "sizeBytes": 6781, + "sha256": "e9476b7df5be5397bec14e2e976aaf252a43c375c9f0ba777d76ce333b57c604" + }, + { + "path": "dist/alphalib/types/robots/file-hash.d.ts", + "sizeBytes": 17641, + "sha256": "96be0831960c7d0ae1f1c5856a6a3c003ae39ae665a3490850c13a9eb1a4fb57" + }, + { + "path": "src/alphalib/types/robots/file-hash.ts", + "sizeBytes": 2861, + "sha256": "733c4313ffcea9fb33b4b6ca9857b682a561b10cd59d1097464ab673c7b96bca" + }, + { + "path": "dist/alphalib/types/robots/file-preview.d.ts", + "sizeBytes": 37390, + "sha256": "9bdfda1a7ab6e8ed51a500afff37b3679515ad533c9551de399bc872ed97719f" + }, + { + "path": "src/alphalib/types/robots/file-preview.ts", + "sizeBytes": 13514, + "sha256": "4bb202a2bc5109d1c1234321237c019cd41986890f19fe114bd85d8c66e905db" + }, + { + "path": "dist/alphalib/types/robots/file-read.d.ts", + "sizeBytes": 16703, + "sha256": "08e84ba786ec43b289048db242e2833844918e550f5fd2033d9426993ce44fad" + }, + { + "path": "src/alphalib/types/robots/file-read.ts", + "sizeBytes": 2549, + "sha256": "5081ae04d3c9db4cb4196b74877e63f7f30ea51e53773cca75095536c7a50963" + }, + { + "path": "dist/alphalib/types/robots/file-serve.d.ts", + "sizeBytes": 17440, + "sha256": "adf1d55b0f2b0c52f32b9861b0ce7a5c7b1e6f0087a35534eaab752d290f1163" + }, + { + "path": "src/alphalib/types/robots/file-serve.ts", + "sizeBytes": 6708, + "sha256": "2f35e8b78f5024e954a4b962549ec71ae61d24953b6e711511cf827fe5f69be1" + }, + { + "path": "dist/alphalib/types/robots/file-verify.d.ts", + "sizeBytes": 18195, + "sha256": "c18447877b8a83c219f662f4e744dc0ab16745404ada77cb6d8260493f9bab32" + }, + { + "path": "src/alphalib/types/robots/file-verify.ts", + "sizeBytes": 4174, + "sha256": "2c1aa8e92175ec0811628d79d3e1d51dfdfcdae2b8dfa53f32434ef416d19acc" + }, + { + "path": "dist/alphalib/types/robots/file-virusscan.d.ts", + "sizeBytes": 18207, + "sha256": "b9cd21c5b31f1921db32af8e84d20c8ca1901ad308cf4b55171e77f87bc8c9de" + }, + { + "path": "src/alphalib/types/robots/file-virusscan.ts", + "sizeBytes": 4593, + "sha256": "b419a7a5c34ce59996b4eb71833a337d8b0e180d36765c6bb0b6d8236ce49b05" + }, + { + "path": "dist/alphalib/types/robots/file-watermark.d.ts", + "sizeBytes": 17410, + "sha256": "d13deaf037252d9d9f27e7b880329665ef6146b637720bd7cdc0de46cb217630" + }, + { + "path": "src/alphalib/types/robots/file-watermark.ts", + "sizeBytes": 2068, + "sha256": "08af2039f3e568d27b91508b8002ce2ee19714817d69360a4e942cf27f820657" + }, + { + "path": "dist/alphalib/types/robots/ftp-import.d.ts", + "sizeBytes": 10382, + "sha256": "02b494b7a354fed368245635c8c79033c22dc41f04d3adc1b25ea82d5860bd89" + }, + { + "path": "src/alphalib/types/robots/ftp-import.ts", + "sizeBytes": 3132, + "sha256": "2f816efd74d6de7773fea4c48c4c51dcee514133e4b74788bb419e3f22ee1b75" + }, + { + "path": "dist/alphalib/types/robots/ftp-store.d.ts", + "sizeBytes": 21536, + "sha256": "7674a4dbc884b65a376ccffff7de07967ec32a384237d34fcc46d6a75e32f3d6" + }, + { + "path": "src/alphalib/types/robots/ftp-store.ts", + "sizeBytes": 4197, + "sha256": "1bbaa2361cc3675a29178cbd0f4fcecaad1033032f154a6da36c5c677a9c9447" + }, + { + "path": "dist/alphalib/types/robots/google-import.d.ts", + "sizeBytes": 9781, + "sha256": "d7cba012863f00d62bfca4b1adb997305747f3edd291ade13cc7d982dfd7bc86" + }, + { + "path": "src/alphalib/types/robots/google-import.ts", + "sizeBytes": 4503, + "sha256": "4e20e80511a5380f1c6f72e5913f85f728699c412f72bec01d01a150f644e346" + }, + { + "path": "dist/alphalib/types/robots/google-store.d.ts", + "sizeBytes": 20168, + "sha256": "69e46d09a51354b678b390873d4039369450e77ec0a047c77e2ebca527e6328a" + }, + { + "path": "src/alphalib/types/robots/google-store.ts", + "sizeBytes": 6183, + "sha256": "e30cb47805e98e3099935ed4113e036b61c5da91bc483e6b9d1d1f6371221a8b" + }, + { + "path": "dist/cli/helpers.d.ts", + "sizeBytes": 571, + "sha256": "f42d526e9b8270d53f94c8759ea94396515f3280f04ef47abab1cfb8445827ba" + }, + { + "path": "src/cli/helpers.ts", + "sizeBytes": 1550, + "sha256": "2a78e29a2c099f330e65db968da8a42cf25614c95374744a96e04e7341b64aab" + }, + { + "path": "dist/alphalib/types/robots/html-convert.d.ts", + "sizeBytes": 22952, + "sha256": "66b4c1ed3f87bd77f402b970f86fcd4869ceab173534a222d029e0ca38e74555" + }, + { + "path": "src/alphalib/types/robots/html-convert.ts", + "sizeBytes": 5966, + "sha256": "35eeb778615a8f9fb18e05b8672c7e4c116bf41c3e42cbb114057524ea9118cf" + }, + { + "path": "dist/alphalib/types/robots/http-import.d.ts", + "sizeBytes": 12754, + "sha256": "07454a99e30a4a8d0c167c657b7f94fe0699ca2515f342cd3dacf94aa4dc8122" + }, + { + "path": "src/alphalib/types/robots/http-import.ts", + "sizeBytes": 6465, + "sha256": "280418e48e6e718e6822f384d151bb6f83a47fd275d05a9e523e935a4a6a70fd" + }, + { + "path": "dist/alphalib/types/robots/image-bgremove.d.ts", + "sizeBytes": 19322, + "sha256": "ce7cf8d8e520806ba5b362c69716a4dffd6d4b14ab2642b31ec964791e70296e" + }, + { + "path": "src/alphalib/types/robots/image-bgremove.ts", + "sizeBytes": 3278, + "sha256": "c9fd592d22e31b933f54eb6f1c83f0aaefa3be47a0f98089da43bbba43d958b4" + }, + { + "path": "dist/alphalib/types/robots/image-describe.d.ts", + "sizeBytes": 19548, + "sha256": "31cb9b81d026344d2587b754dacb5fb2cc6a16cc3c4a8e6be0ee3b85064c9a23" + }, + { + "path": "src/alphalib/types/robots/image-describe.ts", + "sizeBytes": 5324, + "sha256": "2d6ffb899a5b34185e81aed62e8bfbde3fa93ccb18fe73a5a79d74a2b8423083" + }, + { + "path": "dist/alphalib/types/robots/image-facedetect.d.ts", + "sizeBytes": 20924, + "sha256": "87b70b6ec7fc69cb54df4473756f28b9dfc08891f2edbcc2e9343c19fe304259" + }, + { + "path": "src/alphalib/types/robots/image-facedetect.ts", + "sizeBytes": 7149, + "sha256": "caafb37c5d568017348216b74330f7467ccb64d469f32fcf03fd7838021a8c33" + }, + { + "path": "dist/alphalib/types/robots/image-generate.d.ts", + "sizeBytes": 21767, + "sha256": "83ce96595700aac82a23a46643db97314a5cc7d79fd6f3bee6d89541eceaf74c" + }, + { + "path": "src/alphalib/types/robots/image-generate.ts", + "sizeBytes": 3465, + "sha256": "054a6d1a584bddf6cb2636cc4b8984f0da2f497066e15ec5ce1fb0b3e91d5958" + }, + { + "path": "dist/alphalib/types/robots/image-merge.d.ts", + "sizeBytes": 19863, + "sha256": "b7ad4dc952aaae6250d2cfd6ffaedf915ab177c9fa86cad92ff764f71980e2b3" + }, + { + "path": "src/alphalib/types/robots/image-merge.ts", + "sizeBytes": 4399, + "sha256": "f86cde29eaef40c73185ccd5a06cce1b16392af2fe5a3657f537d008516193c5" + }, + { + "path": "dist/alphalib/types/robots/image-ocr.d.ts", + "sizeBytes": 18589, + "sha256": "c7875da64b1b3d96c8febb34de10cba91c8b3a75bf4de71e4347a837ded157e7" + }, + { + "path": "src/alphalib/types/robots/image-ocr.ts", + "sizeBytes": 5013, + "sha256": "e52bf1ef75dcf595c648b071c9b1d0cec7332055ad17205795d87567855e9dc8" + }, + { + "path": "dist/alphalib/types/robots/image-optimize.d.ts", + "sizeBytes": 19364, + "sha256": "fcf380dbaa88647a2659f8fed5b8705179e11eb48b690d93732aa1697ddac733" + }, + { + "path": "src/alphalib/types/robots/image-optimize.ts", + "sizeBytes": 4943, + "sha256": "a28ba8e59e94cbc77d39fe0442115b1ef07edaaaaef39c8039b2cdf2b9740437" + }, + { + "path": "dist/alphalib/types/robots/image-resize.d.ts", + "sizeBytes": 90591, + "sha256": "06048ee955f4b4a4c918553461f5203b6666ee251f0eeebb250765425055bd1f" + }, + { + "path": "src/alphalib/types/robots/image-resize.ts", + "sizeBytes": 28301, + "sha256": "23a89aaa7f7e7721eac3e7dafb1f82fdb5c1277dc30d85cf3e3364c478e45151" + }, + { + "path": "dist/InconsistentResponseError.d.ts", + "sizeBytes": 138, + "sha256": "65e9660f7df57f53d44dc10f9b3b547411b1c32a5ee3c886f2ae02e8d65163ee" + }, + { + "path": "src/InconsistentResponseError.ts", + "sizeBytes": 111, + "sha256": "05f61ae5885cda2995817857c701155383cc4bc63f04a574b07dbd18111ac9ab" + }, + { + "path": "dist/cli/commands/index.d.ts", + "sizeBytes": 110, + "sha256": "8138bd76ab0a7ad7dc62b74d654fd7335de2fa86e1fb58f34788df74005ccc2d" + }, + { + "path": "src/cli/commands/index.ts", + "sizeBytes": 1648, + "sha256": "96dcf864fd177c933434cc0e43ca8fe58aa153332ed5013af6d1012d4fde9d8f" + }, + { + "path": "dist/alphalib/mcache.d.ts", + "sizeBytes": 1881, + "sha256": "35ca1c21bf3de9ba4b28e19637c937a83f15b88dc2081dc9106f6201619e6451" + }, + { + "path": "src/alphalib/mcache.ts", + "sizeBytes": 4861, + "sha256": "6481c39114c8a5d6a514e2cdb0c40ee56f6f8ab56bb0a83ce91ed4f69a58184f" + }, + { + "path": "dist/alphalib/types/robots/meta-read.d.ts", + "sizeBytes": 5750, + "sha256": "f96e77de20bbda56606331d247cd9f08e73c86bbc3734c1f2d650d7ba397edc3" + }, + { + "path": "src/alphalib/types/robots/meta-read.ts", + "sizeBytes": 1626, + "sha256": "d039db9cc3fb5dff8523c398dcfd5ec7a4165e82c386bdcf5c8e10bab79fcade" + }, + { + "path": "dist/alphalib/types/robots/meta-write.d.ts", + "sizeBytes": 154336, + "sha256": "6dbc3ba00bebfbae0453baed961ad5f22c95a6a917d2dafcd208a45a21481aa0" + }, + { + "path": "src/alphalib/types/robots/meta-write.ts", + "sizeBytes": 3125, + "sha256": "49b1262dbcb811d08288f1efb102378b5ffed43c2bf05339d1fb088d0edb5a29" + }, + { + "path": "dist/alphalib/types/robots/minio-import.d.ts", + "sizeBytes": 12032, + "sha256": "56f5ad945624b293847bec369f49c57e4679b37884e4931b1dee102ab52e0636" + }, + { + "path": "src/alphalib/types/robots/minio-import.ts", + "sizeBytes": 4729, + "sha256": "6211c7a5c246574b614f3d7c0735de4a03242ff1c50954b1bd4d1cdc28bf3ae5" + }, + { + "path": "dist/alphalib/types/robots/minio-store.d.ts", + "sizeBytes": 21007, + "sha256": "4381c1c9a5447d4809742d37d11a3ae213e8875a60cad6d918fcf771a5814a8c" + }, + { + "path": "src/alphalib/types/robots/minio-store.ts", + "sizeBytes": 4189, + "sha256": "dc468fce6896ee822cd01eb5c1684e1c157aaf0f91530d965aee360baedb58e3" + }, + { + "path": "dist/alphalib/lib/nativeGlobby.d.ts", + "sizeBytes": 466, + "sha256": "df27afec76a5a459e53a10ac12f263924a1a99caadf239af8d943e1cf6de582d" + }, + { + "path": "src/alphalib/lib/nativeGlobby.ts", + "sizeBytes": 6987, + "sha256": "85bb1d37eb0e2c18f46178d28553a993c35eb2b145cfdddc90425ac5d3f6ec11" + }, + { + "path": "dist/cli/commands/notifications.d.ts", + "sizeBytes": 368, + "sha256": "8a557cc08d30854ae63efdd10cfc65add0eeca9748747bf5976f6264b89c9d62" + }, + { + "path": "src/cli/commands/notifications.ts", + "sizeBytes": 1829, + "sha256": "2cf790e6076b7983c6b88fb6aee38069765e2997b86b897bd0a4878ed5e3f966" + }, + { + "path": "dist/cli/OutputCtl.d.ts", + "sizeBytes": 1612, + "sha256": "bcccf6179f7ee37b2f78c572cc2d1d853ca9511fc90f4c887d8f7f7e95ce559a" + }, + { + "path": "src/cli/OutputCtl.ts", + "sizeBytes": 3397, + "sha256": "d16381cf45ae93b8bc2f42a9f4de9e1a012a21962e33a7ca36071cabca0e616a" + }, + { + "path": "dist/PaginationStream.d.ts", + "sizeBytes": 581, + "sha256": "952b4a59f1af5ba76db5d2aa705a88f5ff0ac58eeb09072b7bb8a1234be88245" + }, + { + "path": "src/PaginationStream.ts", + "sizeBytes": 1204, + "sha256": "ac24517761007d62f9f895cd69f89947cb98c1f79bb7c1038972648464c39260" + }, + { + "path": "dist/PollingTimeoutError.d.ts", + "sizeBytes": 144, + "sha256": "b8c5c12ce75d4ae2c196e4be780a67378547dd15fdccdb2824963e35a8ca4c96" + }, + { + "path": "src/PollingTimeoutError.ts", + "sizeBytes": 129, + "sha256": "52cd41a954b98ea5fdcb4d4b238da5e8203bd5c66a1e9c56bc25520cc6c1a6d5" + }, + { + "path": "dist/alphalib/types/robots/progress-simulate.d.ts", + "sizeBytes": 9245, + "sha256": "12e4ab3ccd73da1e7e7a45463a4eddf2b3091e04adf318d7737c0a56e915ad01" + }, + { + "path": "src/alphalib/types/robots/progress-simulate.ts", + "sizeBytes": 1325, + "sha256": "0591686d6c3787e0af4821649506d88034d3f302b021969dc91d612f7e9b3e8b" + }, + { + "path": "dist/alphalib/types/robots/s3-import.d.ts", + "sizeBytes": 13045, + "sha256": "32ffbd902a5c6c730f53014f642d371798fc110764c75d2ec5ab0a1cda02eb63" + }, + { + "path": "src/alphalib/types/robots/s3-import.ts", + "sizeBytes": 9153, + "sha256": "d3c2beb8aab49ce388cc98a63aa96221c82b70a41b89273e161fd783cf16ece2" + }, + { + "path": "dist/alphalib/types/robots/s3-store.d.ts", + "sizeBytes": 24603, + "sha256": "b2b926fdf8ed60165b5339f98dbbad4c5cb7e82a0d33c92a2e96a6ef87257f19" + }, + { + "path": "src/alphalib/types/robots/s3-store.ts", + "sizeBytes": 10404, + "sha256": "e46e6aa2a6c15c5253b17146af9f68506446ab6032f19dcf86d03abedf22216f" + }, + { + "path": "dist/alphalib/types/robots/script-run.d.ts", + "sizeBytes": 17234, + "sha256": "63547b6645117601d1de851b79bada4301a9b0f62a4bca1cf3e89632754786d0" + }, + { + "path": "src/alphalib/types/robots/script-run.ts", + "sizeBytes": 4500, + "sha256": "1f2ef9f81a63f341d419e289d1483e4c09c11df803771313690182d81e1ce543" + }, + { + "path": "dist/alphalib/types/robots/sftp-import.d.ts", + "sizeBytes": 9785, + "sha256": "a7fd349a62c27f52b20dfe99ed009e6b1fc7b6eaad15b51761e7eaa6f78ca3ed" + }, + { + "path": "src/alphalib/types/robots/sftp-import.ts", + "sizeBytes": 3065, + "sha256": "fe02c9a941b0d77c063dbb5bc2627da6a4828366a97ce6b6d7a43f912dfd22aa" + }, + { + "path": "dist/alphalib/types/robots/sftp-store.d.ts", + "sizeBytes": 20864, + "sha256": "f1c8e65870880f7afca120b6dfb9bad0c8d1289953bc33221ff19035e2f3a291" + }, + { + "path": "src/alphalib/types/robots/sftp-store.ts", + "sizeBytes": 4129, + "sha256": "8a2f0b5f00e281b04d9270286c679f2829e1c2b5186d41e96f495039a865ec08" + }, + { + "path": "dist/alphalib/types/robots/speech-transcribe.d.ts", + "sizeBytes": 19949, + "sha256": "ace8aa2b1d2f366893cb34bee4a0413bf117ff4302ffa2dde911cea802779bd5" + }, + { + "path": "src/alphalib/types/robots/speech-transcribe.ts", + "sizeBytes": 5994, + "sha256": "62516b051ea830ceb759193b72258eb9f71eb1ef592caf583de713afb4735571" + }, + { + "path": "dist/alphalib/types/stackVersions.d.ts", + "sizeBytes": 345, + "sha256": "abffa61231b5d99c58c189e8f2fefe52befa7caf031d89f15f9a9019a77deded" + }, + { + "path": "src/alphalib/types/stackVersions.ts", + "sizeBytes": 321, + "sha256": "2905f2af4f9dc9989eb957552b6a7212015f9ccc3830f5c1b47b3fe493ae23cb" + }, + { + "path": "dist/alphalib/types/robots/supabase-import.d.ts", + "sizeBytes": 12627, + "sha256": "90adca5e04dcfa03b5b36bac2754cc117599d1b35b15641296a7f67077e7c5b6" + }, + { + "path": "src/alphalib/types/robots/supabase-import.ts", + "sizeBytes": 4908, + "sha256": "b5010e2b076201a833ab305dce1ef0d3e27ec02bd9087136b79ccfa8057fc18e" + }, + { + "path": "dist/alphalib/types/robots/supabase-store.d.ts", + "sizeBytes": 21036, + "sha256": "053600e9ef6680faef2101ae027846ca8fa758e09a1e81be61be32a4add5c303" + }, + { + "path": "src/alphalib/types/robots/supabase-store.ts", + "sizeBytes": 4050, + "sha256": "db0973dfb43a0cfa30d27d7fc1cb0f8bca7a89c0eae0ac42583641fd5fbd4eac" + }, + { + "path": "dist/alphalib/types/robots/swift-import.d.ts", + "sizeBytes": 12540, + "sha256": "ac69f5083e4b43aa681f4b4a6250927a99180c35fb8b582d2f9bc92182f843fd" + }, + { + "path": "src/alphalib/types/robots/swift-import.ts", + "sizeBytes": 4769, + "sha256": "db5b4fce872e9379b32cb451238900b1a384d28c1be5481caf5910b8f306a14a" + }, + { + "path": "dist/alphalib/types/robots/swift-store.d.ts", + "sizeBytes": 21515, + "sha256": "54fe47c918a5738080d1e602663fbf800c2545defb8bb0cacb48c3ee6657f2a4" + }, + { + "path": "src/alphalib/types/robots/swift-store.ts", + "sizeBytes": 4259, + "sha256": "a3a48fd577d0a3c8afcbfb0933d3f18e87ab573885287a6be5dc9554720b1168" + }, + { + "path": "dist/cli/template-last-modified.d.ts", + "sizeBytes": 376, + "sha256": "7752e122126205b1a294c18a2fbfb3fdc00f2a88c5f188d277b51e97f0525613" + }, + { + "path": "src/cli/template-last-modified.ts", + "sizeBytes": 3925, + "sha256": "c319bb83a5a3e24fb9998976eeae74a2e9094356e2ac1495d8519df7aaa371d6" + }, + { + "path": "dist/alphalib/types/template.d.ts", + "sizeBytes": 7679978, + "sha256": "758e25908eaa5329ae33a9d694b339e4d8cb6def4459d5a8d1581829eeb667f5" + }, + { + "path": "src/alphalib/types/template.ts", + "sizeBytes": 11635, + "sha256": "4c72d99aefee3ffe6dc9fb285f51773d7446b24b4d6fff4aafb0a8d5b89efa18" + }, + { + "path": "dist/alphalib/types/templateCredential.d.ts", + "sizeBytes": 3526, + "sha256": "09de6d53950e580a1ed01a61fca71047c70637c9df24298e1772b310d188c82a" + }, + { + "path": "src/alphalib/types/templateCredential.ts", + "sizeBytes": 1375, + "sha256": "2d0a63c819e86f0eee3bb4811d32033ede8be5692ff9ba02c30147db42a41520" + }, + { + "path": "dist/cli/commands/templates.d.ts", + "sizeBytes": 2875, + "sha256": "62c3ab8980473a08a6ebfb662d56b517ad8cbc1f8d82988aa7f181e07d010bf0" + }, + { + "path": "src/cli/commands/templates.ts", + "sizeBytes": 16146, + "sha256": "12037ee25c27331aaa630d519c52e6c81520f002f79a74782b4d647d66025b15" + }, + { + "path": "dist/alphalib/types/robots/text-speak.d.ts", + "sizeBytes": 19946, + "sha256": "0360f009914c68cec3f2dbfb143047650c3a9fde1e2f5b31f17462cdf44f5c96" + }, + { + "path": "src/alphalib/types/robots/text-speak.ts", + "sizeBytes": 5446, + "sha256": "61faa11c89a96029931a6f950d7bf85e8cd021b852a90e0fc509fc22f286d297" + }, + { + "path": "dist/alphalib/types/robots/text-translate.d.ts", + "sizeBytes": 30846, + "sha256": "061b8a82ec26534d96598aebd5814921c0776dab9611c68650e538ec4b9f5388" + }, + { + "path": "src/alphalib/types/robots/text-translate.ts", + "sizeBytes": 6167, + "sha256": "60a628fc3a73ac29d989600c4697c80fc20f380713e3070bdbcb065f8bc14244" + }, + { + "path": "dist/alphalib/types/robots/tigris-import.d.ts", + "sizeBytes": 12557, + "sha256": "367ac7fd0910a21d0c91f368125cfbc2ac41b311011a602f0e1196ccbc1c6256" + }, + { + "path": "src/alphalib/types/robots/tigris-import.ts", + "sizeBytes": 4913, + "sha256": "4314fcad1af19426e44a715dfddcf66ca567194c7b1c562816acfef33f51bee0" + }, + { + "path": "dist/alphalib/types/robots/tigris-store.d.ts", + "sizeBytes": 21532, + "sha256": "8c86efd7e6562ff020749b2863272de41dfd2af637a5ae281a3e9d628f6e8460" + }, + { + "path": "src/alphalib/types/robots/tigris-store.ts", + "sizeBytes": 4368, + "sha256": "2ab40d3bf6dfce8c075b506f6ca037ecbcb94f7a8987a1b0a3190facd97e11b7" + }, + { + "path": "dist/alphalib/types/robots/tlcdn-deliver.d.ts", + "sizeBytes": 6141, + "sha256": "2d55583c45f4821e987b2c6c91508a9c5432520dcd184e96f4c744772eac673d" + }, + { + "path": "src/alphalib/types/robots/tlcdn-deliver.ts", + "sizeBytes": 2780, + "sha256": "98a184703f11ae07882a2b4424b0fe606ae32ef7f89331a1e89660a8df27ea1c" + }, + { + "path": "dist/Transloadit.d.ts", + "sizeBytes": 9194, + "sha256": "cbd0295d32aa80473afdfdf5674991228724c5488094936ddae3af329a8d3fe8" + }, + { + "path": "src/Transloadit.ts", + "sizeBytes": 31079, + "sha256": "0e431a0e5454816551e3980adde72035b32b0be00b464e626288477356822842" + }, + { + "path": "dist/alphalib/tryCatch.d.ts", + "sizeBytes": 772, + "sha256": "1eece292533e3695a84b6fa2fcd6c1bd38fc77f9a6b7cef849ff0852b6d79456" + }, + { + "path": "src/alphalib/tryCatch.ts", + "sizeBytes": 880, + "sha256": "e0c9e9a35c092d88ca2c861a3a2d4a75f9efa5101ec50e1c6a52a2ac06ac917c" + }, + { + "path": "dist/alphalib/types/robots/tus-store.d.ts", + "sizeBytes": 19747, + "sha256": "57fc17b9635c1a9c52c8a18c961f45a21ab43668c5e5912a9c7bdbb60464d2a4" + }, + { + "path": "src/alphalib/types/robots/tus-store.ts", + "sizeBytes": 5261, + "sha256": "2360ca08adc4ebbc35c6d4e2a14e5c5f3f4b52e6d57720c5c7958a71cf99ce3f" + }, + { + "path": "dist/tus.d.ts", + "sizeBytes": 698, + "sha256": "71419e8ef5a5308428a59d52bca3be0ad68d5428547d7a44ba6d7c72feba8a93" + }, + { + "path": "src/tus.ts", + "sizeBytes": 5040, + "sha256": "5214697dff961d604f6db254e4ae04f7513913d70734fef8a3eee33ddadd6111" + }, + { + "path": "dist/cli/types.d.ts", + "sizeBytes": 2503, + "sha256": "c62e78eb76dea08fc842b9557143dee71f4b68aaa1171249497f60279844ef31" + }, + { + "path": "src/cli/types.ts", + "sizeBytes": 1988, + "sha256": "34046c83c95ddb5087632149583b39d1e60e3bb5f373788bfb52ae5abf03dd75" + }, + { + "path": "dist/alphalib/types/robots/upload-handle.d.ts", + "sizeBytes": 6141, + "sha256": "f92f0f4fb223e34c6d71bc585ab29c15c6a3a101208e2653c992b23ff43cf54f" + }, + { + "path": "src/alphalib/types/robots/upload-handle.ts", + "sizeBytes": 3439, + "sha256": "4bf3de4456a3aa53d370f4568a0ab1c5423ac8f251d83c62b593fe7d8f814a9d" + }, + { + "path": "dist/alphalib/types/robots/video-adaptive.d.ts", + "sizeBytes": 213318, + "sha256": "7a79447f68a242551c39d73a5e020e09d7bbfbb1321d5d1ad88d353ef9037ec4" + }, + { + "path": "src/alphalib/types/robots/video-adaptive.ts", + "sizeBytes": 6616, + "sha256": "e74aff371f673ca60e8d472ab257bf97fdc0ab53fb097798abefc8392bd575ab" + }, + { + "path": "dist/alphalib/types/robots/video-concat.d.ts", + "sizeBytes": 212250, + "sha256": "d1536349d4065446732696fa8ec3a2807496d2dc302a2649ba11b3b2a2f08a1f" + }, + { + "path": "src/alphalib/types/robots/video-concat.ts", + "sizeBytes": 5221, + "sha256": "3165f6d83e36251c505d443821e3b0ce9697569f050be5369a19728aff4e83d0" + }, + { + "path": "dist/alphalib/types/robots/video-encode.d.ts", + "sizeBytes": 233784, + "sha256": "4e8287533efdd130afd6848f6f66653a4d4366778849894827ab4b066af49205" + }, + { + "path": "src/alphalib/types/robots/video-encode.ts", + "sizeBytes": 5101, + "sha256": "f75e56eb0bc43e46e9fccfef8c11fa86da70fd0ed231ba3125228a9b93b368dc" + }, + { + "path": "dist/alphalib/types/robots/video-merge.d.ts", + "sizeBytes": 216753, + "sha256": "2951bd8b2a929ab0fa5d2f23a092e68017ec1a4e50110005bbdde70363691936" + }, + { + "path": "src/alphalib/types/robots/video-merge.ts", + "sizeBytes": 6305, + "sha256": "8f70bdaf791e1e49e875f0876f6800591288e8f1dc24afad094163cf9cd2bfd5" + }, + { + "path": "dist/alphalib/types/robots/video-ondemand.d.ts", + "sizeBytes": 328276, + "sha256": "da59f54bbb515fbd9ef9cb1a19aa97561c1acd356acf9b1318383f9583ed0545" + }, + { + "path": "src/alphalib/types/robots/video-ondemand.ts", + "sizeBytes": 5479, + "sha256": "e380a593595c0295feaeaee0bc338cd9df43dce8d68f955b3b28b96e7d1528f0" + }, + { + "path": "dist/alphalib/types/robots/video-subtitle.d.ts", + "sizeBytes": 216860, + "sha256": "b0a6152e9b4b8b363c9c2f9f07195320277e2137c80266d55353c30d94d53d3e" + }, + { + "path": "src/alphalib/types/robots/video-subtitle.ts", + "sizeBytes": 5356, + "sha256": "b829093f0b0d8518216a21e9d8d1c8fec095ed97fdf80d0442141c001b2a1aa0" + }, + { + "path": "dist/alphalib/types/robots/video-thumbs.d.ts", + "sizeBytes": 159836, + "sha256": "76099f2d09bc70305d94cdbd22ff6b1d7f0510aba2e41e235ccfebe9f7f121f5" + }, + { + "path": "src/alphalib/types/robots/video-thumbs.ts", + "sizeBytes": 6146, + "sha256": "56ff17844959e49ca07a6dd55699c87164bd903dd67f1763a89b5bdf21132d2e" + }, + { + "path": "dist/alphalib/types/robots/vimeo-import.d.ts", + "sizeBytes": 10628, + "sha256": "c3d04aa4ba8bc9d60dd0b9adbfc1cf58b3f7cfca8e8f3ef7106324f9502cc9cb" + }, + { + "path": "src/alphalib/types/robots/vimeo-import.ts", + "sizeBytes": 4152, + "sha256": "2986132be48ace59ec097593bb5a339ce11a4a6a321a7cd50cc42a86c960a07b" + }, + { + "path": "dist/alphalib/types/robots/vimeo-store.d.ts", + "sizeBytes": 21633, + "sha256": "451488a53edaabdc43a66b68473b80dca87426f38f484d4c45eeed9c653a021a" + }, + { + "path": "src/alphalib/types/robots/vimeo-store.ts", + "sizeBytes": 5071, + "sha256": "ac4db748b1713592ea0aa131ee4dc59e360c676c8b7df3514ae410fb8f992938" + }, + { + "path": "dist/alphalib/types/robots/wasabi-import.d.ts", + "sizeBytes": 12587, + "sha256": "d4066c0bbc2667dd25945e6fc8a14a845cce2cc5c00f05ea8c393081fb5c1689" + }, + { + "path": "src/alphalib/types/robots/wasabi-import.ts", + "sizeBytes": 4898, + "sha256": "441b3730b98c5b2f0b86084d8ef169a7765771d84ef9fff15584d0638cf9b9b9" + }, + { + "path": "dist/alphalib/types/robots/wasabi-store.d.ts", + "sizeBytes": 21544, + "sha256": "f93747dc3012a7d553398f2da21f8e3945cbc082bbe7cba42aadbf5ec41b6aa2" + }, + { + "path": "src/alphalib/types/robots/wasabi-store.ts", + "sizeBytes": 4220, + "sha256": "45d6d2a669f03eb1e49e53f0463cde1a8f873e3562960e6596366569e8a33a6f" + }, + { + "path": "dist/alphalib/types/robots/youtube-store.d.ts", + "sizeBytes": 21333, + "sha256": "738f8455435c681022c8a7a6e703ecb6d1a53c338ea5c7678d04e634485d2f44" + }, + { + "path": "src/alphalib/types/robots/youtube-store.ts", + "sizeBytes": 5592, + "sha256": "d894f341c86bf6d1d21bc328eed7df2d686b07f4e59734a9354da309bcdb73cf" + }, + { + "path": "dist/alphalib/zodParseWithContext.d.ts", + "sizeBytes": 524, + "sha256": "6d8328e22a9419e3c879e1d1ea6ad40fc8c724d31e1351e60e45f3b81472313a" + }, + { + "path": "src/alphalib/zodParseWithContext.ts", + "sizeBytes": 12282, + "sha256": "d5bf1ecfed63b2a910c20048fa89ca52a84e83c4848c4eb891369b6857e55459" + } + ] +} diff --git a/docs/fingerprint/transloadit-baseline.json b/docs/fingerprint/transloadit-baseline.json new file mode 100644 index 00000000..7ce4a3ef --- /dev/null +++ b/docs/fingerprint/transloadit-baseline.json @@ -0,0 +1,2988 @@ +{ + "packageDir": "/home/kvz/code/node-sdk/packages/transloadit", + "tarball": { + "filename": "transloadit-4.1.2.tgz", + "sizeBytes": 1110479, + "sha256": "1f9d45a0d0055c488da28eac563ad79073cedfab16ccf7bd4e5ea9bb50cf655a" + }, + "packageJson": { + "name": "transloadit", + "version": "4.1.2", + "main": "./dist/Transloadit.js", + "exports": { + ".": "./dist/Transloadit.js", + "./package.json": "./package.json" + }, + "files": [ + "dist", + "src" + ] + }, + "files": [ + { + "path": "LICENSE", + "sizeBytes": 1081, + "sha256": "f2ef2628f6aafeca9a1b1230f13c21670af294088cb42d39fb764f8e5d569146" + }, + { + "path": "dist/alphalib/types/robots/_index.js", + "sizeBytes": 27976, + "sha256": "a78ae27fbae9d86e5b49d218a3685fac031cf2f6a6ccae372176f74e73d4af93" + }, + { + "path": "dist/alphalib/types/robots/_instructions-primitives.js", + "sizeBytes": 59552, + "sha256": "a9cb9b01c80fdb77fe5e8874ff58ade6447a725f2012c825ca87d2aaef5a339f" + }, + { + "path": "dist/alphalib/types/robots/ai-chat.js", + "sizeBytes": 9161, + "sha256": "3b750f7fa1d29200895f5b5b789326570fb85c1b65e51bc49bdeee73707dd3e0" + }, + { + "path": "dist/ApiError.js", + "sizeBytes": 1142, + "sha256": "04195425a4e243b510c7532ab81715fab616b626792c78d34cdacfd01b5e001e" + }, + { + "path": "dist/apiTypes.js", + "sizeBytes": 212, + "sha256": "b4f636535a1697010c34d7c2eca37ee2a0646441e3482174a83076a230031f47" + }, + { + "path": "dist/cli/commands/assemblies.js", + "sizeBytes": 41382, + "sha256": "5cf7b811de94982c89bcd5746dd2a25bf3fde22c78a2afb0c738b28de6a42031" + }, + { + "path": "dist/alphalib/types/assembliesGet.js", + "sizeBytes": 1454, + "sha256": "9e597e5a7b157453d64e4076d5eaa3e14af04e0a527faa7fee82e66252524d57" + }, + { + "path": "dist/alphalib/types/robots/assembly-savejson.js", + "sizeBytes": 829, + "sha256": "d5c6fae674126937d25d9f52ec84045c6a90c7c6f33a5b6a0adc96dfa8c94f83" + }, + { + "path": "dist/alphalib/types/assemblyReplay.js", + "sizeBytes": 708, + "sha256": "678ac3ccb4b3d4cfc239bc82315fecf3b6c0ab9830c4262af53094521efe49cd" + }, + { + "path": "dist/alphalib/types/assemblyReplayNotification.js", + "sizeBytes": 648, + "sha256": "99fc21decd44147f9800078b5f2030a5f4572eb939ad0d130e3d1b59aa919e74" + }, + { + "path": "dist/alphalib/types/assemblyStatus.js", + "sizeBytes": 28396, + "sha256": "4466ef1c9ed54e8955cae08480cd7f69d257b92965fa25a32402f1838929e075" + }, + { + "path": "dist/alphalib/types/robots/audio-artwork.js", + "sizeBytes": 3103, + "sha256": "76757ca647e388f1f25b9958d90eee491329ac055e69cc0d44f34f5588e01c32" + }, + { + "path": "dist/alphalib/types/robots/audio-concat.js", + "sizeBytes": 5014, + "sha256": "115fd94cccdb0a79e92b9c6bd0876c8c744d4c9abeba370cf2be6d9ca2e8ac1a" + }, + { + "path": "dist/alphalib/types/robots/audio-encode.js", + "sizeBytes": 2726, + "sha256": "c140e1b162b20e637ea71b3b6448dd4cd12ac7f1f1eb77d9d362458aabda4890" + }, + { + "path": "dist/alphalib/types/robots/audio-loop.js", + "sizeBytes": 2965, + "sha256": "dbe253c901dc7237b4641e5a915a4762b8e06406a5f1472d824ae3aa127e7cdc" + }, + { + "path": "dist/alphalib/types/robots/audio-merge.js", + "sizeBytes": 4445, + "sha256": "afe42b1aeeb4ae4521bdae2f9b66389c3e51a3be31ed6fef5444bf63e661094f" + }, + { + "path": "dist/alphalib/types/robots/audio-waveform.js", + "sizeBytes": 8845, + "sha256": "7303f23c4b4076e6c4df63b591ee5cd3eeef2eb704e6f5f5b09650bacf111c5e" + }, + { + "path": "dist/cli/commands/auth.js", + "sizeBytes": 10038, + "sha256": "58f10fbe648d8a655ececd76da07bfed67b9d737e879ca412f167988a1c164f2" + }, + { + "path": "dist/alphalib/types/robots/azure-import.js", + "sizeBytes": 3151, + "sha256": "6910a81f6a49a8d066f115fe5c01ca858544f1be5e873d69b4005eed8ff149dd" + }, + { + "path": "dist/alphalib/types/robots/azure-store.js", + "sizeBytes": 4221, + "sha256": "f172a4b516cee0ffa08ae7be418062269e1acb38a897758eb53af0ca6d75e6b7" + }, + { + "path": "dist/alphalib/types/robots/backblaze-import.js", + "sizeBytes": 3793, + "sha256": "d485074d56b7ad715a6bafcb24d325ddd82161375777624c2a051605dd08edbc" + }, + { + "path": "dist/alphalib/types/robots/backblaze-store.js", + "sizeBytes": 3067, + "sha256": "894476eb386dab764c1bcf6b432da7fa193181f4206523b1cefac17676fbef1c" + }, + { + "path": "dist/cli/commands/BaseCommand.js", + "sizeBytes": 1895, + "sha256": "1141a59a8ec2f47f6e5d4257b81e44fad8f50d693d1d22d6a0d6a2a08b5f8792" + }, + { + "path": "dist/alphalib/types/bill.js", + "sizeBytes": 223, + "sha256": "3dc47a7b3e5c570bf7ace002fe9438174b553118eee9ff95b5a9170d4c5d904f" + }, + { + "path": "dist/cli/commands/bills.js", + "sizeBytes": 2328, + "sha256": "fee6e43bf67ac5c5b2187300fde17c075e0ffc83a13b04d6485649ce23bd61e3" + }, + { + "path": "dist/cli.js", + "sizeBytes": 1147, + "sha256": "4d52d0cea6f64abe67fd99d9bdf14dee38a51ee9c366eb45f110f38ab008f4dd" + }, + { + "path": "dist/alphalib/types/robots/cloudfiles-import.js", + "sizeBytes": 3721, + "sha256": "e567b21d596f55897ac665f9381eeaae446bbd8b8d0194fe78eced661f90ee7d" + }, + { + "path": "dist/alphalib/types/robots/cloudfiles-store.js", + "sizeBytes": 2774, + "sha256": "4e62566a3bd5f3c721530756d69ffbdebe0f8068c481adc0cab0f89e045bfc98" + }, + { + "path": "dist/alphalib/types/robots/cloudflare-import.js", + "sizeBytes": 4080, + "sha256": "e9c54853a785a5c00c8f505e22f3f945cd916e4cc2ed86fa48df5a7615ceb6ee" + }, + { + "path": "dist/alphalib/types/robots/cloudflare-store.js", + "sizeBytes": 3732, + "sha256": "20620755e2983692110364ca87c5c2a33c682cf5bd0c4f2605fb7778aba0c269" + }, + { + "path": "dist/alphalib/types/robots/digitalocean-import.js", + "sizeBytes": 3732, + "sha256": "27446736ec4af2ed0cbc591d06c54d5ea267463dfdc966293412260fc9d209bc" + }, + { + "path": "dist/alphalib/types/robots/digitalocean-store.js", + "sizeBytes": 3911, + "sha256": "614596c4ce9011844b648692fe95579a630b81c47cf153a35bf5895daddb6930" + }, + { + "path": "dist/alphalib/types/robots/document-autorotate.js", + "sizeBytes": 1855, + "sha256": "f6b173cb57244a55a8b8664e995c4a6ed4fb24496acbbd88b72e236f935747aa" + }, + { + "path": "dist/alphalib/types/robots/document-convert.js", + "sizeBytes": 9264, + "sha256": "48fa4fcf88072fd2b0d5f77efe762fee2a1592696c16b715876856480dffb205" + }, + { + "path": "dist/alphalib/types/robots/document-merge.js", + "sizeBytes": 3113, + "sha256": "48c65c895d8d70c624a1a0485ed2d1330970644ffc253968fbeb1aa7e452dea3" + }, + { + "path": "dist/alphalib/types/robots/document-ocr.js", + "sizeBytes": 4580, + "sha256": "b9210e80eeebdcec9637eabe101d0bf60705b52c62241ad5b8363fd270ace5a3" + }, + { + "path": "dist/alphalib/types/robots/document-split.js", + "sizeBytes": 2052, + "sha256": "1cc694f4eb281b7ea81b3bd13e503d082d510cd7a709d312cd5e6345094e1e1b" + }, + { + "path": "dist/alphalib/types/robots/document-thumbs.js", + "sizeBytes": 9164, + "sha256": "b9b3bee876448a5994fa07b72c503c265ec0c424b720366c3e3f1f458e51106a" + }, + { + "path": "dist/alphalib/types/robots/dropbox-import.js", + "sizeBytes": 2739, + "sha256": "601c199b71199df3b6555751beb56a360601d811a8a101482abeb132dde7ce62" + }, + { + "path": "dist/alphalib/types/robots/dropbox-store.js", + "sizeBytes": 2641, + "sha256": "6cdd30c2c056d0088bada2d9291f161f4586098acdbc1f0b281a122190360287" + }, + { + "path": "dist/alphalib/types/robots/edgly-deliver.js", + "sizeBytes": 1993, + "sha256": "268fedb80d76c3a8406f6ea79ee453ce1b821dc9bdc2bc92977e429aeca9bcc2" + }, + { + "path": "dist/alphalib/types/robots/file-compress.js", + "sizeBytes": 6014, + "sha256": "2ae785374900612d9812ff336cb5de575dd9a3cfe7c07c79c92397f5b58ad8db" + }, + { + "path": "dist/alphalib/types/robots/file-decompress.js", + "sizeBytes": 3815, + "sha256": "ffb6ea41fcb598c2d9ac0931f0eb3a3327ceb78cd76b7975ecb61a797ed48726" + }, + { + "path": "dist/alphalib/types/robots/file-filter.js", + "sizeBytes": 6060, + "sha256": "7a2e6c361c4e23a1357d9771b0b4c953ccebcc0d1ead5f7783afa3042cf2f919" + }, + { + "path": "dist/alphalib/types/robots/file-hash.js", + "sizeBytes": 2160, + "sha256": "da50b20968ed9a1382b6798674d0d09170ddd27ecafba053c8f0cebd3959968c" + }, + { + "path": "dist/alphalib/types/robots/file-preview.js", + "sizeBytes": 12783, + "sha256": "167e45afe25e777baedeeff632cf90d394529a8c6fec6a891619b56257c26333" + }, + { + "path": "dist/alphalib/types/robots/file-read.js", + "sizeBytes": 1794, + "sha256": "990503f1b1c09b3de0220ed00539cc8b3bf8d01bb7b8a8f2da83c3d2f0d20981" + }, + { + "path": "dist/alphalib/types/robots/file-serve.js", + "sizeBytes": 5843, + "sha256": "1da5e06b7a0df96856d6e4f5eee1738f22d9df80a1f8634e0ddf5b0146e6a7ac" + }, + { + "path": "dist/alphalib/types/robots/file-verify.js", + "sizeBytes": 3481, + "sha256": "7400f95c002a9709a3815d1bd6ce7fe929821eb97178e4a2b9ddf66f6ebe827b" + }, + { + "path": "dist/alphalib/types/robots/file-virusscan.js", + "sizeBytes": 3859, + "sha256": "d47518d1d345ace571c9eaaad3e15b49ab4267fd7fb7dfed3d3c9539a4cffca9" + }, + { + "path": "dist/alphalib/types/robots/file-watermark.js", + "sizeBytes": 1228, + "sha256": "474e8f93000f842761a1cebe9282c17eeba8c809f1d8ef25db026796edacbf89" + }, + { + "path": "dist/alphalib/types/robots/ftp-import.js", + "sizeBytes": 2406, + "sha256": "ed845f664e1ae7ab2cf8bf3c061f294f0926bb8889935afefa38e056f98e6526" + }, + { + "path": "dist/alphalib/types/robots/ftp-store.js", + "sizeBytes": 3534, + "sha256": "c4bd648bb097acadbc349406192105367b9d94c516700b99c9f4d7a4b6c7a6f0" + }, + { + "path": "dist/alphalib/types/robots/google-import.js", + "sizeBytes": 3748, + "sha256": "0688e2f84f217ae26b187916b93c6e4f32539c0fc84b6cb162ea2230cd81ae27" + }, + { + "path": "dist/alphalib/types/robots/google-store.js", + "sizeBytes": 5495, + "sha256": "c35a94120a06d17559df3ddf18a18a7d6a89a858ff4cdff4c12a6a3d1dec17ed" + }, + { + "path": "dist/cli/helpers.js", + "sizeBytes": 1239, + "sha256": "3d5c40d5c39207a606a545b1492ab53ebdfbe1592cb66e0b1493de870bbdb6af" + }, + { + "path": "dist/alphalib/types/robots/html-convert.js", + "sizeBytes": 5294, + "sha256": "17c47da30d42ce1209bcb0c53dddc14c119a6c202ae8f0f27afaaf0927a2c72c" + }, + { + "path": "dist/alphalib/types/robots/http-import.js", + "sizeBytes": 5758, + "sha256": "6257ae9fa7e6c9ef61cfd9be9b9f807da82caeaabf7bfb381da61948376a36ee" + }, + { + "path": "dist/alphalib/types/robots/image-bgremove.js", + "sizeBytes": 2515, + "sha256": "0016427ea42441dfba53c65ece6a30586456b14818dafc48d8f025ff910697c2" + }, + { + "path": "dist/alphalib/types/robots/image-describe.js", + "sizeBytes": 4568, + "sha256": "8f1f5d50e461b9ec9f223fa894e278a2fb8198823fbdd8a8cae6ef7bfd50ff5d" + }, + { + "path": "dist/alphalib/types/robots/image-facedetect.js", + "sizeBytes": 6408, + "sha256": "b8b19422756cfe35d038bccd7167fcaee1935932df441de68d9d16598a6c3141" + }, + { + "path": "dist/alphalib/types/robots/image-generate.js", + "sizeBytes": 2681, + "sha256": "85713e4db98b326fca7530d3f50f8fea2acc3bb56c10582f63cb85bc3ceaadf6" + }, + { + "path": "dist/alphalib/types/robots/image-merge.js", + "sizeBytes": 3712, + "sha256": "23c06084dfe66c3ebdbf11f7c93de2e928112814507987902520e0695d2fe6a5" + }, + { + "path": "dist/alphalib/types/robots/image-ocr.js", + "sizeBytes": 4316, + "sha256": "fe040da6c69082e831c8897389f2c32b13f488714ae0d9e97685865176c6ddce" + }, + { + "path": "dist/alphalib/types/robots/image-optimize.js", + "sizeBytes": 4187, + "sha256": "d9d455acad58e028da5c948b735025943ee07b112338c3b66933ed0ff4db9e54" + }, + { + "path": "dist/alphalib/types/robots/image-resize.js", + "sizeBytes": 27934, + "sha256": "7683dca61e77618aad347431b7693fac282d208526dde351ba86387a53c962f4" + }, + { + "path": "dist/InconsistentResponseError.js", + "sizeBytes": 158, + "sha256": "ed9fa27d9022fa08f620bb0f94cc17222c35301dd9bda8bfc59db669c9262ada" + }, + { + "path": "dist/cli/commands/index.js", + "sizeBytes": 1730, + "sha256": "f9b0afff030bd07795df879b5bf78d75ede571973a6a671803c7551b6c3e87c9" + }, + { + "path": "dist/alphalib/mcache.js", + "sizeBytes": 4515, + "sha256": "abcc5fb21d05f7c04bb7c454bdbce15c25f7d4fec03b901291bc8b2925e95d16" + }, + { + "path": "dist/alphalib/types/robots/meta-read.js", + "sizeBytes": 1119, + "sha256": "94bd6bb2e45f20009fffbf2d0395ac702ac4295c1b7e5088ae7cb12e1bdaeb5e" + }, + { + "path": "dist/alphalib/types/robots/meta-write.js", + "sizeBytes": 2446, + "sha256": "5bb3b6372ca87e8a18aa0a3c41a50f7cee87cafdca8e95a885b4de83dbf4611a" + }, + { + "path": "dist/alphalib/types/robots/minio-import.js", + "sizeBytes": 3985, + "sha256": "053ed2a56f66ceb50e780cf4478a6a66c1e2503d0f0f295252beeaaa8a06e1b1" + }, + { + "path": "dist/alphalib/types/robots/minio-store.js", + "sizeBytes": 3504, + "sha256": "6194fc2e78f65aa48c1d5c85d7c9a488e501436f8ef6758077aaa0da8bc44185" + }, + { + "path": "dist/alphalib/lib/nativeGlobby.js", + "sizeBytes": 6315, + "sha256": "33e19cc01c00058e2c46866a7dddc29769edffaab132f412d671f6f52a4be380" + }, + { + "path": "dist/cli/commands/notifications.js", + "sizeBytes": 1640, + "sha256": "5a419d28577d2952f32282ff0227ffa2cfbec6f4c3a48a6974b5485453297e44" + }, + { + "path": "dist/cli/OutputCtl.js", + "sizeBytes": 2838, + "sha256": "ccd55e24ca9b05134aeb5051b2b17a9b1dc181bff73b875f63ccde8071564630" + }, + { + "path": "dist/PaginationStream.js", + "sizeBytes": 1049, + "sha256": "955003e8e346cb275a55989da117fc99d7fb17ad2f2282e02b1940c6b3fceb85" + }, + { + "path": "dist/PollingTimeoutError.js", + "sizeBytes": 172, + "sha256": "bef858dea74a3ac0a03e6d595e6276d63fe272f9cb601d3101eeec8723ebb48a" + }, + { + "path": "dist/alphalib/types/robots/progress-simulate.js", + "sizeBytes": 935, + "sha256": "e01935073eab55214d9e37fa2d25e5615368efb8e9e2aedfa7a765e0d6e2bd84" + }, + { + "path": "dist/alphalib/types/robots/s3-import.js", + "sizeBytes": 8446, + "sha256": "d2abd1d554916505892242fb68b64bcc29350963f97808ccd57e047f487bb00a" + }, + { + "path": "dist/alphalib/types/robots/s3-store.js", + "sizeBytes": 9698, + "sha256": "7ac1cebb40a5959581740147f7ef1e3d680199a9f9e39a3562e6f818fbc5a0cb" + }, + { + "path": "dist/alphalib/types/robots/script-run.js", + "sizeBytes": 3740, + "sha256": "94f608e168909cf2dbb588d4c9c591921fbc2c13d68e11c51784e08b1588649c" + }, + { + "path": "dist/alphalib/types/robots/sftp-import.js", + "sizeBytes": 2340, + "sha256": "453005b909a864b3a6abf2714232e45aa5f7c08303579454301d3e80f2ad8e97" + }, + { + "path": "dist/alphalib/types/robots/sftp-store.js", + "sizeBytes": 3455, + "sha256": "4c023f0931db25b7d99da7f56828d5d7d2132c6e467ceda0bb764a0ec21d2555" + }, + { + "path": "dist/alphalib/types/robots/speech-transcribe.js", + "sizeBytes": 5236, + "sha256": "e60ffe357e734f4031d64f6574d885f009eedd62b77ab4052318f23a35ef8a4e" + }, + { + "path": "dist/alphalib/types/stackVersions.js", + "sizeBytes": 359, + "sha256": "44173300fc46f06c80f670d5a3a72e403cfcaddd7eb60703bc057ece42292ece" + }, + { + "path": "dist/alphalib/types/robots/supabase-import.js", + "sizeBytes": 4131, + "sha256": "774aacd11972e02c5f07217c43dc5a5c553c28191c36ea35e1f276019cbe395b" + }, + { + "path": "dist/alphalib/types/robots/supabase-store.js", + "sizeBytes": 3326, + "sha256": "8d0a8f42a2b26ff4db7a4ff9053bebd4ca9c772df64ffff60b8a433e055ffcea" + }, + { + "path": "dist/alphalib/types/robots/swift-import.js", + "sizeBytes": 4025, + "sha256": "96c59605b04963ad242f8f689b20149d07fd9216d936f097e0ee9646f52411f3" + }, + { + "path": "dist/alphalib/types/robots/swift-store.js", + "sizeBytes": 3574, + "sha256": "0d8af15a6f01c57651930e010d9190341d5ca41be81565b5c10549e21406c3c7" + }, + { + "path": "dist/cli/template-last-modified.js", + "sizeBytes": 4183, + "sha256": "26fb01b74d324df74e120cd7640a64d221214879e44dd695903602afbc01ee58" + }, + { + "path": "dist/alphalib/types/template.js", + "sizeBytes": 10452, + "sha256": "b48fbb82af77032c3076c5016410f251759ad295344557215753d931679c0679" + }, + { + "path": "dist/alphalib/types/templateCredential.js", + "sizeBytes": 1423, + "sha256": "02c74c8b94d3514c65c86af727ccf69acf6f3ef1cac184eea35aab75bd0b554f" + }, + { + "path": "dist/cli/commands/templates.js", + "sizeBytes": 15694, + "sha256": "fc2e8b636bf2f3d6c61bca5d11cf54acdf9f2e23d57d608cf282c94e2f9ea984" + }, + { + "path": "dist/alphalib/types/robots/text-speak.js", + "sizeBytes": 4762, + "sha256": "9021afe8eee26c0a33cbaf894e6151bce60073c8d22a40dab0ef8db8ae37223d" + }, + { + "path": "dist/alphalib/types/robots/text-translate.js", + "sizeBytes": 5413, + "sha256": "e6619ba063df5a848d3f80193b7f3fd427d223d8ea6863f144fdf7ffa2cd6643" + }, + { + "path": "dist/alphalib/types/robots/tigris-import.js", + "sizeBytes": 4164, + "sha256": "1329c999c4e31bf4f91aa76b4376b642367e91cbb2da69914166a83cfe05a899" + }, + { + "path": "dist/alphalib/types/robots/tigris-store.js", + "sizeBytes": 3678, + "sha256": "ebba4a6bfdf08283da83254b89baed774aa8080f40f97e93b90c2831b58461dc" + }, + { + "path": "dist/alphalib/types/robots/tlcdn-deliver.js", + "sizeBytes": 1988, + "sha256": "84f79f7a6d04e6330a7c032d609db16748ece8d750ca3f3023eed43a440f4b15" + }, + { + "path": "dist/Transloadit.js", + "sizeBytes": 28131, + "sha256": "8a9f68704b6dfaef019f10a79fbcc967034e6be6787e3f112c2ee480ae1ba5d3" + }, + { + "path": "dist/alphalib/tryCatch.js", + "sizeBytes": 447, + "sha256": "822422b495de06b013adca2c952371b85c5ce27f05058112384eec0781d7b80b" + }, + { + "path": "dist/alphalib/types/robots/tus-store.js", + "sizeBytes": 4596, + "sha256": "b845028fc26a96ba0509b0f8dc2444bed647144ad2415d167bd495df84d3217b" + }, + { + "path": "dist/tus.js", + "sizeBytes": 5058, + "sha256": "1f287a9083e9264f509833a1193f8cf4fae161cfa6d126ac3f81849f05ca3bc9" + }, + { + "path": "dist/cli/types.js", + "sizeBytes": 1433, + "sha256": "26181f39ef63756230a5ce4a3746b745e07949246d1ec72306f9960207f8bdeb" + }, + { + "path": "dist/alphalib/types/robots/upload-handle.js", + "sizeBytes": 2710, + "sha256": "0da0cf7c28a54af82ac125af0129f885b111b9e48cd64c477865d5438e29974d" + }, + { + "path": "dist/alphalib/types/robots/video-adaptive.js", + "sizeBytes": 6059, + "sha256": "6f2630b0d877d9c3ec398535231ef25cb31d3916e4613eb4b406cba8bc334613" + }, + { + "path": "dist/alphalib/types/robots/video-concat.js", + "sizeBytes": 4707, + "sha256": "fa977c68900d0417506fabc3a2b15dd96571374da707e0f79cfd48e9612e82b9" + }, + { + "path": "dist/alphalib/types/robots/video-encode.js", + "sizeBytes": 4357, + "sha256": "39dc586629c3715b6b9d8534477b79f82d790b052979d09cdd942ea9767f93a2" + }, + { + "path": "dist/alphalib/types/robots/video-merge.js", + "sizeBytes": 5568, + "sha256": "eb7b72fe71fa99aabcd6d83b24f836f1642447d6b8f2d42126698a1b3ee22669" + }, + { + "path": "dist/alphalib/types/robots/video-ondemand.js", + "sizeBytes": 4856, + "sha256": "b3a449cba726f0256be971a52f445f4319b6fe98300782bfae82417f4c910701" + }, + { + "path": "dist/alphalib/types/robots/video-subtitle.js", + "sizeBytes": 4789, + "sha256": "154d14545e5ea067928b4310e904f4c3ebab0be18f2e57fa4afbe252df679063" + }, + { + "path": "dist/alphalib/types/robots/video-thumbs.js", + "sizeBytes": 5469, + "sha256": "f25a86957a6a6a8a0eadd4e5db493405528c0d3aedb4576ddef9d0b0bc1394d3" + }, + { + "path": "dist/alphalib/types/robots/vimeo-import.js", + "sizeBytes": 3486, + "sha256": "7877ea6f6f225d50cb3767c5dd9efa67a08f867ca13ea534939682fc1a65c109" + }, + { + "path": "dist/alphalib/types/robots/vimeo-store.js", + "sizeBytes": 4410, + "sha256": "68f6b9618c990bc00f822a0e61f87ac8972ea0ebb2f795a13f2dd0f5b9e39a4d" + }, + { + "path": "dist/alphalib/types/robots/wasabi-import.js", + "sizeBytes": 4149, + "sha256": "f0886f787e0b56a07bed3abdb6e9e1b142bbc9f6d422ad060426e7f343329cb8" + }, + { + "path": "dist/alphalib/types/robots/wasabi-store.js", + "sizeBytes": 3524, + "sha256": "59f53885f81e1ca96733076d59492b4ea16e090185f98a02f1182d57ceb71930" + }, + { + "path": "dist/alphalib/types/robots/youtube-store.js", + "sizeBytes": 4838, + "sha256": "cc711980ff82e0050c63b28e04714c82b38856d785ac5f9b08eefa89f4a1bd15" + }, + { + "path": "dist/alphalib/zodParseWithContext.js", + "sizeBytes": 13900, + "sha256": "95686fd259cf628f479d483dff11edf5ec4bceb75f9780e079fa2444948260fe" + }, + { + "path": "package.json", + "sizeBytes": 2648, + "sha256": "a2fd83a1adc245ade4d1a376c3d8c92ba286e73412afb89fa5d8ef31212f560d" + }, + { + "path": "dist/alphalib/types/robots/_index.d.ts.map", + "sizeBytes": 83925, + "sha256": "4a58b2d2526d61cf04de1f64cd577281c16010c6c209bf03e2c9472d78e17ca1" + }, + { + "path": "dist/alphalib/types/robots/_index.js.map", + "sizeBytes": 10505, + "sha256": "0a08c1185a523b944b5beed2f8d4148ead599a31bae1233e7a4fe38f9678fc84" + }, + { + "path": "dist/alphalib/types/robots/_instructions-primitives.d.ts.map", + "sizeBytes": 10500, + "sha256": "ca5baf2a027f31493bb771f8bbdd9e7986ef3b5ad28013316c2fa0fedcce1eca" + }, + { + "path": "dist/alphalib/types/robots/_instructions-primitives.js.map", + "sizeBytes": 36172, + "sha256": "3436569bb5194b67b6f7606f4c7225aae7abf35285d4beab3196ed2ff7e30790" + }, + { + "path": "dist/alphalib/types/robots/ai-chat.d.ts.map", + "sizeBytes": 3190, + "sha256": "6921ce50d888101af8bd473fa0c52780df529fba30531295810ec191267a47f9" + }, + { + "path": "dist/alphalib/types/robots/ai-chat.js.map", + "sizeBytes": 7431, + "sha256": "38d154af5baea41967ac69fa5fa725189006ff7afcaaf098cde2178fb340e9c2" + }, + { + "path": "dist/ApiError.d.ts.map", + "sizeBytes": 669, + "sha256": "0f8015ffaa115fe02d3877267c7e236c3ad8b98c2e7bf9f5a66a389464ecdc62" + }, + { + "path": "dist/ApiError.js.map", + "sizeBytes": 1182, + "sha256": "b14ea886615cd781bf6d852ad60154339e06aa8541d70c1a352115807ea8af52" + }, + { + "path": "dist/apiTypes.d.ts.map", + "sizeBytes": 3499, + "sha256": "afccc6e3b08e1a87a42a680ffa1439d4cf0e883a1ebc3b19832b2f25db939987" + }, + { + "path": "dist/apiTypes.js.map", + "sizeBytes": 210, + "sha256": "74dee10f68f3060119affdbe0db6d1ad4cdc8a75b9345bc7107e071da1d69010" + }, + { + "path": "dist/cli/commands/assemblies.d.ts.map", + "sizeBytes": 3081, + "sha256": "3fbad0cc1518bfc5daa6aa1ac0ae071d9ee6d7559b655f1fdc03af4a6fc2b459" + }, + { + "path": "dist/cli/commands/assemblies.js.map", + "sizeBytes": 37213, + "sha256": "81900715bbe8def1da2bbaec42f51b311a083e7382daab88a3f4a899998cf4e9" + }, + { + "path": "dist/alphalib/types/assembliesGet.d.ts.map", + "sizeBytes": 263, + "sha256": "df2b0c48f851d217f2c2d31c8eb287c5eef4864eacaa0850fd3ac340a632487b" + }, + { + "path": "dist/alphalib/types/assembliesGet.js.map", + "sizeBytes": 961, + "sha256": "5295095d35108254c8b002333cf3d4d82d7d64d19be93fad049358ab1e78f217" + }, + { + "path": "dist/alphalib/types/robots/assembly-savejson.d.ts.map", + "sizeBytes": 573, + "sha256": "11ae78232682c6feb764982cb9c5166dd9660bad8f1fbfa26e9884a54daa2a66" + }, + { + "path": "dist/alphalib/types/robots/assembly-savejson.js.map", + "sizeBytes": 701, + "sha256": "1410553d8a5f26d188bb58de38ce04123e63f2d5d749bbfd0fe6495d84dd0078" + }, + { + "path": "dist/alphalib/types/assemblyReplay.d.ts.map", + "sizeBytes": 8129, + "sha256": "ac663fe8af49ddf31ac35347b4f1052c5234b98fb206fc7314cb18669fa4e0d5" + }, + { + "path": "dist/alphalib/types/assemblyReplay.js.map", + "sizeBytes": 612, + "sha256": "e3515c0e8c7af60e5655c8e8befc9d9456f0951c5db08b9fdbdc2753198a0719" + }, + { + "path": "dist/alphalib/types/assemblyReplayNotification.d.ts.map", + "sizeBytes": 8144, + "sha256": "aafb363bad7c9ae2ec4a94a79073be7b80f4e81ef7efe73e0e5b1ed11e853399" + }, + { + "path": "dist/alphalib/types/assemblyReplayNotification.js.map", + "sizeBytes": 477, + "sha256": "912872cd7b257168bb487687c7ca898633269f43849a8c36ac25629002604452" + }, + { + "path": "dist/alphalib/types/assemblyStatus.d.ts.map", + "sizeBytes": 73453, + "sha256": "51782d698296dcc8a71e651ab5c562b5a6939fc83fadf70538ddf0a6fda22532" + }, + { + "path": "dist/alphalib/types/assemblyStatus.js.map", + "sizeBytes": 31739, + "sha256": "d915945fb5cdcae71448b0baa4ad7a6ebd22848f5112858bb326920d8794be42" + }, + { + "path": "dist/alphalib/types/robots/audio-artwork.d.ts.map", + "sizeBytes": 3653, + "sha256": "353a21c0c3103024565abd36aa4767d7b67e04c55916ec2f34993c7aefd01118" + }, + { + "path": "dist/alphalib/types/robots/audio-artwork.js.map", + "sizeBytes": 1835, + "sha256": "8b45f9bcc11be303e408e50c56932d3747bc80c515eaad50fe9b9fc9de32b137" + }, + { + "path": "dist/alphalib/types/robots/audio-concat.d.ts.map", + "sizeBytes": 3700, + "sha256": "11a6b27a252899506dd9a75539c82cc65a94ad95057187cb7d60fd27dcbfd1b8" + }, + { + "path": "dist/alphalib/types/robots/audio-concat.js.map", + "sizeBytes": 2368, + "sha256": "cf43600aa6b1131695f23728291f4550688701f5aad2430631004c4b982a0df0" + }, + { + "path": "dist/alphalib/types/robots/audio-encode.d.ts.map", + "sizeBytes": 3648, + "sha256": "00bcc0475f9cd159b74fabff5ef5a9ec8659929d49dc01f660aa2214e6980c81" + }, + { + "path": "dist/alphalib/types/robots/audio-encode.js.map", + "sizeBytes": 1853, + "sha256": "170083f1b2e97951fe027d364d0508fb9be83f199e755f6c2dba565575b85866" + }, + { + "path": "dist/alphalib/types/robots/audio-loop.d.ts.map", + "sizeBytes": 3658, + "sha256": "506a308f01cfd07b72266b26c4ca70d439135e5333368273612fff8501061237" + }, + { + "path": "dist/alphalib/types/robots/audio-loop.js.map", + "sizeBytes": 1859, + "sha256": "e3c8723e8e8a6456269c5aa19f041beb95eecfb7077bebab05228fa42a0b01ef" + }, + { + "path": "dist/alphalib/types/robots/audio-merge.d.ts.map", + "sizeBytes": 3713, + "sha256": "200ce1e8d4be67a04522707ccb648b918f465f47cea81d253e282a3959d51bb4" + }, + { + "path": "dist/alphalib/types/robots/audio-merge.js.map", + "sizeBytes": 2434, + "sha256": "72aa8bf105e43533deb5a5f3aa3e7865120d9e1b0b92b1f0e7f1f5733be2b0bd" + }, + { + "path": "dist/alphalib/types/robots/audio-waveform.d.ts.map", + "sizeBytes": 3950, + "sha256": "8d1bc9e02a68d4d18d07a8fe5d55c9d1f8bcb5c61978b677deb0fb32f6ddd562" + }, + { + "path": "dist/alphalib/types/robots/audio-waveform.js.map", + "sizeBytes": 4691, + "sha256": "ef7f6f12ae396db830f8c92314712cfd958697a711a26725f6b2281019b7e170" + }, + { + "path": "dist/cli/commands/auth.d.ts.map", + "sizeBytes": 749, + "sha256": "a8fc5875ca97a6dbdb65b7830479c4c4cd9f5949fe4a8c95106700705439e966" + }, + { + "path": "dist/cli/commands/auth.js.map", + "sizeBytes": 9049, + "sha256": "2b239bbfbe26870ca1fb542220842283a0b391267cedbecf298fff39bc8e0da1" + }, + { + "path": "dist/alphalib/types/robots/azure-import.d.ts.map", + "sizeBytes": 994, + "sha256": "f176be84e91088b839e5769a7a249eb0f25ebcb67fde7db712997f46c2c32bd5" + }, + { + "path": "dist/alphalib/types/robots/azure-import.js.map", + "sizeBytes": 1772, + "sha256": "1a699e6491c85a6cea6a6dfd2de985fa5a5b2aef4fb99f92093dab8085c193eb" + }, + { + "path": "dist/alphalib/types/robots/azure-store.d.ts.map", + "sizeBytes": 1344, + "sha256": "24846cd482aa230e9c961d7032bdf5e3e02c21139ad6d4454c46d1dfa9acc94b" + }, + { + "path": "dist/alphalib/types/robots/azure-store.js.map", + "sizeBytes": 2407, + "sha256": "58b0f3158aca067ae6e398a5f4a37935907ce2916ac15e172dcef0ead8d9afcb" + }, + { + "path": "dist/alphalib/types/robots/backblaze-import.d.ts.map", + "sizeBytes": 1003, + "sha256": "c22ab6d436286cb3e0ba6fc739efe5e9a4c9fc073f3d7dc483c8cf2876f5e819" + }, + { + "path": "dist/alphalib/types/robots/backblaze-import.js.map", + "sizeBytes": 1838, + "sha256": "5328598e0fdf883487259800873e9155ebbe3458b5e13eb35247519a9f258e95" + }, + { + "path": "dist/alphalib/types/robots/backblaze-store.d.ts.map", + "sizeBytes": 1268, + "sha256": "b16532e497d1282929b3d743db600f4463052fdafc7b0c953371c86295efcda8" + }, + { + "path": "dist/alphalib/types/robots/backblaze-store.js.map", + "sizeBytes": 1779, + "sha256": "18a983d205a1a89ba39f80ed3f2f9c0af3c5b0bfbda262b09b0fa5510a82495b" + }, + { + "path": "dist/cli/commands/BaseCommand.d.ts.map", + "sizeBytes": 853, + "sha256": "22f956bf0d909d109be49926c456031c783bafccc688056d1911b7440cdf77c3" + }, + { + "path": "dist/cli/commands/BaseCommand.js.map", + "sizeBytes": 1740, + "sha256": "14ec91bb62f94bc02328d085cf1d6b7541f3e17d10703c39f85df40969cd84eb" + }, + { + "path": "dist/alphalib/types/bill.d.ts.map", + "sizeBytes": 233, + "sha256": "100418a9cbbb497bd3deb4fba29c4bff7cd202c869085f2ac1cd1293d403548a" + }, + { + "path": "dist/alphalib/types/bill.js.map", + "sizeBytes": 308, + "sha256": "6e2138660b44631f62207483c146991bd6e0e010067d85af9109d72f89224953" + }, + { + "path": "dist/cli/commands/bills.d.ts.map", + "sizeBytes": 585, + "sha256": "54258ccf4730a4b0989883ab5a4b67b5deb7e7fba3a25581743a20a9fc8bfa82" + }, + { + "path": "dist/cli/commands/bills.js.map", + "sizeBytes": 2277, + "sha256": "15f2a633092558d16a9026ac829a4458e6ff4e3f4b51f5277c40f9785df82cc0" + }, + { + "path": "dist/cli.d.ts.map", + "sizeBytes": 278, + "sha256": "5e6f1a916256a81fdc3e6678644c191a87bf1bbcb273e0e256a1e04533c045cd" + }, + { + "path": "dist/cli.js.map", + "sizeBytes": 1335, + "sha256": "aa838fe53a894d7c2eca041e15963a3ddb50e3bf127911f472da237aec07ae56" + }, + { + "path": "dist/alphalib/types/robots/cloudfiles-import.d.ts.map", + "sizeBytes": 1029, + "sha256": "0869484ee48ae682859788c6ccad011fcc16dab0854a6d6a727cd13a0cfb0902" + }, + { + "path": "dist/alphalib/types/robots/cloudfiles-import.js.map", + "sizeBytes": 1821, + "sha256": "067afacca012fb1d96267aa96370f459b1d85e75727922874910a5edcc07bc50" + }, + { + "path": "dist/alphalib/types/robots/cloudfiles-store.d.ts.map", + "sizeBytes": 1282, + "sha256": "71887d8900734a6c6bf3964018d0143135d016740daf537ddd2d6546a70dd3d4" + }, + { + "path": "dist/alphalib/types/robots/cloudfiles-store.js.map", + "sizeBytes": 1651, + "sha256": "7631f3c8361b3ed5315a26310954e49fd81db2c5a091954087006ba3efb9053b" + }, + { + "path": "dist/alphalib/types/robots/cloudflare-import.d.ts.map", + "sizeBytes": 1029, + "sha256": "ce4aab1f1c2d1be7f7fadf750582c137edd423c1839fb18581539b548184795f" + }, + { + "path": "dist/alphalib/types/robots/cloudflare-import.js.map", + "sizeBytes": 1839, + "sha256": "cb3a961fbf7e141e32692dcce6f94a5c37cba356f0f27e92b97d5bb4eca04da9" + }, + { + "path": "dist/alphalib/types/robots/cloudflare-store.d.ts.map", + "sizeBytes": 1307, + "sha256": "32f6c9187a32831b74c67f07d415f8e5132f7d7f5be73e3e42985f6bfa9ba7dd" + }, + { + "path": "dist/alphalib/types/robots/cloudflare-store.js.map", + "sizeBytes": 1990, + "sha256": "c62aed0ef67118db1bdf93e63a6159295e2dd818c1ee912a25ce54be09089361" + }, + { + "path": "dist/alphalib/types/robots/digitalocean-import.d.ts.map", + "sizeBytes": 1033, + "sha256": "b4ca8b8b29285f0ded9587aa9f66146d2feef4aa22f4fb04dc505e51074d0989" + }, + { + "path": "dist/alphalib/types/robots/digitalocean-import.js.map", + "sizeBytes": 1825, + "sha256": "a79d68e6246b971ce82c154c3f9d4922d8dc887a2387f369ae46dbc5ba0ce531" + }, + { + "path": "dist/alphalib/types/robots/digitalocean-store.d.ts.map", + "sizeBytes": 1323, + "sha256": "5516c371bf38b3d1e4ea87ee409602c49848c3f9891fb2a67648d61fa83a4829" + }, + { + "path": "dist/alphalib/types/robots/digitalocean-store.js.map", + "sizeBytes": 2110, + "sha256": "03bf0e17cc0b4752df73ccdf82aa4ca27e6f5455448d3b5469fb440649eb88dd" + }, + { + "path": "dist/alphalib/types/robots/document-autorotate.d.ts.map", + "sizeBytes": 1203, + "sha256": "2b806b760baa93da32c9ed78d7bf3ff51a2044d08e80e450a366912d7ad7bba2" + }, + { + "path": "dist/alphalib/types/robots/document-autorotate.js.map", + "sizeBytes": 1362, + "sha256": "1e7adcd6fc7ca833092b64c568d60775e939b70b5382c8fc20ae2dd4bcdc2cfe" + }, + { + "path": "dist/alphalib/types/robots/document-convert.d.ts.map", + "sizeBytes": 1306, + "sha256": "ce5ed754a73b39ad8143b4faef3d4132418bf8ffe09e0a294802834c31ac4b72" + }, + { + "path": "dist/alphalib/types/robots/document-convert.js.map", + "sizeBytes": 3091, + "sha256": "35966b6a021abdebdd4d232b6163c4fc9f951eaf7d5c57c3892da71a0cd26736" + }, + { + "path": "dist/alphalib/types/robots/document-merge.d.ts.map", + "sizeBytes": 1217, + "sha256": "c395a64c42eeae620a4522bb6ecce832f885485fee9ac7210bb7ce6c89be8b28" + }, + { + "path": "dist/alphalib/types/robots/document-merge.js.map", + "sizeBytes": 1738, + "sha256": "a058ab91732d822f8044c61040d69d12bc3903e354f34793b91bd72e6695549c" + }, + { + "path": "dist/alphalib/types/robots/document-ocr.d.ts.map", + "sizeBytes": 1225, + "sha256": "18c3a595151d7ede4e0a308bc61cb17c7f7fb5156ae93992868aea16b0d4f307" + }, + { + "path": "dist/alphalib/types/robots/document-ocr.js.map", + "sizeBytes": 1829, + "sha256": "a7b6169af7fb9b26d919cdab1b4b557f0134c5e6a15c53065e8168fd9dfd37e3" + }, + { + "path": "dist/alphalib/types/robots/document-split.d.ts.map", + "sizeBytes": 1204, + "sha256": "c19d3a5ce5d0d7876c1fdb06ae523cc8544c005f2299e0e38a78edea1148eaca" + }, + { + "path": "dist/alphalib/types/robots/document-split.js.map", + "sizeBytes": 1516, + "sha256": "0755d47cbbf71c6a78d9a858ff7e6602f6da2bf6482bf7e6b0adb6a43c779b05" + }, + { + "path": "dist/alphalib/types/robots/document-thumbs.d.ts.map", + "sizeBytes": 1383, + "sha256": "2243134191a54be4ff5f64897a9c50b6bff40a6d8539ab37e641dee1e78adfd2" + }, + { + "path": "dist/alphalib/types/robots/document-thumbs.js.map", + "sizeBytes": 3582, + "sha256": "18c7fccfe0abf172d679805454b84ef121c88c7daffb0172f6d4a2cf04a728a1" + }, + { + "path": "dist/alphalib/types/robots/dropbox-import.d.ts.map", + "sizeBytes": 931, + "sha256": "0f1b530229aad380249ed2cdced0fc3ba4dea4bb25bbdeba5196d1b25b83ca1f" + }, + { + "path": "dist/alphalib/types/robots/dropbox-import.js.map", + "sizeBytes": 1672, + "sha256": "acd1432e1b99bf224913f5cd9e827ace7949a2fc2f12118a8cd7ab5d635f22c1" + }, + { + "path": "dist/alphalib/types/robots/dropbox-store.d.ts.map", + "sizeBytes": 1304, + "sha256": "2929109a87204cf98b4c5bfd411ac7a764028314d02bbb290d2e4dfe79bf812c" + }, + { + "path": "dist/alphalib/types/robots/dropbox-store.js.map", + "sizeBytes": 1785, + "sha256": "d0f7854dfb91540f64c238ca9bee2a6100b84b1239ea6dfa4601e6308ff7ca7b" + }, + { + "path": "dist/alphalib/types/robots/edgly-deliver.d.ts.map", + "sizeBytes": 885, + "sha256": "8f9200e8e330adeea424d735f663ad757326e3662fdb166a44bad09b3443ee8f" + }, + { + "path": "dist/alphalib/types/robots/edgly-deliver.js.map", + "sizeBytes": 1366, + "sha256": "bf5b2ae88cc181f02afaf1ed026a327b8f1d3b4cec26ee2b87cb63d7dee4f17d" + }, + { + "path": "dist/alphalib/types/robots/file-compress.d.ts.map", + "sizeBytes": 1263, + "sha256": "67e26c961677ec63ae7b838ac54ce4ef92897bb844e32ce28980ba28e1a81593" + }, + { + "path": "dist/alphalib/types/robots/file-compress.js.map", + "sizeBytes": 2246, + "sha256": "6db06343a6a9606037cdd68c8aeec52f9db1d6abf621dac94acfd13186309c71" + }, + { + "path": "dist/alphalib/types/robots/file-decompress.d.ts.map", + "sizeBytes": 1194, + "sha256": "e3167146144669a49864af77a2844095e7a704fe8d6c3d138da2e2482fce4329" + }, + { + "path": "dist/alphalib/types/robots/file-decompress.js.map", + "sizeBytes": 1875, + "sha256": "8b480292b5374d7c2eed61ecb7ba958742b710b466a6ca6f8b3ad00ebb318792" + }, + { + "path": "dist/alphalib/types/robots/file-filter.d.ts.map", + "sizeBytes": 1247, + "sha256": "fbed0076ff28a613a9c4f8b6e5941058729b3aa39c196dccc8af789625d2eea3" + }, + { + "path": "dist/alphalib/types/robots/file-filter.js.map", + "sizeBytes": 2086, + "sha256": "a9bb479e9cc2816253fe182250b0b4fbc337301f361b13b8d632286501c2f377" + }, + { + "path": "dist/alphalib/types/robots/file-hash.d.ts.map", + "sizeBytes": 1193, + "sha256": "e32c94f338be64db833c3a52edebc813aa6cd92d1f03fffb463e0d72bbde941d" + }, + { + "path": "dist/alphalib/types/robots/file-hash.js.map", + "sizeBytes": 1611, + "sha256": "f589e306ee342792017d634feadfeb2b9854286960b0fc328ed8c68f2bf139f5" + }, + { + "path": "dist/alphalib/types/robots/file-preview.d.ts.map", + "sizeBytes": 1707, + "sha256": "bbd206d8489b7015987c19cc9a12a831855192f2f41dc718ef037d3cef144d6c" + }, + { + "path": "dist/alphalib/types/robots/file-preview.js.map", + "sizeBytes": 4716, + "sha256": "739b27deb2eba0c132a99420187415b97d3c5728f2d1d6ef57c934a557c41444" + }, + { + "path": "dist/alphalib/types/robots/file-read.d.ts.map", + "sizeBytes": 1181, + "sha256": "ced5f8c632d33c3f195178a40396cdb52f6ecb7b6bde3ceb4f78f97768727c8b" + }, + { + "path": "dist/alphalib/types/robots/file-read.js.map", + "sizeBytes": 1317, + "sha256": "e5dc39318ed77a5b044fa2d0957065adfb39b6badd98e78906a36dec52b78dd6" + }, + { + "path": "dist/alphalib/types/robots/file-serve.d.ts.map", + "sizeBytes": 1266, + "sha256": "2acd526a0502c1d9c0b8039cfadc6ddf84ca19127ad078898b9886fa53f30aa0" + }, + { + "path": "dist/alphalib/types/robots/file-serve.js.map", + "sizeBytes": 1687, + "sha256": "e553a47bd75cf6b95f70cfa50c64f99b7cfa4da788d0a84040adaf63b80eaa5f" + }, + { + "path": "dist/alphalib/types/robots/file-verify.d.ts.map", + "sizeBytes": 1223, + "sha256": "8e27540fb398c334b003fa1226cc4cca7f276841ab9c09d456effc04dc4f8e60" + }, + { + "path": "dist/alphalib/types/robots/file-verify.js.map", + "sizeBytes": 1788, + "sha256": "3e2b26391ed81f2083247cfc101ef82aeeeb02f69665ce999fddc6bab255d6d7" + }, + { + "path": "dist/alphalib/types/robots/file-virusscan.d.ts.map", + "sizeBytes": 1223, + "sha256": "84e84f34c14bced953ced1d8fd1f10987be49fcf7c6b28f3b0d815ea6d01ba56" + }, + { + "path": "dist/alphalib/types/robots/file-virusscan.js.map", + "sizeBytes": 1915, + "sha256": "220df6a5d0b0e7efcdbde6c62ac1354d81e61a377ce800ced9a33300098d818f" + }, + { + "path": "dist/alphalib/types/robots/file-watermark.d.ts.map", + "sizeBytes": 1203, + "sha256": "abf5b2f9284b433b8925e7f3a99e77781b2959dacdebf6dce4048ea00575e6fb" + }, + { + "path": "dist/alphalib/types/robots/file-watermark.js.map", + "sizeBytes": 1017, + "sha256": "6583f0e6b3a04b39758bc60bbd77383f00715365ac714be95b871ba6797050b9" + }, + { + "path": "dist/alphalib/types/robots/ftp-import.d.ts.map", + "sizeBytes": 976, + "sha256": "921de9063ecf8dfe8054bf4ce597658aefd55b28da6935b1dc22b9de4ab50534" + }, + { + "path": "dist/alphalib/types/robots/ftp-import.js.map", + "sizeBytes": 1632, + "sha256": "717f12442854a116abeceb646f0a0b0d43bddf7d355b1261603e365ee2f75954" + }, + { + "path": "dist/alphalib/types/robots/ftp-store.d.ts.map", + "sizeBytes": 1310, + "sha256": "a74b1c73d55dd9d4c277e173246d268c85f28b3c7ea85fb5b5fa202895affa13" + }, + { + "path": "dist/alphalib/types/robots/ftp-store.js.map", + "sizeBytes": 2145, + "sha256": "ce1bf48c1cc713ae843061cba3c3b119475baa5cb6b62ac4b575e50b297bcf71" + }, + { + "path": "dist/alphalib/types/robots/google-import.d.ts.map", + "sizeBytes": 960, + "sha256": "464452457441bede584276700d4aa2905186df95d7b0e8f853fd276556a3ca73" + }, + { + "path": "dist/alphalib/types/robots/google-import.js.map", + "sizeBytes": 1783, + "sha256": "25f2633e57de13845396a75c6f3e7dd4b59cfd8b7587300b5041dcd3970768f4" + }, + { + "path": "dist/alphalib/types/robots/google-store.d.ts.map", + "sizeBytes": 1260, + "sha256": "05d35ccb12ce96d6b2e5372640a02a2c9f8ff34b9ca0da87197e7174d3402429" + }, + { + "path": "dist/alphalib/types/robots/google-store.js.map", + "sizeBytes": 2233, + "sha256": "40ad8416070e56ba35c23114eea8e5503953f1e00248ecd75796be6bab4404de" + }, + { + "path": "dist/cli/helpers.d.ts.map", + "sizeBytes": 697, + "sha256": "7741da5e1fdb8a1fac23517391b55ef32185134622a06f5b044a31f87424de46" + }, + { + "path": "dist/cli/helpers.js.map", + "sizeBytes": 1656, + "sha256": "061d1985ffefb16813c96520199edb566e65fe0b2113dd8c83fe36116a80a9d0" + }, + { + "path": "dist/alphalib/types/robots/html-convert.d.ts.map", + "sizeBytes": 1315, + "sha256": "16452c1d458edb1c509b118e7de72886a1547100c74d636509b904cea851fb7e" + }, + { + "path": "dist/alphalib/types/robots/html-convert.js.map", + "sizeBytes": 2766, + "sha256": "eacdd8d74271b8273f8356dd343de5953253860859932e8370fec23d99c589b0" + }, + { + "path": "dist/alphalib/types/robots/http-import.d.ts.map", + "sizeBytes": 998, + "sha256": "ef3b63e0a2c104c1c3631d430ad09514953fb3555daddb69d0d0e5b5306752ed" + }, + { + "path": "dist/alphalib/types/robots/http-import.js.map", + "sizeBytes": 2893, + "sha256": "d90362259ac8cdcf6e4d304042836bb172bf9e37552f4381d793afb57eff1768" + }, + { + "path": "dist/alphalib/types/robots/image-bgremove.d.ts.map", + "sizeBytes": 1241, + "sha256": "1f2d8c7319395b5228a586231c110b98758a4a11c8a6b91d1b406a085aae7440" + }, + { + "path": "dist/alphalib/types/robots/image-bgremove.js.map", + "sizeBytes": 1885, + "sha256": "a5fbb151ec01dc82e30ab793f405a6b3a1146805511669748079d4a5b54de6a5" + }, + { + "path": "dist/alphalib/types/robots/image-describe.d.ts.map", + "sizeBytes": 1241, + "sha256": "44776af23c24f582ff0f6552d49e01155de4589ed09e52012ec1321a6ca698c8" + }, + { + "path": "dist/alphalib/types/robots/image-describe.js.map", + "sizeBytes": 1899, + "sha256": "10dfe79760d33f0efa5a600b2060b04350fc733bf0e70f1c557e9ba33c83c601" + }, + { + "path": "dist/alphalib/types/robots/image-facedetect.d.ts.map", + "sizeBytes": 1270, + "sha256": "bb9528a90de57e383b99dfdcf5196462ecc5528c101d6a98402b7eb12b8de284" + }, + { + "path": "dist/alphalib/types/robots/image-facedetect.js.map", + "sizeBytes": 2389, + "sha256": "538eabf594bb90fe2cd54eeb27f097b9b42b0ffff95d6ed3f442b47d694ec599" + }, + { + "path": "dist/alphalib/types/robots/image-generate.d.ts.map", + "sizeBytes": 1310, + "sha256": "25b14e8c4cd9e295533e60c401e2fc9a8822a075e2ca2703b8a179fdac957bdf" + }, + { + "path": "dist/alphalib/types/robots/image-generate.js.map", + "sizeBytes": 2205, + "sha256": "ebb90ad613707be35e72b368ef80323c368a9d7751daf0a54a9588223dafe355" + }, + { + "path": "dist/alphalib/types/robots/image-merge.d.ts.map", + "sizeBytes": 1259, + "sha256": "2880a90a84df8f77f0a2c30c1835fda589c87f52ef21f077729ee23dd145b8bc" + }, + { + "path": "dist/alphalib/types/robots/image-merge.js.map", + "sizeBytes": 2080, + "sha256": "564ae537d3189541c9c0b645ff56919c86df8440fac89685b4180b851d862f2a" + }, + { + "path": "dist/alphalib/types/robots/image-ocr.d.ts.map", + "sizeBytes": 1218, + "sha256": "afbf395a44bc6f1a0ef163d6ff6b45dacac15b9b1e01045365f88f1fe4ce403f" + }, + { + "path": "dist/alphalib/types/robots/image-ocr.js.map", + "sizeBytes": 1798, + "sha256": "135f86e2ad0c6690838ce611f6e48375642dbdc9857c19723040c8860d07c620" + }, + { + "path": "dist/alphalib/types/robots/image-optimize.d.ts.map", + "sizeBytes": 1241, + "sha256": "eb4d9d068c6538732c9ccf8d1f335a16444fd10a7f5f838529a81d81fade03aa" + }, + { + "path": "dist/alphalib/types/robots/image-optimize.js.map", + "sizeBytes": 1795, + "sha256": "aae465a013cc6e22dd12b7e44986a7be746a70563c3b7aff861d8229888dac39" + }, + { + "path": "dist/alphalib/types/robots/image-resize.d.ts.map", + "sizeBytes": 2575, + "sha256": "4039c44fe629044c78562654dea361edb3d5fc31f6cbcbaff8e8ef3b3f2c8362" + }, + { + "path": "dist/alphalib/types/robots/image-resize.js.map", + "sizeBytes": 9404, + "sha256": "655db1c155f512b6b8b9fa21e8a6150ecab07e3b366e186694ef2b289f04b688" + }, + { + "path": "dist/InconsistentResponseError.d.ts.map", + "sizeBytes": 208, + "sha256": "5333a56696e4ed761ee350c4a342fd2d90e4b4445c80606ea8ebc7f19210276f" + }, + { + "path": "dist/InconsistentResponseError.js.map", + "sizeBytes": 217, + "sha256": "b1145dc4071d1b9fe9b438dc6cacfad7aa127329c06ed2cfa8c4495d4db289a1" + }, + { + "path": "dist/cli/commands/index.d.ts.map", + "sizeBytes": 198, + "sha256": "02d46596bc9ebce98e9668a9ec4fd017aa2e553b828dfc47dcbcff96bff71f3e" + }, + { + "path": "dist/cli/commands/index.js.map", + "sizeBytes": 1584, + "sha256": "d44b8dc7479c999f6f694cdb90628b32087b5a7a13758f727b5174fdf291fea6" + }, + { + "path": "dist/alphalib/mcache.d.ts.map", + "sizeBytes": 968, + "sha256": "7e45e93e39d2031b2a197c7f145f7a535b83af9435129b21cdbc6577da7fa6f8" + }, + { + "path": "dist/alphalib/mcache.js.map", + "sizeBytes": 4326, + "sha256": "718daef94e4679b5326fda25a3d6060a0bbcf86895e3b6d4865a310767584aa3" + }, + { + "path": "dist/alphalib/types/robots/meta-read.d.ts.map", + "sizeBytes": 733, + "sha256": "74696fc88caf7dab3962245213e1553190b7daa91ce6c98d8842a26ba7aa78e7" + }, + { + "path": "dist/alphalib/types/robots/meta-read.js.map", + "sizeBytes": 944, + "sha256": "bebdb932e6d46cd2075e908e77d6c013127a2aa21bf63f1cd52ee53445431159" + }, + { + "path": "dist/alphalib/types/robots/meta-write.d.ts.map", + "sizeBytes": 3621, + "sha256": "2aa5bcd142a9675827964aaf5ff8faebe2a46b65fbd4f05e393d9ddd683dc312" + }, + { + "path": "dist/alphalib/types/robots/meta-write.js.map", + "sizeBytes": 1763, + "sha256": "0ee3d8d54a5d87e087020ac29cb96997d5ddc154be11f4506d69be0458c2b66e" + }, + { + "path": "dist/alphalib/types/robots/minio-import.d.ts.map", + "sizeBytes": 1018, + "sha256": "daa3c35498566645725c26eaf6cac16cccc7591ad52ec1102fb5568f8a67707e" + }, + { + "path": "dist/alphalib/types/robots/minio-import.js.map", + "sizeBytes": 1826, + "sha256": "54f186aa5939c1a4ee41dec57a40aa2e4e67adc694bff3394ceecef4d5c1e50a" + }, + { + "path": "dist/alphalib/types/robots/minio-store.d.ts.map", + "sizeBytes": 1296, + "sha256": "5cab9c663094f8ed691d3726e183c8205b8dcafcbecadb1209c1aee4a667022e" + }, + { + "path": "dist/alphalib/types/robots/minio-store.js.map", + "sizeBytes": 2022, + "sha256": "09880706215eb25ad7b6c8a07976fcd329853e016d6d680ee71c8657e234ab34" + }, + { + "path": "dist/alphalib/lib/nativeGlobby.d.ts.map", + "sizeBytes": 583, + "sha256": "8f3c30e2d30cfdfb4b7fda43590e36d0cc5f340b8dc75a69314602e19b566047" + }, + { + "path": "dist/alphalib/lib/nativeGlobby.js.map", + "sizeBytes": 7177, + "sha256": "faa6544a8c107851651831356371f77c7a2fbae9810951ad2b18f4968bf7580c" + }, + { + "path": "dist/cli/commands/notifications.d.ts.map", + "sizeBytes": 344, + "sha256": "b6d7ff0ac754f7977f133a78361ca7d469f23319dc9fd8a5a8c9093dac428eb6" + }, + { + "path": "dist/cli/commands/notifications.js.map", + "sizeBytes": 1500, + "sha256": "ba8bc70ab727c78e8641b541d992bb5487b9f8c6af9a329ed527a21d38cf3f45" + }, + { + "path": "dist/cli/OutputCtl.d.ts.map", + "sizeBytes": 1377, + "sha256": "d49a26f7287d6e2dbee8de015c9409ad54fa2a2ed5dfe44e99b2510f95ed93a1" + }, + { + "path": "dist/cli/OutputCtl.js.map", + "sizeBytes": 2947, + "sha256": "4922203562a927063b145504529b677a8205047aace3b4b86cbe8b8f75ef5b70" + }, + { + "path": "dist/PaginationStream.d.ts.map", + "sizeBytes": 664, + "sha256": "9bdfee8e9abe338b8b6deb1b484e54b8c1b20134dc2721ceddd42ad512b70d3c" + }, + { + "path": "dist/PaginationStream.js.map", + "sizeBytes": 1303, + "sha256": "861ba3671e06da0092b9f5d41d687441c16f98fb34c69332bbe4005f993ee12f" + }, + { + "path": "dist/PollingTimeoutError.d.ts.map", + "sizeBytes": 213, + "sha256": "e728bb40a887365822da6380ce35a2d5de504fa4c24e4a61a03a14d365f0b96d" + }, + { + "path": "dist/PollingTimeoutError.js.map", + "sizeBytes": 233, + "sha256": "1bf6de3e9b8f0e62b6900be45f7db6c9dad37510a1ee9dc05c7883319ac15a48" + }, + { + "path": "dist/alphalib/types/robots/progress-simulate.d.ts.map", + "sizeBytes": 750, + "sha256": "aec55a688c0da530314297cf6652d456b032580f848e84cfbb00054b3bc9ab91" + }, + { + "path": "dist/alphalib/types/robots/progress-simulate.js.map", + "sizeBytes": 854, + "sha256": "c743fb4ea5217d34ff665926bd14ecbb259dec99c2de862abfe787ece58817a0" + }, + { + "path": "dist/alphalib/types/robots/s3-import.d.ts.map", + "sizeBytes": 1023, + "sha256": "2fa93c5f1aacff33c82795f1f6f4b7bc533fa4f00dabb0da996021cfb3a36b58" + }, + { + "path": "dist/alphalib/types/robots/s3-import.js.map", + "sizeBytes": 2086, + "sha256": "2a512555a098d4da6785a118506d5992ced9c7f9ad6fadb139e68161425dc5a3" + }, + { + "path": "dist/alphalib/types/robots/s3-store.d.ts.map", + "sizeBytes": 1437, + "sha256": "77b3508baff7e474df2bd8bb4dca53f8fb09ffcd229cd16aa09a35e2d12cd608" + }, + { + "path": "dist/alphalib/types/robots/s3-store.js.map", + "sizeBytes": 2689, + "sha256": "354bf2bfe15c57ef1054242d90e09fc69a50fac4a57f07510b81ab91cefa73df" + }, + { + "path": "dist/alphalib/types/robots/script-run.d.ts.map", + "sizeBytes": 1202, + "sha256": "d73d6e081a159f0446e83051828a2b4cd919e0439ec4c79423fbf74ce938633a" + }, + { + "path": "dist/alphalib/types/robots/script-run.js.map", + "sizeBytes": 1502, + "sha256": "6e61b686b0b26ba5d90c62cdb94d6611e09e57081efe1a7042dda77d127fd51f" + }, + { + "path": "dist/alphalib/types/robots/sftp-import.d.ts.map", + "sizeBytes": 973, + "sha256": "dad41e054b1f3f496000c2fe3eba6222f59230b6af63c38fa75cc3ce37ea862f" + }, + { + "path": "dist/alphalib/types/robots/sftp-import.js.map", + "sizeBytes": 1628, + "sha256": "f903dbbee86ee2801eb24d091c17d85686090035b263a27965987e4766587d75" + }, + { + "path": "dist/alphalib/types/robots/sftp-store.d.ts.map", + "sizeBytes": 1299, + "sha256": "b37328dfadccc7c6d482a2d44e79ddea777c2a4ef8944d91f0039a88beae661d" + }, + { + "path": "dist/alphalib/types/robots/sftp-store.js.map", + "sizeBytes": 1972, + "sha256": "9e1dd16cb07a33783ce79a07599589d90c6991eb4d17f042e07bd41b04be52be" + }, + { + "path": "dist/alphalib/types/robots/speech-transcribe.d.ts.map", + "sizeBytes": 1260, + "sha256": "a0c4d3e87110af87e7d44e6b16edb861a9c8b60b5916f8a084701fb0889928a1" + }, + { + "path": "dist/alphalib/types/robots/speech-transcribe.js.map", + "sizeBytes": 2118, + "sha256": "062dce9e6e903b2013b9ae939e54b1eb63594bf2905bcd49981870b5ab18be67" + }, + { + "path": "dist/alphalib/types/stackVersions.d.ts.map", + "sizeBytes": 181, + "sha256": "8801a952778d7c9ff6ec5da9ab04f67cb8912592a64219bee73f89f9647e0311" + }, + { + "path": "dist/alphalib/types/stackVersions.js.map", + "sizeBytes": 403, + "sha256": "b502d17102001adede09a03d6bc1b8d21c857709ca59699c5771eba6ff187768" + }, + { + "path": "dist/alphalib/types/robots/supabase-import.d.ts.map", + "sizeBytes": 1036, + "sha256": "591b8a2e1f9a882c9a800101271bc944ac6a7720022d634a9f6a52b273d09b12" + }, + { + "path": "dist/alphalib/types/robots/supabase-import.js.map", + "sizeBytes": 1862, + "sha256": "fb87b0dd2b73a22dcb20c1103f640e4c59de2f5cfae91bcaa8bcbacf33646872" + }, + { + "path": "dist/alphalib/types/robots/supabase-store.d.ts.map", + "sizeBytes": 1302, + "sha256": "26b0c17d6bb419f009a55c63720a5a6e01315eee5e47639f827968677dbcbcc7" + }, + { + "path": "dist/alphalib/types/robots/supabase-store.js.map", + "sizeBytes": 1886, + "sha256": "51670b0c87beb48d14581510971e8f6702d6f7536fb19e9779e0dca22261c996" + }, + { + "path": "dist/alphalib/types/robots/swift-import.d.ts.map", + "sizeBytes": 1030, + "sha256": "62f71493d289ab513756c9a0801fea959d1dbfebac53a937d0ce3e40d8412f43" + }, + { + "path": "dist/alphalib/types/robots/swift-import.js.map", + "sizeBytes": 1828, + "sha256": "8266677cc62c1cbf8acbe28d70e38b73ead4da441c24da57549b8d5401ff09a6" + }, + { + "path": "dist/alphalib/types/robots/swift-store.d.ts.map", + "sizeBytes": 1308, + "sha256": "d7861db0ea1879346faf51aa54feea5889457c72f9b890e996a5db6dce5c27a9" + }, + { + "path": "dist/alphalib/types/robots/swift-store.js.map", + "sizeBytes": 2023, + "sha256": "a1051b980e394da608e7b95994ca490f6f9d6dbe5fb4d89707266c495175a347" + }, + { + "path": "dist/cli/template-last-modified.d.ts.map", + "sizeBytes": 492, + "sha256": "927f06c6c3ac447c7459f881ad4150ed0cbec8c199b114452590e25df57d6f44" + }, + { + "path": "dist/cli/template-last-modified.js.map", + "sizeBytes": 4406, + "sha256": "ecf48762d1c4275b90bab7da82b44a9e0c31b2cf8bb62a2f1381c34ceced91c9" + }, + { + "path": "dist/alphalib/types/template.d.ts.map", + "sizeBytes": 133490, + "sha256": "d2e7aac5bedd1f24066164e27d0b68f2737998853c38cc33234379b3c61a9681" + }, + { + "path": "dist/alphalib/types/template.js.map", + "sizeBytes": 5214, + "sha256": "df5e14bc81fcb702b288bb964fe05ac0f6976a0b4061e8dd6cc8586c19d18b9d" + }, + { + "path": "dist/alphalib/types/templateCredential.d.ts.map", + "sizeBytes": 347, + "sha256": "b47bd53ceca8cec91455635a28942287286b87d962de0b32bbf4afd785df1822" + }, + { + "path": "dist/alphalib/types/templateCredential.js.map", + "sizeBytes": 912, + "sha256": "125372bf800fcb2b436edf9b7ac47aeea00a7ae8bc6cd0592b5a1fc4419e1cd1" + }, + { + "path": "dist/cli/commands/templates.d.ts.map", + "sizeBytes": 2355, + "sha256": "dde861e027753cac8ae7255ac4cc9b31d40e86b341ac52d467696294df1b32b3" + }, + { + "path": "dist/cli/commands/templates.js.map", + "sizeBytes": 14657, + "sha256": "d8af75491dfa21dd4c3f6587ef199a28f14450a1533bf78f8437e8581a358fd6" + }, + { + "path": "dist/alphalib/types/robots/text-speak.d.ts.map", + "sizeBytes": 1244, + "sha256": "f6c1fbc4234dc0e9b2d9c57790fcc5678aea31e4aa528703f3ddcb2b31c2d6e3" + }, + { + "path": "dist/alphalib/types/robots/text-speak.js.map", + "sizeBytes": 2081, + "sha256": "152ee18242f273409266c8a95fb15b965069ac9646aebf6e61ddb4a02453146d" + }, + { + "path": "dist/alphalib/types/robots/text-translate.d.ts.map", + "sizeBytes": 1230, + "sha256": "b7b203cfdd08f1e497abc8614b4b2a4d3ce74e15c5c056eaa29d187d94f9927e" + }, + { + "path": "dist/alphalib/types/robots/text-translate.js.map", + "sizeBytes": 2951, + "sha256": "4c09df3519ed7bc416814b2541d8ee0f5b4b77e8dfb72d6d1a57e45ccdcccb32" + }, + { + "path": "dist/alphalib/types/robots/tigris-import.d.ts.map", + "sizeBytes": 1030, + "sha256": "324f86249a446619ec7c815c0a39ac79bf8f543a849a7ae92aabc9e304694c6e" + }, + { + "path": "dist/alphalib/types/robots/tigris-import.js.map", + "sizeBytes": 1910, + "sha256": "817e599e2f948bf8d2a84eaf23ab95a4cc45bf83a1ca126ab359d729378daaba" + }, + { + "path": "dist/alphalib/types/robots/tigris-store.d.ts.map", + "sizeBytes": 1308, + "sha256": "543391776a31c889d13fca5749fbc029c329c3d6ac78b79b4561ebcb07fe9b2d" + }, + { + "path": "dist/alphalib/types/robots/tigris-store.js.map", + "sizeBytes": 2102, + "sha256": "7dfa4c4a70c85be45c9185428fd349ba0c788132a886f71eabb9fff85563de09" + }, + { + "path": "dist/alphalib/types/robots/tlcdn-deliver.d.ts.map", + "sizeBytes": 885, + "sha256": "554ee0091f06fb0600daf9bfb8ec6211d5e5b125e23a4c4807381909df171ee3" + }, + { + "path": "dist/alphalib/types/robots/tlcdn-deliver.js.map", + "sizeBytes": 1365, + "sha256": "2cc54073267b8bcdebf87bfe2b94b78bef94cf5bce3a55c574adb033261a7faf" + }, + { + "path": "dist/Transloadit.d.ts.map", + "sizeBytes": 4955, + "sha256": "3933904963537b1d3c1a33218f154e2c0620fb7e6048135b5291fe6d4affcb84" + }, + { + "path": "dist/Transloadit.js.map", + "sizeBytes": 19217, + "sha256": "b07b0ec19dff6764da41b0dfe6978243370f2d7af425844ff79c95c439f5b1c7" + }, + { + "path": "dist/alphalib/tryCatch.d.ts.map", + "sizeBytes": 555, + "sha256": "5fa048d8181c4b97c76d21af2b1c7353cdd0c0ad7b5f7c0209c1a685ac13b1d8" + }, + { + "path": "dist/alphalib/tryCatch.js.map", + "sizeBytes": 364, + "sha256": "d8b6506cd4a2b2aef2c10d358a01858ee7795ab47ba68dc483cfe05bd8bcd304" + }, + { + "path": "dist/alphalib/types/robots/tus-store.d.ts.map", + "sizeBytes": 1254, + "sha256": "c018e01926cb975e5991815d950d23b794e8da606204d1012afdde5cd40be48c" + }, + { + "path": "dist/alphalib/types/robots/tus-store.js.map", + "sizeBytes": 2130, + "sha256": "e332628b81b5a2f1db21e2163f3e82b1e2d1aee3bdc5da31641f56ac8f8c84cd" + }, + { + "path": "dist/tus.d.ts.map", + "sizeBytes": 675, + "sha256": "4174cce4740dbfaa616f6ebfd69e7d8a67f43e74d7b6e72f783fd5d7aada6b56" + }, + { + "path": "dist/tus.js.map", + "sizeBytes": 4004, + "sha256": "c61b325945542f75b7e227f99388b3da266126a431ad115dbb1da82f0253a73c" + }, + { + "path": "dist/cli/types.d.ts.map", + "sizeBytes": 988, + "sha256": "9ec4dae6c1187072ce760a518f8e2b659b2df95173ece898c6a3e458b48dfe5e" + }, + { + "path": "dist/cli/types.js.map", + "sizeBytes": 1487, + "sha256": "ba68dc25f9bfc67b80d7d0a06927d55d59f1dd722cff7c7b99115fc6d8212d7a" + }, + { + "path": "dist/alphalib/types/robots/upload-handle.d.ts.map", + "sizeBytes": 885, + "sha256": "3cb0f33183cfbcb8cf5cd93dea02062c80415b5a778f4f01e59c203907712338" + }, + { + "path": "dist/alphalib/types/robots/upload-handle.js.map", + "sizeBytes": 1547, + "sha256": "5565507cacaccfbae0ed8af22fa55d0f2e1764db6d5b512ca275d2dc0baa52c7" + }, + { + "path": "dist/alphalib/types/robots/video-adaptive.d.ts.map", + "sizeBytes": 3703, + "sha256": "ad17ce7383971d02115b646735b2e887b02e8cdd431fd8870be7dd5432c0d968" + }, + { + "path": "dist/alphalib/types/robots/video-adaptive.js.map", + "sizeBytes": 2536, + "sha256": "935700eac713fc175e93597246e0f8a3430641e6c014d322fc133ca7a856ea47" + }, + { + "path": "dist/alphalib/types/robots/video-concat.d.ts.map", + "sizeBytes": 3675, + "sha256": "e02b704959be22d2a2b3bf2e085233d2caaff4a30b237ecb84ce9946bacced1b" + }, + { + "path": "dist/alphalib/types/robots/video-concat.js.map", + "sizeBytes": 1976, + "sha256": "1663bd7c7b505072d3f559221615e39f28dd990108def6f05ec2cb6e504fc607" + }, + { + "path": "dist/alphalib/types/robots/video-encode.d.ts.map", + "sizeBytes": 4064, + "sha256": "500f47f6364a47894b08b0fb4907679310c8be7389a46fdf622d454339fd0050" + }, + { + "path": "dist/alphalib/types/robots/video-encode.js.map", + "sizeBytes": 1856, + "sha256": "6461a5e4016592c7e9ea4390b88c1b7b1d614ad81296803c066c7d1946766e2e" + }, + { + "path": "dist/alphalib/types/robots/video-merge.d.ts.map", + "sizeBytes": 3769, + "sha256": "e19e2f55fd7d80116f97b13808d73ea3b213268d3fa8ace990b2e3b22eebcfb5" + }, + { + "path": "dist/alphalib/types/robots/video-merge.js.map", + "sizeBytes": 2366, + "sha256": "5aa6fd3a2e9b7531f1f8abc63fb3d508ad98224f7c9e508f36df4f41f05768d1" + }, + { + "path": "dist/alphalib/types/robots/video-ondemand.d.ts.map", + "sizeBytes": 5342, + "sha256": "c2115e7d16dc0802c7ce4b1df6658f1c78cc53f9b2a9b045c084615901243c40" + }, + { + "path": "dist/alphalib/types/robots/video-ondemand.js.map", + "sizeBytes": 2724, + "sha256": "c20260c3defe1778351ba4119259fb43252554db5467d44676136189ed4deb00" + }, + { + "path": "dist/alphalib/types/robots/video-subtitle.d.ts.map", + "sizeBytes": 3763, + "sha256": "97a1c9a22930643d78fd48ce5749bcfceb81253442f9f2029ed0204de19073ba" + }, + { + "path": "dist/alphalib/types/robots/video-subtitle.js.map", + "sizeBytes": 2827, + "sha256": "af93d44416d43e48085ec00d7be8248967807f6bd3323a927a47beaab122b4a4" + }, + { + "path": "dist/alphalib/types/robots/video-thumbs.d.ts.map", + "sizeBytes": 3722, + "sha256": "f594f39d5f0f72e59ff3d9fa5a9639f8e533c2e553a1c5fedb09b3c6242e30c1" + }, + { + "path": "dist/alphalib/types/robots/video-thumbs.js.map", + "sizeBytes": 2886, + "sha256": "02b98cbff11b63daaf474f4784d1105b4dd56e57d1082e8677cd7d80690cff51" + }, + { + "path": "dist/alphalib/types/robots/vimeo-import.d.ts.map", + "sizeBytes": 972, + "sha256": "9f97e32a87f8e40a48b2669b712cf2d810b7180e2f98c034a1e66b987a786867" + }, + { + "path": "dist/alphalib/types/robots/vimeo-import.js.map", + "sizeBytes": 2142, + "sha256": "912a3b1308b6b9f97052d14bb022f66b12058c4c9f180795b2d6e6d79a26c808" + }, + { + "path": "dist/alphalib/types/robots/vimeo-store.d.ts.map", + "sizeBytes": 1302, + "sha256": "c670114a11cd43867aa15bacace8e1e65a9a9aa83d6f5b42b3f5ae05938d405f" + }, + { + "path": "dist/alphalib/types/robots/vimeo-store.js.map", + "sizeBytes": 2393, + "sha256": "29227154e1ea921a08f345cbb76aa1fe2ca895b597df7bbf95e62d09fb56268e" + }, + { + "path": "dist/alphalib/types/robots/wasabi-import.d.ts.map", + "sizeBytes": 1030, + "sha256": "b7e6ada139a658355a85cef4b883a42c15515c1cc1a89c741e0eeb9f8fa19191" + }, + { + "path": "dist/alphalib/types/robots/wasabi-import.js.map", + "sizeBytes": 1910, + "sha256": "348c647bde3a01cf71187ed1d440fb0fa875ff93e535b8e7024e5f47a30824f4" + }, + { + "path": "dist/alphalib/types/robots/wasabi-store.d.ts.map", + "sizeBytes": 1310, + "sha256": "18968bde05eb5699ce98695f16f65333077ae83af2850fcc65fdc0b9662d0d0d" + }, + { + "path": "dist/alphalib/types/robots/wasabi-store.js.map", + "sizeBytes": 2022, + "sha256": "238f78cc0876c72bea35d35b9ea4dbb3d033dfe35e416007e405d26bd7ca2a45" + }, + { + "path": "dist/alphalib/types/robots/youtube-store.d.ts.map", + "sizeBytes": 1263, + "sha256": "13df33bee9919599cc7854debac40597a412d4ad3673122257fe343928f985ff" + }, + { + "path": "dist/alphalib/types/robots/youtube-store.js.map", + "sizeBytes": 2380, + "sha256": "b59ed4420731f9f161d7de7a4c889eead8868c089b4507e5ce810de59fe0bf17" + }, + { + "path": "dist/alphalib/zodParseWithContext.d.ts.map", + "sizeBytes": 711, + "sha256": "7422db554332a0a7a4e2d58e402c5714a1b67db6a1b2ea98f4709b3c319d3041" + }, + { + "path": "dist/alphalib/zodParseWithContext.js.map", + "sizeBytes": 9563, + "sha256": "c3ee1daecdb3fb7a6fb1ab0d5b5cb327e1620c94b3c16ab522b9b599a04c468a" + }, + { + "path": "README.md", + "sizeBytes": 30169, + "sha256": "47d191fb87e0add731c826a7553a26e2572f34ad6c8506e00b025a21ff322580" + }, + { + "path": "dist/alphalib/types/robots/_index.d.ts", + "sizeBytes": 4680970, + "sha256": "8ffd4631bfe520f6a594365f95301e832c560840456d7c2c086b40f83ed1d566" + }, + { + "path": "src/alphalib/types/robots/_index.ts", + "sizeBytes": 50795, + "sha256": "ae25dd93eac765b3a4f5a3d8a28e49c8e2b88b122f6d1534e5d2779d1186a302" + }, + { + "path": "dist/alphalib/types/robots/_instructions-primitives.d.ts", + "sizeBytes": 207294, + "sha256": "ec04807c7f1ee31c94b2242f05936ccac7a6f4791353fd29f43a0e8860499d3b" + }, + { + "path": "src/alphalib/types/robots/_instructions-primitives.ts", + "sizeBytes": 63648, + "sha256": "3bf5225f4881aff338cbdd430f5eb22e58b5ef793392bcc8c0b6f52507f6b7b3" + }, + { + "path": "dist/alphalib/types/robots/ai-chat.d.ts", + "sizeBytes": 90303, + "sha256": "a6fbd4c6b3d58a37a87ffd6226db7136ddd3d206d7de818c816b48003ab12bad" + }, + { + "path": "src/alphalib/types/robots/ai-chat.ts", + "sizeBytes": 10035, + "sha256": "05f5c0a9aba087f5fb2a83cec845c3bc462d1ada916abe01c6510eecfd99b392" + }, + { + "path": "dist/ApiError.d.ts", + "sizeBytes": 592, + "sha256": "dde26d7ee9dea3fa185dbc490915a99c6785cc1462f96138867f33a75f5f5dee" + }, + { + "path": "src/ApiError.ts", + "sizeBytes": 1335, + "sha256": "12b90cef5bbf9760c478a3882cc85162368b492aed39d1b5e2915df927db8a05" + }, + { + "path": "dist/apiTypes.d.ts", + "sizeBytes": 3730, + "sha256": "5c69b795640fa02650cf0733d262b28488a049848863392bf33a3992eb0049eb" + }, + { + "path": "src/apiTypes.ts", + "sizeBytes": 4042, + "sha256": "6be2494c40b5cee641b33329b6e2bc495c0357ffa3509228c4c531d59dbc0c5a" + }, + { + "path": "dist/cli/commands/assemblies.d.ts", + "sizeBytes": 3569, + "sha256": "cac7dbbcb9f413eb9b391eec586b905675793ab2497beb361d04a1f6f64a89e6" + }, + { + "path": "src/cli/commands/assemblies.ts", + "sizeBytes": 41625, + "sha256": "9989a3f1070e07117fe57371b61118421a6ac626c84836e647d677e4c5babe08" + }, + { + "path": "dist/alphalib/types/assembliesGet.d.ts", + "sizeBytes": 2112, + "sha256": "9708f78e367a541625e567768f5d2bc8339ec49057dd2fa454d5c7215216f7be" + }, + { + "path": "src/alphalib/types/assembliesGet.ts", + "sizeBytes": 1440, + "sha256": "acf47bb92b25c5552a8b19419be3c14323f158726b7ee16e462a248ff190a347" + }, + { + "path": "dist/alphalib/types/robots/assembly-savejson.d.ts", + "sizeBytes": 3053, + "sha256": "8c30087c4cebb948e6373c79e83e5df104c2dc7b669e862f5ffc2ffd2b07c2ed" + }, + { + "path": "src/alphalib/types/robots/assembly-savejson.ts", + "sizeBytes": 1221, + "sha256": "05973f206156273ed5fb7f26a24e34479844fef3012f7086e3ab2fb8e12eee50" + }, + { + "path": "dist/alphalib/types/assemblyReplay.d.ts", + "sizeBytes": 414604, + "sha256": "db12e59f0737b3aab4c748c45905f95ab335157af57cd7de252f1b8fd76eeacb" + }, + { + "path": "src/alphalib/types/assemblyReplay.ts", + "sizeBytes": 716, + "sha256": "dadae8a1525f6d8313d65d52810fdd40ac14f1d2c52aa3fbd9e227ea13482aa8" + }, + { + "path": "dist/alphalib/types/assemblyReplayNotification.d.ts", + "sizeBytes": 414166, + "sha256": "bf372161e78fdbd03487c0967307015b263445be87601c2266637a831e91ac7e" + }, + { + "path": "src/alphalib/types/assemblyReplayNotification.ts", + "sizeBytes": 632, + "sha256": "c6ddb9cc02103ad5f791d25bcfe6b899fa86500b2c457dd2a698db5ec0ebee25" + }, + { + "path": "dist/alphalib/types/assemblyStatus.d.ts", + "sizeBytes": 4247003, + "sha256": "224358eca0d6e38f26d82b64cd10ebc3d2b609824e46a5691d7f8162e21d2218" + }, + { + "path": "src/alphalib/types/assemblyStatus.ts", + "sizeBytes": 29485, + "sha256": "0ef4a2377e4c67c4de4dfd0d2f6475c47e046d4e2df232f3407acf7a53946f4c" + }, + { + "path": "dist/alphalib/types/robots/audio-artwork.d.ts", + "sizeBytes": 167655, + "sha256": "56670af4f3daa10c5d495be972649563a4b912828c1e9988b2b7d8119493af88" + }, + { + "path": "src/alphalib/types/robots/audio-artwork.ts", + "sizeBytes": 3845, + "sha256": "82df928d54f70ea3d34a6602cdfe8ffdb43a8de10a36656263661e4309a54026" + }, + { + "path": "dist/alphalib/types/robots/audio-concat.d.ts", + "sizeBytes": 169966, + "sha256": "9c4150072f77917ca68e63e7862e0c9fee3e7d9df890d54b78f5a734079937e7" + }, + { + "path": "src/alphalib/types/robots/audio-concat.ts", + "sizeBytes": 5511, + "sha256": "a4e0146b728bf265969dad46f8a7536638efee63875906c544d4860e1ed4557a" + }, + { + "path": "dist/alphalib/types/robots/audio-encode.d.ts", + "sizeBytes": 167466, + "sha256": "922043005e80121472fc0ff17a50c49ec9fa4173130e1563adab679cd279fa0f" + }, + { + "path": "src/alphalib/types/robots/audio-encode.ts", + "sizeBytes": 3451, + "sha256": "be951d6318aab6473087dbea86193b69837a58796aae68761b3d31ec88efc975" + }, + { + "path": "dist/alphalib/types/robots/audio-loop.d.ts", + "sizeBytes": 167878, + "sha256": "751a9f62bae4eeb980ac67ed6646fb9522e1328abc3d40bc913309a5c8fc1420" + }, + { + "path": "src/alphalib/types/robots/audio-loop.ts", + "sizeBytes": 3676, + "sha256": "d91da780538c130608a56936f148a9537b2cedde0e233e3872b7ec70b678ef83" + }, + { + "path": "dist/alphalib/types/robots/audio-merge.d.ts", + "sizeBytes": 170479, + "sha256": "7ff8b6a67c07fd16d9c2323da40deb65d7f193cdeb277182adef3b4a8a661ff7" + }, + { + "path": "src/alphalib/types/robots/audio-merge.ts", + "sizeBytes": 4923, + "sha256": "fa524f4d4f7186dc7b001d27bba6b8397edf746d2853a3e280aa28a60aab990a" + }, + { + "path": "dist/alphalib/types/robots/audio-waveform.d.ts", + "sizeBytes": 168087, + "sha256": "e6778bd4b1c71a3b87bbdaa097b8efc97efd29962279fafe39cf75d802cdce4c" + }, + { + "path": "src/alphalib/types/robots/audio-waveform.ts", + "sizeBytes": 9454, + "sha256": "4b61b0599fade66e3a0d954eea55f980e9745eff3f19341ce8f6786a80469d3f" + }, + { + "path": "dist/cli/commands/auth.d.ts", + "sizeBytes": 936, + "sha256": "20a1d35fb55fad8af33fb6decede3cbf2cd621007fe443d2866e9975bbe23b20" + }, + { + "path": "src/cli/commands/auth.ts", + "sizeBytes": 10502, + "sha256": "0dea0295f4f5798dd619860d01fe6f50f59efd99a83c36c1e35803d584f158aa" + }, + { + "path": "dist/alphalib/types/robots/azure-import.d.ts", + "sizeBytes": 11036, + "sha256": "fb0980e7bd0fcca08b195cc79b5f168e874a25673c97f9340ad89449d2a9c25b" + }, + { + "path": "src/alphalib/types/robots/azure-import.ts", + "sizeBytes": 3901, + "sha256": "162715ca46c65fce521ceefa5141d96ae5a8d51ca8ed8d045af6cd55f27282eb" + }, + { + "path": "dist/alphalib/types/robots/azure-store.d.ts", + "sizeBytes": 23317, + "sha256": "7bd4f1c2d33efe2a62e8dc2efd851f61785960145c1954058eeb8882daf7177f" + }, + { + "path": "src/alphalib/types/robots/azure-store.ts", + "sizeBytes": 4870, + "sha256": "98ece7cd3cd7575ffa57cc301aec88a2465851addda92a43d6f914e74fe44cb1" + }, + { + "path": "dist/alphalib/types/robots/backblaze-import.d.ts", + "sizeBytes": 11200, + "sha256": "656737162a0c93544d22a8a6d5c640892644a5363ef91fcdaa26d7e9f5fe2664" + }, + { + "path": "src/alphalib/types/robots/backblaze-import.ts", + "sizeBytes": 4572, + "sha256": "143f75fd564c8ab4b580eaec2ca3599df03738e93b6d265de650903f32c708f4" + }, + { + "path": "dist/alphalib/types/robots/backblaze-store.d.ts", + "sizeBytes": 19655, + "sha256": "728cd867f57b1ff2a919ef4f226e2a0ca9513813f92165a6d25bb51735cc22d9" + }, + { + "path": "src/alphalib/types/robots/backblaze-store.ts", + "sizeBytes": 3810, + "sha256": "60a83e189004145990e911398693103f92826456698c7b1e77f1951f832d8367" + }, + { + "path": "dist/cli/commands/BaseCommand.d.ts", + "sizeBytes": 944, + "sha256": "9f132cb73d644f225f0efa48ab02f0edc6a82c9db7db080e84ae2281d9cf845c" + }, + { + "path": "src/cli/commands/BaseCommand.ts", + "sizeBytes": 2146, + "sha256": "d0cab4ebb72ce5d555be82bf3de4ba1f09dd223b71702bc53527928cf1c7ac91" + }, + { + "path": "dist/alphalib/types/bill.d.ts", + "sizeBytes": 1340, + "sha256": "addda1ad6e3507c37fa8eaa835c6ed57ede1206a732d0237e40f109d329549db" + }, + { + "path": "src/alphalib/types/bill.ts", + "sizeBytes": 188, + "sha256": "2ec2d5c86e068794e85195e417a515e383a70e2741926c3a949d10002a1ad13b" + }, + { + "path": "dist/cli/commands/bills.d.ts", + "sizeBytes": 590, + "sha256": "08945c847c67df1e376230d66034497ac77cf548e33354b595d304b47ae86203" + }, + { + "path": "src/cli/commands/bills.ts", + "sizeBytes": 2402, + "sha256": "c3272b2808ff8ff1a2924aaea1431e01fb0bd46205861d5621d28ac08ef4f5f9" + }, + { + "path": "dist/cli.d.ts", + "sizeBytes": 256, + "sha256": "c0b85d46fb05f111ab4b71bf0adc491e71b78efd5b5344b74599e4126477979b" + }, + { + "path": "src/cli.ts", + "sizeBytes": 1101, + "sha256": "9f7fa1f5565e87ffdf37abd416e6e77661d3cdba15513ae37fc9a5952a24abc0" + }, + { + "path": "dist/alphalib/types/robots/cloudfiles-import.d.ts", + "sizeBytes": 12289, + "sha256": "420c6b08193c73d5acb5b533160f437847cac7707dbfb0d19772bcd3e1a6d56f" + }, + { + "path": "src/alphalib/types/robots/cloudfiles-import.ts", + "sizeBytes": 4513, + "sha256": "39f9b5ef411d7ebf410fd2d14dd47c5332ace0deda99b02a0bec775b3f1e6248" + }, + { + "path": "dist/alphalib/types/robots/cloudfiles-store.d.ts", + "sizeBytes": 20090, + "sha256": "7e427617b89defb34f177c39ed4cfc57e9546e81569054be66c3a3fdcc1d004e" + }, + { + "path": "src/alphalib/types/robots/cloudfiles-store.ts", + "sizeBytes": 3545, + "sha256": "e9e41caeb42b5518893e7abe897975e133854c98df0cae60a48d51cb0f835d7d" + }, + { + "path": "dist/alphalib/types/robots/cloudflare-import.d.ts", + "sizeBytes": 12177, + "sha256": "6b349b174338e3814f7afe52af2e53271f0357845dea3fb33ee2ba663f545302" + }, + { + "path": "src/alphalib/types/robots/cloudflare-import.ts", + "sizeBytes": 4880, + "sha256": "3f2055ece87c5d7e583d6237395a4da4fda556fc01505254c3d0a6a126939142" + }, + { + "path": "dist/alphalib/types/robots/cloudflare-store.d.ts", + "sizeBytes": 21058, + "sha256": "06a0b0724e064c0f8cc75210d7630e3147b5a086a7f82eaff618701e772fe934" + }, + { + "path": "src/alphalib/types/robots/cloudflare-store.ts", + "sizeBytes": 4479, + "sha256": "be419be86bd4a2bd1d54cad4c0a0670d52e8bff6b7a51a7d31eaeecb6c64f932" + }, + { + "path": "dist/alphalib/types/robots/digitalocean-import.d.ts", + "sizeBytes": 12247, + "sha256": "1e2ea0768661803a5f478255a60e075031eacc6755f198d24e3e19579b471ab2" + }, + { + "path": "src/alphalib/types/robots/digitalocean-import.ts", + "sizeBytes": 4556, + "sha256": "f48028a8d933f4521ef27b82848c82ec3d139c3baff7f7f46942e2b4b76e58e6" + }, + { + "path": "dist/alphalib/types/robots/digitalocean-store.d.ts", + "sizeBytes": 21638, + "sha256": "86c25da875cdf43b1adebe42e50b0125df3fb570950d7818a94908c942efd785" + }, + { + "path": "src/alphalib/types/robots/digitalocean-store.ts", + "sizeBytes": 4676, + "sha256": "1f6cb42c4dea85cf1919b8d535247ef07c4d989e7d497e10a98fe5190c8e5083" + }, + { + "path": "dist/alphalib/types/robots/document-autorotate.d.ts", + "sizeBytes": 16993, + "sha256": "ee70d548c8ca611c2cc14f313bf245c3134cf3dcf04750aac29d05a5ad0975ba" + }, + { + "path": "src/alphalib/types/robots/document-autorotate.ts", + "sizeBytes": 2719, + "sha256": "174985d88d0e927eb16a23d831c23fa924ffd0527a436692acaad3c609b8b2fb" + }, + { + "path": "dist/alphalib/types/robots/document-convert.d.ts", + "sizeBytes": 24638, + "sha256": "6a9a0d28e5e25d40de359d63d524519c4c24904b6eb332b940a8c4dbbe0d61dc" + }, + { + "path": "src/alphalib/types/robots/document-convert.ts", + "sizeBytes": 9975, + "sha256": "b5f73d19418a5bb67b1895d647b0f3f4328130540e476620b0ca1fecf3e084a1" + }, + { + "path": "dist/alphalib/types/robots/document-merge.d.ts", + "sizeBytes": 18042, + "sha256": "7f60c8960caade2fae1a9478c4075c120eb4858dec888e5060a95756c4e4ac5d" + }, + { + "path": "src/alphalib/types/robots/document-merge.ts", + "sizeBytes": 3835, + "sha256": "265357067cf08c107cec5fdab7951810832559e4902e7eff0ae3474b1c3bceb2" + }, + { + "path": "dist/alphalib/types/robots/document-ocr.d.ts", + "sizeBytes": 18676, + "sha256": "de5e1f257cf9c796390a18365d2883cb9e43b1465d2259fd967d3d3370217908" + }, + { + "path": "src/alphalib/types/robots/document-ocr.ts", + "sizeBytes": 5306, + "sha256": "3b47017593ee1014cbb6c320a9ef5b04a6962958cb0006d269144972cb89f9ed" + }, + { + "path": "dist/alphalib/types/robots/document-split.d.ts", + "sizeBytes": 17616, + "sha256": "99431a94cba0901a94742e7bcad58745a74f388ad3a0a42a944afe69065bd543" + }, + { + "path": "src/alphalib/types/robots/document-split.ts", + "sizeBytes": 2867, + "sha256": "a8965112f587ff50452a72e3448e9802de1850f63a300f2643ba841c8414cceb" + }, + { + "path": "dist/alphalib/types/robots/document-thumbs.d.ts", + "sizeBytes": 28207, + "sha256": "cf8bbcd2f6c105baf34678306b32ab0bce1f1b7b409173f08c52646f806b2588" + }, + { + "path": "src/alphalib/types/robots/document-thumbs.ts", + "sizeBytes": 9807, + "sha256": "3a2d6c877178fb51a0d77d44ff0e11fdf59af26cd3839818b5a3a74665961ab3" + }, + { + "path": "dist/alphalib/types/robots/dropbox-import.d.ts", + "sizeBytes": 8522, + "sha256": "24118bc0f2c3a8daa16eec2d2d9bfe74e4a39776fa7936316161bc446c9af408" + }, + { + "path": "src/alphalib/types/robots/dropbox-import.ts", + "sizeBytes": 3505, + "sha256": "80d05dfafc8be76e46712016da81a25fff3e1e9edff7787124b4d417a2003103" + }, + { + "path": "dist/alphalib/types/robots/dropbox-store.d.ts", + "sizeBytes": 18629, + "sha256": "56fbd6ad497fc0e28d1956afecfad9aa77dfecdd7bef23d29595c3224171dd97" + }, + { + "path": "src/alphalib/types/robots/dropbox-store.ts", + "sizeBytes": 3465, + "sha256": "5f0ecce1e742d401864a14c9599b618b7c27743926b9c4452b6f56d9491e4866" + }, + { + "path": "dist/alphalib/types/robots/edgly-deliver.d.ts", + "sizeBytes": 6141, + "sha256": "153eb50b9d1ee94be2e1ec67179e70a1568ff0b1eac63ef8cc5ba66325b9b867" + }, + { + "path": "src/alphalib/types/robots/edgly-deliver.ts", + "sizeBytes": 2785, + "sha256": "40d7a8b6567fd80057781d7a24f093e9e1918ee45c3191085ee1bc256f9eeb44" + }, + { + "path": "dist/alphalib/types/robots/file-compress.d.ts", + "sizeBytes": 20135, + "sha256": "70a145f269bdd19a0dfede425e33849d3b4ba56631f11739d58d648f8d0b3089" + }, + { + "path": "src/alphalib/types/robots/file-compress.ts", + "sizeBytes": 6687, + "sha256": "b28ad0566636340b965e4fba2f5b9c7da277cbba7bba75237cca5e4d07affd27" + }, + { + "path": "dist/alphalib/types/robots/file-decompress.d.ts", + "sizeBytes": 16983, + "sha256": "5579a9407db5af3e825f06bc34d2a5f11b0d6b87e1bcff6ddd3a15dac8a2cd9c" + }, + { + "path": "src/alphalib/types/robots/file-decompress.ts", + "sizeBytes": 4628, + "sha256": "8c460d61060e013707889172190ed615775467d3d69ee7850efd977e86bc58ff" + }, + { + "path": "dist/alphalib/types/robots/file-filter.d.ts", + "sizeBytes": 30301, + "sha256": "477f0767ef827af10ab26e016e308bff23f2989e868f2c815f4f23cd94b3313f" + }, + { + "path": "src/alphalib/types/robots/file-filter.ts", + "sizeBytes": 6781, + "sha256": "e9476b7df5be5397bec14e2e976aaf252a43c375c9f0ba777d76ce333b57c604" + }, + { + "path": "dist/alphalib/types/robots/file-hash.d.ts", + "sizeBytes": 17641, + "sha256": "96be0831960c7d0ae1f1c5856a6a3c003ae39ae665a3490850c13a9eb1a4fb57" + }, + { + "path": "src/alphalib/types/robots/file-hash.ts", + "sizeBytes": 2861, + "sha256": "733c4313ffcea9fb33b4b6ca9857b682a561b10cd59d1097464ab673c7b96bca" + }, + { + "path": "dist/alphalib/types/robots/file-preview.d.ts", + "sizeBytes": 37390, + "sha256": "9bdfda1a7ab6e8ed51a500afff37b3679515ad533c9551de399bc872ed97719f" + }, + { + "path": "src/alphalib/types/robots/file-preview.ts", + "sizeBytes": 13514, + "sha256": "4bb202a2bc5109d1c1234321237c019cd41986890f19fe114bd85d8c66e905db" + }, + { + "path": "dist/alphalib/types/robots/file-read.d.ts", + "sizeBytes": 16703, + "sha256": "08e84ba786ec43b289048db242e2833844918e550f5fd2033d9426993ce44fad" + }, + { + "path": "src/alphalib/types/robots/file-read.ts", + "sizeBytes": 2549, + "sha256": "5081ae04d3c9db4cb4196b74877e63f7f30ea51e53773cca75095536c7a50963" + }, + { + "path": "dist/alphalib/types/robots/file-serve.d.ts", + "sizeBytes": 17440, + "sha256": "adf1d55b0f2b0c52f32b9861b0ce7a5c7b1e6f0087a35534eaab752d290f1163" + }, + { + "path": "src/alphalib/types/robots/file-serve.ts", + "sizeBytes": 6708, + "sha256": "2f35e8b78f5024e954a4b962549ec71ae61d24953b6e711511cf827fe5f69be1" + }, + { + "path": "dist/alphalib/types/robots/file-verify.d.ts", + "sizeBytes": 18195, + "sha256": "c18447877b8a83c219f662f4e744dc0ab16745404ada77cb6d8260493f9bab32" + }, + { + "path": "src/alphalib/types/robots/file-verify.ts", + "sizeBytes": 4174, + "sha256": "2c1aa8e92175ec0811628d79d3e1d51dfdfcdae2b8dfa53f32434ef416d19acc" + }, + { + "path": "dist/alphalib/types/robots/file-virusscan.d.ts", + "sizeBytes": 18207, + "sha256": "b9cd21c5b31f1921db32af8e84d20c8ca1901ad308cf4b55171e77f87bc8c9de" + }, + { + "path": "src/alphalib/types/robots/file-virusscan.ts", + "sizeBytes": 4593, + "sha256": "b419a7a5c34ce59996b4eb71833a337d8b0e180d36765c6bb0b6d8236ce49b05" + }, + { + "path": "dist/alphalib/types/robots/file-watermark.d.ts", + "sizeBytes": 17410, + "sha256": "d13deaf037252d9d9f27e7b880329665ef6146b637720bd7cdc0de46cb217630" + }, + { + "path": "src/alphalib/types/robots/file-watermark.ts", + "sizeBytes": 2068, + "sha256": "08af2039f3e568d27b91508b8002ce2ee19714817d69360a4e942cf27f820657" + }, + { + "path": "dist/alphalib/types/robots/ftp-import.d.ts", + "sizeBytes": 10382, + "sha256": "02b494b7a354fed368245635c8c79033c22dc41f04d3adc1b25ea82d5860bd89" + }, + { + "path": "src/alphalib/types/robots/ftp-import.ts", + "sizeBytes": 3132, + "sha256": "2f816efd74d6de7773fea4c48c4c51dcee514133e4b74788bb419e3f22ee1b75" + }, + { + "path": "dist/alphalib/types/robots/ftp-store.d.ts", + "sizeBytes": 21536, + "sha256": "7674a4dbc884b65a376ccffff7de07967ec32a384237d34fcc46d6a75e32f3d6" + }, + { + "path": "src/alphalib/types/robots/ftp-store.ts", + "sizeBytes": 4197, + "sha256": "1bbaa2361cc3675a29178cbd0f4fcecaad1033032f154a6da36c5c677a9c9447" + }, + { + "path": "dist/alphalib/types/robots/google-import.d.ts", + "sizeBytes": 9781, + "sha256": "d7cba012863f00d62bfca4b1adb997305747f3edd291ade13cc7d982dfd7bc86" + }, + { + "path": "src/alphalib/types/robots/google-import.ts", + "sizeBytes": 4503, + "sha256": "4e20e80511a5380f1c6f72e5913f85f728699c412f72bec01d01a150f644e346" + }, + { + "path": "dist/alphalib/types/robots/google-store.d.ts", + "sizeBytes": 20168, + "sha256": "69e46d09a51354b678b390873d4039369450e77ec0a047c77e2ebca527e6328a" + }, + { + "path": "src/alphalib/types/robots/google-store.ts", + "sizeBytes": 6183, + "sha256": "e30cb47805e98e3099935ed4113e036b61c5da91bc483e6b9d1d1f6371221a8b" + }, + { + "path": "dist/cli/helpers.d.ts", + "sizeBytes": 571, + "sha256": "f42d526e9b8270d53f94c8759ea94396515f3280f04ef47abab1cfb8445827ba" + }, + { + "path": "src/cli/helpers.ts", + "sizeBytes": 1550, + "sha256": "2a78e29a2c099f330e65db968da8a42cf25614c95374744a96e04e7341b64aab" + }, + { + "path": "dist/alphalib/types/robots/html-convert.d.ts", + "sizeBytes": 22952, + "sha256": "66b4c1ed3f87bd77f402b970f86fcd4869ceab173534a222d029e0ca38e74555" + }, + { + "path": "src/alphalib/types/robots/html-convert.ts", + "sizeBytes": 5966, + "sha256": "35eeb778615a8f9fb18e05b8672c7e4c116bf41c3e42cbb114057524ea9118cf" + }, + { + "path": "dist/alphalib/types/robots/http-import.d.ts", + "sizeBytes": 12754, + "sha256": "07454a99e30a4a8d0c167c657b7f94fe0699ca2515f342cd3dacf94aa4dc8122" + }, + { + "path": "src/alphalib/types/robots/http-import.ts", + "sizeBytes": 6465, + "sha256": "280418e48e6e718e6822f384d151bb6f83a47fd275d05a9e523e935a4a6a70fd" + }, + { + "path": "dist/alphalib/types/robots/image-bgremove.d.ts", + "sizeBytes": 19322, + "sha256": "ce7cf8d8e520806ba5b362c69716a4dffd6d4b14ab2642b31ec964791e70296e" + }, + { + "path": "src/alphalib/types/robots/image-bgremove.ts", + "sizeBytes": 3278, + "sha256": "c9fd592d22e31b933f54eb6f1c83f0aaefa3be47a0f98089da43bbba43d958b4" + }, + { + "path": "dist/alphalib/types/robots/image-describe.d.ts", + "sizeBytes": 19548, + "sha256": "31cb9b81d026344d2587b754dacb5fb2cc6a16cc3c4a8e6be0ee3b85064c9a23" + }, + { + "path": "src/alphalib/types/robots/image-describe.ts", + "sizeBytes": 5324, + "sha256": "2d6ffb899a5b34185e81aed62e8bfbde3fa93ccb18fe73a5a79d74a2b8423083" + }, + { + "path": "dist/alphalib/types/robots/image-facedetect.d.ts", + "sizeBytes": 20924, + "sha256": "87b70b6ec7fc69cb54df4473756f28b9dfc08891f2edbcc2e9343c19fe304259" + }, + { + "path": "src/alphalib/types/robots/image-facedetect.ts", + "sizeBytes": 7149, + "sha256": "caafb37c5d568017348216b74330f7467ccb64d469f32fcf03fd7838021a8c33" + }, + { + "path": "dist/alphalib/types/robots/image-generate.d.ts", + "sizeBytes": 21767, + "sha256": "83ce96595700aac82a23a46643db97314a5cc7d79fd6f3bee6d89541eceaf74c" + }, + { + "path": "src/alphalib/types/robots/image-generate.ts", + "sizeBytes": 3465, + "sha256": "054a6d1a584bddf6cb2636cc4b8984f0da2f497066e15ec5ce1fb0b3e91d5958" + }, + { + "path": "dist/alphalib/types/robots/image-merge.d.ts", + "sizeBytes": 19863, + "sha256": "b7ad4dc952aaae6250d2cfd6ffaedf915ab177c9fa86cad92ff764f71980e2b3" + }, + { + "path": "src/alphalib/types/robots/image-merge.ts", + "sizeBytes": 4399, + "sha256": "f86cde29eaef40c73185ccd5a06cce1b16392af2fe5a3657f537d008516193c5" + }, + { + "path": "dist/alphalib/types/robots/image-ocr.d.ts", + "sizeBytes": 18589, + "sha256": "c7875da64b1b3d96c8febb34de10cba91c8b3a75bf4de71e4347a837ded157e7" + }, + { + "path": "src/alphalib/types/robots/image-ocr.ts", + "sizeBytes": 5013, + "sha256": "e52bf1ef75dcf595c648b071c9b1d0cec7332055ad17205795d87567855e9dc8" + }, + { + "path": "dist/alphalib/types/robots/image-optimize.d.ts", + "sizeBytes": 19364, + "sha256": "fcf380dbaa88647a2659f8fed5b8705179e11eb48b690d93732aa1697ddac733" + }, + { + "path": "src/alphalib/types/robots/image-optimize.ts", + "sizeBytes": 4943, + "sha256": "a28ba8e59e94cbc77d39fe0442115b1ef07edaaaaef39c8039b2cdf2b9740437" + }, + { + "path": "dist/alphalib/types/robots/image-resize.d.ts", + "sizeBytes": 90591, + "sha256": "06048ee955f4b4a4c918553461f5203b6666ee251f0eeebb250765425055bd1f" + }, + { + "path": "src/alphalib/types/robots/image-resize.ts", + "sizeBytes": 28301, + "sha256": "23a89aaa7f7e7721eac3e7dafb1f82fdb5c1277dc30d85cf3e3364c478e45151" + }, + { + "path": "dist/InconsistentResponseError.d.ts", + "sizeBytes": 138, + "sha256": "65e9660f7df57f53d44dc10f9b3b547411b1c32a5ee3c886f2ae02e8d65163ee" + }, + { + "path": "src/InconsistentResponseError.ts", + "sizeBytes": 111, + "sha256": "05f61ae5885cda2995817857c701155383cc4bc63f04a574b07dbd18111ac9ab" + }, + { + "path": "dist/cli/commands/index.d.ts", + "sizeBytes": 110, + "sha256": "8138bd76ab0a7ad7dc62b74d654fd7335de2fa86e1fb58f34788df74005ccc2d" + }, + { + "path": "src/cli/commands/index.ts", + "sizeBytes": 1648, + "sha256": "96dcf864fd177c933434cc0e43ca8fe58aa153332ed5013af6d1012d4fde9d8f" + }, + { + "path": "dist/alphalib/mcache.d.ts", + "sizeBytes": 1881, + "sha256": "35ca1c21bf3de9ba4b28e19637c937a83f15b88dc2081dc9106f6201619e6451" + }, + { + "path": "src/alphalib/mcache.ts", + "sizeBytes": 4861, + "sha256": "6481c39114c8a5d6a514e2cdb0c40ee56f6f8ab56bb0a83ce91ed4f69a58184f" + }, + { + "path": "dist/alphalib/types/robots/meta-read.d.ts", + "sizeBytes": 5750, + "sha256": "f96e77de20bbda56606331d247cd9f08e73c86bbc3734c1f2d650d7ba397edc3" + }, + { + "path": "src/alphalib/types/robots/meta-read.ts", + "sizeBytes": 1626, + "sha256": "d039db9cc3fb5dff8523c398dcfd5ec7a4165e82c386bdcf5c8e10bab79fcade" + }, + { + "path": "dist/alphalib/types/robots/meta-write.d.ts", + "sizeBytes": 154336, + "sha256": "6dbc3ba00bebfbae0453baed961ad5f22c95a6a917d2dafcd208a45a21481aa0" + }, + { + "path": "src/alphalib/types/robots/meta-write.ts", + "sizeBytes": 3125, + "sha256": "49b1262dbcb811d08288f1efb102378b5ffed43c2bf05339d1fb088d0edb5a29" + }, + { + "path": "dist/alphalib/types/robots/minio-import.d.ts", + "sizeBytes": 12032, + "sha256": "56f5ad945624b293847bec369f49c57e4679b37884e4931b1dee102ab52e0636" + }, + { + "path": "src/alphalib/types/robots/minio-import.ts", + "sizeBytes": 4729, + "sha256": "6211c7a5c246574b614f3d7c0735de4a03242ff1c50954b1bd4d1cdc28bf3ae5" + }, + { + "path": "dist/alphalib/types/robots/minio-store.d.ts", + "sizeBytes": 21007, + "sha256": "4381c1c9a5447d4809742d37d11a3ae213e8875a60cad6d918fcf771a5814a8c" + }, + { + "path": "src/alphalib/types/robots/minio-store.ts", + "sizeBytes": 4189, + "sha256": "dc468fce6896ee822cd01eb5c1684e1c157aaf0f91530d965aee360baedb58e3" + }, + { + "path": "dist/alphalib/lib/nativeGlobby.d.ts", + "sizeBytes": 466, + "sha256": "df27afec76a5a459e53a10ac12f263924a1a99caadf239af8d943e1cf6de582d" + }, + { + "path": "src/alphalib/lib/nativeGlobby.ts", + "sizeBytes": 6987, + "sha256": "85bb1d37eb0e2c18f46178d28553a993c35eb2b145cfdddc90425ac5d3f6ec11" + }, + { + "path": "dist/cli/commands/notifications.d.ts", + "sizeBytes": 368, + "sha256": "8a557cc08d30854ae63efdd10cfc65add0eeca9748747bf5976f6264b89c9d62" + }, + { + "path": "src/cli/commands/notifications.ts", + "sizeBytes": 1829, + "sha256": "2cf790e6076b7983c6b88fb6aee38069765e2997b86b897bd0a4878ed5e3f966" + }, + { + "path": "dist/cli/OutputCtl.d.ts", + "sizeBytes": 1612, + "sha256": "bcccf6179f7ee37b2f78c572cc2d1d853ca9511fc90f4c887d8f7f7e95ce559a" + }, + { + "path": "src/cli/OutputCtl.ts", + "sizeBytes": 3397, + "sha256": "d16381cf45ae93b8bc2f42a9f4de9e1a012a21962e33a7ca36071cabca0e616a" + }, + { + "path": "dist/PaginationStream.d.ts", + "sizeBytes": 581, + "sha256": "952b4a59f1af5ba76db5d2aa705a88f5ff0ac58eeb09072b7bb8a1234be88245" + }, + { + "path": "src/PaginationStream.ts", + "sizeBytes": 1204, + "sha256": "ac24517761007d62f9f895cd69f89947cb98c1f79bb7c1038972648464c39260" + }, + { + "path": "dist/PollingTimeoutError.d.ts", + "sizeBytes": 144, + "sha256": "b8c5c12ce75d4ae2c196e4be780a67378547dd15fdccdb2824963e35a8ca4c96" + }, + { + "path": "src/PollingTimeoutError.ts", + "sizeBytes": 129, + "sha256": "52cd41a954b98ea5fdcb4d4b238da5e8203bd5c66a1e9c56bc25520cc6c1a6d5" + }, + { + "path": "dist/alphalib/types/robots/progress-simulate.d.ts", + "sizeBytes": 9245, + "sha256": "12e4ab3ccd73da1e7e7a45463a4eddf2b3091e04adf318d7737c0a56e915ad01" + }, + { + "path": "src/alphalib/types/robots/progress-simulate.ts", + "sizeBytes": 1325, + "sha256": "0591686d6c3787e0af4821649506d88034d3f302b021969dc91d612f7e9b3e8b" + }, + { + "path": "dist/alphalib/types/robots/s3-import.d.ts", + "sizeBytes": 13045, + "sha256": "32ffbd902a5c6c730f53014f642d371798fc110764c75d2ec5ab0a1cda02eb63" + }, + { + "path": "src/alphalib/types/robots/s3-import.ts", + "sizeBytes": 9153, + "sha256": "d3c2beb8aab49ce388cc98a63aa96221c82b70a41b89273e161fd783cf16ece2" + }, + { + "path": "dist/alphalib/types/robots/s3-store.d.ts", + "sizeBytes": 24603, + "sha256": "b2b926fdf8ed60165b5339f98dbbad4c5cb7e82a0d33c92a2e96a6ef87257f19" + }, + { + "path": "src/alphalib/types/robots/s3-store.ts", + "sizeBytes": 10404, + "sha256": "e46e6aa2a6c15c5253b17146af9f68506446ab6032f19dcf86d03abedf22216f" + }, + { + "path": "dist/alphalib/types/robots/script-run.d.ts", + "sizeBytes": 17234, + "sha256": "63547b6645117601d1de851b79bada4301a9b0f62a4bca1cf3e89632754786d0" + }, + { + "path": "src/alphalib/types/robots/script-run.ts", + "sizeBytes": 4500, + "sha256": "1f2ef9f81a63f341d419e289d1483e4c09c11df803771313690182d81e1ce543" + }, + { + "path": "dist/alphalib/types/robots/sftp-import.d.ts", + "sizeBytes": 9785, + "sha256": "a7fd349a62c27f52b20dfe99ed009e6b1fc7b6eaad15b51761e7eaa6f78ca3ed" + }, + { + "path": "src/alphalib/types/robots/sftp-import.ts", + "sizeBytes": 3065, + "sha256": "fe02c9a941b0d77c063dbb5bc2627da6a4828366a97ce6b6d7a43f912dfd22aa" + }, + { + "path": "dist/alphalib/types/robots/sftp-store.d.ts", + "sizeBytes": 20864, + "sha256": "f1c8e65870880f7afca120b6dfb9bad0c8d1289953bc33221ff19035e2f3a291" + }, + { + "path": "src/alphalib/types/robots/sftp-store.ts", + "sizeBytes": 4129, + "sha256": "8a2f0b5f00e281b04d9270286c679f2829e1c2b5186d41e96f495039a865ec08" + }, + { + "path": "dist/alphalib/types/robots/speech-transcribe.d.ts", + "sizeBytes": 19949, + "sha256": "ace8aa2b1d2f366893cb34bee4a0413bf117ff4302ffa2dde911cea802779bd5" + }, + { + "path": "src/alphalib/types/robots/speech-transcribe.ts", + "sizeBytes": 5994, + "sha256": "62516b051ea830ceb759193b72258eb9f71eb1ef592caf583de713afb4735571" + }, + { + "path": "dist/alphalib/types/stackVersions.d.ts", + "sizeBytes": 345, + "sha256": "abffa61231b5d99c58c189e8f2fefe52befa7caf031d89f15f9a9019a77deded" + }, + { + "path": "src/alphalib/types/stackVersions.ts", + "sizeBytes": 321, + "sha256": "2905f2af4f9dc9989eb957552b6a7212015f9ccc3830f5c1b47b3fe493ae23cb" + }, + { + "path": "dist/alphalib/types/robots/supabase-import.d.ts", + "sizeBytes": 12627, + "sha256": "90adca5e04dcfa03b5b36bac2754cc117599d1b35b15641296a7f67077e7c5b6" + }, + { + "path": "src/alphalib/types/robots/supabase-import.ts", + "sizeBytes": 4908, + "sha256": "b5010e2b076201a833ab305dce1ef0d3e27ec02bd9087136b79ccfa8057fc18e" + }, + { + "path": "dist/alphalib/types/robots/supabase-store.d.ts", + "sizeBytes": 21036, + "sha256": "053600e9ef6680faef2101ae027846ca8fa758e09a1e81be61be32a4add5c303" + }, + { + "path": "src/alphalib/types/robots/supabase-store.ts", + "sizeBytes": 4050, + "sha256": "db0973dfb43a0cfa30d27d7fc1cb0f8bca7a89c0eae0ac42583641fd5fbd4eac" + }, + { + "path": "dist/alphalib/types/robots/swift-import.d.ts", + "sizeBytes": 12540, + "sha256": "ac69f5083e4b43aa681f4b4a6250927a99180c35fb8b582d2f9bc92182f843fd" + }, + { + "path": "src/alphalib/types/robots/swift-import.ts", + "sizeBytes": 4769, + "sha256": "db5b4fce872e9379b32cb451238900b1a384d28c1be5481caf5910b8f306a14a" + }, + { + "path": "dist/alphalib/types/robots/swift-store.d.ts", + "sizeBytes": 21515, + "sha256": "54fe47c918a5738080d1e602663fbf800c2545defb8bb0cacb48c3ee6657f2a4" + }, + { + "path": "src/alphalib/types/robots/swift-store.ts", + "sizeBytes": 4259, + "sha256": "a3a48fd577d0a3c8afcbfb0933d3f18e87ab573885287a6be5dc9554720b1168" + }, + { + "path": "dist/cli/template-last-modified.d.ts", + "sizeBytes": 376, + "sha256": "7752e122126205b1a294c18a2fbfb3fdc00f2a88c5f188d277b51e97f0525613" + }, + { + "path": "src/cli/template-last-modified.ts", + "sizeBytes": 3925, + "sha256": "c319bb83a5a3e24fb9998976eeae74a2e9094356e2ac1495d8519df7aaa371d6" + }, + { + "path": "dist/alphalib/types/template.d.ts", + "sizeBytes": 7679978, + "sha256": "758e25908eaa5329ae33a9d694b339e4d8cb6def4459d5a8d1581829eeb667f5" + }, + { + "path": "src/alphalib/types/template.ts", + "sizeBytes": 11635, + "sha256": "4c72d99aefee3ffe6dc9fb285f51773d7446b24b4d6fff4aafb0a8d5b89efa18" + }, + { + "path": "dist/alphalib/types/templateCredential.d.ts", + "sizeBytes": 3526, + "sha256": "09de6d53950e580a1ed01a61fca71047c70637c9df24298e1772b310d188c82a" + }, + { + "path": "src/alphalib/types/templateCredential.ts", + "sizeBytes": 1375, + "sha256": "2d0a63c819e86f0eee3bb4811d32033ede8be5692ff9ba02c30147db42a41520" + }, + { + "path": "dist/cli/commands/templates.d.ts", + "sizeBytes": 2875, + "sha256": "62c3ab8980473a08a6ebfb662d56b517ad8cbc1f8d82988aa7f181e07d010bf0" + }, + { + "path": "src/cli/commands/templates.ts", + "sizeBytes": 16146, + "sha256": "12037ee25c27331aaa630d519c52e6c81520f002f79a74782b4d647d66025b15" + }, + { + "path": "dist/alphalib/types/robots/text-speak.d.ts", + "sizeBytes": 19946, + "sha256": "0360f009914c68cec3f2dbfb143047650c3a9fde1e2f5b31f17462cdf44f5c96" + }, + { + "path": "src/alphalib/types/robots/text-speak.ts", + "sizeBytes": 5446, + "sha256": "61faa11c89a96029931a6f950d7bf85e8cd021b852a90e0fc509fc22f286d297" + }, + { + "path": "dist/alphalib/types/robots/text-translate.d.ts", + "sizeBytes": 30846, + "sha256": "061b8a82ec26534d96598aebd5814921c0776dab9611c68650e538ec4b9f5388" + }, + { + "path": "src/alphalib/types/robots/text-translate.ts", + "sizeBytes": 6167, + "sha256": "60a628fc3a73ac29d989600c4697c80fc20f380713e3070bdbcb065f8bc14244" + }, + { + "path": "dist/alphalib/types/robots/tigris-import.d.ts", + "sizeBytes": 12557, + "sha256": "367ac7fd0910a21d0c91f368125cfbc2ac41b311011a602f0e1196ccbc1c6256" + }, + { + "path": "src/alphalib/types/robots/tigris-import.ts", + "sizeBytes": 4913, + "sha256": "4314fcad1af19426e44a715dfddcf66ca567194c7b1c562816acfef33f51bee0" + }, + { + "path": "dist/alphalib/types/robots/tigris-store.d.ts", + "sizeBytes": 21532, + "sha256": "8c86efd7e6562ff020749b2863272de41dfd2af637a5ae281a3e9d628f6e8460" + }, + { + "path": "src/alphalib/types/robots/tigris-store.ts", + "sizeBytes": 4368, + "sha256": "2ab40d3bf6dfce8c075b506f6ca037ecbcb94f7a8987a1b0a3190facd97e11b7" + }, + { + "path": "dist/alphalib/types/robots/tlcdn-deliver.d.ts", + "sizeBytes": 6141, + "sha256": "2d55583c45f4821e987b2c6c91508a9c5432520dcd184e96f4c744772eac673d" + }, + { + "path": "src/alphalib/types/robots/tlcdn-deliver.ts", + "sizeBytes": 2780, + "sha256": "98a184703f11ae07882a2b4424b0fe606ae32ef7f89331a1e89660a8df27ea1c" + }, + { + "path": "dist/Transloadit.d.ts", + "sizeBytes": 9194, + "sha256": "cbd0295d32aa80473afdfdf5674991228724c5488094936ddae3af329a8d3fe8" + }, + { + "path": "src/Transloadit.ts", + "sizeBytes": 31079, + "sha256": "0e431a0e5454816551e3980adde72035b32b0be00b464e626288477356822842" + }, + { + "path": "dist/alphalib/tryCatch.d.ts", + "sizeBytes": 772, + "sha256": "1eece292533e3695a84b6fa2fcd6c1bd38fc77f9a6b7cef849ff0852b6d79456" + }, + { + "path": "src/alphalib/tryCatch.ts", + "sizeBytes": 880, + "sha256": "e0c9e9a35c092d88ca2c861a3a2d4a75f9efa5101ec50e1c6a52a2ac06ac917c" + }, + { + "path": "dist/alphalib/types/robots/tus-store.d.ts", + "sizeBytes": 19747, + "sha256": "57fc17b9635c1a9c52c8a18c961f45a21ab43668c5e5912a9c7bdbb60464d2a4" + }, + { + "path": "src/alphalib/types/robots/tus-store.ts", + "sizeBytes": 5261, + "sha256": "2360ca08adc4ebbc35c6d4e2a14e5c5f3f4b52e6d57720c5c7958a71cf99ce3f" + }, + { + "path": "dist/tus.d.ts", + "sizeBytes": 698, + "sha256": "71419e8ef5a5308428a59d52bca3be0ad68d5428547d7a44ba6d7c72feba8a93" + }, + { + "path": "src/tus.ts", + "sizeBytes": 5040, + "sha256": "5214697dff961d604f6db254e4ae04f7513913d70734fef8a3eee33ddadd6111" + }, + { + "path": "dist/cli/types.d.ts", + "sizeBytes": 2503, + "sha256": "c62e78eb76dea08fc842b9557143dee71f4b68aaa1171249497f60279844ef31" + }, + { + "path": "src/cli/types.ts", + "sizeBytes": 1988, + "sha256": "34046c83c95ddb5087632149583b39d1e60e3bb5f373788bfb52ae5abf03dd75" + }, + { + "path": "dist/alphalib/types/robots/upload-handle.d.ts", + "sizeBytes": 6141, + "sha256": "f92f0f4fb223e34c6d71bc585ab29c15c6a3a101208e2653c992b23ff43cf54f" + }, + { + "path": "src/alphalib/types/robots/upload-handle.ts", + "sizeBytes": 3439, + "sha256": "4bf3de4456a3aa53d370f4568a0ab1c5423ac8f251d83c62b593fe7d8f814a9d" + }, + { + "path": "dist/alphalib/types/robots/video-adaptive.d.ts", + "sizeBytes": 213318, + "sha256": "7a79447f68a242551c39d73a5e020e09d7bbfbb1321d5d1ad88d353ef9037ec4" + }, + { + "path": "src/alphalib/types/robots/video-adaptive.ts", + "sizeBytes": 6616, + "sha256": "e74aff371f673ca60e8d472ab257bf97fdc0ab53fb097798abefc8392bd575ab" + }, + { + "path": "dist/alphalib/types/robots/video-concat.d.ts", + "sizeBytes": 212250, + "sha256": "d1536349d4065446732696fa8ec3a2807496d2dc302a2649ba11b3b2a2f08a1f" + }, + { + "path": "src/alphalib/types/robots/video-concat.ts", + "sizeBytes": 5221, + "sha256": "3165f6d83e36251c505d443821e3b0ce9697569f050be5369a19728aff4e83d0" + }, + { + "path": "dist/alphalib/types/robots/video-encode.d.ts", + "sizeBytes": 233784, + "sha256": "4e8287533efdd130afd6848f6f66653a4d4366778849894827ab4b066af49205" + }, + { + "path": "src/alphalib/types/robots/video-encode.ts", + "sizeBytes": 5101, + "sha256": "f75e56eb0bc43e46e9fccfef8c11fa86da70fd0ed231ba3125228a9b93b368dc" + }, + { + "path": "dist/alphalib/types/robots/video-merge.d.ts", + "sizeBytes": 216753, + "sha256": "2951bd8b2a929ab0fa5d2f23a092e68017ec1a4e50110005bbdde70363691936" + }, + { + "path": "src/alphalib/types/robots/video-merge.ts", + "sizeBytes": 6305, + "sha256": "8f70bdaf791e1e49e875f0876f6800591288e8f1dc24afad094163cf9cd2bfd5" + }, + { + "path": "dist/alphalib/types/robots/video-ondemand.d.ts", + "sizeBytes": 328276, + "sha256": "da59f54bbb515fbd9ef9cb1a19aa97561c1acd356acf9b1318383f9583ed0545" + }, + { + "path": "src/alphalib/types/robots/video-ondemand.ts", + "sizeBytes": 5479, + "sha256": "e380a593595c0295feaeaee0bc338cd9df43dce8d68f955b3b28b96e7d1528f0" + }, + { + "path": "dist/alphalib/types/robots/video-subtitle.d.ts", + "sizeBytes": 216860, + "sha256": "b0a6152e9b4b8b363c9c2f9f07195320277e2137c80266d55353c30d94d53d3e" + }, + { + "path": "src/alphalib/types/robots/video-subtitle.ts", + "sizeBytes": 5356, + "sha256": "b829093f0b0d8518216a21e9d8d1c8fec095ed97fdf80d0442141c001b2a1aa0" + }, + { + "path": "dist/alphalib/types/robots/video-thumbs.d.ts", + "sizeBytes": 159836, + "sha256": "76099f2d09bc70305d94cdbd22ff6b1d7f0510aba2e41e235ccfebe9f7f121f5" + }, + { + "path": "src/alphalib/types/robots/video-thumbs.ts", + "sizeBytes": 6146, + "sha256": "56ff17844959e49ca07a6dd55699c87164bd903dd67f1763a89b5bdf21132d2e" + }, + { + "path": "dist/alphalib/types/robots/vimeo-import.d.ts", + "sizeBytes": 10628, + "sha256": "c3d04aa4ba8bc9d60dd0b9adbfc1cf58b3f7cfca8e8f3ef7106324f9502cc9cb" + }, + { + "path": "src/alphalib/types/robots/vimeo-import.ts", + "sizeBytes": 4152, + "sha256": "2986132be48ace59ec097593bb5a339ce11a4a6a321a7cd50cc42a86c960a07b" + }, + { + "path": "dist/alphalib/types/robots/vimeo-store.d.ts", + "sizeBytes": 21633, + "sha256": "451488a53edaabdc43a66b68473b80dca87426f38f484d4c45eeed9c653a021a" + }, + { + "path": "src/alphalib/types/robots/vimeo-store.ts", + "sizeBytes": 5071, + "sha256": "ac4db748b1713592ea0aa131ee4dc59e360c676c8b7df3514ae410fb8f992938" + }, + { + "path": "dist/alphalib/types/robots/wasabi-import.d.ts", + "sizeBytes": 12587, + "sha256": "d4066c0bbc2667dd25945e6fc8a14a845cce2cc5c00f05ea8c393081fb5c1689" + }, + { + "path": "src/alphalib/types/robots/wasabi-import.ts", + "sizeBytes": 4898, + "sha256": "441b3730b98c5b2f0b86084d8ef169a7765771d84ef9fff15584d0638cf9b9b9" + }, + { + "path": "dist/alphalib/types/robots/wasabi-store.d.ts", + "sizeBytes": 21544, + "sha256": "f93747dc3012a7d553398f2da21f8e3945cbc082bbe7cba42aadbf5ec41b6aa2" + }, + { + "path": "src/alphalib/types/robots/wasabi-store.ts", + "sizeBytes": 4220, + "sha256": "45d6d2a669f03eb1e49e53f0463cde1a8f873e3562960e6596366569e8a33a6f" + }, + { + "path": "dist/alphalib/types/robots/youtube-store.d.ts", + "sizeBytes": 21333, + "sha256": "738f8455435c681022c8a7a6e703ecb6d1a53c338ea5c7678d04e634485d2f44" + }, + { + "path": "src/alphalib/types/robots/youtube-store.ts", + "sizeBytes": 5592, + "sha256": "d894f341c86bf6d1d21bc328eed7df2d686b07f4e59734a9354da309bcdb73cf" + }, + { + "path": "dist/alphalib/zodParseWithContext.d.ts", + "sizeBytes": 524, + "sha256": "6d8328e22a9419e3c879e1d1ea6ad40fc8c724d31e1351e60e45f3b81472313a" + }, + { + "path": "src/alphalib/zodParseWithContext.ts", + "sizeBytes": 12282, + "sha256": "d5bf1ecfed63b2a910c20048fa89ca52a84e83c4848c4eb891369b6857e55459" + } + ] +} diff --git a/docs/todo.md b/docs/todo.md new file mode 100644 index 00000000..aced4e18 --- /dev/null +++ b/docs/todo.md @@ -0,0 +1,53 @@ +# TODO + +## Compatibility verification (byte-for-byte) +- [x] Baseline: run `npm pack` or `yarn pack` from current `transloadit` +- [x] Record tarball SHA256 +- [x] Record file list + sizes + per-file SHA256 inside tarball +- [x] Record `package.json` fields that affect install (name, version, main, types, exports, files) +- [x] After refactor: run `yarn workspace transloadit pack` +- [x] Compute same fingerprint +- [x] Assert byte-for-byte identity + +## Fingerprint script +- [x] Add `scripts/fingerprint-pack.js` +- [x] Use `npm pack --json` to get tarball name +- [x] Hash the tarball SHA256 +- [x] Hash each file via `tar -tf` + `tar -xOf` +- [x] Output JSON artifact for comparison +- [x] Support `node scripts/fingerprint-pack.js .` +- [x] Support `node scripts/fingerprint-pack.js packages/transloadit` + +## Versioning and releases +- [x] Add Changesets at workspace root +- [ ] Use `yarn changeset` for changes +- [ ] Use `yarn changeset version` to bump versions +- [ ] Use `yarn changeset publish` to publish packages +- [x] Keep `transloadit` and `@transloadit/node` versions synchronized +- [ ] Decide whether `@transloadit/types` tracks same version or diverges + +## Implementation phases +### Phase 1: monorepo scaffolding +- [x] Add root workspace config +- [x] Add Changesets config +- [x] Add base tsconfig +- [x] Move current package to `packages/node` +- [x] Ensure existing build/test scripts still pass + +### Phase 2: @transloadit/types (re-export alphalib) +- [x] Keep alphalib as synced source of truth +- [x] Re-export from `src/alphalib/types` via TS path mapping or barrel files +- [x] Build `.d.ts` only, no runtime JS + +### Phase 3: @transloadit/zod +- [x] Generate Zod schemas in `packages/zod` from alphalib types +- [x] Depend on `zod` + +### Phase 4: transloadit wrapper +- [ ] Publish identical output to `@transloadit/node` +- [ ] Copy dist + transform `package.json` +- [ ] Confirm publish via Changesets + +### Phase 5: compatibility validation +- [ ] Record baseline fingerprint from current `transloadit` +- [ ] Validate refactor output matches baseline (except package name if applicable) diff --git a/package.json b/package.json index 263c7949..9120a274 100644 --- a/package.json +++ b/package.json @@ -1,95 +1,26 @@ { - "name": "transloadit", - "version": "4.1.2", - "description": "Node.js SDK for Transloadit", + "name": "transloadit-node-sdk", + "private": true, "type": "module", - "keywords": [ - "transloadit", - "encoding", - "transcoding", - "video", - "audio", - "mp3" - ], - "author": "Tim Koschuetzki ", "packageManager": "yarn@4.12.0", - "engines": { - "node": ">= 20" - }, - "dependencies": { - "@aws-sdk/client-s3": "^3.891.0", - "@aws-sdk/s3-request-presigner": "^3.891.0", - "@transloadit/sev-logger": "^0.0.15", - "clipanion": "^4.0.0-rc.4", - "debug": "^4.4.3", - "dotenv": "^17.2.3", - "form-data": "^4.0.4", - "got": "14.4.9", - "into-stream": "^9.0.0", - "is-stream": "^4.0.1", - "node-watch": "^0.7.4", - "p-map": "^7.0.3", - "p-queue": "^9.0.1", - "recursive-readdir": "^2.2.3", - "tus-js-client": "^4.3.1", - "typanion": "^3.14.0", - "type-fest": "^4.41.0", - "zod": "3.25.76" - }, - "devDependencies": { - "@biomejs/biome": "^2.2.4", - "@types/debug": "^4.1.12", - "@types/minimist": "^1.2.5", - "@types/node": "^24.10.3", - "@types/recursive-readdir": "^2.2.4", - "@types/temp": "^0.9.4", - "@vitest/coverage-v8": "^3.2.4", - "badge-maker": "^5.0.2", - "execa": "9.6.0", - "image-size": "^2.0.2", - "knip": "^5.73.3", - "minimatch": "^10.1.1", - "nock": "^14.0.10", - "npm-run-all": "^4.1.5", - "p-retry": "^7.0.0", - "rimraf": "^6.1.2", - "temp": "^0.9.4", - "tsx": "4.21.0", - "typescript": "5.9.3", - "vitest": "^3.2.4" - }, - "repository": { - "type": "git", - "url": "git://github.com/transloadit/node-sdk.git" - }, - "directories": { - "src": "./src" - }, + "workspaces": [ + "packages/*" + ], "scripts": { - "check": "yarn exec knip --fix --allow-remove-files --no-config-hints && yarn lint:ts && yarn fix && yarn test:unit", - "fix:js": "biome check --write .", - "lint:ts": "tsc --build", - "fix:js:unsafe": "biome check --write . --unsafe", - "lint:js": "biome check .", - "lint": "npm-run-all --parallel 'lint:js'", - "fix": "npm-run-all --serial 'fix:js'", - "lint:deps": "knip --dependencies --no-progress", - "fix:deps": "knip --dependencies --no-progress --fix", - "knip": "knip --no-config-hints --no-progress", - "prepack": "rm -f tsconfig.tsbuildinfo tsconfig.build.tsbuildinfo && tsc --build tsconfig.build.json", - "test:unit": "vitest run --coverage ./test/unit", - "test:e2e": "vitest run ./test/e2e", - "test": "vitest run --coverage" - }, - "license": "MIT", - "main": "./dist/Transloadit.js", - "exports": { - ".": "./dist/Transloadit.js", - "./package.json": "./package.json" + "check": "yarn workspace @transloadit/node check && yarn workspace @transloadit/types check && yarn workspace @transloadit/zod check", + "lint:js": "yarn workspace @transloadit/node lint:js", + "lint:ts": "yarn workspace @transloadit/node lint:ts", + "lint": "yarn workspace @transloadit/node lint", + "fix": "yarn workspace @transloadit/node fix", + "fix:js": "yarn workspace @transloadit/node fix:js", + "fix:js:unsafe": "yarn workspace @transloadit/node fix:js:unsafe", + "knip": "yarn workspace @transloadit/node knip", + "pack": "node scripts/prepare-transloadit.js && npm pack --ignore-scripts --prefix packages/transloadit && mv packages/transloadit/*.tgz .", + "test:unit": "yarn workspace @transloadit/node test:unit", + "test:e2e": "yarn workspace @transloadit/node test:e2e", + "test": "yarn workspace @transloadit/node test" }, - "files": [ - "dist", - "src" - ], - "bin": "./dist/cli.js" + "devDependencies": { + "@changesets/cli": "^2.29.7" + } } diff --git a/packages/node/LICENSE b/packages/node/LICENSE new file mode 100644 index 00000000..6a259963 --- /dev/null +++ b/packages/node/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Tim Koschuetzki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/node/README.md b/packages/node/README.md new file mode 100644 index 00000000..c041b465 --- /dev/null +++ b/packages/node/README.md @@ -0,0 +1,698 @@ +[![Build Status](https://github.com/transloadit/node-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/transloadit/node-sdk/actions/workflows/ci.yml) +[![Coverage](https://codecov.io/gh/transloadit/node-sdk/branch/main/graph/badge.svg)](https://codecov.io/gh/transloadit/node-sdk) + + + + + + Transloadit Logo + + + +This is the official **Node.js** SDK for [Transloadit](https://transloadit.com)'s file uploading and encoding service. + +## Intro + +[Transloadit](https://transloadit.com) is a service that helps you handle file +uploads, resize, crop and watermark your images, make GIFs, transcode your +videos, extract thumbnails, generate audio waveforms, [and so much more](https://transloadit.com/demos/). In +short, [Transloadit](https://transloadit.com) is the Swiss Army Knife for your +files. + +This is a **Node.js** SDK to make it easy to talk to the +[Transloadit](https://transloadit.com) REST API. + +## Requirements + +- [Node.js](https://nodejs.org/en/) version 20 or newer +- [A Transloadit account](https://transloadit.com/signup/) ([free signup](https://transloadit.com/pricing/)) +- [Your API credentials](https://transloadit.com/c/template-credentials) (`authKey`, `authSecret`) + +## Install + +Inside your project, type: + +```bash +yarn add transloadit +``` + +or + +```bash +npm install --save transloadit +``` + +## Command Line Interface (CLI) + +This package includes a full-featured CLI for interacting with Transloadit from your terminal. + +### Quick Start + +```bash +# Set your credentials +export TRANSLOADIT_KEY="YOUR_TRANSLOADIT_KEY" +export TRANSLOADIT_SECRET="YOUR_TRANSLOADIT_SECRET" + +# See all available commands +npx transloadit --help +``` + +### Processing Media + +Create Assemblies to process files using Assembly Instructions (steps) or Templates: + +```bash +# Process a file using a steps file +npx transloadit assemblies create --steps steps.json --input image.jpg --output result.jpg + +# Process using a Template +npx transloadit assemblies create --template YOUR_TEMPLATE_ID --input image.jpg --output result.jpg + +# Process with custom fields +npx transloadit assemblies create --template YOUR_TEMPLATE_ID --field size=100 --input image.jpg --output thumb.jpg + +# Process a directory of files +npx transloadit assemblies create --template YOUR_TEMPLATE_ID --input images/ --output thumbs/ + +# Process recursively with file watching +npx transloadit assemblies create --template YOUR_TEMPLATE_ID --input images/ --output thumbs/ --recursive --watch + +# Process multiple files in a single assembly +npx transloadit assemblies create --template YOUR_TEMPLATE_ID --input file1.jpg --input file2.jpg --output results/ --single-assembly + +# Limit concurrent processing (default: 5) +npx transloadit assemblies create --template YOUR_TEMPLATE_ID --input images/ --output thumbs/ --concurrency 2 +``` + +### Managing Assemblies + +```bash +# List recent assemblies +npx transloadit assemblies list + +# List assemblies with filters +npx transloadit assemblies list --after 2024-01-01 --before 2024-12-31 + +# Get assembly status +npx transloadit assemblies get ASSEMBLY_ID + +# Cancel an assembly +npx transloadit assemblies delete ASSEMBLY_ID + +# Replay an assembly (re-run with original instructions) +npx transloadit assemblies replay ASSEMBLY_ID + +# Replay with different steps +npx transloadit assemblies replay --steps new-steps.json ASSEMBLY_ID + +# Replay using latest template version +npx transloadit assemblies replay --reparse-template ASSEMBLY_ID +``` + +### Managing Templates + +```bash +# List all templates +npx transloadit templates list + +# Get template content +npx transloadit templates get TEMPLATE_ID + +# Create a template from a JSON file +npx transloadit templates create my-template template.json + +# Modify a template +npx transloadit templates modify TEMPLATE_ID template.json + +# Rename a template +npx transloadit templates modify TEMPLATE_ID --name new-name + +# Delete a template +npx transloadit templates delete TEMPLATE_ID + +# Sync local template files with Transloadit (bidirectional) +npx transloadit templates sync templates/*.json +npx transloadit templates sync --recursive templates/ +``` + +### Billing + +```bash +# Get bill for a month +npx transloadit bills get 2024-01 + +# Get detailed bill as JSON +npx transloadit bills get 2024-01 --json +``` + +### Assembly Notifications + +```bash +# Replay a notification +npx transloadit assembly-notifications replay ASSEMBLY_ID + +# Replay to a different URL +npx transloadit assembly-notifications replay --notify-url https://example.com/hook ASSEMBLY_ID +``` + +### Signature Generation + +```bash +# Generate a signature for assembly params +echo '{"steps":{}}' | npx transloadit auth signature + +# Generate with specific algorithm +echo '{"steps":{}}' | npx transloadit auth signature --algorithm sha256 + +# Generate a signed Smart CDN URL +echo '{"workspace":"my-workspace","template":"my-template","input":"image.jpg"}' | npx transloadit auth smart-cdn +``` + +### CLI Options + +All commands support these common options: + +- `--json, -j` - Output results as JSON (useful for scripting) +- `--log-level, -l` - Set log verbosity level by name or number (default: notice) +- `--endpoint` - Custom API endpoint URL (or set `TRANSLOADIT_ENDPOINT` env var) +- `--help, -h` - Show help for a command + +The `assemblies create` command additionally supports: + +- `--single-assembly` - Pass all input files to a single assembly instead of one assembly per file + +#### Log Levels + +The CLI uses [syslog severity levels](https://en.wikipedia.org/wiki/Syslog#Severity_level). Lower = more severe, higher = more verbose: + +| Level | Value | Description | +| -------- | ----- | ------------------------------------- | +| `err` | 3 | Error conditions | +| `warn` | 4 | Warning conditions | +| `notice` | 5 | Normal but significant **(default)** | +| `info` | 6 | Informational messages | +| `debug` | 7 | Debug-level messages | +| `trace` | 8 | Most verbose/detailed | + +You can use either the level name or its numeric value: + +```bash +# Show only errors and warnings +npx transloadit assemblies list -l warn +npx transloadit assemblies list -l 4 + +# Show debug output +npx transloadit assemblies list -l debug +npx transloadit assemblies list -l 7 +``` + +## SDK Usage + +The following code will upload an image and resize it to a thumbnail: + +```javascript +import { Transloadit } from 'transloadit' + +const transloadit = new Transloadit({ + authKey: 'YOUR_TRANSLOADIT_KEY', + authSecret: 'YOUR_TRANSLOADIT_SECRET', +}) + +try { + const options = { + files: { + file1: '/PATH/TO/FILE.jpg', + }, + params: { + steps: { + // You can have many Steps. In this case we will just resize any inputs (:original) + resize: { + use: ':original', + robot: '/image/resize', + result: true, + width: 75, + height: 75, + }, + }, + // OR if you already created a template, you can use it instead of "steps": + // template_id: 'YOUR_TEMPLATE_ID', + }, + waitForCompletion: true, // Wait for the Assembly (job) to finish executing before returning + } + + const status = await transloadit.createAssembly(options) + + if (status.results.resize) { + console.log('✅ Success - Your resized image:', status.results.resize[0].ssl_url) + } else { + console.log("❌ The Assembly didn't produce any output. Make sure you used a valid image file") + } +} catch (err) { + console.error('❌ Unable to process Assembly.', err) + if (err instanceof ApiError && err.assemblyId) { + console.error(`💡 More info: https://transloadit.com/assemblies/${err.assemblyId}`) + } +} +``` + +You can find [details about your executed Assemblies here](https://transloadit.com/assemblies). + +## Examples + +- [Upload and resize image](https://github.com/transloadit/node-sdk/blob/main/examples/resize_an_image.ts) +- [Upload image and convert to WebP](https://github.com/transloadit/node-sdk/blob/main/examples/convert_to_webp.ts) +- [Rasterize SVG to PNG](https://github.com/transloadit/node-sdk/blob/main/examples/rasterize_svg_to_png.ts) +- [Crop a face out of an image and download the result](https://github.com/transloadit/node-sdk/blob/main/examples/face_detect_download.ts) +- [Retry example](https://github.com/transloadit/node-sdk/blob/main/examples/retry.ts) +- [Calculate total costs (GB usage)](https://github.com/transloadit/node-sdk/blob/main/examples/fetch_costs_of_all_assemblies_in_timeframe.ts) +- [Templates CRUD](https://github.com/transloadit/node-sdk/blob/main/examples/template_api.ts) +- [Template Credentials CRUD](https://github.com/transloadit/node-sdk/blob/main/examples/credentials.ts) + +For more fully working examples take a look at [`examples/`](https://github.com/transloadit/node-sdk/blob/main/examples/). + +For more example use cases and information about the available robots and their parameters, check out the [Transloadit website](https://transloadit.com/). + +## API + +These are the public methods on the `Transloadit` object and their descriptions. The methods are based on the [Transloadit API](https://transloadit.com/docs/api/). + +Table of contents: + +- [Main](#main) +- [Assemblies](#assemblies) +- [Assembly notifications](#assembly-notifications) +- [Templates](#templates) +- [Template Credentials](#template-credentials) +- [Errors](#errors) +- [Rate limiting & auto retry](#rate-limiting--auto-retry) + +### Main + +#### constructor(options) + +Returns a new instance of the client. + +The `options` object can contain the following keys: + +- `authKey` **(required)** - see [requirements](#requirements) +- `authSecret` **(required)** - see [requirements](#requirements) +- `endpoint` (default `'https://api2.transloadit.com'`) +- `maxRetries` (default `5`) - see [Rate limiting & auto retry](#rate-limiting--auto-retry) +- `gotRetry` (default `0`) - see [Rate limiting & auto retry](#rate-limiting--auto-retry) +- `timeout` (default `60000`: 1 minute) - the timeout (in milliseconds) for all requests (except `createAssembly`) +- `validateResponses` (default `false`) + +### Assemblies + +#### async createAssembly(options) + +Creates a new Assembly on Transloadit and optionally upload the specified `files` and `uploads`. + +You can provide the following keys inside the `options` object: + +- `params` **(required)** - An object containing keys defining the Assembly's behavior with the following keys: (See also [API doc](https://transloadit.com/docs/api/assemblies-post/) and [examples](#examples)) + - `steps` - Assembly instructions - See [Transloadit docs](https://transloadit.com/docs/topics/assembly-instructions/) and [demos](https://transloadit.com/demos/) for inspiration. + - `template_id` - The ID of the Template that contains your Assembly Instructions. **One of either `steps` or `template_id` is required.** If you specify both, then [any Steps will overrule the template](https://transloadit.com/docs/topics/templates/#overruling-templates-at-runtime). + - `fields` - An object of form fields to add to the request, to make use of in the Assembly instructions via [Assembly variables](https://transloadit.com/docs#assembly-variables). + - `notify_url` - Transloadit can send a Pingback to your server when the Assembly is completed. We'll send the Assembly Status in JSON encoded string inside a transloadit field in a multipart POST request to the URL supplied here. +- `files` - An object (key-value pairs) containing one or more file paths to upload and use in your Assembly. The _key_ is the _field name_ and the _value_ is the path to the file to be uploaded. The _field name_ and the file's name may be used in the ([Assembly instructions](https://transloadit.com/docs/topics/assembly-instructions/)) (`params`.`steps`) to refer to the particular file. See example below. + - `'fieldName': '/path/to/file'` + - more files... +- `uploads` - An object (key-value pairs) containing one or more files to upload and use in your Assembly. The _key_ is the _file name_ and the _value_ is the _content_ of the file to be uploaded. _Value_ can be one of many types: + - `'fieldName': (Readable | Buffer | TypedArray | ArrayBuffer | string | Iterable | AsyncIterable | Promise)`. + - more uploads... +- `waitForCompletion` - A boolean (default is `false`) to indicate whether you want to wait for the Assembly to finish with all encoding results present before the promise is fulfilled. If `waitForCompletion` is `true`, this SDK will poll for status updates and fulfill the promise when all encoding work is done. +- `timeout` - Number of milliseconds to wait before aborting (default `86400000`: 24 hours). +- `onUploadProgress` - An optional function that will be periodically called with the file upload progress, which is an with an object containing: + - `uploadedBytes` - Number of bytes uploaded so far. + - `totalBytes` - Total number of bytes to upload or `undefined` if unknown (Streams). +- `onAssemblyProgress` - Once the Assembly has started processing this will be periodically called with the _Assembly Execution Status_ (result of `getAssembly`) **only if `waitForCompletion` is `true`**. +- `chunkSize` - (for uploads) a number indicating the maximum size of a tus `PATCH` request body in bytes. Default to `Infinity` for file uploads and 50MB for streams of unknown length. See [tus-js-client](https://github.com/tus/tus-js-client/blob/master/docs/api.md#chunksize). +- `uploadConcurrency` - Maximum number of concurrent tus file uploads to occur at any given time (default 10.) + +**NOTE**: Make sure the key in `files` and `uploads` is not one of `signature`, `params` or `max_size`. + +Example code showing all options: + +```js +await transloadit.createAssembly({ + files: { + file1: '/path/to/file.jpg' + // ... + }, + uploads: { + 'file2.bin': Buffer.from([0, 0, 7]), // A buffer + 'file3.txt': 'file contents', // A string + 'file4.jpg': process.stdin // A stream + // ... + }, + params: { + steps: { ... }, + template_id: 'MY_TEMPLATE_ID', + fields: { + field1: 'Field value', + // ... + }, + notify_url: 'https://example.com/notify-url', + }, + waitForCompletion: true, + timeout: 60000, + onUploadProgress, + onAssemblyProgress, +}) +``` + +Example `onUploadProgress` and `onAssemblyProgress` handlers: + +```javascript +function onUploadProgress({ uploadedBytes, totalBytes }) { + // NOTE: totalBytes may be undefined + console.log(`♻️ Upload progress polled: ${uploadedBytes} of ${totalBytes} bytes uploaded.`) +} +function onAssemblyProgress(assembly) { + console.log( + `♻️ Assembly progress polled: ${assembly.error ? assembly.error : assembly.ok} ${ + assembly.assembly_id + } ... ` + ) +} +``` + +**Tip:** `createAssembly` returns a `Promise` with an extra property `assemblyId`. This can be used to retrieve the Assembly ID before the Assembly has even been created. Useful for debugging by logging this ID when the request starts, for example: + +```js +const promise = transloadit.createAssembly(options) +console.log('Creating', promise.assemblyId) +const status = await promise +``` + +See also: + +- [API documentation](https://transloadit.com/docs/api/assemblies-post/) +- Error codes and retry logic below + +#### async listAssemblies(params) + +Retrieve Assemblies according to the given `params`. + +Valid params can be `page`, `pagesize`, `type`, `fromdate`, `todate` and `keywords`. Please consult the [API documentation](https://transloadit.com/docs/api/assemblies-get/) for details. + +The method returns an object containing these properties: + +- `items`: An `Array` of up to `pagesize` Assemblies +- `count`: Total number of Assemblies + +#### streamAssemblies(params) + +Creates an `objectMode` `Readable` stream that automates handling of `listAssemblies` pagination. It accepts the same `params` as `listAssemblies`. + +This can be used to iterate through Assemblies: + +```javascript +const assemblyStream = transloadit.streamAssemblies({ fromdate: '2016-08-19 01:15:00 UTC' }) + +assemblyStream.on('readable', function () { + const assembly = assemblyStream.read() + if (assembly == null) console.log('end of stream') + + console.log(assembly.id) +}) +``` + +Results can also be piped. Here's an example using +[through2](https://github.com/rvagg/through2): + +```javascript +const assemblyStream = transloadit.streamAssemblies({ fromdate: '2016-08-19 01:15:00 UTC' }) + +assemblyStream + .pipe( + through.obj(function (chunk, enc, callback) { + this.push(chunk.id + '\n') + callback() + }) + ) + .pipe(fs.createWriteStream('assemblies.txt')) +``` + +#### async getAssembly(assemblyId) + +Retrieves the JSON status of the Assembly identified by the given `assemblyId`. See [API documentation](https://transloadit.com/docs/api/assemblies-assembly-id-get/). + +#### async cancelAssembly(assemblyId) + +Removes the Assembly identified by the given `assemblyId` from the memory of the Transloadit machines, ultimately cancelling it. This does not delete the Assembly from the database - you can still access it on `https://transloadit.com/assemblies/{assembly_id}` in your Transloadit account. This also does not delete any files associated with the Assembly from the Transloadit servers. See [API documentation](https://transloadit.com/docs/api/assemblies-assembly-id-delete/). + +#### async replayAssembly(assemblyId, params) + +Replays the Assembly identified by the given `assemblyId` (required argument). Optionally you can also provide a `notify_url` key inside `params` if you want to change the notification target. See [API documentation](https://transloadit.com/docs/api/assemblies-assembly-id-replay-post/) for more info about `params`. + +The response from the `replayAssembly` is minimal and does not contain much information about the replayed assembly. Please call `getAssembly` or `awaitAssemblyCompletion` after replay to get more information: + +```js +const replayAssemblyResponse = await transloadit.replayAssembly(failedAssemblyId) + +const assembly = await transloadit.getAssembly(replayAssemblyResponse.assembly_id) +// Or +const completedAssembly = await transloadit.awaitAssemblyCompletion( + replayAssemblyResponse.assembly_id +) +``` + +#### async awaitAssemblyCompletion(assemblyId, opts) + +This function will continously poll the specified Assembly `assemblyId` and resolve when it is done uploading and executing (until `result.ok` is no longer `ASSEMBLY_UPLOADING`, `ASSEMBLY_EXECUTING` or `ASSEMBLY_REPLAYING`). It resolves with the same value as `getAssembly`. + +`opts` is an object with the keys: + +- `onAssemblyProgress` - A progress function called on each poll. See `createAssembly` +- `timeout` - How many milliseconds until polling times out (default: no timeout) +- `interval` - Poll interval in milliseconds (default `1000`) +- `signal` - An `AbortSignal` to cancel polling. When aborted, the promise rejects with an `AbortError`. +- `onPoll` - A callback invoked at the start of each poll iteration. Return `false` to stop polling early and resolve with the last known status. Useful for implementing custom cancellation logic (e.g., superseding assemblies in watch mode). + +#### getLastUsedAssemblyUrl() + +Returns the internal url that was used for the last call to `createAssembly`. This is meant to be used for debugging purposes. + +### Assembly notifications + +#### async replayAssemblyNotification(assemblyId, params) + +Replays the notification for the Assembly identified by the given `assemblyId` (required argument). Optionally you can also provide a `notify_url` key inside `params` if you want to change the notification target. See [API documentation](https://transloadit.com/docs/api/assembly-notifications-assembly-id-replay-post/) for more info about `params`. + +### Templates + +Templates are Steps that can be reused. [See example template code](examples/template_api.ts). + +#### async createTemplate(params) + +Creates a template the provided params. The required `params` keys are: + +- `name` - The template name +- `template` - The template JSON object containing its `steps` + +See also [API documentation](https://transloadit.com/docs/api/templates-post/). + +```js +const template = { + steps: { + encode: { + use: ':original', + robot: '/video/encode', + preset: 'ipad-high', + }, + thumbnail: { + use: 'encode', + robot: '/video/thumbnails', + }, + }, +} + +const result = await transloadit.createTemplate({ name: 'my-template-name', template }) +console.log('✅ Template created with template_id', result.id) +``` + +#### async editTemplate(templateId, params) + +Updates the template represented by the given `templateId` with the new value. The `params` works just like the one from the `createTemplate` call. See [API documentation](https://transloadit.com/docs/api/templates-template-id-put/). + +#### async getTemplate(templateId) + +Retrieves the name and the template JSON for the template represented by the given `templateId`. See [API documentation](https://transloadit.com/docs/api/templates-template-id-get/). + +#### async deleteTemplate(templateId) + +Deletes the template represented by the given `templateId`. See [API documentation](https://transloadit.com/docs/api/templates-template-id-delete/). + +#### async listTemplates(params) + +Retrieve all your templates. See [API documentation](https://transloadit.com/docs/api/templates-template-id-get/) for more info about `params`. + +The method returns an object containing these properties: + +- `items`: An `Array` of up to `pagesize` templates +- `count`: Total number of templates + +#### streamTemplates(params) + +Creates an `objectMode` `Readable` stream that automates handling of `listTemplates` pagination. Similar to `streamAssemblies`. + +### Template Credentials + +Template Credentials allow you to store third-party credentials (e.g., AWS S3, Google Cloud Storage, FTP) securely on Transloadit for use in your Assembly Instructions. + +#### async createTemplateCredential(params) + +Creates a new Template Credential. The `params` object should contain the credential configuration. See [API documentation](https://transloadit.com/docs/api/template-credentials-post/). + +#### async editTemplateCredential(credentialId, params) + +Updates an existing Template Credential identified by `credentialId`. See [API documentation](https://transloadit.com/docs/api/template-credentials-credential-id-put/). + +#### async deleteTemplateCredential(credentialId) + +Deletes the Template Credential identified by `credentialId`. See [API documentation](https://transloadit.com/docs/api/template-credentials-credential-id-delete/). + +#### async getTemplateCredential(credentialId) + +Retrieves the Template Credential identified by `credentialId`. See [API documentation](https://transloadit.com/docs/api/template-credentials-credential-id-get/). + +#### async listTemplateCredentials(params) + +Lists all Template Credentials. See [API documentation](https://transloadit.com/docs/api/template-credentials-get/). + +#### streamTemplateCredentials(params) + +Creates an `objectMode` `Readable` stream that automates handling of `listTemplateCredentials` pagination. Similar to `streamAssemblies`. + +### Other + +#### setDefaultTimeout(timeout) + +Same as `constructor` `timeout` option: Set the default timeout (in milliseconds) for all requests (except `createAssembly`) + +#### async getBill(date) + +Retrieves the billing data for a given `date` string with format `YYYY-MM`. See [API documentation](https://transloadit.com/docs/api/bill-date-get/). + +#### calcSignature(params) + +Calculates a signature for the given `params` JSON object. If the `params` object does not include an `authKey` or `expires` keys (and their values) in the `auth` sub-key, then they are set automatically. + +This function returns an object with the key `signature` (containing the calculated signature string) and a key `params`, which contains the stringified version of the passed `params` object (including the set expires and authKey keys). + +See [Signature Generation](#signature-generation) in the CLI section for command-line usage. + +#### getSignedSmartCDNUrl(params) + +Constructs a signed Smart CDN URL, as defined in the [API documentation](https://transloadit.com/docs/topics/signature-authentication/#smart-cdn). `params` must be an object with the following properties: + +- `workspace` - Workspace slug (required) +- `template` - Template slug or template ID (required) +- `input` - Input value that is provided as `${fields.input}` in the template (required) +- `urlParams` - Object with additional parameters for the URL query string (optional) +- `expiresAt` - Expiration timestamp of the signature in milliseconds since UNIX epoch. Defaults to 1 hour from now. (optional) + +Example: + +```js +const client = new Transloadit({ authKey: 'foo_key', authSecret: 'foo_secret' }) +const url = client.getSignedSmartCDNUrl({ + workspace: 'foo_workspace', + template: 'foo_template', + input: 'foo_input', + urlParams: { + foo: 'bar', + }, +}) + +// url is: +// https://foo_workspace.tlcdn.com/foo_template/foo_input?auth_key=foo_key&exp=1714525200000&foo=bar&sig=sha256:9548915ec70a5f0d05de9497289e792201ceec19a526fe315f4f4fd2e7e377ac +``` + +### Errors + +Any errors originating from Node.js will be passed on and we use [GOT](https://github.com/sindresorhus/got) v11 for HTTP requests. [Errors from `got`](https://github.com/sindresorhus/got/tree/v11.8.6?tab=readme-ov-file#errors) will also be passed on, _except_ the `got.HTTPError` which will be replaced with a `transloadit.ApiError`, which will have its `cause` property set to the instance of the original `got.HTTPError`. `transloadit.ApiError` has these properties: + +- `code` (`string`) - [The Transloadit API error code](https://transloadit.com/docs/api/response-codes/#error-codes). +- `rawMessage` (`string`) - A textual representation of the Transloadit API error. +- `reason` (`string`) - Additional information about the Transloadit API error. +- `assemblyId`: (`string`) - If the request is related to an assembly, this will be the ID of the assembly. +- `assemblySslUrl` (`string`) - If the request is related to an assembly, this will be the SSL URL to the assembly . + +To identify errors you can either check its props or use `instanceof`, e.g.: + +```js +try { + await transloadit.createAssembly(options) +} catch (err) { + if (err instanceof got.TimeoutError) { + return console.error('The request timed out', err) + } + if (err.code === 'ENOENT') { + return console.error('Cannot open file', err) + } + if (err instanceof ApiError && err.code === 'ASSEMBLY_INVALID_STEPS') { + return console.error('Invalid Assembly Steps', err) + } +} +``` + +**Note:** Assemblies that have an error status (`assembly.error`) will only result in an error being thrown from `createAssembly` and `replayAssembly`. For other Assembly methods, no errors will be thrown, but any error can be found in the response's `error` property (also `ApiError.code`). + +- [More information on Transloadit errors (`ApiError.code`)](https://transloadit.com/docs/api/response-codes/#error-codes) +- [More information on request errors](https://github.com/sindresorhus/got#errors) + +### Rate limiting & auto retry + +There are three kinds of retries: + +#### Retry on rate limiting (`maxRetries`, default `5`) + +All functions of the client automatically obey all rate limiting imposed by Transloadit (e.g. `RATE_LIMIT_REACHED`), so there is no need to write your own wrapper scripts to handle rate limits. The SDK will by default retry requests **5 times** with auto back-off (See `maxRetries` constructor option). + +#### GOT HTTP retries (`gotRetry`, default `{ limit: 0 }`) + +Because we use [got](https://github.com/sindresorhus/got) under the hood, you can pass a `gotRetry` constructor option which is passed on to `got`. This offers great flexibility for handling retries on network errors and HTTP status codes with auto back-off. See [`got` `retry` object documentation](https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md). + +**Note that the above `maxRetries` option does not affect the `gotRetry` logic.** + +#### Validate API responses (`validateResponses`, default `false`) + +As we have ported the JavaScript SDK to TypeScript in v4, we are now also validating API responses using `zod` schemas. Having schema validation enabled (`true`), guarantees that the data returned by the SDK adheres to the TypeScript types of this SDK. However we are still working on improving the schemas and they are not yet 100% complete. This means that if you hit a bug in the schemas, a `zod` schema validation error will be thrown. If you encounter such an error, please report it and we will fix it as soon as possible. If you set this option to `false`, schema validation will be disabled, and you won't get any such errors, however the TypeScript types will not protect you should such a bug be encountered. + +#### Custom retry logic + +If you want to retry on other errors, please see the [retry example code](examples/retry.ts). + +- https://transloadit.com/docs/api/rate-limiting/ +- https://transloadit.com/blog/2012/04/introducing-rate-limiting/ + +## Debugging + +This project uses [debug](https://github.com/visionmedia/debug) so you can run node with the `DEBUG=transloadit` evironment variable to enable verbose logging. Example: + +```bash +DEBUG=transloadit* npx tsx examples/template_api.ts +``` + +## Maintainers + +- [Mikael Finstad](https://github.com/mifi) + +### Changelog + +See [Releases](https://github.com/transloadit/node-sdk/releases) + +## Attribution + +Thanks to [Ian Hansen](https://github.com/supershabam) for donating the `transloadit` npm name. You can still access his code under [`v0.0.0`](https://www.npmjs.com/package/transloadit/v/0.0.0). + +## License + +[MIT](LICENSE) © [Transloadit](https://transloadit.com) + +## Development + +See [CONTRIBUTING](./CONTRIBUTING.md). diff --git a/examples/convert_to_webp.ts b/packages/node/examples/convert_to_webp.ts similarity index 100% rename from examples/convert_to_webp.ts rename to packages/node/examples/convert_to_webp.ts diff --git a/examples/credentials.ts b/packages/node/examples/credentials.ts similarity index 100% rename from examples/credentials.ts rename to packages/node/examples/credentials.ts diff --git a/examples/face_detect_download.ts b/packages/node/examples/face_detect_download.ts similarity index 100% rename from examples/face_detect_download.ts rename to packages/node/examples/face_detect_download.ts diff --git a/examples/fetch_costs_of_all_assemblies_in_timeframe.ts b/packages/node/examples/fetch_costs_of_all_assemblies_in_timeframe.ts similarity index 100% rename from examples/fetch_costs_of_all_assemblies_in_timeframe.ts rename to packages/node/examples/fetch_costs_of_all_assemblies_in_timeframe.ts diff --git a/examples/fixtures/berkley.jpg b/packages/node/examples/fixtures/berkley.jpg similarity index 100% rename from examples/fixtures/berkley.jpg rename to packages/node/examples/fixtures/berkley.jpg diff --git a/examples/fixtures/circle.svg b/packages/node/examples/fixtures/circle.svg similarity index 100% rename from examples/fixtures/circle.svg rename to packages/node/examples/fixtures/circle.svg diff --git a/examples/rasterize_svg_to_png.ts b/packages/node/examples/rasterize_svg_to_png.ts similarity index 100% rename from examples/rasterize_svg_to_png.ts rename to packages/node/examples/rasterize_svg_to_png.ts diff --git a/examples/resize_an_image.ts b/packages/node/examples/resize_an_image.ts similarity index 100% rename from examples/resize_an_image.ts rename to packages/node/examples/resize_an_image.ts diff --git a/examples/retry.ts b/packages/node/examples/retry.ts similarity index 100% rename from examples/retry.ts rename to packages/node/examples/retry.ts diff --git a/examples/template_api.ts b/packages/node/examples/template_api.ts similarity index 100% rename from examples/template_api.ts rename to packages/node/examples/template_api.ts diff --git a/knip.ts b/packages/node/knip.ts similarity index 83% rename from knip.ts rename to packages/node/knip.ts index 63cecad6..df913853 100644 --- a/knip.ts +++ b/packages/node/knip.ts @@ -25,12 +25,17 @@ const config: KnipConfig = { '@types/minimist', 'minimatch', 'tsx', + // Tooling invoked via package.json scripts; knip's binary detection is disabled. + '@biomejs/biome', + 'npm-run-all', ], ignoreExportsUsedInFile: { type: true, interface: true, }, rules: { + // Binary resolution is unreliable with Yarn PnP; avoid false positives. + binaries: 'off', exports: 'warn', types: 'warn', nsExports: 'warn', diff --git a/packages/node/package.json b/packages/node/package.json new file mode 100644 index 00000000..d1f7afef --- /dev/null +++ b/packages/node/package.json @@ -0,0 +1,85 @@ +{ + "name": "@transloadit/node", + "version": "4.1.2", + "description": "Node.js SDK for Transloadit", + "type": "module", + "keywords": ["transloadit", "encoding", "transcoding", "video", "audio", "mp3"], + "author": "Tim Koschuetzki ", + "packageManager": "yarn@4.12.0", + "engines": { + "node": ">= 20" + }, + "dependencies": { + "@aws-sdk/client-s3": "^3.891.0", + "@aws-sdk/s3-request-presigner": "^3.891.0", + "@transloadit/sev-logger": "^0.0.15", + "clipanion": "^4.0.0-rc.4", + "debug": "^4.4.3", + "dotenv": "^17.2.3", + "form-data": "^4.0.4", + "got": "14.4.9", + "into-stream": "^9.0.0", + "is-stream": "^4.0.1", + "node-watch": "^0.7.4", + "p-map": "^7.0.3", + "p-queue": "^9.0.1", + "recursive-readdir": "^2.2.3", + "tus-js-client": "^4.3.1", + "typanion": "^3.14.0", + "type-fest": "^4.41.0", + "zod": "3.25.76" + }, + "devDependencies": { + "@biomejs/biome": "^2.2.4", + "@types/debug": "^4.1.12", + "@types/minimist": "^1.2.5", + "@types/node": "^24.10.3", + "@types/recursive-readdir": "^2.2.4", + "@types/temp": "^0.9.4", + "@vitest/coverage-v8": "^3.2.4", + "badge-maker": "^5.0.2", + "execa": "9.6.0", + "image-size": "^2.0.2", + "knip": "^5.73.3", + "minimatch": "^10.1.1", + "nock": "^14.0.10", + "npm-run-all": "^4.1.5", + "p-retry": "^7.0.0", + "rimraf": "^6.1.2", + "temp": "^0.9.4", + "tsx": "4.21.0", + "typescript": "5.9.3", + "vitest": "^3.2.4" + }, + "repository": { + "type": "git", + "url": "git://github.com/transloadit/node-sdk.git" + }, + "directories": { + "src": "./src" + }, + "scripts": { + "check": "yarn exec knip --fix --allow-remove-files --no-config-hints && yarn lint:ts && yarn fix && yarn test:unit", + "fix:js": "biome check --write .", + "lint:ts": "tsc --build", + "fix:js:unsafe": "biome check --write . --unsafe", + "lint:js": "biome check .", + "lint": "npm-run-all --parallel 'lint:js'", + "fix": "npm-run-all --serial 'fix:js'", + "lint:deps": "knip --dependencies --no-progress", + "fix:deps": "knip --dependencies --no-progress --fix", + "knip": "knip --no-config-hints --no-progress", + "prepack": "rm -f tsconfig.tsbuildinfo tsconfig.build.tsbuildinfo && tsc --build tsconfig.build.json", + "test:unit": "vitest run --coverage ./test/unit", + "test:e2e": "vitest run ./test/e2e", + "test": "vitest run --coverage" + }, + "license": "MIT", + "main": "./dist/Transloadit.js", + "exports": { + ".": "./dist/Transloadit.js", + "./package.json": "./package.json" + }, + "files": ["dist", "src"], + "bin": "./dist/cli.js" +} diff --git a/src/ApiError.ts b/packages/node/src/ApiError.ts similarity index 100% rename from src/ApiError.ts rename to packages/node/src/ApiError.ts diff --git a/src/InconsistentResponseError.ts b/packages/node/src/InconsistentResponseError.ts similarity index 100% rename from src/InconsistentResponseError.ts rename to packages/node/src/InconsistentResponseError.ts diff --git a/src/PaginationStream.ts b/packages/node/src/PaginationStream.ts similarity index 100% rename from src/PaginationStream.ts rename to packages/node/src/PaginationStream.ts diff --git a/src/PollingTimeoutError.ts b/packages/node/src/PollingTimeoutError.ts similarity index 100% rename from src/PollingTimeoutError.ts rename to packages/node/src/PollingTimeoutError.ts diff --git a/src/Transloadit.ts b/packages/node/src/Transloadit.ts similarity index 100% rename from src/Transloadit.ts rename to packages/node/src/Transloadit.ts diff --git a/src/alphalib/lib/nativeGlobby.ts b/packages/node/src/alphalib/lib/nativeGlobby.ts similarity index 100% rename from src/alphalib/lib/nativeGlobby.ts rename to packages/node/src/alphalib/lib/nativeGlobby.ts diff --git a/src/alphalib/mcache.ts b/packages/node/src/alphalib/mcache.ts similarity index 100% rename from src/alphalib/mcache.ts rename to packages/node/src/alphalib/mcache.ts diff --git a/src/alphalib/tryCatch.ts b/packages/node/src/alphalib/tryCatch.ts similarity index 100% rename from src/alphalib/tryCatch.ts rename to packages/node/src/alphalib/tryCatch.ts diff --git a/src/alphalib/types/assembliesGet.ts b/packages/node/src/alphalib/types/assembliesGet.ts similarity index 100% rename from src/alphalib/types/assembliesGet.ts rename to packages/node/src/alphalib/types/assembliesGet.ts diff --git a/src/alphalib/types/assemblyReplay.ts b/packages/node/src/alphalib/types/assemblyReplay.ts similarity index 100% rename from src/alphalib/types/assemblyReplay.ts rename to packages/node/src/alphalib/types/assemblyReplay.ts diff --git a/src/alphalib/types/assemblyReplayNotification.ts b/packages/node/src/alphalib/types/assemblyReplayNotification.ts similarity index 100% rename from src/alphalib/types/assemblyReplayNotification.ts rename to packages/node/src/alphalib/types/assemblyReplayNotification.ts diff --git a/src/alphalib/types/assemblyStatus.ts b/packages/node/src/alphalib/types/assemblyStatus.ts similarity index 100% rename from src/alphalib/types/assemblyStatus.ts rename to packages/node/src/alphalib/types/assemblyStatus.ts diff --git a/src/alphalib/types/bill.ts b/packages/node/src/alphalib/types/bill.ts similarity index 100% rename from src/alphalib/types/bill.ts rename to packages/node/src/alphalib/types/bill.ts diff --git a/src/alphalib/types/robots/_index.ts b/packages/node/src/alphalib/types/robots/_index.ts similarity index 100% rename from src/alphalib/types/robots/_index.ts rename to packages/node/src/alphalib/types/robots/_index.ts diff --git a/src/alphalib/types/robots/_instructions-primitives.ts b/packages/node/src/alphalib/types/robots/_instructions-primitives.ts similarity index 100% rename from src/alphalib/types/robots/_instructions-primitives.ts rename to packages/node/src/alphalib/types/robots/_instructions-primitives.ts diff --git a/src/alphalib/types/robots/ai-chat.ts b/packages/node/src/alphalib/types/robots/ai-chat.ts similarity index 100% rename from src/alphalib/types/robots/ai-chat.ts rename to packages/node/src/alphalib/types/robots/ai-chat.ts diff --git a/src/alphalib/types/robots/assembly-savejson.ts b/packages/node/src/alphalib/types/robots/assembly-savejson.ts similarity index 100% rename from src/alphalib/types/robots/assembly-savejson.ts rename to packages/node/src/alphalib/types/robots/assembly-savejson.ts diff --git a/src/alphalib/types/robots/audio-artwork.ts b/packages/node/src/alphalib/types/robots/audio-artwork.ts similarity index 100% rename from src/alphalib/types/robots/audio-artwork.ts rename to packages/node/src/alphalib/types/robots/audio-artwork.ts diff --git a/src/alphalib/types/robots/audio-concat.ts b/packages/node/src/alphalib/types/robots/audio-concat.ts similarity index 100% rename from src/alphalib/types/robots/audio-concat.ts rename to packages/node/src/alphalib/types/robots/audio-concat.ts diff --git a/src/alphalib/types/robots/audio-encode.ts b/packages/node/src/alphalib/types/robots/audio-encode.ts similarity index 100% rename from src/alphalib/types/robots/audio-encode.ts rename to packages/node/src/alphalib/types/robots/audio-encode.ts diff --git a/src/alphalib/types/robots/audio-loop.ts b/packages/node/src/alphalib/types/robots/audio-loop.ts similarity index 100% rename from src/alphalib/types/robots/audio-loop.ts rename to packages/node/src/alphalib/types/robots/audio-loop.ts diff --git a/src/alphalib/types/robots/audio-merge.ts b/packages/node/src/alphalib/types/robots/audio-merge.ts similarity index 100% rename from src/alphalib/types/robots/audio-merge.ts rename to packages/node/src/alphalib/types/robots/audio-merge.ts diff --git a/src/alphalib/types/robots/audio-waveform.ts b/packages/node/src/alphalib/types/robots/audio-waveform.ts similarity index 100% rename from src/alphalib/types/robots/audio-waveform.ts rename to packages/node/src/alphalib/types/robots/audio-waveform.ts diff --git a/src/alphalib/types/robots/azure-import.ts b/packages/node/src/alphalib/types/robots/azure-import.ts similarity index 100% rename from src/alphalib/types/robots/azure-import.ts rename to packages/node/src/alphalib/types/robots/azure-import.ts diff --git a/src/alphalib/types/robots/azure-store.ts b/packages/node/src/alphalib/types/robots/azure-store.ts similarity index 100% rename from src/alphalib/types/robots/azure-store.ts rename to packages/node/src/alphalib/types/robots/azure-store.ts diff --git a/src/alphalib/types/robots/backblaze-import.ts b/packages/node/src/alphalib/types/robots/backblaze-import.ts similarity index 100% rename from src/alphalib/types/robots/backblaze-import.ts rename to packages/node/src/alphalib/types/robots/backblaze-import.ts diff --git a/src/alphalib/types/robots/backblaze-store.ts b/packages/node/src/alphalib/types/robots/backblaze-store.ts similarity index 100% rename from src/alphalib/types/robots/backblaze-store.ts rename to packages/node/src/alphalib/types/robots/backblaze-store.ts diff --git a/src/alphalib/types/robots/cloudfiles-import.ts b/packages/node/src/alphalib/types/robots/cloudfiles-import.ts similarity index 100% rename from src/alphalib/types/robots/cloudfiles-import.ts rename to packages/node/src/alphalib/types/robots/cloudfiles-import.ts diff --git a/src/alphalib/types/robots/cloudfiles-store.ts b/packages/node/src/alphalib/types/robots/cloudfiles-store.ts similarity index 100% rename from src/alphalib/types/robots/cloudfiles-store.ts rename to packages/node/src/alphalib/types/robots/cloudfiles-store.ts diff --git a/src/alphalib/types/robots/cloudflare-import.ts b/packages/node/src/alphalib/types/robots/cloudflare-import.ts similarity index 100% rename from src/alphalib/types/robots/cloudflare-import.ts rename to packages/node/src/alphalib/types/robots/cloudflare-import.ts diff --git a/src/alphalib/types/robots/cloudflare-store.ts b/packages/node/src/alphalib/types/robots/cloudflare-store.ts similarity index 100% rename from src/alphalib/types/robots/cloudflare-store.ts rename to packages/node/src/alphalib/types/robots/cloudflare-store.ts diff --git a/src/alphalib/types/robots/digitalocean-import.ts b/packages/node/src/alphalib/types/robots/digitalocean-import.ts similarity index 100% rename from src/alphalib/types/robots/digitalocean-import.ts rename to packages/node/src/alphalib/types/robots/digitalocean-import.ts diff --git a/src/alphalib/types/robots/digitalocean-store.ts b/packages/node/src/alphalib/types/robots/digitalocean-store.ts similarity index 100% rename from src/alphalib/types/robots/digitalocean-store.ts rename to packages/node/src/alphalib/types/robots/digitalocean-store.ts diff --git a/src/alphalib/types/robots/document-autorotate.ts b/packages/node/src/alphalib/types/robots/document-autorotate.ts similarity index 100% rename from src/alphalib/types/robots/document-autorotate.ts rename to packages/node/src/alphalib/types/robots/document-autorotate.ts diff --git a/src/alphalib/types/robots/document-convert.ts b/packages/node/src/alphalib/types/robots/document-convert.ts similarity index 100% rename from src/alphalib/types/robots/document-convert.ts rename to packages/node/src/alphalib/types/robots/document-convert.ts diff --git a/src/alphalib/types/robots/document-merge.ts b/packages/node/src/alphalib/types/robots/document-merge.ts similarity index 100% rename from src/alphalib/types/robots/document-merge.ts rename to packages/node/src/alphalib/types/robots/document-merge.ts diff --git a/src/alphalib/types/robots/document-ocr.ts b/packages/node/src/alphalib/types/robots/document-ocr.ts similarity index 100% rename from src/alphalib/types/robots/document-ocr.ts rename to packages/node/src/alphalib/types/robots/document-ocr.ts diff --git a/src/alphalib/types/robots/document-split.ts b/packages/node/src/alphalib/types/robots/document-split.ts similarity index 100% rename from src/alphalib/types/robots/document-split.ts rename to packages/node/src/alphalib/types/robots/document-split.ts diff --git a/src/alphalib/types/robots/document-thumbs.ts b/packages/node/src/alphalib/types/robots/document-thumbs.ts similarity index 100% rename from src/alphalib/types/robots/document-thumbs.ts rename to packages/node/src/alphalib/types/robots/document-thumbs.ts diff --git a/src/alphalib/types/robots/dropbox-import.ts b/packages/node/src/alphalib/types/robots/dropbox-import.ts similarity index 100% rename from src/alphalib/types/robots/dropbox-import.ts rename to packages/node/src/alphalib/types/robots/dropbox-import.ts diff --git a/src/alphalib/types/robots/dropbox-store.ts b/packages/node/src/alphalib/types/robots/dropbox-store.ts similarity index 100% rename from src/alphalib/types/robots/dropbox-store.ts rename to packages/node/src/alphalib/types/robots/dropbox-store.ts diff --git a/src/alphalib/types/robots/edgly-deliver.ts b/packages/node/src/alphalib/types/robots/edgly-deliver.ts similarity index 100% rename from src/alphalib/types/robots/edgly-deliver.ts rename to packages/node/src/alphalib/types/robots/edgly-deliver.ts diff --git a/src/alphalib/types/robots/file-compress.ts b/packages/node/src/alphalib/types/robots/file-compress.ts similarity index 100% rename from src/alphalib/types/robots/file-compress.ts rename to packages/node/src/alphalib/types/robots/file-compress.ts diff --git a/src/alphalib/types/robots/file-decompress.ts b/packages/node/src/alphalib/types/robots/file-decompress.ts similarity index 100% rename from src/alphalib/types/robots/file-decompress.ts rename to packages/node/src/alphalib/types/robots/file-decompress.ts diff --git a/src/alphalib/types/robots/file-filter.ts b/packages/node/src/alphalib/types/robots/file-filter.ts similarity index 100% rename from src/alphalib/types/robots/file-filter.ts rename to packages/node/src/alphalib/types/robots/file-filter.ts diff --git a/src/alphalib/types/robots/file-hash.ts b/packages/node/src/alphalib/types/robots/file-hash.ts similarity index 100% rename from src/alphalib/types/robots/file-hash.ts rename to packages/node/src/alphalib/types/robots/file-hash.ts diff --git a/src/alphalib/types/robots/file-preview.ts b/packages/node/src/alphalib/types/robots/file-preview.ts similarity index 100% rename from src/alphalib/types/robots/file-preview.ts rename to packages/node/src/alphalib/types/robots/file-preview.ts diff --git a/src/alphalib/types/robots/file-read.ts b/packages/node/src/alphalib/types/robots/file-read.ts similarity index 100% rename from src/alphalib/types/robots/file-read.ts rename to packages/node/src/alphalib/types/robots/file-read.ts diff --git a/src/alphalib/types/robots/file-serve.ts b/packages/node/src/alphalib/types/robots/file-serve.ts similarity index 100% rename from src/alphalib/types/robots/file-serve.ts rename to packages/node/src/alphalib/types/robots/file-serve.ts diff --git a/src/alphalib/types/robots/file-verify.ts b/packages/node/src/alphalib/types/robots/file-verify.ts similarity index 100% rename from src/alphalib/types/robots/file-verify.ts rename to packages/node/src/alphalib/types/robots/file-verify.ts diff --git a/src/alphalib/types/robots/file-virusscan.ts b/packages/node/src/alphalib/types/robots/file-virusscan.ts similarity index 100% rename from src/alphalib/types/robots/file-virusscan.ts rename to packages/node/src/alphalib/types/robots/file-virusscan.ts diff --git a/src/alphalib/types/robots/file-watermark.ts b/packages/node/src/alphalib/types/robots/file-watermark.ts similarity index 100% rename from src/alphalib/types/robots/file-watermark.ts rename to packages/node/src/alphalib/types/robots/file-watermark.ts diff --git a/src/alphalib/types/robots/ftp-import.ts b/packages/node/src/alphalib/types/robots/ftp-import.ts similarity index 100% rename from src/alphalib/types/robots/ftp-import.ts rename to packages/node/src/alphalib/types/robots/ftp-import.ts diff --git a/src/alphalib/types/robots/ftp-store.ts b/packages/node/src/alphalib/types/robots/ftp-store.ts similarity index 100% rename from src/alphalib/types/robots/ftp-store.ts rename to packages/node/src/alphalib/types/robots/ftp-store.ts diff --git a/src/alphalib/types/robots/google-import.ts b/packages/node/src/alphalib/types/robots/google-import.ts similarity index 100% rename from src/alphalib/types/robots/google-import.ts rename to packages/node/src/alphalib/types/robots/google-import.ts diff --git a/src/alphalib/types/robots/google-store.ts b/packages/node/src/alphalib/types/robots/google-store.ts similarity index 100% rename from src/alphalib/types/robots/google-store.ts rename to packages/node/src/alphalib/types/robots/google-store.ts diff --git a/src/alphalib/types/robots/html-convert.ts b/packages/node/src/alphalib/types/robots/html-convert.ts similarity index 100% rename from src/alphalib/types/robots/html-convert.ts rename to packages/node/src/alphalib/types/robots/html-convert.ts diff --git a/src/alphalib/types/robots/http-import.ts b/packages/node/src/alphalib/types/robots/http-import.ts similarity index 100% rename from src/alphalib/types/robots/http-import.ts rename to packages/node/src/alphalib/types/robots/http-import.ts diff --git a/src/alphalib/types/robots/image-bgremove.ts b/packages/node/src/alphalib/types/robots/image-bgremove.ts similarity index 100% rename from src/alphalib/types/robots/image-bgremove.ts rename to packages/node/src/alphalib/types/robots/image-bgremove.ts diff --git a/src/alphalib/types/robots/image-describe.ts b/packages/node/src/alphalib/types/robots/image-describe.ts similarity index 100% rename from src/alphalib/types/robots/image-describe.ts rename to packages/node/src/alphalib/types/robots/image-describe.ts diff --git a/src/alphalib/types/robots/image-facedetect.ts b/packages/node/src/alphalib/types/robots/image-facedetect.ts similarity index 100% rename from src/alphalib/types/robots/image-facedetect.ts rename to packages/node/src/alphalib/types/robots/image-facedetect.ts diff --git a/src/alphalib/types/robots/image-generate.ts b/packages/node/src/alphalib/types/robots/image-generate.ts similarity index 100% rename from src/alphalib/types/robots/image-generate.ts rename to packages/node/src/alphalib/types/robots/image-generate.ts diff --git a/src/alphalib/types/robots/image-merge.ts b/packages/node/src/alphalib/types/robots/image-merge.ts similarity index 100% rename from src/alphalib/types/robots/image-merge.ts rename to packages/node/src/alphalib/types/robots/image-merge.ts diff --git a/src/alphalib/types/robots/image-ocr.ts b/packages/node/src/alphalib/types/robots/image-ocr.ts similarity index 100% rename from src/alphalib/types/robots/image-ocr.ts rename to packages/node/src/alphalib/types/robots/image-ocr.ts diff --git a/src/alphalib/types/robots/image-optimize.ts b/packages/node/src/alphalib/types/robots/image-optimize.ts similarity index 100% rename from src/alphalib/types/robots/image-optimize.ts rename to packages/node/src/alphalib/types/robots/image-optimize.ts diff --git a/src/alphalib/types/robots/image-resize.ts b/packages/node/src/alphalib/types/robots/image-resize.ts similarity index 100% rename from src/alphalib/types/robots/image-resize.ts rename to packages/node/src/alphalib/types/robots/image-resize.ts diff --git a/src/alphalib/types/robots/meta-read.ts b/packages/node/src/alphalib/types/robots/meta-read.ts similarity index 100% rename from src/alphalib/types/robots/meta-read.ts rename to packages/node/src/alphalib/types/robots/meta-read.ts diff --git a/src/alphalib/types/robots/meta-write.ts b/packages/node/src/alphalib/types/robots/meta-write.ts similarity index 98% rename from src/alphalib/types/robots/meta-write.ts rename to packages/node/src/alphalib/types/robots/meta-write.ts index eca0cfc4..46852e8d 100644 --- a/src/alphalib/types/robots/meta-write.ts +++ b/packages/node/src/alphalib/types/robots/meta-write.ts @@ -53,8 +53,7 @@ export const robotMetaWriteInstructionsSchema = robotBase **Note:** This Robot currently accepts images, videos and audio files. `), data_to_write: z - .object({}) - .passthrough() + .record(z.unknown()) .default({}) .describe(` A key/value map defining the metadata to write into the file. diff --git a/src/alphalib/types/robots/minio-import.ts b/packages/node/src/alphalib/types/robots/minio-import.ts similarity index 100% rename from src/alphalib/types/robots/minio-import.ts rename to packages/node/src/alphalib/types/robots/minio-import.ts diff --git a/src/alphalib/types/robots/minio-store.ts b/packages/node/src/alphalib/types/robots/minio-store.ts similarity index 100% rename from src/alphalib/types/robots/minio-store.ts rename to packages/node/src/alphalib/types/robots/minio-store.ts diff --git a/src/alphalib/types/robots/progress-simulate.ts b/packages/node/src/alphalib/types/robots/progress-simulate.ts similarity index 100% rename from src/alphalib/types/robots/progress-simulate.ts rename to packages/node/src/alphalib/types/robots/progress-simulate.ts diff --git a/src/alphalib/types/robots/s3-import.ts b/packages/node/src/alphalib/types/robots/s3-import.ts similarity index 100% rename from src/alphalib/types/robots/s3-import.ts rename to packages/node/src/alphalib/types/robots/s3-import.ts diff --git a/src/alphalib/types/robots/s3-store.ts b/packages/node/src/alphalib/types/robots/s3-store.ts similarity index 100% rename from src/alphalib/types/robots/s3-store.ts rename to packages/node/src/alphalib/types/robots/s3-store.ts diff --git a/src/alphalib/types/robots/script-run.ts b/packages/node/src/alphalib/types/robots/script-run.ts similarity index 100% rename from src/alphalib/types/robots/script-run.ts rename to packages/node/src/alphalib/types/robots/script-run.ts diff --git a/src/alphalib/types/robots/sftp-import.ts b/packages/node/src/alphalib/types/robots/sftp-import.ts similarity index 100% rename from src/alphalib/types/robots/sftp-import.ts rename to packages/node/src/alphalib/types/robots/sftp-import.ts diff --git a/src/alphalib/types/robots/sftp-store.ts b/packages/node/src/alphalib/types/robots/sftp-store.ts similarity index 100% rename from src/alphalib/types/robots/sftp-store.ts rename to packages/node/src/alphalib/types/robots/sftp-store.ts diff --git a/src/alphalib/types/robots/speech-transcribe.ts b/packages/node/src/alphalib/types/robots/speech-transcribe.ts similarity index 100% rename from src/alphalib/types/robots/speech-transcribe.ts rename to packages/node/src/alphalib/types/robots/speech-transcribe.ts diff --git a/src/alphalib/types/robots/supabase-import.ts b/packages/node/src/alphalib/types/robots/supabase-import.ts similarity index 100% rename from src/alphalib/types/robots/supabase-import.ts rename to packages/node/src/alphalib/types/robots/supabase-import.ts diff --git a/src/alphalib/types/robots/supabase-store.ts b/packages/node/src/alphalib/types/robots/supabase-store.ts similarity index 100% rename from src/alphalib/types/robots/supabase-store.ts rename to packages/node/src/alphalib/types/robots/supabase-store.ts diff --git a/src/alphalib/types/robots/swift-import.ts b/packages/node/src/alphalib/types/robots/swift-import.ts similarity index 100% rename from src/alphalib/types/robots/swift-import.ts rename to packages/node/src/alphalib/types/robots/swift-import.ts diff --git a/src/alphalib/types/robots/swift-store.ts b/packages/node/src/alphalib/types/robots/swift-store.ts similarity index 100% rename from src/alphalib/types/robots/swift-store.ts rename to packages/node/src/alphalib/types/robots/swift-store.ts diff --git a/src/alphalib/types/robots/text-speak.ts b/packages/node/src/alphalib/types/robots/text-speak.ts similarity index 100% rename from src/alphalib/types/robots/text-speak.ts rename to packages/node/src/alphalib/types/robots/text-speak.ts diff --git a/src/alphalib/types/robots/text-translate.ts b/packages/node/src/alphalib/types/robots/text-translate.ts similarity index 100% rename from src/alphalib/types/robots/text-translate.ts rename to packages/node/src/alphalib/types/robots/text-translate.ts diff --git a/src/alphalib/types/robots/tigris-import.ts b/packages/node/src/alphalib/types/robots/tigris-import.ts similarity index 100% rename from src/alphalib/types/robots/tigris-import.ts rename to packages/node/src/alphalib/types/robots/tigris-import.ts diff --git a/src/alphalib/types/robots/tigris-store.ts b/packages/node/src/alphalib/types/robots/tigris-store.ts similarity index 100% rename from src/alphalib/types/robots/tigris-store.ts rename to packages/node/src/alphalib/types/robots/tigris-store.ts diff --git a/src/alphalib/types/robots/tlcdn-deliver.ts b/packages/node/src/alphalib/types/robots/tlcdn-deliver.ts similarity index 100% rename from src/alphalib/types/robots/tlcdn-deliver.ts rename to packages/node/src/alphalib/types/robots/tlcdn-deliver.ts diff --git a/src/alphalib/types/robots/tus-store.ts b/packages/node/src/alphalib/types/robots/tus-store.ts similarity index 100% rename from src/alphalib/types/robots/tus-store.ts rename to packages/node/src/alphalib/types/robots/tus-store.ts diff --git a/src/alphalib/types/robots/upload-handle.ts b/packages/node/src/alphalib/types/robots/upload-handle.ts similarity index 100% rename from src/alphalib/types/robots/upload-handle.ts rename to packages/node/src/alphalib/types/robots/upload-handle.ts diff --git a/src/alphalib/types/robots/video-adaptive.ts b/packages/node/src/alphalib/types/robots/video-adaptive.ts similarity index 100% rename from src/alphalib/types/robots/video-adaptive.ts rename to packages/node/src/alphalib/types/robots/video-adaptive.ts diff --git a/src/alphalib/types/robots/video-concat.ts b/packages/node/src/alphalib/types/robots/video-concat.ts similarity index 100% rename from src/alphalib/types/robots/video-concat.ts rename to packages/node/src/alphalib/types/robots/video-concat.ts diff --git a/src/alphalib/types/robots/video-encode.ts b/packages/node/src/alphalib/types/robots/video-encode.ts similarity index 100% rename from src/alphalib/types/robots/video-encode.ts rename to packages/node/src/alphalib/types/robots/video-encode.ts diff --git a/src/alphalib/types/robots/video-merge.ts b/packages/node/src/alphalib/types/robots/video-merge.ts similarity index 100% rename from src/alphalib/types/robots/video-merge.ts rename to packages/node/src/alphalib/types/robots/video-merge.ts diff --git a/src/alphalib/types/robots/video-ondemand.ts b/packages/node/src/alphalib/types/robots/video-ondemand.ts similarity index 100% rename from src/alphalib/types/robots/video-ondemand.ts rename to packages/node/src/alphalib/types/robots/video-ondemand.ts diff --git a/src/alphalib/types/robots/video-subtitle.ts b/packages/node/src/alphalib/types/robots/video-subtitle.ts similarity index 100% rename from src/alphalib/types/robots/video-subtitle.ts rename to packages/node/src/alphalib/types/robots/video-subtitle.ts diff --git a/src/alphalib/types/robots/video-thumbs.ts b/packages/node/src/alphalib/types/robots/video-thumbs.ts similarity index 100% rename from src/alphalib/types/robots/video-thumbs.ts rename to packages/node/src/alphalib/types/robots/video-thumbs.ts diff --git a/src/alphalib/types/robots/vimeo-import.ts b/packages/node/src/alphalib/types/robots/vimeo-import.ts similarity index 100% rename from src/alphalib/types/robots/vimeo-import.ts rename to packages/node/src/alphalib/types/robots/vimeo-import.ts diff --git a/src/alphalib/types/robots/vimeo-store.ts b/packages/node/src/alphalib/types/robots/vimeo-store.ts similarity index 100% rename from src/alphalib/types/robots/vimeo-store.ts rename to packages/node/src/alphalib/types/robots/vimeo-store.ts diff --git a/src/alphalib/types/robots/wasabi-import.ts b/packages/node/src/alphalib/types/robots/wasabi-import.ts similarity index 100% rename from src/alphalib/types/robots/wasabi-import.ts rename to packages/node/src/alphalib/types/robots/wasabi-import.ts diff --git a/src/alphalib/types/robots/wasabi-store.ts b/packages/node/src/alphalib/types/robots/wasabi-store.ts similarity index 100% rename from src/alphalib/types/robots/wasabi-store.ts rename to packages/node/src/alphalib/types/robots/wasabi-store.ts diff --git a/src/alphalib/types/robots/youtube-store.ts b/packages/node/src/alphalib/types/robots/youtube-store.ts similarity index 100% rename from src/alphalib/types/robots/youtube-store.ts rename to packages/node/src/alphalib/types/robots/youtube-store.ts diff --git a/src/alphalib/types/stackVersions.ts b/packages/node/src/alphalib/types/stackVersions.ts similarity index 100% rename from src/alphalib/types/stackVersions.ts rename to packages/node/src/alphalib/types/stackVersions.ts diff --git a/src/alphalib/types/template.ts b/packages/node/src/alphalib/types/template.ts similarity index 100% rename from src/alphalib/types/template.ts rename to packages/node/src/alphalib/types/template.ts diff --git a/src/alphalib/types/templateCredential.ts b/packages/node/src/alphalib/types/templateCredential.ts similarity index 100% rename from src/alphalib/types/templateCredential.ts rename to packages/node/src/alphalib/types/templateCredential.ts diff --git a/src/alphalib/zodParseWithContext.ts b/packages/node/src/alphalib/zodParseWithContext.ts similarity index 100% rename from src/alphalib/zodParseWithContext.ts rename to packages/node/src/alphalib/zodParseWithContext.ts diff --git a/src/apiTypes.ts b/packages/node/src/apiTypes.ts similarity index 100% rename from src/apiTypes.ts rename to packages/node/src/apiTypes.ts diff --git a/src/cli.ts b/packages/node/src/cli.ts similarity index 100% rename from src/cli.ts rename to packages/node/src/cli.ts diff --git a/src/cli/OutputCtl.ts b/packages/node/src/cli/OutputCtl.ts similarity index 100% rename from src/cli/OutputCtl.ts rename to packages/node/src/cli/OutputCtl.ts diff --git a/src/cli/commands/BaseCommand.ts b/packages/node/src/cli/commands/BaseCommand.ts similarity index 100% rename from src/cli/commands/BaseCommand.ts rename to packages/node/src/cli/commands/BaseCommand.ts diff --git a/src/cli/commands/assemblies.ts b/packages/node/src/cli/commands/assemblies.ts similarity index 100% rename from src/cli/commands/assemblies.ts rename to packages/node/src/cli/commands/assemblies.ts diff --git a/src/cli/commands/auth.ts b/packages/node/src/cli/commands/auth.ts similarity index 100% rename from src/cli/commands/auth.ts rename to packages/node/src/cli/commands/auth.ts diff --git a/src/cli/commands/bills.ts b/packages/node/src/cli/commands/bills.ts similarity index 100% rename from src/cli/commands/bills.ts rename to packages/node/src/cli/commands/bills.ts diff --git a/src/cli/commands/index.ts b/packages/node/src/cli/commands/index.ts similarity index 100% rename from src/cli/commands/index.ts rename to packages/node/src/cli/commands/index.ts diff --git a/src/cli/commands/notifications.ts b/packages/node/src/cli/commands/notifications.ts similarity index 100% rename from src/cli/commands/notifications.ts rename to packages/node/src/cli/commands/notifications.ts diff --git a/src/cli/commands/templates.ts b/packages/node/src/cli/commands/templates.ts similarity index 100% rename from src/cli/commands/templates.ts rename to packages/node/src/cli/commands/templates.ts diff --git a/src/cli/helpers.ts b/packages/node/src/cli/helpers.ts similarity index 100% rename from src/cli/helpers.ts rename to packages/node/src/cli/helpers.ts diff --git a/src/cli/template-last-modified.ts b/packages/node/src/cli/template-last-modified.ts similarity index 100% rename from src/cli/template-last-modified.ts rename to packages/node/src/cli/template-last-modified.ts diff --git a/src/cli/types.ts b/packages/node/src/cli/types.ts similarity index 100% rename from src/cli/types.ts rename to packages/node/src/cli/types.ts diff --git a/src/tus.ts b/packages/node/src/tus.ts similarity index 100% rename from src/tus.ts rename to packages/node/src/tus.ts diff --git a/test/e2e/cli/OutputCtl.ts b/packages/node/test/e2e/cli/OutputCtl.ts similarity index 100% rename from test/e2e/cli/OutputCtl.ts rename to packages/node/test/e2e/cli/OutputCtl.ts diff --git a/test/e2e/cli/assemblies-create.test.ts b/packages/node/test/e2e/cli/assemblies-create.test.ts similarity index 100% rename from test/e2e/cli/assemblies-create.test.ts rename to packages/node/test/e2e/cli/assemblies-create.test.ts diff --git a/test/e2e/cli/assemblies-list.test.ts b/packages/node/test/e2e/cli/assemblies-list.test.ts similarity index 100% rename from test/e2e/cli/assemblies-list.test.ts rename to packages/node/test/e2e/cli/assemblies-list.test.ts diff --git a/test/e2e/cli/assemblies.test.ts b/packages/node/test/e2e/cli/assemblies.test.ts similarity index 100% rename from test/e2e/cli/assemblies.test.ts rename to packages/node/test/e2e/cli/assemblies.test.ts diff --git a/test/e2e/cli/bills.test.ts b/packages/node/test/e2e/cli/bills.test.ts similarity index 100% rename from test/e2e/cli/bills.test.ts rename to packages/node/test/e2e/cli/bills.test.ts diff --git a/test/e2e/cli/cli.test.ts b/packages/node/test/e2e/cli/cli.test.ts similarity index 100% rename from test/e2e/cli/cli.test.ts rename to packages/node/test/e2e/cli/cli.test.ts diff --git a/test/e2e/cli/templates.test.ts b/packages/node/test/e2e/cli/templates.test.ts similarity index 100% rename from test/e2e/cli/templates.test.ts rename to packages/node/test/e2e/cli/templates.test.ts diff --git a/test/e2e/cli/test-utils.ts b/packages/node/test/e2e/cli/test-utils.ts similarity index 100% rename from test/e2e/cli/test-utils.ts rename to packages/node/test/e2e/cli/test-utils.ts diff --git a/test/e2e/fixtures/zerobytes.jpg b/packages/node/test/e2e/fixtures/zerobytes.jpg similarity index 100% rename from test/e2e/fixtures/zerobytes.jpg rename to packages/node/test/e2e/fixtures/zerobytes.jpg diff --git a/test/e2e/live-api.test.ts b/packages/node/test/e2e/live-api.test.ts similarity index 100% rename from test/e2e/live-api.test.ts rename to packages/node/test/e2e/live-api.test.ts diff --git a/test/generate-coverage-badge.ts b/packages/node/test/generate-coverage-badge.ts similarity index 100% rename from test/generate-coverage-badge.ts rename to packages/node/test/generate-coverage-badge.ts diff --git a/test/testserver.ts b/packages/node/test/testserver.ts similarity index 100% rename from test/testserver.ts rename to packages/node/test/testserver.ts diff --git a/test/tunnel.ts b/packages/node/test/tunnel.ts similarity index 100% rename from test/tunnel.ts rename to packages/node/test/tunnel.ts diff --git a/test/unit/cli/test-cli.test.ts b/packages/node/test/unit/cli/test-cli.test.ts similarity index 100% rename from test/unit/cli/test-cli.test.ts rename to packages/node/test/unit/cli/test-cli.test.ts diff --git a/test/unit/mock-http.test.ts b/packages/node/test/unit/mock-http.test.ts similarity index 100% rename from test/unit/mock-http.test.ts rename to packages/node/test/unit/mock-http.test.ts diff --git a/test/unit/test-pagination-stream.test.ts b/packages/node/test/unit/test-pagination-stream.test.ts similarity index 100% rename from test/unit/test-pagination-stream.test.ts rename to packages/node/test/unit/test-pagination-stream.test.ts diff --git a/test/unit/test-transloadit-client.test.ts b/packages/node/test/unit/test-transloadit-client.test.ts similarity index 100% rename from test/unit/test-transloadit-client.test.ts rename to packages/node/test/unit/test-transloadit-client.test.ts diff --git a/test/unit/transloadit-advanced.test.ts b/packages/node/test/unit/transloadit-advanced.test.ts similarity index 100% rename from test/unit/transloadit-advanced.test.ts rename to packages/node/test/unit/transloadit-advanced.test.ts diff --git a/test/unit/tus.test.ts b/packages/node/test/unit/tus.test.ts similarity index 100% rename from test/unit/tus.test.ts rename to packages/node/test/unit/tus.test.ts diff --git a/test/util.ts b/packages/node/test/util.ts similarity index 100% rename from test/util.ts rename to packages/node/test/util.ts diff --git a/tsconfig.build.json b/packages/node/tsconfig.build.json similarity index 100% rename from tsconfig.build.json rename to packages/node/tsconfig.build.json diff --git a/packages/node/tsconfig.json b/packages/node/tsconfig.json new file mode 100644 index 00000000..0520d979 --- /dev/null +++ b/packages/node/tsconfig.json @@ -0,0 +1,16 @@ +{ + "exclude": ["dist", "src", "coverage", "examples"], + "references": [{ "path": "./tsconfig.build.json" }], + "compilerOptions": { + "checkJs": true, + "erasableSyntaxOnly": true, + "isolatedModules": true, + "module": "NodeNext", + "allowImportingTsExtensions": true, + "noImplicitOverride": true, + "noEmit": true, + "resolveJsonModule": true, + "strict": true, + "types": ["vitest/globals"] + } +} diff --git a/vitest.config.ts b/packages/node/vitest.config.ts similarity index 100% rename from vitest.config.ts rename to packages/node/vitest.config.ts diff --git a/packages/transloadit/LICENSE b/packages/transloadit/LICENSE new file mode 100644 index 00000000..6a259963 --- /dev/null +++ b/packages/transloadit/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Tim Koschuetzki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/transloadit/README.md b/packages/transloadit/README.md new file mode 100644 index 00000000..c041b465 --- /dev/null +++ b/packages/transloadit/README.md @@ -0,0 +1,698 @@ +[![Build Status](https://github.com/transloadit/node-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/transloadit/node-sdk/actions/workflows/ci.yml) +[![Coverage](https://codecov.io/gh/transloadit/node-sdk/branch/main/graph/badge.svg)](https://codecov.io/gh/transloadit/node-sdk) + + + + + + Transloadit Logo + + + +This is the official **Node.js** SDK for [Transloadit](https://transloadit.com)'s file uploading and encoding service. + +## Intro + +[Transloadit](https://transloadit.com) is a service that helps you handle file +uploads, resize, crop and watermark your images, make GIFs, transcode your +videos, extract thumbnails, generate audio waveforms, [and so much more](https://transloadit.com/demos/). In +short, [Transloadit](https://transloadit.com) is the Swiss Army Knife for your +files. + +This is a **Node.js** SDK to make it easy to talk to the +[Transloadit](https://transloadit.com) REST API. + +## Requirements + +- [Node.js](https://nodejs.org/en/) version 20 or newer +- [A Transloadit account](https://transloadit.com/signup/) ([free signup](https://transloadit.com/pricing/)) +- [Your API credentials](https://transloadit.com/c/template-credentials) (`authKey`, `authSecret`) + +## Install + +Inside your project, type: + +```bash +yarn add transloadit +``` + +or + +```bash +npm install --save transloadit +``` + +## Command Line Interface (CLI) + +This package includes a full-featured CLI for interacting with Transloadit from your terminal. + +### Quick Start + +```bash +# Set your credentials +export TRANSLOADIT_KEY="YOUR_TRANSLOADIT_KEY" +export TRANSLOADIT_SECRET="YOUR_TRANSLOADIT_SECRET" + +# See all available commands +npx transloadit --help +``` + +### Processing Media + +Create Assemblies to process files using Assembly Instructions (steps) or Templates: + +```bash +# Process a file using a steps file +npx transloadit assemblies create --steps steps.json --input image.jpg --output result.jpg + +# Process using a Template +npx transloadit assemblies create --template YOUR_TEMPLATE_ID --input image.jpg --output result.jpg + +# Process with custom fields +npx transloadit assemblies create --template YOUR_TEMPLATE_ID --field size=100 --input image.jpg --output thumb.jpg + +# Process a directory of files +npx transloadit assemblies create --template YOUR_TEMPLATE_ID --input images/ --output thumbs/ + +# Process recursively with file watching +npx transloadit assemblies create --template YOUR_TEMPLATE_ID --input images/ --output thumbs/ --recursive --watch + +# Process multiple files in a single assembly +npx transloadit assemblies create --template YOUR_TEMPLATE_ID --input file1.jpg --input file2.jpg --output results/ --single-assembly + +# Limit concurrent processing (default: 5) +npx transloadit assemblies create --template YOUR_TEMPLATE_ID --input images/ --output thumbs/ --concurrency 2 +``` + +### Managing Assemblies + +```bash +# List recent assemblies +npx transloadit assemblies list + +# List assemblies with filters +npx transloadit assemblies list --after 2024-01-01 --before 2024-12-31 + +# Get assembly status +npx transloadit assemblies get ASSEMBLY_ID + +# Cancel an assembly +npx transloadit assemblies delete ASSEMBLY_ID + +# Replay an assembly (re-run with original instructions) +npx transloadit assemblies replay ASSEMBLY_ID + +# Replay with different steps +npx transloadit assemblies replay --steps new-steps.json ASSEMBLY_ID + +# Replay using latest template version +npx transloadit assemblies replay --reparse-template ASSEMBLY_ID +``` + +### Managing Templates + +```bash +# List all templates +npx transloadit templates list + +# Get template content +npx transloadit templates get TEMPLATE_ID + +# Create a template from a JSON file +npx transloadit templates create my-template template.json + +# Modify a template +npx transloadit templates modify TEMPLATE_ID template.json + +# Rename a template +npx transloadit templates modify TEMPLATE_ID --name new-name + +# Delete a template +npx transloadit templates delete TEMPLATE_ID + +# Sync local template files with Transloadit (bidirectional) +npx transloadit templates sync templates/*.json +npx transloadit templates sync --recursive templates/ +``` + +### Billing + +```bash +# Get bill for a month +npx transloadit bills get 2024-01 + +# Get detailed bill as JSON +npx transloadit bills get 2024-01 --json +``` + +### Assembly Notifications + +```bash +# Replay a notification +npx transloadit assembly-notifications replay ASSEMBLY_ID + +# Replay to a different URL +npx transloadit assembly-notifications replay --notify-url https://example.com/hook ASSEMBLY_ID +``` + +### Signature Generation + +```bash +# Generate a signature for assembly params +echo '{"steps":{}}' | npx transloadit auth signature + +# Generate with specific algorithm +echo '{"steps":{}}' | npx transloadit auth signature --algorithm sha256 + +# Generate a signed Smart CDN URL +echo '{"workspace":"my-workspace","template":"my-template","input":"image.jpg"}' | npx transloadit auth smart-cdn +``` + +### CLI Options + +All commands support these common options: + +- `--json, -j` - Output results as JSON (useful for scripting) +- `--log-level, -l` - Set log verbosity level by name or number (default: notice) +- `--endpoint` - Custom API endpoint URL (or set `TRANSLOADIT_ENDPOINT` env var) +- `--help, -h` - Show help for a command + +The `assemblies create` command additionally supports: + +- `--single-assembly` - Pass all input files to a single assembly instead of one assembly per file + +#### Log Levels + +The CLI uses [syslog severity levels](https://en.wikipedia.org/wiki/Syslog#Severity_level). Lower = more severe, higher = more verbose: + +| Level | Value | Description | +| -------- | ----- | ------------------------------------- | +| `err` | 3 | Error conditions | +| `warn` | 4 | Warning conditions | +| `notice` | 5 | Normal but significant **(default)** | +| `info` | 6 | Informational messages | +| `debug` | 7 | Debug-level messages | +| `trace` | 8 | Most verbose/detailed | + +You can use either the level name or its numeric value: + +```bash +# Show only errors and warnings +npx transloadit assemblies list -l warn +npx transloadit assemblies list -l 4 + +# Show debug output +npx transloadit assemblies list -l debug +npx transloadit assemblies list -l 7 +``` + +## SDK Usage + +The following code will upload an image and resize it to a thumbnail: + +```javascript +import { Transloadit } from 'transloadit' + +const transloadit = new Transloadit({ + authKey: 'YOUR_TRANSLOADIT_KEY', + authSecret: 'YOUR_TRANSLOADIT_SECRET', +}) + +try { + const options = { + files: { + file1: '/PATH/TO/FILE.jpg', + }, + params: { + steps: { + // You can have many Steps. In this case we will just resize any inputs (:original) + resize: { + use: ':original', + robot: '/image/resize', + result: true, + width: 75, + height: 75, + }, + }, + // OR if you already created a template, you can use it instead of "steps": + // template_id: 'YOUR_TEMPLATE_ID', + }, + waitForCompletion: true, // Wait for the Assembly (job) to finish executing before returning + } + + const status = await transloadit.createAssembly(options) + + if (status.results.resize) { + console.log('✅ Success - Your resized image:', status.results.resize[0].ssl_url) + } else { + console.log("❌ The Assembly didn't produce any output. Make sure you used a valid image file") + } +} catch (err) { + console.error('❌ Unable to process Assembly.', err) + if (err instanceof ApiError && err.assemblyId) { + console.error(`💡 More info: https://transloadit.com/assemblies/${err.assemblyId}`) + } +} +``` + +You can find [details about your executed Assemblies here](https://transloadit.com/assemblies). + +## Examples + +- [Upload and resize image](https://github.com/transloadit/node-sdk/blob/main/examples/resize_an_image.ts) +- [Upload image and convert to WebP](https://github.com/transloadit/node-sdk/blob/main/examples/convert_to_webp.ts) +- [Rasterize SVG to PNG](https://github.com/transloadit/node-sdk/blob/main/examples/rasterize_svg_to_png.ts) +- [Crop a face out of an image and download the result](https://github.com/transloadit/node-sdk/blob/main/examples/face_detect_download.ts) +- [Retry example](https://github.com/transloadit/node-sdk/blob/main/examples/retry.ts) +- [Calculate total costs (GB usage)](https://github.com/transloadit/node-sdk/blob/main/examples/fetch_costs_of_all_assemblies_in_timeframe.ts) +- [Templates CRUD](https://github.com/transloadit/node-sdk/blob/main/examples/template_api.ts) +- [Template Credentials CRUD](https://github.com/transloadit/node-sdk/blob/main/examples/credentials.ts) + +For more fully working examples take a look at [`examples/`](https://github.com/transloadit/node-sdk/blob/main/examples/). + +For more example use cases and information about the available robots and their parameters, check out the [Transloadit website](https://transloadit.com/). + +## API + +These are the public methods on the `Transloadit` object and their descriptions. The methods are based on the [Transloadit API](https://transloadit.com/docs/api/). + +Table of contents: + +- [Main](#main) +- [Assemblies](#assemblies) +- [Assembly notifications](#assembly-notifications) +- [Templates](#templates) +- [Template Credentials](#template-credentials) +- [Errors](#errors) +- [Rate limiting & auto retry](#rate-limiting--auto-retry) + +### Main + +#### constructor(options) + +Returns a new instance of the client. + +The `options` object can contain the following keys: + +- `authKey` **(required)** - see [requirements](#requirements) +- `authSecret` **(required)** - see [requirements](#requirements) +- `endpoint` (default `'https://api2.transloadit.com'`) +- `maxRetries` (default `5`) - see [Rate limiting & auto retry](#rate-limiting--auto-retry) +- `gotRetry` (default `0`) - see [Rate limiting & auto retry](#rate-limiting--auto-retry) +- `timeout` (default `60000`: 1 minute) - the timeout (in milliseconds) for all requests (except `createAssembly`) +- `validateResponses` (default `false`) + +### Assemblies + +#### async createAssembly(options) + +Creates a new Assembly on Transloadit and optionally upload the specified `files` and `uploads`. + +You can provide the following keys inside the `options` object: + +- `params` **(required)** - An object containing keys defining the Assembly's behavior with the following keys: (See also [API doc](https://transloadit.com/docs/api/assemblies-post/) and [examples](#examples)) + - `steps` - Assembly instructions - See [Transloadit docs](https://transloadit.com/docs/topics/assembly-instructions/) and [demos](https://transloadit.com/demos/) for inspiration. + - `template_id` - The ID of the Template that contains your Assembly Instructions. **One of either `steps` or `template_id` is required.** If you specify both, then [any Steps will overrule the template](https://transloadit.com/docs/topics/templates/#overruling-templates-at-runtime). + - `fields` - An object of form fields to add to the request, to make use of in the Assembly instructions via [Assembly variables](https://transloadit.com/docs#assembly-variables). + - `notify_url` - Transloadit can send a Pingback to your server when the Assembly is completed. We'll send the Assembly Status in JSON encoded string inside a transloadit field in a multipart POST request to the URL supplied here. +- `files` - An object (key-value pairs) containing one or more file paths to upload and use in your Assembly. The _key_ is the _field name_ and the _value_ is the path to the file to be uploaded. The _field name_ and the file's name may be used in the ([Assembly instructions](https://transloadit.com/docs/topics/assembly-instructions/)) (`params`.`steps`) to refer to the particular file. See example below. + - `'fieldName': '/path/to/file'` + - more files... +- `uploads` - An object (key-value pairs) containing one or more files to upload and use in your Assembly. The _key_ is the _file name_ and the _value_ is the _content_ of the file to be uploaded. _Value_ can be one of many types: + - `'fieldName': (Readable | Buffer | TypedArray | ArrayBuffer | string | Iterable | AsyncIterable | Promise)`. + - more uploads... +- `waitForCompletion` - A boolean (default is `false`) to indicate whether you want to wait for the Assembly to finish with all encoding results present before the promise is fulfilled. If `waitForCompletion` is `true`, this SDK will poll for status updates and fulfill the promise when all encoding work is done. +- `timeout` - Number of milliseconds to wait before aborting (default `86400000`: 24 hours). +- `onUploadProgress` - An optional function that will be periodically called with the file upload progress, which is an with an object containing: + - `uploadedBytes` - Number of bytes uploaded so far. + - `totalBytes` - Total number of bytes to upload or `undefined` if unknown (Streams). +- `onAssemblyProgress` - Once the Assembly has started processing this will be periodically called with the _Assembly Execution Status_ (result of `getAssembly`) **only if `waitForCompletion` is `true`**. +- `chunkSize` - (for uploads) a number indicating the maximum size of a tus `PATCH` request body in bytes. Default to `Infinity` for file uploads and 50MB for streams of unknown length. See [tus-js-client](https://github.com/tus/tus-js-client/blob/master/docs/api.md#chunksize). +- `uploadConcurrency` - Maximum number of concurrent tus file uploads to occur at any given time (default 10.) + +**NOTE**: Make sure the key in `files` and `uploads` is not one of `signature`, `params` or `max_size`. + +Example code showing all options: + +```js +await transloadit.createAssembly({ + files: { + file1: '/path/to/file.jpg' + // ... + }, + uploads: { + 'file2.bin': Buffer.from([0, 0, 7]), // A buffer + 'file3.txt': 'file contents', // A string + 'file4.jpg': process.stdin // A stream + // ... + }, + params: { + steps: { ... }, + template_id: 'MY_TEMPLATE_ID', + fields: { + field1: 'Field value', + // ... + }, + notify_url: 'https://example.com/notify-url', + }, + waitForCompletion: true, + timeout: 60000, + onUploadProgress, + onAssemblyProgress, +}) +``` + +Example `onUploadProgress` and `onAssemblyProgress` handlers: + +```javascript +function onUploadProgress({ uploadedBytes, totalBytes }) { + // NOTE: totalBytes may be undefined + console.log(`♻️ Upload progress polled: ${uploadedBytes} of ${totalBytes} bytes uploaded.`) +} +function onAssemblyProgress(assembly) { + console.log( + `♻️ Assembly progress polled: ${assembly.error ? assembly.error : assembly.ok} ${ + assembly.assembly_id + } ... ` + ) +} +``` + +**Tip:** `createAssembly` returns a `Promise` with an extra property `assemblyId`. This can be used to retrieve the Assembly ID before the Assembly has even been created. Useful for debugging by logging this ID when the request starts, for example: + +```js +const promise = transloadit.createAssembly(options) +console.log('Creating', promise.assemblyId) +const status = await promise +``` + +See also: + +- [API documentation](https://transloadit.com/docs/api/assemblies-post/) +- Error codes and retry logic below + +#### async listAssemblies(params) + +Retrieve Assemblies according to the given `params`. + +Valid params can be `page`, `pagesize`, `type`, `fromdate`, `todate` and `keywords`. Please consult the [API documentation](https://transloadit.com/docs/api/assemblies-get/) for details. + +The method returns an object containing these properties: + +- `items`: An `Array` of up to `pagesize` Assemblies +- `count`: Total number of Assemblies + +#### streamAssemblies(params) + +Creates an `objectMode` `Readable` stream that automates handling of `listAssemblies` pagination. It accepts the same `params` as `listAssemblies`. + +This can be used to iterate through Assemblies: + +```javascript +const assemblyStream = transloadit.streamAssemblies({ fromdate: '2016-08-19 01:15:00 UTC' }) + +assemblyStream.on('readable', function () { + const assembly = assemblyStream.read() + if (assembly == null) console.log('end of stream') + + console.log(assembly.id) +}) +``` + +Results can also be piped. Here's an example using +[through2](https://github.com/rvagg/through2): + +```javascript +const assemblyStream = transloadit.streamAssemblies({ fromdate: '2016-08-19 01:15:00 UTC' }) + +assemblyStream + .pipe( + through.obj(function (chunk, enc, callback) { + this.push(chunk.id + '\n') + callback() + }) + ) + .pipe(fs.createWriteStream('assemblies.txt')) +``` + +#### async getAssembly(assemblyId) + +Retrieves the JSON status of the Assembly identified by the given `assemblyId`. See [API documentation](https://transloadit.com/docs/api/assemblies-assembly-id-get/). + +#### async cancelAssembly(assemblyId) + +Removes the Assembly identified by the given `assemblyId` from the memory of the Transloadit machines, ultimately cancelling it. This does not delete the Assembly from the database - you can still access it on `https://transloadit.com/assemblies/{assembly_id}` in your Transloadit account. This also does not delete any files associated with the Assembly from the Transloadit servers. See [API documentation](https://transloadit.com/docs/api/assemblies-assembly-id-delete/). + +#### async replayAssembly(assemblyId, params) + +Replays the Assembly identified by the given `assemblyId` (required argument). Optionally you can also provide a `notify_url` key inside `params` if you want to change the notification target. See [API documentation](https://transloadit.com/docs/api/assemblies-assembly-id-replay-post/) for more info about `params`. + +The response from the `replayAssembly` is minimal and does not contain much information about the replayed assembly. Please call `getAssembly` or `awaitAssemblyCompletion` after replay to get more information: + +```js +const replayAssemblyResponse = await transloadit.replayAssembly(failedAssemblyId) + +const assembly = await transloadit.getAssembly(replayAssemblyResponse.assembly_id) +// Or +const completedAssembly = await transloadit.awaitAssemblyCompletion( + replayAssemblyResponse.assembly_id +) +``` + +#### async awaitAssemblyCompletion(assemblyId, opts) + +This function will continously poll the specified Assembly `assemblyId` and resolve when it is done uploading and executing (until `result.ok` is no longer `ASSEMBLY_UPLOADING`, `ASSEMBLY_EXECUTING` or `ASSEMBLY_REPLAYING`). It resolves with the same value as `getAssembly`. + +`opts` is an object with the keys: + +- `onAssemblyProgress` - A progress function called on each poll. See `createAssembly` +- `timeout` - How many milliseconds until polling times out (default: no timeout) +- `interval` - Poll interval in milliseconds (default `1000`) +- `signal` - An `AbortSignal` to cancel polling. When aborted, the promise rejects with an `AbortError`. +- `onPoll` - A callback invoked at the start of each poll iteration. Return `false` to stop polling early and resolve with the last known status. Useful for implementing custom cancellation logic (e.g., superseding assemblies in watch mode). + +#### getLastUsedAssemblyUrl() + +Returns the internal url that was used for the last call to `createAssembly`. This is meant to be used for debugging purposes. + +### Assembly notifications + +#### async replayAssemblyNotification(assemblyId, params) + +Replays the notification for the Assembly identified by the given `assemblyId` (required argument). Optionally you can also provide a `notify_url` key inside `params` if you want to change the notification target. See [API documentation](https://transloadit.com/docs/api/assembly-notifications-assembly-id-replay-post/) for more info about `params`. + +### Templates + +Templates are Steps that can be reused. [See example template code](examples/template_api.ts). + +#### async createTemplate(params) + +Creates a template the provided params. The required `params` keys are: + +- `name` - The template name +- `template` - The template JSON object containing its `steps` + +See also [API documentation](https://transloadit.com/docs/api/templates-post/). + +```js +const template = { + steps: { + encode: { + use: ':original', + robot: '/video/encode', + preset: 'ipad-high', + }, + thumbnail: { + use: 'encode', + robot: '/video/thumbnails', + }, + }, +} + +const result = await transloadit.createTemplate({ name: 'my-template-name', template }) +console.log('✅ Template created with template_id', result.id) +``` + +#### async editTemplate(templateId, params) + +Updates the template represented by the given `templateId` with the new value. The `params` works just like the one from the `createTemplate` call. See [API documentation](https://transloadit.com/docs/api/templates-template-id-put/). + +#### async getTemplate(templateId) + +Retrieves the name and the template JSON for the template represented by the given `templateId`. See [API documentation](https://transloadit.com/docs/api/templates-template-id-get/). + +#### async deleteTemplate(templateId) + +Deletes the template represented by the given `templateId`. See [API documentation](https://transloadit.com/docs/api/templates-template-id-delete/). + +#### async listTemplates(params) + +Retrieve all your templates. See [API documentation](https://transloadit.com/docs/api/templates-template-id-get/) for more info about `params`. + +The method returns an object containing these properties: + +- `items`: An `Array` of up to `pagesize` templates +- `count`: Total number of templates + +#### streamTemplates(params) + +Creates an `objectMode` `Readable` stream that automates handling of `listTemplates` pagination. Similar to `streamAssemblies`. + +### Template Credentials + +Template Credentials allow you to store third-party credentials (e.g., AWS S3, Google Cloud Storage, FTP) securely on Transloadit for use in your Assembly Instructions. + +#### async createTemplateCredential(params) + +Creates a new Template Credential. The `params` object should contain the credential configuration. See [API documentation](https://transloadit.com/docs/api/template-credentials-post/). + +#### async editTemplateCredential(credentialId, params) + +Updates an existing Template Credential identified by `credentialId`. See [API documentation](https://transloadit.com/docs/api/template-credentials-credential-id-put/). + +#### async deleteTemplateCredential(credentialId) + +Deletes the Template Credential identified by `credentialId`. See [API documentation](https://transloadit.com/docs/api/template-credentials-credential-id-delete/). + +#### async getTemplateCredential(credentialId) + +Retrieves the Template Credential identified by `credentialId`. See [API documentation](https://transloadit.com/docs/api/template-credentials-credential-id-get/). + +#### async listTemplateCredentials(params) + +Lists all Template Credentials. See [API documentation](https://transloadit.com/docs/api/template-credentials-get/). + +#### streamTemplateCredentials(params) + +Creates an `objectMode` `Readable` stream that automates handling of `listTemplateCredentials` pagination. Similar to `streamAssemblies`. + +### Other + +#### setDefaultTimeout(timeout) + +Same as `constructor` `timeout` option: Set the default timeout (in milliseconds) for all requests (except `createAssembly`) + +#### async getBill(date) + +Retrieves the billing data for a given `date` string with format `YYYY-MM`. See [API documentation](https://transloadit.com/docs/api/bill-date-get/). + +#### calcSignature(params) + +Calculates a signature for the given `params` JSON object. If the `params` object does not include an `authKey` or `expires` keys (and their values) in the `auth` sub-key, then they are set automatically. + +This function returns an object with the key `signature` (containing the calculated signature string) and a key `params`, which contains the stringified version of the passed `params` object (including the set expires and authKey keys). + +See [Signature Generation](#signature-generation) in the CLI section for command-line usage. + +#### getSignedSmartCDNUrl(params) + +Constructs a signed Smart CDN URL, as defined in the [API documentation](https://transloadit.com/docs/topics/signature-authentication/#smart-cdn). `params` must be an object with the following properties: + +- `workspace` - Workspace slug (required) +- `template` - Template slug or template ID (required) +- `input` - Input value that is provided as `${fields.input}` in the template (required) +- `urlParams` - Object with additional parameters for the URL query string (optional) +- `expiresAt` - Expiration timestamp of the signature in milliseconds since UNIX epoch. Defaults to 1 hour from now. (optional) + +Example: + +```js +const client = new Transloadit({ authKey: 'foo_key', authSecret: 'foo_secret' }) +const url = client.getSignedSmartCDNUrl({ + workspace: 'foo_workspace', + template: 'foo_template', + input: 'foo_input', + urlParams: { + foo: 'bar', + }, +}) + +// url is: +// https://foo_workspace.tlcdn.com/foo_template/foo_input?auth_key=foo_key&exp=1714525200000&foo=bar&sig=sha256:9548915ec70a5f0d05de9497289e792201ceec19a526fe315f4f4fd2e7e377ac +``` + +### Errors + +Any errors originating from Node.js will be passed on and we use [GOT](https://github.com/sindresorhus/got) v11 for HTTP requests. [Errors from `got`](https://github.com/sindresorhus/got/tree/v11.8.6?tab=readme-ov-file#errors) will also be passed on, _except_ the `got.HTTPError` which will be replaced with a `transloadit.ApiError`, which will have its `cause` property set to the instance of the original `got.HTTPError`. `transloadit.ApiError` has these properties: + +- `code` (`string`) - [The Transloadit API error code](https://transloadit.com/docs/api/response-codes/#error-codes). +- `rawMessage` (`string`) - A textual representation of the Transloadit API error. +- `reason` (`string`) - Additional information about the Transloadit API error. +- `assemblyId`: (`string`) - If the request is related to an assembly, this will be the ID of the assembly. +- `assemblySslUrl` (`string`) - If the request is related to an assembly, this will be the SSL URL to the assembly . + +To identify errors you can either check its props or use `instanceof`, e.g.: + +```js +try { + await transloadit.createAssembly(options) +} catch (err) { + if (err instanceof got.TimeoutError) { + return console.error('The request timed out', err) + } + if (err.code === 'ENOENT') { + return console.error('Cannot open file', err) + } + if (err instanceof ApiError && err.code === 'ASSEMBLY_INVALID_STEPS') { + return console.error('Invalid Assembly Steps', err) + } +} +``` + +**Note:** Assemblies that have an error status (`assembly.error`) will only result in an error being thrown from `createAssembly` and `replayAssembly`. For other Assembly methods, no errors will be thrown, but any error can be found in the response's `error` property (also `ApiError.code`). + +- [More information on Transloadit errors (`ApiError.code`)](https://transloadit.com/docs/api/response-codes/#error-codes) +- [More information on request errors](https://github.com/sindresorhus/got#errors) + +### Rate limiting & auto retry + +There are three kinds of retries: + +#### Retry on rate limiting (`maxRetries`, default `5`) + +All functions of the client automatically obey all rate limiting imposed by Transloadit (e.g. `RATE_LIMIT_REACHED`), so there is no need to write your own wrapper scripts to handle rate limits. The SDK will by default retry requests **5 times** with auto back-off (See `maxRetries` constructor option). + +#### GOT HTTP retries (`gotRetry`, default `{ limit: 0 }`) + +Because we use [got](https://github.com/sindresorhus/got) under the hood, you can pass a `gotRetry` constructor option which is passed on to `got`. This offers great flexibility for handling retries on network errors and HTTP status codes with auto back-off. See [`got` `retry` object documentation](https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md). + +**Note that the above `maxRetries` option does not affect the `gotRetry` logic.** + +#### Validate API responses (`validateResponses`, default `false`) + +As we have ported the JavaScript SDK to TypeScript in v4, we are now also validating API responses using `zod` schemas. Having schema validation enabled (`true`), guarantees that the data returned by the SDK adheres to the TypeScript types of this SDK. However we are still working on improving the schemas and they are not yet 100% complete. This means that if you hit a bug in the schemas, a `zod` schema validation error will be thrown. If you encounter such an error, please report it and we will fix it as soon as possible. If you set this option to `false`, schema validation will be disabled, and you won't get any such errors, however the TypeScript types will not protect you should such a bug be encountered. + +#### Custom retry logic + +If you want to retry on other errors, please see the [retry example code](examples/retry.ts). + +- https://transloadit.com/docs/api/rate-limiting/ +- https://transloadit.com/blog/2012/04/introducing-rate-limiting/ + +## Debugging + +This project uses [debug](https://github.com/visionmedia/debug) so you can run node with the `DEBUG=transloadit` evironment variable to enable verbose logging. Example: + +```bash +DEBUG=transloadit* npx tsx examples/template_api.ts +``` + +## Maintainers + +- [Mikael Finstad](https://github.com/mifi) + +### Changelog + +See [Releases](https://github.com/transloadit/node-sdk/releases) + +## Attribution + +Thanks to [Ian Hansen](https://github.com/supershabam) for donating the `transloadit` npm name. You can still access his code under [`v0.0.0`](https://www.npmjs.com/package/transloadit/v/0.0.0). + +## License + +[MIT](LICENSE) © [Transloadit](https://transloadit.com) + +## Development + +See [CONTRIBUTING](./CONTRIBUTING.md). diff --git a/packages/transloadit/package.json b/packages/transloadit/package.json new file mode 100644 index 00000000..263c7949 --- /dev/null +++ b/packages/transloadit/package.json @@ -0,0 +1,95 @@ +{ + "name": "transloadit", + "version": "4.1.2", + "description": "Node.js SDK for Transloadit", + "type": "module", + "keywords": [ + "transloadit", + "encoding", + "transcoding", + "video", + "audio", + "mp3" + ], + "author": "Tim Koschuetzki ", + "packageManager": "yarn@4.12.0", + "engines": { + "node": ">= 20" + }, + "dependencies": { + "@aws-sdk/client-s3": "^3.891.0", + "@aws-sdk/s3-request-presigner": "^3.891.0", + "@transloadit/sev-logger": "^0.0.15", + "clipanion": "^4.0.0-rc.4", + "debug": "^4.4.3", + "dotenv": "^17.2.3", + "form-data": "^4.0.4", + "got": "14.4.9", + "into-stream": "^9.0.0", + "is-stream": "^4.0.1", + "node-watch": "^0.7.4", + "p-map": "^7.0.3", + "p-queue": "^9.0.1", + "recursive-readdir": "^2.2.3", + "tus-js-client": "^4.3.1", + "typanion": "^3.14.0", + "type-fest": "^4.41.0", + "zod": "3.25.76" + }, + "devDependencies": { + "@biomejs/biome": "^2.2.4", + "@types/debug": "^4.1.12", + "@types/minimist": "^1.2.5", + "@types/node": "^24.10.3", + "@types/recursive-readdir": "^2.2.4", + "@types/temp": "^0.9.4", + "@vitest/coverage-v8": "^3.2.4", + "badge-maker": "^5.0.2", + "execa": "9.6.0", + "image-size": "^2.0.2", + "knip": "^5.73.3", + "minimatch": "^10.1.1", + "nock": "^14.0.10", + "npm-run-all": "^4.1.5", + "p-retry": "^7.0.0", + "rimraf": "^6.1.2", + "temp": "^0.9.4", + "tsx": "4.21.0", + "typescript": "5.9.3", + "vitest": "^3.2.4" + }, + "repository": { + "type": "git", + "url": "git://github.com/transloadit/node-sdk.git" + }, + "directories": { + "src": "./src" + }, + "scripts": { + "check": "yarn exec knip --fix --allow-remove-files --no-config-hints && yarn lint:ts && yarn fix && yarn test:unit", + "fix:js": "biome check --write .", + "lint:ts": "tsc --build", + "fix:js:unsafe": "biome check --write . --unsafe", + "lint:js": "biome check .", + "lint": "npm-run-all --parallel 'lint:js'", + "fix": "npm-run-all --serial 'fix:js'", + "lint:deps": "knip --dependencies --no-progress", + "fix:deps": "knip --dependencies --no-progress --fix", + "knip": "knip --no-config-hints --no-progress", + "prepack": "rm -f tsconfig.tsbuildinfo tsconfig.build.tsbuildinfo && tsc --build tsconfig.build.json", + "test:unit": "vitest run --coverage ./test/unit", + "test:e2e": "vitest run ./test/e2e", + "test": "vitest run --coverage" + }, + "license": "MIT", + "main": "./dist/Transloadit.js", + "exports": { + ".": "./dist/Transloadit.js", + "./package.json": "./package.json" + }, + "files": [ + "dist", + "src" + ], + "bin": "./dist/cli.js" +} diff --git a/packages/transloadit/src/ApiError.ts b/packages/transloadit/src/ApiError.ts new file mode 100644 index 00000000..9719a678 --- /dev/null +++ b/packages/transloadit/src/ApiError.ts @@ -0,0 +1,49 @@ +import type { RequestError } from 'got' +import { HTTPError } from 'got' + +export interface TransloaditErrorResponseBody { + error?: string + message?: string + reason?: string + assembly_ssl_url?: string + assembly_id?: string +} + +export class ApiError extends Error { + override name = 'ApiError' + + // there might not be an error code (or message) if the server didn't respond with any JSON response at all + // e.g. if there was a 500 in the HTTP reverse proxy + code?: string + + rawMessage?: string + + reason?: string + + assemblySslUrl?: string + + assemblyId?: string + + override cause?: RequestError | undefined + + constructor(params: { cause?: RequestError; body: TransloaditErrorResponseBody | undefined }) { + const { cause, body = {} } = params + + const parts = ['API error'] + if (cause instanceof HTTPError && cause?.response.statusCode) + parts.push(`(HTTP ${cause.response.statusCode})`) + if (body.error) parts.push(`${body.error}:`) + if (body.message) parts.push(body.message) + if (body.assembly_ssl_url) parts.push(body.assembly_ssl_url) + + const message = parts.join(' ') + + super(message) + this.rawMessage = body.message + this.reason = body.reason + this.assemblyId = body.assembly_id + this.assemblySslUrl = body.assembly_ssl_url + this.code = body.error + this.cause = cause + } +} diff --git a/packages/transloadit/src/InconsistentResponseError.ts b/packages/transloadit/src/InconsistentResponseError.ts new file mode 100644 index 00000000..455fe499 --- /dev/null +++ b/packages/transloadit/src/InconsistentResponseError.ts @@ -0,0 +1,3 @@ +export default class InconsistentResponseError extends Error { + override name = 'InconsistentResponseError' +} diff --git a/packages/transloadit/src/PaginationStream.ts b/packages/transloadit/src/PaginationStream.ts new file mode 100644 index 00000000..c0d463cc --- /dev/null +++ b/packages/transloadit/src/PaginationStream.ts @@ -0,0 +1,54 @@ +import { Readable } from 'node:stream' +import type { PaginationList, PaginationListWithCount } from './apiTypes.ts' + +type FetchPage = ( + pageno: number, +) => + | PaginationList + | PromiseLike> + | PaginationListWithCount + | PromiseLike> + +export default class PaginationStream extends Readable { + private _fetchPage: FetchPage + + private _nitems?: number + + private _pageno = 0 + + private _items: T[] = [] + + private _itemsRead = 0 + + constructor(fetchPage: FetchPage) { + super({ objectMode: true }) + this._fetchPage = fetchPage + } + + override async _read() { + if (this._items.length > 0) { + this._itemsRead++ + process.nextTick(() => this.push(this._items.pop())) + return + } + + if (this._nitems != null && this._itemsRead >= this._nitems) { + process.nextTick(() => this.push(null)) + return + } + + try { + const { items, ...rest } = await this._fetchPage(++this._pageno) + if ('count' in rest) { + this._nitems = rest.count + } + + this._items = Array.from(items) + this._items.reverse() + + this._read() + } catch (err) { + this.emit('error', err) + } + } +} diff --git a/packages/transloadit/src/PollingTimeoutError.ts b/packages/transloadit/src/PollingTimeoutError.ts new file mode 100644 index 00000000..e26cd9c9 --- /dev/null +++ b/packages/transloadit/src/PollingTimeoutError.ts @@ -0,0 +1,5 @@ +export default class PollingTimeoutError extends Error { + override name = 'PollingTimeoutError' + + code = 'POLLING_TIMED_OUT' +} diff --git a/packages/transloadit/src/Transloadit.ts b/packages/transloadit/src/Transloadit.ts new file mode 100644 index 00000000..a093675e --- /dev/null +++ b/packages/transloadit/src/Transloadit.ts @@ -0,0 +1,980 @@ +import * as assert from 'node:assert' +import { createHmac, randomUUID } from 'node:crypto' +import { constants, createReadStream } from 'node:fs' +import { access } from 'node:fs/promises' +import type { Readable } from 'node:stream' +import { setTimeout as delay } from 'node:timers/promises' +import debug from 'debug' +import FormData from 'form-data' +import type { Delays, Headers, OptionsOfJSONResponseBody, RetryOptions } from 'got' +import got, { HTTPError, RequestError } from 'got' +import intoStream, { type Input as IntoStreamInput } from 'into-stream' +import { isReadableStream, isStream } from 'is-stream' +import pMap from 'p-map' +import packageJson from '../package.json' with { type: 'json' } +import type { TransloaditErrorResponseBody } from './ApiError.ts' +import { ApiError } from './ApiError.ts' +import type { + AssemblyIndex, + AssemblyIndexItem, + AssemblyStatus, +} from './alphalib/types/assemblyStatus.ts' +import { assemblyIndexSchema, assemblyStatusSchema } from './alphalib/types/assemblyStatus.ts' +import { zodParseWithContext } from './alphalib/zodParseWithContext.ts' +import type { + BaseResponse, + BillResponse, + CreateAssemblyParams, + CreateTemplateCredentialParams, + CreateTemplateParams, + EditTemplateParams, + ListAssembliesParams, + ListedTemplate, + ListTemplateCredentialsParams, + ListTemplatesParams, + OptionalAuthParams, + PaginationListWithCount, + ReplayAssemblyNotificationParams, + ReplayAssemblyNotificationResponse, + ReplayAssemblyParams, + ReplayAssemblyResponse, + TemplateCredentialResponse, + TemplateCredentialsResponse, + TemplateResponse, +} from './apiTypes.ts' +import InconsistentResponseError from './InconsistentResponseError.ts' +import PaginationStream from './PaginationStream.ts' +import PollingTimeoutError from './PollingTimeoutError.ts' +import type { Stream } from './tus.ts' +import { sendTusRequest } from './tus.ts' + +// See https://github.com/sindresorhus/got/tree/v11.8.6?tab=readme-ov-file#errors +// Expose relevant errors +export { + HTTPError, + MaxRedirectsError, + ParseError, + ReadError, + RequestError, + TimeoutError, + UploadError, +} from 'got' + +export type { AssemblyStatus } from './alphalib/types/assemblyStatus.ts' +export * from './apiTypes.ts' +export { InconsistentResponseError, ApiError } + +const log = debug('transloadit') +const logWarn = debug('transloadit:warn') + +export interface UploadProgress { + uploadedBytes?: number | undefined + totalBytes?: number | undefined +} + +const { version } = packageJson + +export type AssemblyProgress = (assembly: AssemblyStatus) => void + +export interface CreateAssemblyOptions { + params?: CreateAssemblyParams + files?: { + [name: string]: string + } + uploads?: { + [name: string]: Readable | IntoStreamInput + } + waitForCompletion?: boolean + chunkSize?: number + uploadConcurrency?: number + timeout?: number + onUploadProgress?: (uploadProgress: UploadProgress) => void + onAssemblyProgress?: AssemblyProgress + assemblyId?: string + /** + * Optional AbortSignal to cancel the assembly creation and upload. + * When aborted, any in-flight HTTP requests and TUS uploads will be cancelled. + */ + signal?: AbortSignal +} + +export interface AwaitAssemblyCompletionOptions { + onAssemblyProgress?: AssemblyProgress + timeout?: number + interval?: number + startTimeMs?: number + /** + * Optional AbortSignal to cancel polling. + * When aborted, the polling loop will stop and throw an AbortError. + */ + signal?: AbortSignal + /** + * Optional callback invoked before each poll iteration. + * Return `false` to stop polling early and return the current assembly status. + * Useful for watch mode where a newer job may supersede the current one. + */ + onPoll?: () => boolean | undefined +} + +export interface SmartCDNUrlOptions { + /** + * Workspace slug + */ + workspace: string + /** + * Template slug or template ID + */ + template: string + /** + * Input value that is provided as `${fields.input}` in the template + */ + input: string + /** + * Additional parameters for the URL query string + */ + urlParams?: Record + /** + * Expiration timestamp of the signature in milliseconds since UNIX epoch. + * Defaults to 1 hour from now. + */ + expiresAt?: number +} + +export type Fields = Record + +// A special promise that lets the user immediately get the assembly ID (synchronously before the request is sent) +interface CreateAssemblyPromise extends Promise { + assemblyId: string +} + +// Not sure if this is still a problem with the API, but throw a special error type so the user can retry if needed +function checkAssemblyUrls(result: AssemblyStatus) { + if (result.assembly_url == null || result.assembly_ssl_url == null) { + throw new InconsistentResponseError('Server returned an incomplete assembly response (no URL)') + } +} + +function getHrTimeMs(): number { + return Number(process.hrtime.bigint() / 1000000n) +} + +function checkResult(result: T | { error: string }): asserts result is T { + // In case server returned a successful HTTP status code, but an `error` in the JSON object + // This happens sometimes, for example when createAssembly with an invalid file (IMPORT_FILE_ERROR) + if ( + typeof result === 'object' && + result !== null && + 'error' in result && + typeof result.error === 'string' + ) { + throw new ApiError({ body: result }) // in this case there is no `cause` because we don't have an HTTPError + } +} + +export interface Options { + authKey: string + authSecret: string + endpoint?: string + maxRetries?: number + timeout?: number + gotRetry?: Partial + validateResponses?: boolean +} + +export class Transloadit { + private _authKey: string + + private _authSecret: string + + private _endpoint: string + + private _maxRetries: number + + private _defaultTimeout: number + + private _gotRetry: Partial + + private _lastUsedAssemblyUrl = '' + + private _validateResponses = false + + constructor(opts: Options) { + if (opts?.authKey == null) { + throw new Error('Please provide an authKey') + } + + if (opts.authSecret == null) { + throw new Error('Please provide an authSecret') + } + + if (opts.endpoint?.endsWith('/')) { + throw new Error('Trailing slash in endpoint is not allowed') + } + + this._authKey = opts.authKey + this._authSecret = opts.authSecret + this._endpoint = opts.endpoint || 'https://api2.transloadit.com' + this._maxRetries = opts.maxRetries != null ? opts.maxRetries : 5 + this._defaultTimeout = opts.timeout != null ? opts.timeout : 60000 + + // Passed on to got https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md + this._gotRetry = opts.gotRetry != null ? opts.gotRetry : { limit: 0 } + + if (opts.validateResponses != null) this._validateResponses = opts.validateResponses + } + + getLastUsedAssemblyUrl(): string { + return this._lastUsedAssemblyUrl + } + + setDefaultTimeout(timeout: number): void { + this._defaultTimeout = timeout + } + + /** + * Create an Assembly + * + * @param opts assembly options + */ + createAssembly(opts: CreateAssemblyOptions = {}): CreateAssemblyPromise { + const { + params = {}, + waitForCompletion = false, + chunkSize: requestedChunkSize = Number.POSITIVE_INFINITY, + uploadConcurrency = 10, + timeout = 24 * 60 * 60 * 1000, // 1 day + onUploadProgress = () => {}, + onAssemblyProgress = () => {}, + files = {}, + uploads = {}, + assemblyId, + signal, + } = opts + + // Keep track of how long the request took + const startTimeMs = getHrTimeMs() + + // Undocumented feature to allow specifying a custom assembly id from the client + // Not recommended for general use due to security. E.g if the user doesn't provide a cryptographically + // secure ID, then anyone could access the assembly. + let effectiveAssemblyId: string + if (assemblyId != null) { + effectiveAssemblyId = assemblyId + } else { + effectiveAssemblyId = randomUUID().replace(/-/g, '') + } + const urlSuffix = `/assemblies/${effectiveAssemblyId}` + + // We want to be able to return the promise immediately with custom data + const promise = (async () => { + this._lastUsedAssemblyUrl = `${this._endpoint}${urlSuffix}` + + await pMap( + Object.entries(files), + async ([, path]) => access(path, constants.F_OK | constants.R_OK), + { concurrency: 5 }, + ) + + // Convert uploads to streams + const streamsMap = Object.fromEntries( + Object.entries(uploads).map(([label, value]) => { + const isReadable = isReadableStream(value) + if (!isReadable && isStream(value)) { + // https://github.com/transloadit/node-sdk/issues/92 + throw new Error(`Upload named "${label}" is not a Readable stream`) + } + + return [label, isReadableStream(value) ? value : intoStream(value)] + }), + ) + + // Wrap in object structure (so we can store whether it's a pathless stream or not) + const allStreamsMap = Object.fromEntries( + Object.entries(streamsMap).map(([label, stream]) => [label, { stream }]), + ) + + // Create streams from files too + for (const [label, path] of Object.entries(files)) { + const stream = createReadStream(path) + allStreamsMap[label] = { stream, path } // File streams have path + } + + const allStreams = Object.values(allStreamsMap) + + // Pause all streams + for (const { stream } of allStreams) { + stream.pause() + } + + // If any stream emits error, we want to handle this and exit with error. + // This promise races against createAssemblyAndUpload() below via Promise.race(). + // When createAssemblyAndUpload wins the race, this promise becomes "orphaned" - + // it's no longer awaited, but stream error handlers remain attached. + // The no-op catch prevents Node's unhandled rejection warning if a stream + // errors after the race is already won. + const streamErrorPromise = new Promise((_resolve, reject) => { + for (const { stream } of allStreams) { + stream.on('error', reject) + } + }) + streamErrorPromise.catch(() => {}) + + const createAssemblyAndUpload = async () => { + const result: AssemblyStatus = await this._remoteJson({ + urlSuffix, + method: 'post', + timeout: { request: timeout }, + params, + fields: { + tus_num_expected_upload_files: allStreams.length, + }, + signal, + }) + checkResult(result) + + if (Object.keys(allStreamsMap).length > 0) { + await sendTusRequest({ + streamsMap: allStreamsMap, + assembly: result, + onProgress: onUploadProgress, + requestedChunkSize, + uploadConcurrency, + signal, + }) + } + + if (!waitForCompletion) return result + + if (result.assembly_id == null) { + throw new InconsistentResponseError( + 'Server returned an assembly response without an assembly_id after creation', + ) + } + const awaitResult = await this.awaitAssemblyCompletion(result.assembly_id, { + timeout, + onAssemblyProgress, + startTimeMs, + signal, + }) + checkResult(awaitResult) + return awaitResult + } + + return Promise.race([createAssemblyAndUpload(), streamErrorPromise]) + })() + + // This allows the user to use or log the assemblyId even before it has been created for easier debugging + return Object.assign(promise, { assemblyId: effectiveAssemblyId }) + } + + async awaitAssemblyCompletion( + assemblyId: string, + { + onAssemblyProgress = () => {}, + timeout, + startTimeMs = getHrTimeMs(), + interval = 1000, + signal, + onPoll, + }: AwaitAssemblyCompletionOptions = {}, + ): Promise { + assert.ok(assemblyId) + + let lastResult: AssemblyStatus | undefined + + while (true) { + // Check if caller wants to stop polling early + if (onPoll?.() === false && lastResult) { + return lastResult + } + + // Check if aborted before making the request + if (signal?.aborted) { + throw signal.reason ?? new DOMException('Aborted', 'AbortError') + } + + const result = await this.getAssembly(assemblyId, { signal }) + lastResult = result + + // If 'ok' is not in result, it implies a terminal state (e.g., error, completed, canceled). + // If 'ok' is present, then we check if it's one of the non-terminal polling states. + if ( + !('ok' in result) || + (result.ok !== 'ASSEMBLY_UPLOADING' && + result.ok !== 'ASSEMBLY_EXECUTING' && + // ASSEMBLY_REPLAYING is not a valid 'ok' status for polling, it means it's done replaying. + // The API does not seem to have an ASSEMBLY_REPLAYING status in the typical polling loop. + // It's usually a final status from the replay endpoint. + // For polling, we only care about UPLOADING and EXECUTING. + // If a replay operation puts it into a pollable state, that state would be EXECUTING. + result.ok !== 'ASSEMBLY_REPLAYING') // This line might need review based on actual API behavior for replayed assembly polling + ) { + return result // Done! + } + + try { + onAssemblyProgress(result) + } catch (err) { + log('Caught onAssemblyProgress error', err) + } + + const nowMs = getHrTimeMs() + if (timeout != null && nowMs - startTimeMs >= timeout) { + throw new PollingTimeoutError('Polling timed out') + } + + // Make the sleep abortable, ensuring listener cleanup to prevent memory leaks + await new Promise((resolve, reject) => { + const timeoutId = setTimeout(() => { + signal?.removeEventListener('abort', onAbort) + resolve() + }, interval) + + function onAbort() { + clearTimeout(timeoutId) + reject(signal?.reason ?? new DOMException('Aborted', 'AbortError')) + } + + signal?.addEventListener('abort', onAbort, { once: true }) + }) + } + } + + maybeThrowInconsistentResponseError(message: string) { + const err = new InconsistentResponseError(message) + + // @TODO, once our schemas have matured, we should remove the option and always throw the error here. + // But as it stands, schemas are new, and we can't easily update all customer's node-sdks, + // so there will be a long tail of throws if we enable this now. + if (this._validateResponses) { + throw err + } + + console.error( + `---\nPlease report this error to Transloadit (support@transloadit.com). We are working on better schemas for our API and this looks like something we do not cover yet: \n\n${err}\nThank you in advance!\n---\n`, + ) + } + + /** + * Cancel the assembly + * + * @param assemblyId assembly ID + * @returns after the assembly is deleted + */ + async cancelAssembly(assemblyId: string): Promise { + const { assembly_ssl_url: url } = await this.getAssembly(assemblyId) + const rawResult = await this._remoteJson, OptionalAuthParams>({ + url, + method: 'delete', + }) + + const parsedResult = zodParseWithContext(assemblyStatusSchema, rawResult) + + if (!parsedResult.success) { + this.maybeThrowInconsistentResponseError( + `The API responded with data that does not match the expected schema while cancelling Assembly: ${assemblyId}.\n${parsedResult.humanReadable}`, + ) + } + + checkAssemblyUrls(rawResult as AssemblyStatus) + return rawResult as AssemblyStatus + } + + /** + * Replay an Assembly + * + * @param assemblyId of the assembly to replay + * @param optional params + * @returns after the replay is started + */ + async replayAssembly( + assemblyId: string, + params: ReplayAssemblyParams = {}, + ): Promise { + const result: ReplayAssemblyResponse = await this._remoteJson({ + urlSuffix: `/assemblies/${assemblyId}/replay`, + method: 'post', + ...(Object.keys(params).length > 0 && { params }), + }) + checkResult(result) + return result + } + + /** + * Replay an Assembly notification + * + * @param assemblyId of the assembly whose notification to replay + * @param optional params + * @returns after the replay is started + */ + async replayAssemblyNotification( + assemblyId: string, + params: ReplayAssemblyNotificationParams = {}, + ): Promise { + return await this._remoteJson({ + urlSuffix: `/assembly_notifications/${assemblyId}/replay`, + method: 'post', + ...(Object.keys(params).length > 0 && { params }), + }) + } + + /** + * List all assemblies + * + * @param params optional request options + * @returns list of Assemblies + */ + async listAssemblies( + params?: ListAssembliesParams, + ): Promise> { + const rawResponse = await this._remoteJson< + PaginationListWithCount>, + ListAssembliesParams + >({ + urlSuffix: '/assemblies', + method: 'get', + params: params || {}, + }) + + if ( + rawResponse == null || + typeof rawResponse !== 'object' || + !Array.isArray(rawResponse.items) + ) { + throw new InconsistentResponseError( + 'API response for listAssemblies is malformed or missing items array', + ) + } + + const parsedResult = zodParseWithContext(assemblyIndexSchema, rawResponse.items) + + if (!parsedResult.success) { + this.maybeThrowInconsistentResponseError( + `API response for listAssemblies contained items that do not match the expected schema.\n${parsedResult.humanReadable}`, + ) + } + + return { + items: rawResponse.items as AssemblyIndex, + count: rawResponse.count, + } + } + + streamAssemblies(params: ListAssembliesParams): Readable { + return new PaginationStream(async (page) => this.listAssemblies({ ...params, page })) + } + + /** + * Get an Assembly + * + * @param assemblyId the Assembly Id + * @param options optional request options + * @returns the retrieved Assembly + */ + async getAssembly( + assemblyId: string, + options?: { signal?: AbortSignal }, + ): Promise { + const rawResult = await this._remoteJson, OptionalAuthParams>({ + urlSuffix: `/assemblies/${assemblyId}`, + signal: options?.signal, + }) + + const parsedResult = zodParseWithContext(assemblyStatusSchema, rawResult) + + if (!parsedResult.success) { + this.maybeThrowInconsistentResponseError( + `The API responded with data that does not match the expected schema while getting Assembly: ${assemblyId}.\n${parsedResult.humanReadable}`, + ) + } + + checkAssemblyUrls(rawResult as AssemblyStatus) + return rawResult as AssemblyStatus + } + + /** + * Create a Credential + * + * @param params optional request options + * @returns when the Credential is created + */ + async createTemplateCredential( + params: CreateTemplateCredentialParams, + ): Promise { + return await this._remoteJson({ + urlSuffix: '/template_credentials', + method: 'post', + params: params || {}, + }) + } + + /** + * Edit a Credential + * + * @param credentialId the Credential ID + * @param params optional request options + * @returns when the Credential is edited + */ + async editTemplateCredential( + credentialId: string, + params: CreateTemplateCredentialParams, + ): Promise { + return await this._remoteJson({ + urlSuffix: `/template_credentials/${credentialId}`, + method: 'put', + params: params || {}, + }) + } + + /** + * Delete a Credential + * + * @param credentialId the Credential ID + * @returns when the Credential is deleted + */ + async deleteTemplateCredential(credentialId: string): Promise { + return await this._remoteJson({ + urlSuffix: `/template_credentials/${credentialId}`, + method: 'delete', + }) + } + + /** + * Get a Credential + * + * @param credentialId the Credential ID + * @returns when the Credential is retrieved + */ + async getTemplateCredential(credentialId: string): Promise { + return await this._remoteJson({ + urlSuffix: `/template_credentials/${credentialId}`, + method: 'get', + }) + } + + /** + * List all TemplateCredentials + * + * @param params optional request options + * @returns the list of templates + */ + async listTemplateCredentials( + params?: ListTemplateCredentialsParams, + ): Promise { + return await this._remoteJson({ + urlSuffix: '/template_credentials', + method: 'get', + params: params || {}, + }) + } + + streamTemplateCredentials(params: ListTemplateCredentialsParams) { + return new PaginationStream(async (page) => ({ + items: (await this.listTemplateCredentials({ ...params, page })).credentials, + })) + } + + /** + * Create an Assembly Template + * + * @param params optional request options + * @returns when the template is created + */ + async createTemplate(params: CreateTemplateParams): Promise { + return await this._remoteJson({ + urlSuffix: '/templates', + method: 'post', + params: params || {}, + }) + } + + /** + * Edit an Assembly Template + * + * @param templateId the template ID + * @param params optional request options + * @returns when the template is edited + */ + async editTemplate(templateId: string, params: EditTemplateParams): Promise { + return await this._remoteJson({ + urlSuffix: `/templates/${templateId}`, + method: 'put', + params: params || {}, + }) + } + + /** + * Delete an Assembly Template + * + * @param templateId the template ID + * @returns when the template is deleted + */ + async deleteTemplate(templateId: string): Promise { + return await this._remoteJson({ + urlSuffix: `/templates/${templateId}`, + method: 'delete', + }) + } + + /** + * Get an Assembly Template + * + * @param templateId the template ID + * @returns when the template is retrieved + */ + async getTemplate(templateId: string): Promise { + return await this._remoteJson({ + urlSuffix: `/templates/${templateId}`, + method: 'get', + }) + } + + /** + * List all Assembly Templates + * + * @param params optional request options + * @returns the list of templates + */ + async listTemplates( + params?: ListTemplatesParams, + ): Promise> { + return await this._remoteJson({ + urlSuffix: '/templates', + method: 'get', + params: params || {}, + }) + } + + streamTemplates(params?: ListTemplatesParams): PaginationStream { + return new PaginationStream(async (page) => this.listTemplates({ ...params, page })) + } + + /** + * Get account Billing details for a specific month + * + * @param month the date for the required billing in the format yyyy-mm + * @returns with billing data + * @see https://transloadit.com/docs/api/bill-date-get/ + */ + async getBill(month: string): Promise { + assert.ok(month, 'month is required') + return await this._remoteJson({ + urlSuffix: `/bill/${month}`, + method: 'get', + }) + } + + calcSignature( + params: OptionalAuthParams, + algorithm?: string, + ): { signature: string; params: string } { + const jsonParams = this._prepareParams(params) + const signature = this._calcSignature(jsonParams, algorithm) + + return { signature, params: jsonParams } + } + + /** + * Construct a signed Smart CDN URL. See https://transloadit.com/docs/topics/signature-authentication/#smart-cdn. + */ + getSignedSmartCDNUrl(opts: SmartCDNUrlOptions): string { + if (opts.workspace == null || opts.workspace === '') + throw new TypeError('workspace is required') + if (opts.template == null || opts.template === '') throw new TypeError('template is required') + if (opts.input == null) throw new TypeError('input is required') // `input` can be an empty string. + + const workspaceSlug = encodeURIComponent(opts.workspace) + const templateSlug = encodeURIComponent(opts.template) + const inputField = encodeURIComponent(opts.input) + const expiresAt = opts.expiresAt || Date.now() + 60 * 60 * 1000 // 1 hour + + const queryParams = new URLSearchParams() + for (const [key, value] of Object.entries(opts.urlParams || {})) { + if (Array.isArray(value)) { + for (const val of value) { + queryParams.append(key, `${val}`) + } + } else { + queryParams.append(key, `${value}`) + } + } + + queryParams.set('auth_key', this._authKey) + queryParams.set('exp', `${expiresAt}`) + // The signature changes depending on the order of the query parameters. We therefore sort them on the client- + // and server-side to ensure that we do not get mismatching signatures if a proxy changes the order of query + // parameters or implementations handle query parameters ordering differently. + queryParams.sort() + + const stringToSign = `${workspaceSlug}/${templateSlug}/${inputField}?${queryParams}` + const algorithm = 'sha256' + const signature = createHmac(algorithm, this._authSecret).update(stringToSign).digest('hex') + + queryParams.set('sig', `sha256:${signature}`) + const signedUrl = `https://${workspaceSlug}.tlcdn.com/${templateSlug}/${inputField}?${queryParams}` + return signedUrl + } + + private _calcSignature(toSign: string, algorithm = 'sha384'): string { + return `${algorithm}:${createHmac(algorithm, this._authSecret) + .update(Buffer.from(toSign, 'utf-8')) + .digest('hex')}` + } + + // Sets the multipart/form-data for POST, PUT and DELETE requests, including + // the streams, the signed params, and any additional fields. + private _appendForm(form: FormData, params: OptionalAuthParams, fields?: Fields): void { + const sigData = this.calcSignature(params) + const jsonParams = sigData.params + const { signature } = sigData + + form.append('params', jsonParams) + + if (fields != null) { + for (const [key, val] of Object.entries(fields)) { + form.append(key, val) + } + } + + form.append('signature', signature) + } + + // Implements HTTP GET query params, handling the case where the url already + // has params. + private _appendParamsToUrl(url: string, params: OptionalAuthParams): string { + const { signature, params: jsonParams } = this.calcSignature(params) + + const prefix = url.indexOf('?') === -1 ? '?' : '&' + + return `${url}${prefix}signature=${signature}¶ms=${encodeURIComponent(jsonParams)}` + } + + // Responsible for including auth parameters in all requests + private _prepareParams(paramsIn?: OptionalAuthParams): string { + let params = paramsIn + if (params == null) { + params = {} + } + if (params.auth == null) { + params.auth = {} + } + if (params.auth.key == null) { + params.auth.key = this._authKey + } + if (params.auth.expires == null) { + params.auth.expires = this._getExpiresDate() + } + + return JSON.stringify(params) + } + + // We want to mock this method + private _getExpiresDate(): string { + const expiresDate = new Date() + expiresDate.setDate(expiresDate.getDate() + 1) + return expiresDate.toISOString() + } + + // Responsible for making API calls. Automatically sends streams with any POST, + // PUT or DELETE requests. Automatically adds signature parameters to all + // requests. Also automatically parses the JSON response. + private async _remoteJson(opts: { + urlSuffix?: string + url?: string + timeout?: Delays + method?: 'delete' | 'get' | 'post' | 'put' + params?: TParams + fields?: Fields + headers?: Headers + signal?: AbortSignal + }): Promise { + const { + urlSuffix, + url: urlInput, + timeout = { request: this._defaultTimeout }, + method = 'get', + params = {}, + fields, + headers, + signal, + } = opts + + // Allow providing either a `urlSuffix` or a full `url` + if (!urlSuffix && !urlInput) throw new Error('No URL provided') + let url = urlInput || `${this._endpoint}${urlSuffix}` + + if (method === 'get') { + url = this._appendParamsToUrl(url, params) + } + + log('Sending request', method, url) + + // todo use got.retry instead because we are no longer using FormData (which is a stream and can only be used once) + // https://github.com/sindresorhus/got/issues/1282 + for (let retryCount = 0; ; retryCount++) { + let form: FormData | undefined + + if (method === 'post' || method === 'put' || method === 'delete') { + form = new FormData() + this._appendForm(form, params, fields) + } + + const requestOpts: OptionsOfJSONResponseBody = { + retry: this._gotRetry, + body: form, + timeout, + headers: { + 'Transloadit-Client': `node-sdk:${version}`, + 'User-Agent': undefined, // Remove got's user-agent + ...headers, + }, + responseType: 'json', + signal, + } + + try { + const request = got[method](url, requestOpts) + const { body } = await request + // console.log(body) + return body + } catch (err) { + if (!(err instanceof RequestError)) throw err + + if (err instanceof HTTPError) { + const { statusCode, body } = err.response + logWarn('HTTP error', statusCode, body) + + // check whether we should retry + // https://transloadit.com/blog/2012/04/introducing-rate-limiting/ + if ( + typeof body === 'object' && + body != null && + 'error' in body && + 'info' in body && + typeof body.info === 'object' && + body.info != null && + 'retryIn' in body.info && + typeof body.info.retryIn === 'number' && + Boolean(body.info.retryIn) && + retryCount < this._maxRetries && // 413 taken from https://transloadit.com/blog/2012/04/introducing-rate-limiting/ + // todo can 413 be removed? + ((statusCode === 413 && body.error === 'RATE_LIMIT_REACHED') || statusCode === 429) + ) { + const { retryIn: retryInSec } = body.info + logWarn(`Rate limit reached, retrying request in approximately ${retryInSec} seconds.`) + const retryInMs = 1000 * (retryInSec * (1 + 0.1 * Math.random())) + await delay(retryInMs) + // Retry + } else { + throw new ApiError({ + cause: err, + body: err.response?.body as TransloaditErrorResponseBody | undefined, + }) // todo don't assert type + } + } else { + throw err + } + } + } + } +} diff --git a/packages/transloadit/src/alphalib/lib/nativeGlobby.ts b/packages/transloadit/src/alphalib/lib/nativeGlobby.ts new file mode 100644 index 00000000..61d0dcd3 --- /dev/null +++ b/packages/transloadit/src/alphalib/lib/nativeGlobby.ts @@ -0,0 +1,244 @@ +import type { GlobOptionsWithoutFileTypes } from 'node:fs' +import * as fs from 'node:fs' +import { glob as fsGlob, stat as statAsync } from 'node:fs/promises' +import path from 'node:path' + +type PatternInput = string | readonly string[] +const { globSync: fsGlobSync } = fs + +export interface NativeGlobbyOptions { + cwd?: string + absolute?: boolean + onlyFiles?: boolean + ignore?: readonly string[] +} + +interface NormalizedOptions { + cwd?: string + absolute: boolean + onlyFiles: boolean + ignore?: readonly string[] +} + +interface Candidate { + rawPath: string + absolutePath: string +} + +const normalizeSlashes = (value: string) => value.replace(/\\/g, '/') + +const toAbsolutePath = (rawPath: string, cwd?: string): string => { + if (path.isAbsolute(rawPath)) { + return rawPath + } + if (cwd) { + return path.join(cwd, rawPath) + } + return path.resolve(rawPath) +} + +const normalizeOptions = (options: NativeGlobbyOptions = {}): NormalizedOptions => ({ + cwd: options.cwd ? path.resolve(options.cwd) : undefined, + absolute: options.absolute ?? false, + onlyFiles: options.onlyFiles ?? true, + ignore: options.ignore && options.ignore.length > 0 ? options.ignore : undefined, +}) + +const hasGlobMagic = (pattern: string) => /[*?[\]{}()!]/.test(pattern) + +const expandPatternAsync = async (pattern: string, options: NormalizedOptions) => { + if (hasGlobMagic(pattern)) { + return [pattern] + } + + try { + const absolute = toAbsolutePath(pattern, options.cwd) + const stats = await statAsync(absolute) + if (stats.isDirectory()) { + const expanded = normalizeSlashes(path.join(pattern, '**/*')) + return [expanded] + } + } catch { + // ignore missing paths; fall back to original pattern + } + + return [pattern] +} + +const expandPatternSync = (pattern: string, options: NormalizedOptions) => { + if (hasGlobMagic(pattern)) { + return [pattern] + } + + try { + const absolute = toAbsolutePath(pattern, options.cwd) + const stats = fs.statSync(absolute) + if (stats.isDirectory()) { + const expanded = normalizeSlashes(path.join(pattern, '**/*')) + return [expanded] + } + } catch { + // ignore missing paths; fall back to original pattern + } + + return [pattern] +} + +const splitPatterns = (patterns: PatternInput) => { + const list = Array.isArray(patterns) ? patterns : [patterns] + const positive: string[] = [] + const negative: string[] = [] + + for (const pattern of list) { + if (pattern.startsWith('!')) { + const negated = pattern.slice(1) + if (negated) { + negative.push(negated) + } + } else { + positive.push(pattern) + } + } + + return { positive, negative } +} + +const toGlobOptions = (options: NormalizedOptions): GlobOptionsWithoutFileTypes => { + const globOptions: GlobOptionsWithoutFileTypes = { withFileTypes: false } + if (options.cwd) { + globOptions.cwd = options.cwd + } + if (options.ignore) { + // Node's glob implementation uses `exclude` for ignore patterns + globOptions.exclude = options.ignore + } + return globOptions +} + +const filterFilesAsync = async (candidates: Candidate[], requireFiles: boolean) => { + if (!requireFiles) { + return candidates + } + + const filtered = await Promise.all( + candidates.map(async (candidate) => { + try { + const stats = await statAsync(candidate.absolutePath) + return stats.isFile() ? candidate : null + } catch { + return null + } + }), + ) + + return filtered.filter(Boolean) as Candidate[] +} + +const filterFilesSync = (candidates: Candidate[], requireFiles: boolean) => { + if (!requireFiles) { + return candidates + } + + const filtered: Candidate[] = [] + for (const candidate of candidates) { + try { + const stats = fs.statSync(candidate.absolutePath) + if (stats.isFile()) { + filtered.push(candidate) + } + } catch { + // Ignore files that cannot be stat'ed + } + } + return filtered +} + +const formatResult = (candidate: Candidate, options: NormalizedOptions) => { + const output = options.absolute ? candidate.absolutePath : candidate.rawPath + return normalizeSlashes(output) +} + +const collectMatchesAsync = async (pattern: string, options: NormalizedOptions) => { + const matches: Candidate[] = [] + for await (const match of fsGlob(pattern, toGlobOptions(options))) { + matches.push({ + rawPath: match as string, + absolutePath: toAbsolutePath(match as string, options.cwd), + }) + } + const filtered = await filterFilesAsync(matches, options.onlyFiles) + return filtered.map((candidate) => formatResult(candidate, options)) +} + +const collectMatchesSync = (pattern: string, options: NormalizedOptions) => { + const matches = (fsGlobSync(pattern, toGlobOptions(options)) as string[]).map((match) => ({ + rawPath: match, + absolutePath: toAbsolutePath(match, options.cwd), + })) + const filtered = filterFilesSync(matches, options.onlyFiles) + return filtered.map((candidate) => formatResult(candidate, options)) +} + +type GlobbyLike = { + (patterns: PatternInput, options?: NativeGlobbyOptions): Promise + sync(patterns: PatternInput, options?: NativeGlobbyOptions): string[] +} + +export const nativeGlobby: GlobbyLike = Object.assign( + async (patterns: PatternInput, options?: NativeGlobbyOptions) => { + const normalized = normalizeOptions(options) + const { positive, negative } = splitPatterns(patterns) + const expandedPositives = ( + await Promise.all(positive.map((pattern) => expandPatternAsync(pattern, normalized))) + ).flat() + const expandedNegatives = ( + await Promise.all(negative.map((pattern) => expandPatternAsync(pattern, normalized))) + ).flat() + const results = new Set() + + for (const pattern of expandedPositives) { + const matches = await collectMatchesAsync(pattern, normalized) + for (const match of matches) { + results.add(match) + } + } + + for (const pattern of expandedNegatives) { + const matches = await collectMatchesAsync(pattern, normalized) + for (const match of matches) { + results.delete(match) + } + } + + return Array.from(results) + }, + { + sync(patterns: PatternInput, options?: NativeGlobbyOptions) { + const normalized = normalizeOptions(options) + const { positive, negative } = splitPatterns(patterns) + const expandedPositives = positive.flatMap((pattern) => + expandPatternSync(pattern, normalized), + ) + const expandedNegatives = negative.flatMap((pattern) => + expandPatternSync(pattern, normalized), + ) + const results = new Set() + + for (const pattern of expandedPositives) { + const matches = collectMatchesSync(pattern, normalized) + for (const match of matches) { + results.add(match) + } + } + + for (const pattern of expandedNegatives) { + const matches = collectMatchesSync(pattern, normalized) + for (const match of matches) { + results.delete(match) + } + } + + return Array.from(results) + }, + }, +) diff --git a/packages/transloadit/src/alphalib/mcache.ts b/packages/transloadit/src/alphalib/mcache.ts new file mode 100644 index 00000000..852a7df9 --- /dev/null +++ b/packages/transloadit/src/alphalib/mcache.ts @@ -0,0 +1,184 @@ +import type { SevLogger } from '@transloadit/sev-logger' +import type { Schema } from 'zod' + +export interface McacheOpts { + ttlMs?: number + zodSchema?: Schema + logger?: SevLogger + /** + * Maximum number of entries in the cache. When exceeded, oldest entries are removed. + * Defaults to 10,000 entries. + */ + maxSize?: number + /** + * Custom key generator function. If not provided, uses JSON.stringify. + */ + keyFn?: (...args: unknown[]) => string +} + +interface CacheEntry { + value: T + timestamp: number +} + +/** + * Memory cache abstraction to help cache function results in-process. + * + * Example: + * + * const cache = new Mcache({ ttlMs: 1000 * 60 * 10 }) + * + * async function fetchInstances(region: string): Promise { + * return cache.get(region, async () => { + * // Do work, e.g. fetch instances from AWS + * return await this._fetchInstances(region) + * }) + * } + */ +export class Mcache { + #cache: Map> + #opts: Required> & + Pick + + constructor(opts: McacheOpts = {}) { + this.#cache = new Map() + this.#opts = { + ttlMs: opts.ttlMs ?? Number.POSITIVE_INFINITY, + maxSize: opts.maxSize ?? 10_000, + zodSchema: opts.zodSchema, + logger: opts.logger, + keyFn: opts.keyFn, + } + } + + /** + * Get a value from cache, or compute it using the provided function. + * The cache key is generated from the args using JSON.stringify by default, + * or using the custom keyFn if provided. + */ + async get(producer: () => Promise | T, ...args: unknown[]): Promise { + const key = this.#opts.keyFn ? this.#opts.keyFn(...args) : JSON.stringify(args) + const cached = this.#cache.get(key) + + if (cached) { + const age = Date.now() - cached.timestamp + if (age <= this.#opts.ttlMs || this.#opts.ttlMs === Number.POSITIVE_INFINITY) { + this.#opts.logger?.debug(`Cache hit for key ${key} (age: ${age}ms)`) + return cached.value + } + + this.#opts.logger?.debug( + `Cache expired for key ${key} (age: ${age}ms > ${this.#opts.ttlMs}ms)`, + ) + this.#cache.delete(key) + } + + this.#opts.logger?.debug(`Cache miss for key ${key}, computing value`) + const value = await producer() + + // Validate if schema provided + if (this.#opts.zodSchema) { + this.#opts.zodSchema.parse(value) + } + + this.#set(key, value) + return value + } + + /** + * Set a value in the cache directly. + */ + set(value: T, ...args: unknown[]): void { + const key = this.#opts.keyFn ? this.#opts.keyFn(...args) : JSON.stringify(args) + + // Validate if schema provided + if (this.#opts.zodSchema) { + this.#opts.zodSchema.parse(value) + } + + this.#set(key, value) + } + + /** + * Check if a key exists in cache and is not expired. + */ + has(...args: unknown[]): boolean { + const key = this.#opts.keyFn ? this.#opts.keyFn(...args) : JSON.stringify(args) + const cached = this.#cache.get(key) + + if (!cached) { + return false + } + + const age = Date.now() - cached.timestamp + if (age > this.#opts.ttlMs && this.#opts.ttlMs !== Number.POSITIVE_INFINITY) { + this.#cache.delete(key) + return false + } + + return true + } + + /** + * Clear all entries from the cache. + */ + clear(): void { + this.#cache.clear() + } + + /** + * Delete a specific entry from the cache. + */ + delete(...args: unknown[]): boolean { + const key = this.#opts.keyFn ? this.#opts.keyFn(...args) : JSON.stringify(args) + return this.#cache.delete(key) + } + + /** + * Get the current size of the cache. + */ + get size(): number { + return this.#cache.size + } + + /** + * Clean up expired entries and enforce size limit. + */ + cleanup(): void { + const now = Date.now() + + // Remove expired entries + if (this.#opts.ttlMs !== Number.POSITIVE_INFINITY) { + for (const [key, entry] of this.#cache.entries()) { + if (now - entry.timestamp > this.#opts.ttlMs) { + this.#cache.delete(key) + } + } + } + + // Enforce size limit by removing oldest entries + if (this.#cache.size > this.#opts.maxSize) { + const entries = Array.from(this.#cache.entries()) + entries.sort((a, b) => a[1].timestamp - b[1].timestamp) + const toRemove = entries.slice(0, this.#cache.size - this.#opts.maxSize) + for (const [key] of toRemove) { + this.#cache.delete(key) + } + this.#opts.logger?.debug( + `Cache size limit reached, removed ${toRemove.length} oldest entries`, + ) + } + } + + #set(key: string, value: T): void { + this.#cache.set(key, { + value, + timestamp: Date.now(), + }) + + // Trigger cleanup if we're over size limit + if (this.#cache.size > this.#opts.maxSize) { + this.cleanup() + } + } +} diff --git a/packages/transloadit/src/alphalib/tryCatch.ts b/packages/transloadit/src/alphalib/tryCatch.ts new file mode 100644 index 00000000..c3dc8dd5 --- /dev/null +++ b/packages/transloadit/src/alphalib/tryCatch.ts @@ -0,0 +1,30 @@ +/** + * Represents a successful result where error is null and data is present + */ +export type Success = [null, T] + +/** + * Represents a failure result where error contains an error instance and data is null + */ +export type Failure = [E, null] + +/** + * Represents the result of an operation that can either succeed with T or fail with E + */ +export type Result = Success | Failure + +/** + * Wraps a promise in a try-catch block and returns a tuple of [error, data] + * where exactly one value is non-null + * + * @param promise The promise to execute safely + * @returns A tuple of [error, data] where one is null + */ +export async function tryCatch(promise: Promise): Promise> { + try { + const data = await promise + return [null, data] as Success + } catch (error) { + return [error as E, null] as Failure + } +} diff --git a/packages/transloadit/src/alphalib/types/assembliesGet.ts b/packages/transloadit/src/alphalib/types/assembliesGet.ts new file mode 100644 index 00000000..17633b1c --- /dev/null +++ b/packages/transloadit/src/alphalib/types/assembliesGet.ts @@ -0,0 +1,42 @@ +import z from 'zod' + +import { assemblyAuthInstructionsSchema } from './template.ts' + +export const assembliesGetSchema = z + .object({ + auth: assemblyAuthInstructionsSchema, + page: z + .number() + .int() + .default(1) + .describe('Specifies the current page, within the current pagination'), + pagesize: z + .number() + .int() + .min(1) + .max(5000) + .default(50) + .describe( + 'Specifies how many Assemblies to be received per API request, which is useful for pagination.', + ), + type: z + .enum(['all', 'uploading', 'executing', 'canceled', 'completed', 'failed', 'request_aborted']) + .describe('Specifies the types of Assemblies to be retrieved.'), + fromdate: z + .string() + .describe( + 'Specifies the minimum Assembly UTC creation date/time. Only Assemblies after this time will be retrieved. Use the format `Y-m-d H:i:s`.', + ), + todate: z + .string() + .default('NOW()') + .describe( + 'Specifies the maximum Assembly UTC creation date/time. Only Assemblies before this time will be retrieved. Use the format `Y-m-d H:i:s`.', + ), + keywords: z + .array(z.string()) + .describe( + 'Specifies keywords to be matched in the Assembly Status. The Assembly fields checked include the `id`, `redirect_url`, `fields`, and `notify_url`, as well as error messages and files used.', + ), + }) + .strict() diff --git a/packages/transloadit/src/alphalib/types/assemblyReplay.ts b/packages/transloadit/src/alphalib/types/assemblyReplay.ts new file mode 100644 index 00000000..0a2d5e44 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/assemblyReplay.ts @@ -0,0 +1,24 @@ +import { z } from 'zod' + +import { + assemblyAuthInstructionsSchema, + fieldsSchema, + notifyUrlSchema, + optionalStepsSchema, + templateIdSchema, +} from './template.ts' + +export const assemblyReplaySchema = z + .object({ + auth: assemblyAuthInstructionsSchema, + steps: optionalStepsSchema as typeof optionalStepsSchema, + template_id: templateIdSchema, + notify_url: notifyUrlSchema, + fields: fieldsSchema, + reparse_template: z + .union([z.literal(0), z.literal(1)]) + .describe( + 'Specify `1` to reparse the Template used in your Assembly (useful if the Template changed in the meantime). Alternatively, `0` replays the identical Steps used in the Assembly.', + ), + }) + .strict() diff --git a/packages/transloadit/src/alphalib/types/assemblyReplayNotification.ts b/packages/transloadit/src/alphalib/types/assemblyReplayNotification.ts new file mode 100644 index 00000000..ce8ee098 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/assemblyReplayNotification.ts @@ -0,0 +1,16 @@ +import { z } from 'zod' + +import { assemblyAuthInstructionsSchema, optionalStepsSchema } from './template.ts' + +export const assemblyReplayNotificationSchema = z + .object({ + auth: assemblyAuthInstructionsSchema, + steps: optionalStepsSchema as typeof optionalStepsSchema, + wait: z + .boolean() + .default(true) + .describe( + 'If it is provided with the value `false`, then the API request will return immediately even though the Notification is still in progress. This can be useful if your server takes some time to respond, but you do not want the replay API request to hang.', + ), + }) + .strict() diff --git a/packages/transloadit/src/alphalib/types/assemblyStatus.ts b/packages/transloadit/src/alphalib/types/assemblyStatus.ts new file mode 100644 index 00000000..6dea5e00 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/assemblyStatus.ts @@ -0,0 +1,794 @@ +import { z } from 'zod' + +export const assemblyBusyCodeSchema = z.enum(['ASSEMBLY_UPLOADING']) + +export const assemblyStatusOkCodeSchema = z.enum([ + 'ASSEMBLY_CANCELED', + 'ASSEMBLY_COMPLETED', + 'ASSEMBLY_EXECUTING', + 'ASSEMBLY_EXPIRED', + 'ASSEMBLY_REPLAYING', + 'ASSEMBLY_UPLOADING', + 'REQUEST_ABORTED', + // 'ASSEMBLY_EXECUTION_PROGRESS_FETCHED', + // 'ASSEMBLY_FILE_ACCEPTED', + // 'ASSEMBLY_FILE_RESERVED', +]) + +export const assemblyStatusErrCodeSchema = z.enum([ + 'ADMIN_PERMISSIONS_REQUIRED', + 'ASSEMBLY_ACCOUNT_MISMATCH', + 'ASSEMBLY_CANNOT_BE_REPLAYED', + 'ASSEMBLY_COULD_NOT_BE_CREATED', + 'ASSEMBLY_CRASHED', + 'ASSEMBLY_DISALLOWED_ROBOTS_USED', + 'ASSEMBLY_EMPTY_STEPS', + 'ASSEMBLY_EXPIRED', + 'ASSEMBLY_FILE_NOT_RESERVED', + 'ASSEMBLY_INFINITE', + 'ASSEMBLY_INSTANCE_NOT_FOUND', + 'ASSEMBLY_INVALID_NOTIFY_URL', + 'ASSEMBLY_INVALID_NUM_EXPECTED_UPLOAD_FILES_PARAM', + 'ASSEMBLY_INVALID_STEPS', + 'ASSEMBLY_JOB_ENQUEUE_ERROR', + 'ASSEMBLY_LIST_ERROR', + 'ASSEMBLY_MEMORY_LIMIT_EXCEEDED', + 'ASSEMBLY_NO_CHARGEABLE_STEP', + 'ASSEMBLY_NO_STEPS', + 'ASSEMBLY_NOT_CAPABLE', + 'ASSEMBLY_NOT_FINISHED', + 'ASSEMBLY_NOT_FOUND', + 'ASSEMBLY_NOT_REPLAYED', + 'ASSEMBLY_NOTIFICATION_NOT_PERSISTED', + 'ASSEMBLY_ROBOT_MISSING', + 'ASSEMBLY_SATURATED', + 'ASSEMBLY_STATUS_NOT_FOUND', + 'ASSEMBLY_STATUS_PARSE_ERROR', + 'ASSEMBLY_STEP_INVALID_ROBOT', + 'ASSEMBLY_STEP_INVALID_USE', + 'ASSEMBLY_STEP_INVALID', + 'ASSEMBLY_STEP_NO_ROBOT', + 'ASSEMBLY_STEP_UNKNOWN_ROBOT', + 'ASSEMBLY_STEP_UNKNOWN_USE', + 'ASSEMBLY_URL_TRANSFORM_MISSING', + 'AUTH_EXPIRED', + 'AUTH_KEY_SCOPES_NOT_FOUND', + 'AUTH_KEYS_NOT_FOUND', + 'AUTH_SECRET_NOT_RETRIEVED', + 'AZURE_STORE_ACCESS_DENIED', + 'BACKBLAZE_IMPORT_ACCESS_DENIED', + 'BACKBLAZE_IMPORT_NOT_FOUND', + 'BACKBLAZE_STORE_ACCESS_DENIED', + 'BACKBLAZE_STORE_FAILURE', + 'BAD_PRICING', + 'BILL_LIMIT_EXCEEDED', + 'CANNOT_ACCEPT_NEW_ASSEMBLIES', + 'CANNOT_FETCH_ACTIVE_ASSEMBLIES', + 'CDN_REQUIRED', + 'CLOUDFILES_IMPORT_ACCESS_DENIED', + 'CLOUDFILES_IMPORT_NOT_FOUND', + 'CLOUDFILES_STORE_ACCESS_DENIED', + 'CLOUDFILES_STORE_ERROR', + 'CLOUDFLARE_IMPORT_VALIDATION', + 'DIGITALOCEAN_STORE_ACCESS_DENIED', + 'DO_NOT_REUSE_ASSEMBLY_IDS', + 'DOCUMENT_CONVERT_UNSUPPORTED_CONVERSION', + 'DOCUMENT_SPLIT_VALIDATION', + 'FILE_DOWNLOAD_ERROR', + 'FILE_FILTER_DECLINED_FILE', + 'FILE_FILTER_INVALID_OPERATOR', + 'FILE_FILTER_VALIDATION', + 'FILE_META_DATA_ERROR', + 'FILE_PREVIEW_VALIDATION', + 'FILE_READ_VALIDATION_ERROR', + 'FILE_VERIFY_INVALID_FILE', + 'FILE_VIRUSSCAN_DECLINED_FILE', + 'GET_ACCOUNT_DB_ERROR', + 'GET_ACCOUNT_UNKNOWN_AUTH_KEY', + 'GOOGLE_IMPORT_VALIDATION', + 'GOOGLE_STORE_VALIDATION', + 'HTML_CONVERT_VALIDATION', + 'HTTP_IMPORT_ACCESS_DENIED', + 'HTTP_IMPORT_FAILURE', + 'HTTP_IMPORT_NOT_FOUND', + 'HTTP_IMPORT_VALIDATION', + 'IMAGE_DESCRIBE_VALIDATION', + 'IMAGE_RESIZE_ERROR', + 'IMAGE_RESIZE_VALIDATION', + 'IMPORT_FILE_ERROR', + 'INCOMPLETE_PRICING', + 'INSUFFICIENT_AUTH_SCOPE', + 'INTERNAL_COMMAND_ERROR', + 'INTERNAL_COMMAND_TIMEOUT', + 'INVALID_ASSEMBLY_STATUS', + 'INVALID_AUTH_EXPIRES_PARAMETER', + 'INVALID_AUTH_KEY_PARAMETER', + 'INVALID_AUTH_MAX_SIZE_PARAMETER', + 'INVALID_AUTH_REFERER_PARAMETER', + 'INVALID_FILE_META_DATA', + 'INVALID_FORM_DATA', + 'INVALID_INPUT_ERROR', + 'INVALID_PARAMS_FIELD', + 'INVALID_SIGNATURE', + 'INVALID_STEP_NAME', + 'INVALID_TEMPLATE_FIELD', + 'INVALID_UPLOAD_HANDLE_STEP_NAME', + 'MAX_SIZE_EXCEEDED', + 'NO_AUTH_EXPIRES_PARAMETER', + 'NO_AUTH_KEY_PARAMETER', + 'NO_AUTH_PARAMETER', + 'NO_COUNTRY', + 'NO_OBJECT_AUTH_PARAMETER', + 'NO_OBJECT_PARAMS_FIELD', + 'NO_PARAMS_FIELD', + 'NO_PRICING', + 'NO_RESULT_STEP_FOUND', + 'NO_RPC_RESULT_FROM_IMAGE_RESIZER', + 'NO_SIGNATURE_FIELD', + 'NO_TEMPLATE_ID', + 'PLAN_LIMIT_EXCEEDED', + 'POSSIBLY_MALICIOUS_FILE_FOUND', + 'PRIORITY_JOB_SLOTS_NOT_FOUND', + 'RATE_LIMIT_REACHED', + 'REFERER_MISMATCH', + 'REQUEST_PREMATURE_CLOSED', + 'ROBOT_VALIDATION_BASE_ERROR', + 'S3_ACCESS_DENIED', + 'S3_IMPORT_ACCESS_DENIED', + 'S3_IMPORT_VALIDATION', + 'S3_NOT_FOUND', + 'S3_STORE_ACCESS_DENIED', + 'S3_STORE_VALIDATION', + 'SERVER_403', + 'SERVER_404', + 'SERVER_500', + 'SIGNATURE_REUSE_DETECTED', + 'TEMPLATE_CREDENTIALS_INJECTION_ERROR', + 'TEMPLATE_DB_ERROR', + 'TEMPLATE_DENIES_STEPS_OVERRIDE', + 'TEMPLATE_INVALID_JSON', + 'TEMPLATE_NOT_FOUND', + 'TMP_FILE_DOWNLOAD_ERROR', + 'USER_COMMAND_ERROR', + 'VERIFIED_EMAIL_REQUIRED', + 'VIDEO_ENCODE_VALIDATION', + 'VIMEO_IMPORT_FAILURE', + 'WORKER_JOB_ERROR', +]) + +const assemblyStatusMetaSchema = z + .object({ + width: z.union([z.number(), z.null()]).optional(), + height: z.union([z.number(), z.null()]).optional(), + date_file_modified: z.string().nullable().optional(), + aspect_ratio: z.union([z.number(), z.string(), z.null()]).optional(), + has_clipping_path: z.boolean().optional(), + frame_count: z.union([z.number(), z.null()]).optional(), + colorspace: z.string().nullable().optional(), + has_transparency: z.boolean().nullable().optional(), + average_color: z.string().nullable().optional(), + svgViewBoxWidth: z.union([z.number(), z.null()]).optional(), + svgViewBoxHeight: z.union([z.number(), z.null()]).optional(), + date_recorded: z.union([z.string(), z.number()]).nullable().optional(), + date_file_created: z.string().nullable().optional(), + title: z.union([z.string(), z.number()]).nullable().optional(), + description: z.string().nullable().optional(), + duration: z.union([z.number(), z.null()]).optional(), + location: z.string().nullable().optional(), + city: z.string().nullable().optional(), + state: z.string().nullable().optional(), + rights: z.union([z.string(), z.number()]).nullable().optional(), + country: z.string().nullable().optional(), + country_code: z.string().nullable().optional(), + keywords: z + .union([z.string(), z.array(z.union([z.string(), z.number()]))]) + .nullable() + .optional(), + aperture: z.union([z.number(), z.null()]).optional(), + exposure_compensation: z.union([z.number(), z.string()]).nullable().optional(), + exposure_mode: z.string().nullable().optional(), + exposure_time: z.union([z.number(), z.string()]).nullable().optional(), + flash: z.string().nullable().optional(), + focal_length: z.string().nullable().optional(), + f_number: z.union([z.number(), z.null()]).optional(), + iso: z.union([z.number(), z.null()]).optional(), + light_value: z.union([z.number(), z.null()]).optional(), + metering_mode: z.string().nullable().optional(), + shutter_speed: z.union([z.number(), z.string()]).nullable().optional(), + white_balance: z.string().nullable().optional(), + device_name: z.string().nullable().optional(), + device_vendor: z.string().nullable().optional(), + device_software: z.union([z.string(), z.number()]).nullable().optional(), + latitude: z.union([z.number(), z.null()]).optional(), + longitude: z.union([z.number(), z.null()]).optional(), + orientation: z.union([z.string(), z.number()]).nullable().optional(), + creator: z.string().nullable().optional(), + author: z.string().nullable().optional(), + copyright: z.string().nullable().optional(), + copyright_notice: z.union([z.string(), z.number()]).nullable().optional(), + dominant_colors: z.array(z.string()).nullable().optional(), + xp_title: z.string().nullable().optional(), + xp_comment: z.string().nullable().optional(), + xp_keywords: z.string().nullable().optional(), + xp_subject: z.string().nullable().optional(), + recognized_text: z + .union([ + z.array(z.string()), + z.array( + z + .object({ + text: z.string(), + boundingPolygon: z.array(z.object({ x: z.number(), y: z.number() })), + }) + .passthrough(), + ), + ]) + .optional(), + descriptions: z + .array( + z.union([z.string(), z.object({ name: z.string(), confidence: z.number() }).passthrough()]), + ) + .optional(), + framerate: z.union([z.number(), z.null()]).optional(), + mean_volume: z.union([z.number(), z.null()]).optional(), + video_bitrate: z.union([z.number(), z.null()]).optional(), + overall_bitrate: z.union([z.number(), z.null()]).optional(), + video_codec: z.string().nullable().optional(), + audio_bitrate: z.union([z.number(), z.null()]).optional(), + audio_samplerate: z.union([z.number(), z.null()]).optional(), + audio_channels: z.union([z.number(), z.null()]).optional(), + audio_channel_layout: z.union([z.string(), z.null()]).optional(), + audio_sample_format: z.union([z.string(), z.null()]).optional(), + audio_profile: z.union([z.string(), z.null()]).optional(), + audio_codec: z.union([z.string(), z.null()]).optional(), + num_audio_streams: z.union([z.number(), z.null()]).optional(), + num_video_streams: z.union([z.number(), z.null()]).optional(), + num_subtitles: z.union([z.number(), z.null()]).optional(), + bit_depth: z.union([z.number(), z.null()]).optional(), + seekable: z.union([z.boolean(), z.null()]).optional(), + pixel_format: z.union([z.string(), z.null()]).optional(), + reference_count: z.union([z.number(), z.null()]).optional(), + time_base: z.union([z.string(), z.null()]).optional(), + streams: z + .union([ + z.object({ + video: z.array(z.unknown()).optional(), + audio: z.array(z.unknown()).optional(), + subtitle: z.array(z.unknown()).optional(), + }), + z.null(), + ]) + .optional(), + rotation: z.union([z.number(), z.null()]).optional(), + album: z.string().nullable().optional(), + comment: z.string().nullable().optional(), + year: z.union([z.string(), z.number()]).nullable().optional(), + encoding_profile: z.string().nullable().optional(), + encoding_level: z.string().nullable().optional(), + has_artwork: z.union([z.boolean(), z.null()]).optional(), + has_alpha_channel: z.boolean().nullable().optional(), + beats_per_minute: z.union([z.number(), z.null()]).optional(), + genre: z.union([z.string(), z.number()]).nullable().optional(), + artist: z.string().nullable().optional(), + performer: z.string().nullable().optional(), + lyrics: z.string().nullable().optional(), + band: z.string().nullable().optional(), + disc: z.union([z.string(), z.number()]).nullable().optional(), + track: z.union([z.string(), z.number()]).nullable().optional(), + turbo: z.boolean().nullable().optional(), + encoder: z.string().nullable().optional(), + thumb_index: z.number().nullable().optional(), + thumb_offset: z + .preprocess((val) => (typeof val === 'string' ? Number.parseInt(val, 10) : val), z.number()) + .nullable() + .optional(), + page_count: z.union([z.number(), z.null()]).optional(), + page_size: z.string().nullable().optional(), + producer: z.string().nullable().optional(), + create_date: z.string().nullable().optional(), + modify_date: z.union([z.string(), z.number()]).nullable().optional(), + colortransfer: z.string().nullable().optional(), + colorprimaries: z.string().nullable().optional(), + archive_directory: z.string().nullable().optional(), + relative_path: z.string().nullable().optional(), + segment_index: z.number().nullable().optional(), + starts_at: z.string().nullable().optional(), + ends_at: z.string().nullable().optional(), + resolution: z.string().nullable().optional(), + bandwidth: z.number().nullable().optional(), + closed_captions: z.boolean().nullable().optional(), + codecs: z.string().nullable().optional(), + storage_url: z.string().optional(), + version_id: z.string().optional(), + faces: z + .array( + z + .object({ + x1: z.number(), + y1: z.number(), + x2: z.number(), + y2: z.number(), + confidence: z.number().optional(), + width: z.number(), + height: z.number(), + }) + .passthrough(), + ) + .nullable() + .optional(), + reason: z.string().optional(), + step: z.string().optional(), + previousStep: z.string().optional(), + exitCode: z.number().nullable().optional(), + exitSignal: z.string().nullable().optional(), + stdout: z.string().optional(), + stderr: z.string().optional(), + cmd: z.union([z.string(), z.array(z.union([z.string(), z.number()]))]).optional(), + worker: z.string().optional(), + word_count: z.union([z.number(), z.null()]).optional(), + character_count: z.union([z.number(), z.null()]).optional(), + character_count_with_spaces: z.union([z.number(), z.null()]).optional(), + line_count: z.union([z.number(), z.null()]).optional(), + paragraph_count: z.union([z.number(), z.null()]).optional(), + }) + .passthrough() +export type AssemblyStatusMeta = z.infer + +// Need to export the schema itself for assemblyStatusForTests.ts +export { assemblyStatusMetaSchema } + +const hlsNestedMetaSchema = z.object({ + relative_path: z.string().optional(), + duration: z.number().optional(), + width: z.number().optional(), + height: z.number().optional(), + framerate: z.number().optional(), + overall_bitrate: z.number().optional(), + aspect_ratio: z.number().optional(), + video_codec: z.string().optional(), + audio_samplerate: z.number().optional(), + audio_channels: z.number().optional(), + num_audio_streams: z.number().optional(), + audio_codec: z.string().optional(), + seekable: z.boolean().optional(), + date_file_modified: z.string().optional(), + encoding_profile: z.string().optional(), + encoding_level: z.string().optional(), + has_artwork: z.boolean().optional(), + has_alpha_channel: z.boolean().optional(), + version_id: z.string().optional(), +}) + +const hlsPlaylistSchema = z.object({ + name: z.union([z.string(), z.number()]).optional(), + content: z.string().optional(), + relative_path: z.string().optional(), + stream: z.string().optional(), + meta: hlsNestedMetaSchema.optional(), +}) + +export const assemblyStatusUploadSchema = z + .object({ + id: z.string(), + name: z.string(), + basename: z.string(), + ext: z.string(), + size: z.number(), + mime: z.string(), + type: z.string().nullable(), + field: z.string().nullable(), + md5hash: z.string().nullable(), + original_id: z.union([z.string(), z.array(z.string())]), + original_basename: z.string(), + original_name: z.string(), + original_path: z.string(), + original_md5hash: z.string().nullable(), + from_batch_import: z.boolean(), + is_tus_file: z.boolean(), + tus_upload_url: z.string().nullable(), + url: z.string().nullable(), + ssl_url: z.string().nullable(), + meta: assemblyStatusMetaSchema, + user_meta: z.record(z.unknown()).optional(), + as: z + .union([z.string(), z.array(z.string())]) + .nullable() + .optional(), + is_temp_url: z.boolean().optional(), + queue: z.string().nullable().optional(), + queue_time: z.number().optional(), + exec_time: z.number().optional(), + import_url: z.string().optional(), + cost: z.union([z.number(), z.null()]).optional(), + }) + .passthrough() +export type AssemblyStatusUpload = z.infer + +export const assemblyStatusUploadsSchema = z.array(assemblyStatusUploadSchema) +export type AssemblyStatusUploads = z.infer + +export const assemblyStatusResultSchema = z + .object({ + id: z.string().optional(), + basename: z.string().nullable().optional(), + field: z.string().nullable().optional(), + md5hash: z.string().nullable().optional(), + original_id: z.union([z.string(), z.array(z.string())]).optional(), + original_basename: z.string().nullable().optional(), + original_path: z.string().nullable().optional(), + original_md5hash: z.string().nullable().optional(), + from_batch_import: z.boolean().optional(), + is_tus_file: z.boolean().optional(), + tus_upload_url: z.string().nullable().optional(), + is_temp_url: z.boolean().optional(), + cost: z.number().nullable().optional(), + duration_human: z.string().nullable().optional(), + duration: z.number().nullable().optional(), + exec_time: z.number().nullable().optional(), + ext: z.string().nullable().optional(), + filepath: z.string().nullable().optional(), + path: z.string().nullable().optional(), + height: z.number().nullable().optional(), + meta: assemblyStatusMetaSchema.nullable().optional(), + mime: z.string().nullable().optional(), + name: z.string().nullable().optional(), + original_name: z.string().nullable().optional(), + preview: z.string().nullable().optional(), + queue_time: z.number().nullable().optional(), + queue: z.string().nullable().optional(), + size_human: z.string().nullable().optional(), + size: z.number().nullable().optional(), + ssl_url: z.string().nullable().optional(), + type: z.string().nullable().optional(), + url: z.string().nullable().optional(), + user_meta: z + .record(z.union([z.string(), z.number()])) + .nullable() + .optional(), + width: z.number().nullable().optional(), + as: z + .union([z.string(), z.array(z.string())]) + .nullable() + .optional(), + queueTime: z.number().nullable().optional(), + execTime: z.number().nullable().optional(), + import_url: z.string().optional(), + signed_url: z.string().optional(), + signed_ssl_url: z.string().optional(), + ios_url: z.string().optional(), + streaming_url: z.string().optional(), + remote_path: z.string().optional(), + playlists: z.array(hlsPlaylistSchema).optional(), + hls_url: z.string().optional(), + forcedFileExt: z.string().optional(), + // Robot-specific metadata added at runtime by /vimeo/import + vimeo: z + .object({ + title: z.string(), + uri: z.string(), + }) + .optional(), + }) + .passthrough() +export type AssemblyStatusResult = z.infer + +export const assemblyStatusResultsSchema = z.record(z.array(assemblyStatusResultSchema)) +export type AssemblyStatusResults = z.infer + +// Define a more specific schema for debuginfo if its structure is known +export const debugInfoSchema = z + .object({ + err: z.unknown().optional(), // Or a more specific error type if known + screenshot_ssl_url: z.string().optional(), + screenshot_filepath: z.string().optional(), + screenshot_s3_url: z.string().optional(), // Add s3 URL field + console_filepath: z.string().optional(), + console_ssl_url: z.string().optional(), // Add console SSL URL field + console_s3_url: z.string().optional(), // Add console s3 URL field + }) + .passthrough() + +export const assemblyStatusBaseSchema = z.object({ + message: z.string().optional(), + admin_cmd: z.unknown().optional(), + assemblyId: z.string().optional(), + assembly_id: z.string().optional(), + parent_id: z.string().nullable().optional(), + account_id: z.string().optional(), + account_name: z.string().nullable().optional(), + account_slug: z.string().nullable().optional(), + api_auth_key_id: z.string().nullable().optional(), + template_id: z.string().nullable().optional(), + template_name: z.string().nullable().optional(), + instance: z.string().optional(), + region: z.string().optional(), + assembly_url: z.string().optional(), + assembly_ssl_url: z.string().optional(), + uppyserver_url: z.string().optional(), + companion_url: z.string().optional(), + websocket_url: z.string().optional(), + update_stream_url: z.string().optional(), + tus_url: z.string().optional(), + bytes_received: z.number().optional(), + bytes_expected: z.number().nullable().optional(), + upload_duration: z.number().optional(), + client_agent: z.string().nullable().optional(), + client_ip: z.string().nullable().optional(), + client_referer: z.string().nullable().optional(), + transloadit_client: z.string().nullable().optional(), + start_date: z.string().optional(), + upload_meta_data_extracted: z.boolean().optional(), + warnings: z + .array( + z + .object({ level: z.literal('notice').or(z.literal('warning')), msg: z.string() }) + .passthrough(), + ) + .optional(), + is_infinite: z.boolean().optional(), + error: z.undefined().optional(), + has_dupe_jobs: z.boolean().optional(), + execution_start: z.string().nullable().optional(), + execution_duration: z.number().nullable().optional(), + queue_duration: z.number().optional(), + jobs_queue_duration: z.number().optional(), + notify_start: z.string().nullable().optional(), + notify_url: z.string().nullable().optional(), + notify_status: z.string().nullable().optional(), + notify_response_code: z.number().nullable().optional(), + notify_response_data: z.string().nullable().optional(), + notify_duration: z.number().nullable().optional(), + last_job_completed: z.string().nullable().optional(), + fields: z.record(z.unknown()).optional(), + running_jobs: z.array(z.string()).optional(), + bytes_usage: z.number().optional(), + usage_tags: z.string().optional(), + executing_jobs: z.array(z.string()).optional(), + started_jobs: z.array(z.string()).optional(), + parent_assembly_status: z.unknown().nullable().optional(), + params: z.string().nullable().optional(), + template: z.string().nullable().optional(), + merged_params: z.string().nullable().optional(), + num_input_files: z.number().optional(), + uploads: assemblyStatusUploadsSchema.optional(), + results: assemblyStatusResultsSchema.optional(), + build_id: z.string().optional(), + expected_tus_uploads: z.number().optional(), + started_tus_uploads: z.number().optional(), + finished_tus_uploads: z.number().optional(), + virusname: z.string().optional(), + tus_uploads: z + .array( + z + .object({ + filename: z.string(), + fieldname: z.string(), + user_meta: z.record(z.unknown()).optional(), + size: z.number(), + offset: z.number(), + finished: z.boolean(), + upload_url: z.string(), + local_path: z.string().optional(), + }) + .passthrough(), + ) + .optional(), + debuginfo: debugInfoSchema.optional(), + step: z.string().optional(), + previousStep: z.string().optional(), + worker: z.string().optional(), + info: z + .object({ + retryIn: z.number().optional(), + }) + .optional(), +}) + +export const assemblyStatusBusySchema = z + .object({ + ok: assemblyBusyCodeSchema, + // TODO: Does busy status also share base fields? Need example. + // Assuming for now it might share some base fields but not all recursively? + // Let's make it extend the *non-recursive* base for now. + }) + .extend(assemblyStatusBaseSchema.shape) + .passthrough() + +export const assemblyStatusOkSchema = assemblyStatusBaseSchema + .extend({ + ok: assemblyStatusOkCodeSchema, + }) + .passthrough() + +export const assemblyStatusErrSchema = assemblyStatusBaseSchema + .extend({ + error: assemblyStatusErrCodeSchema, + ok: z.null().optional(), + retries: z.number().optional(), + numRetries: z.number().optional(), + reason: z.string().optional(), + step: z.string().optional(), + previousStep: z.string().optional(), + path: z.string().optional(), + exitCode: z.number().nullable().optional(), + exitSignal: z.string().nullable().optional(), + stdout: z.string().optional(), + stderr: z.string().optional(), + cmd: z.union([z.string(), z.array(z.union([z.string(), z.number()]))]).optional(), + admin_cmd: z.union([z.string(), z.array(z.union([z.string(), z.number()]))]).optional(), + worker: z.string().optional(), + headers: z.record(z.unknown()).optional(), + retryable: z.boolean().optional(), + err: z.unknown().optional(), + }) + .passthrough() + +// Represents a low-level system error not mapped to standard assembly errors. +// Happened in Assemblies: +// - 13ca71f3b8714859b48ec11e49be10f1 +// - 14ef7ab868e84350b2c0b70c9f3b2df1 +// - 83b21b12c30b416f82651464635f05f1 +// - 9198732f03cf40adbf778ae28fd52ef1 +// - dfa372cef24a420092f1be42af6d1df1 +// - e975612bc76e4738b759d1b36bc527f1 +// All for Workspace: 6f86325febd14de4bfb38cbd04ee1f39 +export const assemblyStatusSysErrSchema = assemblyStatusBaseSchema + .extend({ + // Changed from .object() + // No 'ok' or 'error' discriminator + errno: z.number(), + code: z.string(), + syscall: z.string(), + path: z.string().optional(), // Path might be present + // Consider adding other potential sys error fields if observed later + }) + .passthrough() + +// Final schema defined lazily to handle recursion +// We break up inference to avoid: +// error TS7056: The inferred type of this node exceeds the maximum length the compiler will serialize. An explicit type annotation is needed. +export const assemblyStatusSchema: z.ZodUnion< + [ + typeof assemblyStatusBusySchema, + typeof assemblyStatusOkSchema, + typeof assemblyStatusErrSchema, + typeof assemblyStatusSysErrSchema, + ] +> = z.union([ + assemblyStatusBusySchema, // Use schema defined above + assemblyStatusOkSchema, // Use schema defined above + assemblyStatusErrSchema, // Use schema defined above + assemblyStatusSysErrSchema, // Add the new system error state +]) + +export type AssemblyStatus = z.infer + +/** + * Type guard to check if an assembly has an error. + */ +export function hasError( + assembly: AssemblyStatus | undefined | null, + particularErrorCode?: z.infer, +): assembly is AssemblyStatus & { error: string } { + const errorExists = + Boolean(assembly) && assembly != null && typeof assembly === 'object' && 'error' in assembly + + if (particularErrorCode) { + return errorExists && assembly.error === particularErrorCode + } + + return errorExists +} + +export function hasSpecificError( + assembly: AssemblyStatus | null | undefined, + errorType: string, +): boolean { + if (!assembly) return false + + if (hasError(assembly) && assembly.error === errorType) { + return true + } + + if (typeof assembly === 'object' && assembly !== null && errorType in assembly) { + const candidate = Reflect.get(assembly, errorType) + return Boolean(candidate) + } + + return false +} + +/** + * Type guard to check if an assembly has an ok status + */ +export function hasOk( + assembly: AssemblyStatus | undefined | null, + particularOkCode?: z.infer, +): assembly is AssemblyStatus & { ok: string } { + const okExists = + Boolean(assembly) && assembly != null && typeof assembly === 'object' && 'ok' in assembly + + if (particularOkCode) { + return okExists && assembly.ok === particularOkCode + } + return okExists +} + +/** + * Returns the error value if it exists or undefined + */ +export function getError(assembly: AssemblyStatus | undefined | null): string | undefined { + return assembly && assembly != null && typeof assembly === 'object' && 'error' in assembly + ? String(assembly.error) + : undefined +} + +/** + * Returns the ok value if it exists or undefined + */ +export function getOk(assembly: AssemblyStatus | undefined | null): string | undefined { + return assembly && assembly != null && typeof assembly === 'object' && 'ok' in assembly + ? String(assembly.ok) + : undefined +} + +/** + * This type and these functions below are compatibility helpers for + * working with partial assembly status objects during the transition + * from the old types to the new Zod-based schema. + */ +export type PartialAssemblyStatus = Partial + +export function hasErrorPartial( + assembly: PartialAssemblyStatus | undefined | null, +): assembly is PartialAssemblyStatus & { error: string } { + return ( + Boolean(assembly) && + assembly != null && + typeof assembly === 'object' && + 'error' in assembly && + Boolean(assembly.error) + ) +} + +export function hasOkPartial( + assembly: PartialAssemblyStatus | undefined | null, +): assembly is PartialAssemblyStatus & { ok: string } { + return ( + Boolean(assembly) && + assembly != null && + typeof assembly === 'object' && + 'ok' in assembly && + Boolean(assembly.ok) + ) +} + +// Schema for items returned by the List Assemblies endpoint +export const assemblyIndexItemSchema = z + .object({ + id: z.string(), // Likely always present for a list item + parent_id: assemblyStatusBaseSchema.shape.parent_id.optional(), + account_id: assemblyStatusBaseSchema.shape.account_id.unwrap().optional(), + template_id: assemblyStatusBaseSchema.shape.template_id.optional(), + instance: assemblyStatusBaseSchema.shape.instance.unwrap().optional(), + notify_url: assemblyStatusBaseSchema.shape.notify_url.optional(), + redirect_url: z.string().nullable().optional(), + files: z.string().nullable(), // JSON stringified, specific to list item, CAN BE NULL + warning_count: z.number().optional(), + execution_duration: assemblyStatusBaseSchema.shape.execution_duration.optional(), + execution_start: assemblyStatusBaseSchema.shape.execution_start.optional(), + region: assemblyStatusBaseSchema.shape.region.optional(), + num_input_files: assemblyStatusBaseSchema.shape.num_input_files.optional(), + bytes_usage: assemblyStatusBaseSchema.shape.bytes_usage.optional(), + ok: assemblyStatusOkCodeSchema.nullable().optional(), + error: assemblyStatusErrCodeSchema.nullable().optional(), + created: z.string(), + created_ts: z.number().optional(), + template_name: z.string().nullable().optional(), + }) + .passthrough() + +export type AssemblyIndexItem = z.infer + +export const assemblyIndexSchema = z.array(assemblyIndexItemSchema) +export type AssemblyIndex = z.infer diff --git a/packages/transloadit/src/alphalib/types/bill.ts b/packages/transloadit/src/alphalib/types/bill.ts new file mode 100644 index 00000000..e8e08099 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/bill.ts @@ -0,0 +1,9 @@ +import { z } from 'zod' + +import { assemblyAuthInstructionsSchema } from './template.ts' + +export const billSchema = z + .object({ + auth: assemblyAuthInstructionsSchema, + }) + .strict() diff --git a/packages/transloadit/src/alphalib/types/robots/_index.ts b/packages/transloadit/src/alphalib/types/robots/_index.ts new file mode 100644 index 00000000..7cc47a21 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/_index.ts @@ -0,0 +1,1201 @@ +import { z } from 'zod' +import { + meta as aiChatMeta, + interpolatableRobotAiChatInstructionsSchema, + interpolatableRobotAiChatInstructionsWithHiddenFieldsSchema, +} from './ai-chat.ts' +import { + meta as audioArtworkMeta, + interpolatableRobotAudioArtworkInstructionsSchema, + interpolatableRobotAudioArtworkInstructionsWithHiddenFieldsSchema, +} from './audio-artwork.ts' +import { + meta as audioConcatMeta, + interpolatableRobotAudioConcatInstructionsSchema, + interpolatableRobotAudioConcatInstructionsWithHiddenFieldsSchema, +} from './audio-concat.ts' +import { + meta as audioEncodeMeta, + interpolatableRobotAudioEncodeInstructionsSchema, + interpolatableRobotAudioEncodeInstructionsWithHiddenFieldsSchema, +} from './audio-encode.ts' +import { + meta as audioLoopMeta, + interpolatableRobotAudioLoopInstructionsSchema, + interpolatableRobotAudioLoopInstructionsWithHiddenFieldsSchema, +} from './audio-loop.ts' +import { + meta as audioMergeMeta, + interpolatableRobotAudioMergeInstructionsSchema, + interpolatableRobotAudioMergeInstructionsWithHiddenFieldsSchema, +} from './audio-merge.ts' +import { + meta as audioWaveformMeta, + interpolatableRobotAudioWaveformInstructionsSchema, + interpolatableRobotAudioWaveformInstructionsWithHiddenFieldsSchema, +} from './audio-waveform.ts' +import { + meta as azureImportMeta, + interpolatableRobotAzureImportInstructionsSchema, + interpolatableRobotAzureImportInstructionsWithHiddenFieldsSchema, +} from './azure-import.ts' +import { + meta as azureStoreMeta, + interpolatableRobotAzureStoreInstructionsSchema, + interpolatableRobotAzureStoreInstructionsWithHiddenFieldsSchema, +} from './azure-store.ts' +import { + meta as backblazeImportMeta, + interpolatableRobotBackblazeImportInstructionsSchema, + interpolatableRobotBackblazeImportInstructionsWithHiddenFieldsSchema, +} from './backblaze-import.ts' +import { + meta as backblazeStoreMeta, + interpolatableRobotBackblazeStoreInstructionsSchema, + interpolatableRobotBackblazeStoreInstructionsWithHiddenFieldsSchema, +} from './backblaze-store.ts' +import { + meta as cloudfilesImportMeta, + interpolatableRobotCloudfilesImportInstructionsSchema, + interpolatableRobotCloudfilesImportInstructionsWithHiddenFieldsSchema, +} from './cloudfiles-import.ts' +import { + meta as cloudfilesStoreMeta, + interpolatableRobotCloudfilesStoreInstructionsSchema, + interpolatableRobotCloudfilesStoreInstructionsWithHiddenFieldsSchema, +} from './cloudfiles-store.ts' +import { + meta as cloudflareImportMeta, + interpolatableRobotCloudflareImportInstructionsSchema, + interpolatableRobotCloudflareImportInstructionsWithHiddenFieldsSchema, +} from './cloudflare-import.ts' +import { + meta as cloudflareStoreMeta, + interpolatableRobotCloudflareStoreInstructionsSchema, + interpolatableRobotCloudflareStoreInstructionsWithHiddenFieldsSchema, +} from './cloudflare-store.ts' +import { + meta as digitaloceanImportMeta, + interpolatableRobotDigitaloceanImportInstructionsSchema, + interpolatableRobotDigitaloceanImportInstructionsWithHiddenFieldsSchema, +} from './digitalocean-import.ts' +import { + meta as digitaloceanStoreMeta, + interpolatableRobotDigitaloceanStoreInstructionsSchema, + interpolatableRobotDigitaloceanStoreInstructionsWithHiddenFieldsSchema, +} from './digitalocean-store.ts' +import { + meta as documentAutorotateMeta, + interpolatableRobotDocumentAutorotateInstructionsSchema, + interpolatableRobotDocumentAutorotateInstructionsWithHiddenFieldsSchema, +} from './document-autorotate.ts' +import { + meta as documentConvertMeta, + interpolatableRobotDocumentConvertInstructionsSchema, + interpolatableRobotDocumentConvertInstructionsWithHiddenFieldsSchema, +} from './document-convert.ts' +import { + meta as documentMergeMeta, + interpolatableRobotDocumentMergeInstructionsSchema, + interpolatableRobotDocumentMergeInstructionsWithHiddenFieldsSchema, +} from './document-merge.ts' +import { + meta as documentOcrMeta, + interpolatableRobotDocumentOcrInstructionsSchema, + interpolatableRobotDocumentOcrInstructionsWithHiddenFieldsSchema, +} from './document-ocr.ts' +import { + meta as documentSplitMeta, + interpolatableRobotDocumentSplitInstructionsSchema, + interpolatableRobotDocumentSplitInstructionsWithHiddenFieldsSchema, +} from './document-split.ts' +import { + meta as documentThumbsMeta, + interpolatableRobotDocumentThumbsInstructionsSchema, + interpolatableRobotDocumentThumbsInstructionsWithHiddenFieldsSchema, +} from './document-thumbs.ts' +import { + meta as dropboxImportMeta, + interpolatableRobotDropboxImportInstructionsSchema, + interpolatableRobotDropboxImportInstructionsWithHiddenFieldsSchema, +} from './dropbox-import.ts' +import { + meta as dropboxStoreMeta, + interpolatableRobotDropboxStoreInstructionsSchema, + interpolatableRobotDropboxStoreInstructionsWithHiddenFieldsSchema, +} from './dropbox-store.ts' +import { + meta as edglyDeliverMeta, + interpolatableRobotEdglyDeliverInstructionsSchema, + interpolatableRobotEdglyDeliverInstructionsWithHiddenFieldsSchema, +} from './edgly-deliver.ts' +import { + meta as fileCompressMeta, + interpolatableRobotFileCompressInstructionsSchema, + interpolatableRobotFileCompressInstructionsWithHiddenFieldsSchema, +} from './file-compress.ts' +import { + meta as fileDecompressMeta, + interpolatableRobotFileDecompressInstructionsSchema, + interpolatableRobotFileDecompressInstructionsWithHiddenFieldsSchema, +} from './file-decompress.ts' +import { + meta as fileFilterMeta, + interpolatableRobotFileFilterInstructionsSchema, + interpolatableRobotFileFilterInstructionsWithHiddenFieldsSchema, +} from './file-filter.ts' +import { + meta as fileHashMeta, + interpolatableRobotFileHashInstructionsSchema, + interpolatableRobotFileHashInstructionsWithHiddenFieldsSchema, +} from './file-hash.ts' +import { + meta as filePreviewMeta, + interpolatableRobotFilePreviewInstructionsSchema, + interpolatableRobotFilePreviewInstructionsWithHiddenFieldsSchema, +} from './file-preview.ts' +import { + meta as fileReadMeta, + interpolatableRobotFileReadInstructionsSchema, + interpolatableRobotFileReadInstructionsWithHiddenFieldsSchema, +} from './file-read.ts' +import { + meta as fileServeMeta, + interpolatableRobotFileServeInstructionsSchema, + interpolatableRobotFileServeInstructionsWithHiddenFieldsSchema, +} from './file-serve.ts' +import { + meta as fileVerifyMeta, + interpolatableRobotFileVerifyInstructionsSchema, + interpolatableRobotFileVerifyInstructionsWithHiddenFieldsSchema, +} from './file-verify.ts' +import { + meta as fileVirusscanMeta, + interpolatableRobotFileVirusscanInstructionsSchema, + interpolatableRobotFileVirusscanInstructionsWithHiddenFieldsSchema, +} from './file-virusscan.ts' +import { + interpolatableRobotFileWatermarkInstructionsSchema, + interpolatableRobotFileWatermarkInstructionsWithHiddenFieldsSchema, +} from './file-watermark.ts' +import { + meta as ftpImportMeta, + interpolatableRobotFtpImportInstructionsSchema, + interpolatableRobotFtpImportInstructionsWithHiddenFieldsSchema, +} from './ftp-import.ts' +import { + meta as ftpStoreMeta, + interpolatableRobotFtpStoreInstructionsSchema, + interpolatableRobotFtpStoreInstructionsWithHiddenFieldsSchema, +} from './ftp-store.ts' +import { + meta as googleImportMeta, + interpolatableRobotGoogleImportInstructionsSchema, + interpolatableRobotGoogleImportInstructionsWithHiddenFieldsSchema, +} from './google-import.ts' +import { + meta as googleStoreMeta, + interpolatableRobotGoogleStoreInstructionsSchema, + interpolatableRobotGoogleStoreInstructionsWithHiddenFieldsSchema, +} from './google-store.ts' +import { + meta as htmlConvertMeta, + interpolatableRobotHtmlConvertInstructionsSchema, + interpolatableRobotHtmlConvertInstructionsWithHiddenFieldsSchema, +} from './html-convert.ts' +import { + meta as httpImportMeta, + interpolatableRobotHttpImportInstructionsSchema, + interpolatableRobotHttpImportInstructionsWithHiddenFieldsSchema, +} from './http-import.ts' +import { + meta as imageBgremoveMeta, + interpolatableRobotImageBgremoveInstructionsSchema, + interpolatableRobotImageBgremoveInstructionsWithHiddenFieldsSchema, +} from './image-bgremove.ts' +import { + meta as imageDescribeMeta, + interpolatableRobotImageDescribeInstructionsSchema, + interpolatableRobotImageDescribeInstructionsWithHiddenFieldsSchema, +} from './image-describe.ts' +import { + meta as imageFacedetectMeta, + interpolatableRobotImageFacedetectInstructionsSchema, + interpolatableRobotImageFacedetectInstructionsWithHiddenFieldsSchema, +} from './image-facedetect.ts' +import { + meta as imageGenerateMeta, + interpolatableRobotImageGenerateInstructionsSchema, + interpolatableRobotImageGenerateInstructionsWithHiddenFieldsSchema, +} from './image-generate.ts' +import { + meta as imageMergeMeta, + interpolatableRobotImageMergeInstructionsSchema, + interpolatableRobotImageMergeInstructionsWithHiddenFieldsSchema, +} from './image-merge.ts' +import { + meta as imageOcrMeta, + interpolatableRobotImageOcrInstructionsSchema, + interpolatableRobotImageOcrInstructionsWithHiddenFieldsSchema, +} from './image-ocr.ts' +import { + meta as imageOptimizeMeta, + interpolatableRobotImageOptimizeInstructionsSchema, + interpolatableRobotImageOptimizeInstructionsWithHiddenFieldsSchema, +} from './image-optimize.ts' +import { + meta as imageResizeMeta, + interpolatableRobotImageResizeInstructionsSchema, + interpolatableRobotImageResizeInstructionsWithHiddenFieldsSchema, +} from './image-resize.ts' +import { + interpolatableRobotMetaReadInstructionsSchema, + interpolatableRobotMetaReadInstructionsWithHiddenFieldsSchema, +} from './meta-read.ts' +import { + interpolatableRobotMetaWriteInstructionsSchema, + interpolatableRobotMetaWriteInstructionsWithHiddenFieldsSchema, + meta as metaWriteMeta, +} from './meta-write.ts' +import { + interpolatableRobotMinioImportInstructionsSchema, + interpolatableRobotMinioImportInstructionsWithHiddenFieldsSchema, + meta as minioImportMeta, +} from './minio-import.ts' +import { + interpolatableRobotMinioStoreInstructionsSchema, + interpolatableRobotMinioStoreInstructionsWithHiddenFieldsSchema, + meta as minioStoreMeta, +} from './minio-store.ts' +import { interpolatableRobotProgressSimulateInstructionsSchema } from './progress-simulate.ts' +import { + interpolatableRobotS3ImportInstructionsSchema, + interpolatableRobotS3ImportInstructionsWithHiddenFieldsSchema, + meta as s3ImportMeta, +} from './s3-import.ts' +import { + interpolatableRobotS3StoreInstructionsSchema, + interpolatableRobotS3StoreInstructionsWithHiddenFieldsSchema, + meta as s3StoreMeta, +} from './s3-store.ts' +import { + interpolatableRobotScriptRunInstructionsSchema, + interpolatableRobotScriptRunInstructionsWithHiddenFieldsSchema, + meta as scriptRunMeta, +} from './script-run.ts' +import { + interpolatableRobotSftpImportInstructionsSchema, + interpolatableRobotSftpImportInstructionsWithHiddenFieldsSchema, + meta as sftpImportMeta, +} from './sftp-import.ts' +import { + interpolatableRobotSftpStoreInstructionsSchema, + interpolatableRobotSftpStoreInstructionsWithHiddenFieldsSchema, + meta as sftpStoreMeta, +} from './sftp-store.ts' +import { + interpolatableRobotSpeechTranscribeInstructionsSchema, + interpolatableRobotSpeechTranscribeInstructionsWithHiddenFieldsSchema, + meta as speechTranscribeMeta, +} from './speech-transcribe.ts' +import { + interpolatableRobotSupabaseImportInstructionsSchema, + interpolatableRobotSupabaseImportInstructionsWithHiddenFieldsSchema, + meta as supabaseImportMeta, +} from './supabase-import.ts' +import { + interpolatableRobotSupabaseStoreInstructionsSchema, + interpolatableRobotSupabaseStoreInstructionsWithHiddenFieldsSchema, + meta as supabaseStoreMeta, +} from './supabase-store.ts' +import { + interpolatableRobotSwiftImportInstructionsSchema, + interpolatableRobotSwiftImportInstructionsWithHiddenFieldsSchema, + meta as swiftImportMeta, +} from './swift-import.ts' +import { + interpolatableRobotSwiftStoreInstructionsSchema, + interpolatableRobotSwiftStoreInstructionsWithHiddenFieldsSchema, + meta as swiftStoreMeta, +} from './swift-store.ts' +import { + interpolatableRobotTextSpeakInstructionsSchema, + interpolatableRobotTextSpeakInstructionsWithHiddenFieldsSchema, + meta as textSpeakMeta, +} from './text-speak.ts' +import { + interpolatableRobotTextTranslateInstructionsSchema, + interpolatableRobotTextTranslateInstructionsWithHiddenFieldsSchema, + meta as textTranslateMeta, +} from './text-translate.ts' +import { + interpolatableRobotTigrisImportInstructionsSchema, + interpolatableRobotTigrisImportInstructionsWithHiddenFieldsSchema, + meta as tigrisImport, +} from './tigris-import.ts' +import { + interpolatableRobotTigrisStoreInstructionsSchema, + interpolatableRobotTigrisStoreInstructionsWithHiddenFieldsSchema, + meta as tigrisStore, +} from './tigris-store.ts' +import { + interpolatableRobotTlcdnDeliverInstructionsSchema, + interpolatableRobotTlcdnDeliverInstructionsWithHiddenFieldsSchema, + meta as tlcdnDeliverMeta, +} from './tlcdn-deliver.ts' +import { + interpolatableRobotTusStoreInstructionsSchema, + interpolatableRobotTusStoreInstructionsWithHiddenFieldsSchema, + meta as tusStoreMeta, +} from './tus-store.ts' +import { + interpolatableRobotUploadHandleInstructionsSchema, + interpolatableRobotUploadHandleInstructionsWithHiddenFieldsSchema, + meta as uploadHandleMeta, +} from './upload-handle.ts' +import { + interpolatableRobotVideoAdaptiveInstructionsSchema, + interpolatableRobotVideoAdaptiveInstructionsWithHiddenFieldsSchema, + meta as videoAdaptiveMeta, +} from './video-adaptive.ts' +import { + interpolatableRobotVideoConcatInstructionsSchema, + interpolatableRobotVideoConcatInstructionsWithHiddenFieldsSchema, + meta as videoConcatMeta, +} from './video-concat.ts' +import { + interpolatableRobotVideoEncodeInstructionsSchema, + interpolatableRobotVideoEncodeInstructionsWithHiddenFieldsSchema, + meta as videoEncodeMeta, +} from './video-encode.ts' +import { + interpolatableRobotVideoMergeInstructionsSchema, + interpolatableRobotVideoMergeInstructionsWithHiddenFieldsSchema, + meta as videoMergeMeta, +} from './video-merge.ts' +import { + interpolatableRobotVideoOndemandInstructionsSchema, + interpolatableRobotVideoOndemandInstructionsWithHiddenFieldsSchema, + meta as videoOndemandMeta, +} from './video-ondemand.ts' +import { + interpolatableRobotVideoSubtitleInstructionsSchema, + interpolatableRobotVideoSubtitleInstructionsWithHiddenFieldsSchema, + meta as videoSubtitleMeta, +} from './video-subtitle.ts' +import { + interpolatableRobotVideoThumbsInstructionsSchema, + interpolatableRobotVideoThumbsInstructionsWithHiddenFieldsSchema, + meta as videoThumbsMeta, +} from './video-thumbs.ts' +import { + interpolatableRobotVimeoImportInstructionsSchema, + interpolatableRobotVimeoImportInstructionsWithHiddenFieldsSchema, + meta as vimeoImportMeta, +} from './vimeo-import.ts' +import { + interpolatableRobotVimeoStoreInstructionsSchema, + interpolatableRobotVimeoStoreInstructionsWithHiddenFieldsSchema, + meta as vimeoStoreMeta, +} from './vimeo-store.ts' +import { + interpolatableRobotWasabiImportInstructionsSchema, + interpolatableRobotWasabiImportInstructionsWithHiddenFieldsSchema, + meta as wasabiImportMeta, +} from './wasabi-import.ts' +import { + interpolatableRobotWasabiStoreInstructionsSchema, + interpolatableRobotWasabiStoreInstructionsWithHiddenFieldsSchema, + meta as wasabiStoreMeta, +} from './wasabi-store.ts' +import { + interpolatableRobotYoutubeStoreInstructionsSchema, + interpolatableRobotYoutubeStoreInstructionsWithHiddenFieldsSchema, + meta as youtubeStoreMeta, +} from './youtube-store.ts' + +const robotStepsInstructions = [ + interpolatableRobotAudioArtworkInstructionsSchema, + interpolatableRobotAudioConcatInstructionsSchema, + interpolatableRobotAudioEncodeInstructionsSchema, + interpolatableRobotAudioLoopInstructionsSchema, + interpolatableRobotAudioMergeInstructionsSchema, + interpolatableRobotAudioWaveformInstructionsSchema, + interpolatableRobotAzureImportInstructionsSchema, + interpolatableRobotAzureStoreInstructionsSchema, + interpolatableRobotBackblazeImportInstructionsSchema, + interpolatableRobotBackblazeStoreInstructionsSchema, + interpolatableRobotCloudfilesImportInstructionsSchema, + interpolatableRobotCloudfilesStoreInstructionsSchema, + interpolatableRobotCloudflareImportInstructionsSchema, + interpolatableRobotCloudflareStoreInstructionsSchema, + interpolatableRobotDigitaloceanImportInstructionsSchema, + interpolatableRobotDigitaloceanStoreInstructionsSchema, + interpolatableRobotDocumentAutorotateInstructionsSchema, + interpolatableRobotDocumentConvertInstructionsSchema, + interpolatableRobotDocumentMergeInstructionsSchema, + interpolatableRobotDocumentOcrInstructionsSchema, + interpolatableRobotFileReadInstructionsSchema, + interpolatableRobotDocumentSplitInstructionsSchema, + interpolatableRobotDocumentThumbsInstructionsSchema, + interpolatableRobotDropboxImportInstructionsSchema, + interpolatableRobotDropboxStoreInstructionsSchema, + interpolatableRobotEdglyDeliverInstructionsSchema, + interpolatableRobotFileCompressInstructionsSchema, + interpolatableRobotFileDecompressInstructionsSchema, + interpolatableRobotFileFilterInstructionsSchema, + interpolatableRobotFileHashInstructionsSchema, + interpolatableRobotFilePreviewInstructionsSchema, + interpolatableRobotFileServeInstructionsSchema, + interpolatableRobotFileVerifyInstructionsSchema, + interpolatableRobotFileVirusscanInstructionsSchema, + interpolatableRobotFtpImportInstructionsSchema, + interpolatableRobotFtpStoreInstructionsSchema, + interpolatableRobotGoogleImportInstructionsSchema, + interpolatableRobotGoogleStoreInstructionsSchema, + interpolatableRobotHtmlConvertInstructionsSchema, + interpolatableRobotHttpImportInstructionsSchema, + interpolatableRobotImageBgremoveInstructionsSchema, + interpolatableRobotImageDescribeInstructionsSchema, + interpolatableRobotImageFacedetectInstructionsSchema, + interpolatableRobotImageGenerateInstructionsSchema, + interpolatableRobotImageMergeInstructionsSchema, + interpolatableRobotImageOcrInstructionsSchema, + interpolatableRobotImageOptimizeInstructionsSchema, + interpolatableRobotImageResizeInstructionsSchema, + interpolatableRobotMetaWriteInstructionsSchema, + interpolatableRobotMinioImportInstructionsSchema, + interpolatableRobotMinioStoreInstructionsSchema, + interpolatableRobotS3ImportInstructionsSchema, + interpolatableRobotS3StoreInstructionsSchema, + interpolatableRobotScriptRunInstructionsSchema, + interpolatableRobotSftpImportInstructionsSchema, + interpolatableRobotSftpStoreInstructionsSchema, + interpolatableRobotSpeechTranscribeInstructionsSchema, + interpolatableRobotSupabaseImportInstructionsSchema, + interpolatableRobotSupabaseStoreInstructionsSchema, + interpolatableRobotSwiftImportInstructionsSchema, + interpolatableRobotSwiftStoreInstructionsSchema, + interpolatableRobotTextSpeakInstructionsSchema, + interpolatableRobotTextTranslateInstructionsSchema, + interpolatableRobotAiChatInstructionsSchema, + interpolatableRobotTigrisImportInstructionsSchema, + interpolatableRobotTigrisStoreInstructionsSchema, + interpolatableRobotTlcdnDeliverInstructionsSchema, + interpolatableRobotTusStoreInstructionsSchema, + interpolatableRobotUploadHandleInstructionsSchema, + interpolatableRobotVideoAdaptiveInstructionsSchema, + interpolatableRobotVideoConcatInstructionsSchema, + interpolatableRobotVideoEncodeInstructionsSchema, + interpolatableRobotVideoMergeInstructionsSchema, + interpolatableRobotVideoOndemandInstructionsSchema, + interpolatableRobotVideoSubtitleInstructionsSchema, + interpolatableRobotVideoThumbsInstructionsSchema, + interpolatableRobotVimeoImportInstructionsSchema, + interpolatableRobotVimeoStoreInstructionsSchema, + interpolatableRobotWasabiImportInstructionsSchema, + interpolatableRobotWasabiStoreInstructionsSchema, + interpolatableRobotYoutubeStoreInstructionsSchema, +] as const + +const robotStepsInstructionsWithHiddenFields = [ + interpolatableRobotAudioArtworkInstructionsWithHiddenFieldsSchema, + interpolatableRobotAudioConcatInstructionsWithHiddenFieldsSchema, + interpolatableRobotAudioEncodeInstructionsWithHiddenFieldsSchema, + interpolatableRobotAudioLoopInstructionsWithHiddenFieldsSchema, + interpolatableRobotAudioMergeInstructionsWithHiddenFieldsSchema, + interpolatableRobotAudioWaveformInstructionsWithHiddenFieldsSchema, + interpolatableRobotAzureImportInstructionsWithHiddenFieldsSchema, + interpolatableRobotAzureStoreInstructionsWithHiddenFieldsSchema, + interpolatableRobotBackblazeImportInstructionsWithHiddenFieldsSchema, + interpolatableRobotBackblazeStoreInstructionsWithHiddenFieldsSchema, + interpolatableRobotCloudfilesImportInstructionsWithHiddenFieldsSchema, + interpolatableRobotCloudfilesStoreInstructionsWithHiddenFieldsSchema, + interpolatableRobotCloudflareImportInstructionsWithHiddenFieldsSchema, + interpolatableRobotCloudflareStoreInstructionsWithHiddenFieldsSchema, + interpolatableRobotDigitaloceanImportInstructionsWithHiddenFieldsSchema, + interpolatableRobotDigitaloceanStoreInstructionsWithHiddenFieldsSchema, + interpolatableRobotDocumentAutorotateInstructionsWithHiddenFieldsSchema, + interpolatableRobotDocumentConvertInstructionsWithHiddenFieldsSchema, + interpolatableRobotDocumentMergeInstructionsWithHiddenFieldsSchema, + interpolatableRobotDocumentOcrInstructionsWithHiddenFieldsSchema, + interpolatableRobotFileReadInstructionsWithHiddenFieldsSchema, + interpolatableRobotDocumentSplitInstructionsWithHiddenFieldsSchema, + interpolatableRobotDocumentThumbsInstructionsWithHiddenFieldsSchema, + interpolatableRobotDropboxImportInstructionsWithHiddenFieldsSchema, + interpolatableRobotDropboxStoreInstructionsWithHiddenFieldsSchema, + interpolatableRobotEdglyDeliverInstructionsWithHiddenFieldsSchema, + interpolatableRobotFileCompressInstructionsWithHiddenFieldsSchema, + interpolatableRobotFileDecompressInstructionsWithHiddenFieldsSchema, + interpolatableRobotFileFilterInstructionsWithHiddenFieldsSchema, + interpolatableRobotFileHashInstructionsWithHiddenFieldsSchema, + interpolatableRobotFilePreviewInstructionsWithHiddenFieldsSchema, + interpolatableRobotFileServeInstructionsWithHiddenFieldsSchema, + interpolatableRobotFileVerifyInstructionsWithHiddenFieldsSchema, + interpolatableRobotFileVirusscanInstructionsWithHiddenFieldsSchema, + interpolatableRobotFileWatermarkInstructionsWithHiddenFieldsSchema, + interpolatableRobotFtpImportInstructionsWithHiddenFieldsSchema, + interpolatableRobotFtpStoreInstructionsWithHiddenFieldsSchema, + interpolatableRobotGoogleImportInstructionsWithHiddenFieldsSchema, + interpolatableRobotGoogleStoreInstructionsWithHiddenFieldsSchema, + interpolatableRobotHtmlConvertInstructionsWithHiddenFieldsSchema, + interpolatableRobotHttpImportInstructionsWithHiddenFieldsSchema, + interpolatableRobotImageBgremoveInstructionsWithHiddenFieldsSchema, + interpolatableRobotImageDescribeInstructionsWithHiddenFieldsSchema, + interpolatableRobotImageFacedetectInstructionsWithHiddenFieldsSchema, + interpolatableRobotImageGenerateInstructionsWithHiddenFieldsSchema, + interpolatableRobotImageMergeInstructionsWithHiddenFieldsSchema, + interpolatableRobotImageOcrInstructionsWithHiddenFieldsSchema, + interpolatableRobotImageOptimizeInstructionsWithHiddenFieldsSchema, + interpolatableRobotImageResizeInstructionsWithHiddenFieldsSchema, + interpolatableRobotMetaWriteInstructionsWithHiddenFieldsSchema, + interpolatableRobotMinioImportInstructionsWithHiddenFieldsSchema, + interpolatableRobotMinioStoreInstructionsWithHiddenFieldsSchema, + interpolatableRobotS3ImportInstructionsWithHiddenFieldsSchema, + interpolatableRobotS3StoreInstructionsWithHiddenFieldsSchema, + interpolatableRobotScriptRunInstructionsWithHiddenFieldsSchema, + interpolatableRobotSftpImportInstructionsWithHiddenFieldsSchema, + interpolatableRobotSftpStoreInstructionsWithHiddenFieldsSchema, + interpolatableRobotSpeechTranscribeInstructionsWithHiddenFieldsSchema, + interpolatableRobotSupabaseImportInstructionsWithHiddenFieldsSchema, + interpolatableRobotSupabaseStoreInstructionsWithHiddenFieldsSchema, + interpolatableRobotSwiftImportInstructionsWithHiddenFieldsSchema, + interpolatableRobotSwiftStoreInstructionsWithHiddenFieldsSchema, + interpolatableRobotTextSpeakInstructionsWithHiddenFieldsSchema, + interpolatableRobotTextTranslateInstructionsWithHiddenFieldsSchema, + interpolatableRobotAiChatInstructionsWithHiddenFieldsSchema, + interpolatableRobotTigrisImportInstructionsWithHiddenFieldsSchema, + interpolatableRobotTigrisStoreInstructionsWithHiddenFieldsSchema, + interpolatableRobotTlcdnDeliverInstructionsWithHiddenFieldsSchema, + interpolatableRobotTusStoreInstructionsWithHiddenFieldsSchema, + interpolatableRobotUploadHandleInstructionsWithHiddenFieldsSchema, + interpolatableRobotVideoAdaptiveInstructionsWithHiddenFieldsSchema, + interpolatableRobotVideoConcatInstructionsWithHiddenFieldsSchema, + interpolatableRobotVideoEncodeInstructionsWithHiddenFieldsSchema, + interpolatableRobotVideoMergeInstructionsWithHiddenFieldsSchema, + interpolatableRobotVideoOndemandInstructionsWithHiddenFieldsSchema, + interpolatableRobotVideoSubtitleInstructionsWithHiddenFieldsSchema, + interpolatableRobotVideoThumbsInstructionsWithHiddenFieldsSchema, + interpolatableRobotVimeoImportInstructionsWithHiddenFieldsSchema, + interpolatableRobotVimeoStoreInstructionsWithHiddenFieldsSchema, + interpolatableRobotWasabiImportInstructionsWithHiddenFieldsSchema, + interpolatableRobotWasabiStoreInstructionsWithHiddenFieldsSchema, + interpolatableRobotYoutubeStoreInstructionsWithHiddenFieldsSchema, +] as const + +/** + * Public robot instructions + */ +export type RobotsSchema = z.infer +export const robotsSchema = z.discriminatedUnion('robot', [...robotStepsInstructions]) +export const robotsWithHiddenFieldsSchema = z.discriminatedUnion('robot', [ + ...robotStepsInstructionsWithHiddenFields, +]) + +/** + * All robot instructions, including private ones. + */ +export const robotsWithHiddenBotsSchema = z.discriminatedUnion('robot', [ + ...robotStepsInstructions, + interpolatableRobotFileWatermarkInstructionsSchema, + interpolatableRobotMetaReadInstructionsSchema, + interpolatableRobotProgressSimulateInstructionsSchema, +]) +export const robotsWithHiddenBotsAndFieldsSchema = z.discriminatedUnion('robot', [ + ...robotStepsInstructionsWithHiddenFields, + interpolatableRobotMetaReadInstructionsWithHiddenFieldsSchema, + interpolatableRobotProgressSimulateInstructionsSchema, +]) + +export type RobotsWithHiddenBots = z.infer +export type RobotsWithHiddenBotsAndFields = z.infer + +export const robotsMeta = { + aiChatMeta, + audioArtworkMeta, + audioConcatMeta, + audioEncodeMeta, + audioLoopMeta, + audioMergeMeta, + audioWaveformMeta, + azureImportMeta, + azureStoreMeta, + backblazeImportMeta, + backblazeStoreMeta, + cloudfilesImportMeta, + cloudfilesStoreMeta, + cloudflareImportMeta, + cloudflareStoreMeta, + digitaloceanImportMeta, + digitaloceanStoreMeta, + documentAutorotateMeta, + documentConvertMeta, + documentMergeMeta, + documentOcrMeta, + documentSplitMeta, + documentThumbsMeta, + dropboxImportMeta, + dropboxStoreMeta, + edglyDeliverMeta, + fileCompressMeta, + fileDecompressMeta, + fileFilterMeta, + fileHashMeta, + filePreviewMeta, + fileReadMeta, + fileServeMeta, + fileVerifyMeta, + fileVirusscanMeta, + ftpImportMeta, + ftpStoreMeta, + googleImportMeta, + googleStoreMeta, + htmlConvertMeta, + httpImportMeta, + imageDescribeMeta, + imageFacedetectMeta, + imageBgremoveMeta, + imageGenerateMeta, + imageMergeMeta, + imageOcrMeta, + imageOptimizeMeta, + imageResizeMeta, + metaWriteMeta, + minioImportMeta, + minioStoreMeta, + s3ImportMeta, + s3StoreMeta, + scriptRunMeta, + sftpImportMeta, + sftpStoreMeta, + speechTranscribeMeta, + supabaseImportMeta, + supabaseStoreMeta, + swiftImportMeta, + swiftStoreMeta, + textSpeakMeta, + textTranslateMeta, + tigrisImport, + tigrisStore, + tlcdnDeliverMeta, + tusStoreMeta, + uploadHandleMeta, + videoAdaptiveMeta, + videoConcatMeta, + videoEncodeMeta, + videoMergeMeta, + videoOndemandMeta, + videoSubtitleMeta, + videoThumbsMeta, + vimeoImportMeta, + vimeoStoreMeta, + wasabiImportMeta, + wasabiStoreMeta, + youtubeStoreMeta, +} + +export type { + InterpolatableRobotAiChatInstructions, + InterpolatableRobotAiChatInstructionsInput, + InterpolatableRobotAiChatInstructionsWithHiddenFields, + InterpolatableRobotAiChatInstructionsWithHiddenFieldsInput, +} from './ai-chat.ts' +export type { + InterpolatableRobotAssemblySavejsonInstructions, + InterpolatableRobotAssemblySavejsonInstructionsInput, +} from './assembly-savejson.ts' +export type { + InterpolatableRobotAudioArtworkInstructions, + InterpolatableRobotAudioArtworkInstructionsInput, + InterpolatableRobotAudioArtworkInstructionsWithHiddenFields, + InterpolatableRobotAudioArtworkInstructionsWithHiddenFieldsInput, +} from './audio-artwork.ts' +export type { + InterpolatableRobotAudioConcatInstructions, + InterpolatableRobotAudioConcatInstructionsInput, + InterpolatableRobotAudioConcatInstructionsWithHiddenFields, + InterpolatableRobotAudioConcatInstructionsWithHiddenFieldsInput, +} from './audio-concat.ts' +export type { + InterpolatableRobotAudioEncodeInstructions, + InterpolatableRobotAudioEncodeInstructionsInput, + InterpolatableRobotAudioEncodeInstructionsWithHiddenFields, + InterpolatableRobotAudioEncodeInstructionsWithHiddenFieldsInput, +} from './audio-encode.ts' +export type { + InterpolatableRobotAudioLoopInstructions, + InterpolatableRobotAudioLoopInstructionsInput, + InterpolatableRobotAudioLoopInstructionsWithHiddenFields, + InterpolatableRobotAudioLoopInstructionsWithHiddenFieldsInput, +} from './audio-loop.ts' +export type { + InterpolatableRobotAudioMergeInstructions, + InterpolatableRobotAudioMergeInstructionsInput, + InterpolatableRobotAudioMergeInstructionsWithHiddenFields, + InterpolatableRobotAudioMergeInstructionsWithHiddenFieldsInput, +} from './audio-merge.ts' +export type { + InterpolatableRobotAudioWaveformInstructions, + InterpolatableRobotAudioWaveformInstructionsInput, + InterpolatableRobotAudioWaveformInstructionsWithHiddenFields, + InterpolatableRobotAudioWaveformInstructionsWithHiddenFieldsInput, +} from './audio-waveform.ts' +export type { + InterpolatableRobotAzureImportInstructions, + InterpolatableRobotAzureImportInstructionsInput, + InterpolatableRobotAzureImportInstructionsWithHiddenFields, + InterpolatableRobotAzureImportInstructionsWithHiddenFieldsInput, +} from './azure-import.ts' +export type { + InterpolatableRobotAzureStoreInstructions, + InterpolatableRobotAzureStoreInstructionsInput, + InterpolatableRobotAzureStoreInstructionsWithHiddenFields, + InterpolatableRobotAzureStoreInstructionsWithHiddenFieldsInput, +} from './azure-store.ts' +export type { + InterpolatableRobotBackblazeImportInstructions, + InterpolatableRobotBackblazeImportInstructionsInput, + InterpolatableRobotBackblazeImportInstructionsWithHiddenFields, + InterpolatableRobotBackblazeImportInstructionsWithHiddenFieldsInput, +} from './backblaze-import.ts' +export type { + InterpolatableRobotBackblazeStoreInstructions, + InterpolatableRobotBackblazeStoreInstructionsInput, + InterpolatableRobotBackblazeStoreInstructionsWithHiddenFields, + InterpolatableRobotBackblazeStoreInstructionsWithHiddenFieldsInput, +} from './backblaze-store.ts' +export type { + InterpolatableRobotCloudfilesImportInstructions, + InterpolatableRobotCloudfilesImportInstructionsInput, + InterpolatableRobotCloudfilesImportInstructionsWithHiddenFields, + InterpolatableRobotCloudfilesImportInstructionsWithHiddenFieldsInput, +} from './cloudfiles-import.ts' +export type { + InterpolatableRobotCloudfilesStoreInstructions, + InterpolatableRobotCloudfilesStoreInstructionsInput, + InterpolatableRobotCloudfilesStoreInstructionsWithHiddenFields, + InterpolatableRobotCloudfilesStoreInstructionsWithHiddenFieldsInput, +} from './cloudfiles-store.ts' +export type { + InterpolatableRobotCloudflareImportInstructions, + InterpolatableRobotCloudflareImportInstructionsInput, + InterpolatableRobotCloudflareImportInstructionsWithHiddenFields, + InterpolatableRobotCloudflareImportInstructionsWithHiddenFieldsInput, +} from './cloudflare-import.ts' +export type { + InterpolatableRobotCloudflareStoreInstructions, + InterpolatableRobotCloudflareStoreInstructionsInput, + InterpolatableRobotCloudflareStoreInstructionsWithHiddenFields, + InterpolatableRobotCloudflareStoreInstructionsWithHiddenFieldsInput, +} from './cloudflare-store.ts' +export type { + InterpolatableRobotDigitaloceanImportInstructions, + InterpolatableRobotDigitaloceanImportInstructionsInput, + InterpolatableRobotDigitaloceanImportInstructionsWithHiddenFields, + InterpolatableRobotDigitaloceanImportInstructionsWithHiddenFieldsInput, +} from './digitalocean-import.ts' +export type { + InterpolatableRobotDigitaloceanStoreInstructions, + InterpolatableRobotDigitaloceanStoreInstructionsInput, + InterpolatableRobotDigitaloceanStoreInstructionsWithHiddenFields, + InterpolatableRobotDigitaloceanStoreInstructionsWithHiddenFieldsInput, +} from './digitalocean-store.ts' +export type { + InterpolatableRobotDocumentAutorotateInstructions, + InterpolatableRobotDocumentAutorotateInstructionsInput, + InterpolatableRobotDocumentAutorotateInstructionsWithHiddenFields, + InterpolatableRobotDocumentAutorotateInstructionsWithHiddenFieldsInput, +} from './document-autorotate.ts' +export type { + InterpolatableRobotDocumentConvertInstructions, + InterpolatableRobotDocumentConvertInstructionsInput, + InterpolatableRobotDocumentConvertInstructionsWithHiddenFields, + InterpolatableRobotDocumentConvertInstructionsWithHiddenFieldsInput, +} from './document-convert.ts' +export type { + InterpolatableRobotDocumentMergeInstructions, + InterpolatableRobotDocumentMergeInstructionsInput, + InterpolatableRobotDocumentMergeInstructionsWithHiddenFields, + InterpolatableRobotDocumentMergeInstructionsWithHiddenFieldsInput, +} from './document-merge.ts' +export type { + InterpolatableRobotDocumentOcrInstructions, + InterpolatableRobotDocumentOcrInstructionsInput, + InterpolatableRobotDocumentOcrInstructionsWithHiddenFields, + InterpolatableRobotDocumentOcrInstructionsWithHiddenFieldsInput, +} from './document-ocr.ts' +export type { + InterpolatableRobotDocumentSplitInstructions, + InterpolatableRobotDocumentSplitInstructionsInput, + InterpolatableRobotDocumentSplitInstructionsWithHiddenFields, + InterpolatableRobotDocumentSplitInstructionsWithHiddenFieldsInput, +} from './document-split.ts' +export type { + InterpolatableRobotDocumentThumbsInstructions, + InterpolatableRobotDocumentThumbsInstructionsInput, + InterpolatableRobotDocumentThumbsInstructionsWithHiddenFields, + InterpolatableRobotDocumentThumbsInstructionsWithHiddenFieldsInput, +} from './document-thumbs.ts' +export type { + InterpolatableRobotDropboxImportInstructions, + InterpolatableRobotDropboxImportInstructionsInput, + InterpolatableRobotDropboxImportInstructionsWithHiddenFields, + InterpolatableRobotDropboxImportInstructionsWithHiddenFieldsInput, +} from './dropbox-import.ts' +export type { + InterpolatableRobotDropboxStoreInstructions, + InterpolatableRobotDropboxStoreInstructionsInput, + InterpolatableRobotDropboxStoreInstructionsWithHiddenFields, + InterpolatableRobotDropboxStoreInstructionsWithHiddenFieldsInput, +} from './dropbox-store.ts' +export type { + InterpolatableRobotEdglyDeliverInstructions, + InterpolatableRobotEdglyDeliverInstructionsInput, + InterpolatableRobotEdglyDeliverInstructionsWithHiddenFields, + InterpolatableRobotEdglyDeliverInstructionsWithHiddenFieldsInput, +} from './edgly-deliver.ts' +export type { + InterpolatableRobotFileCompressInstructions, + InterpolatableRobotFileCompressInstructionsInput, + InterpolatableRobotFileCompressInstructionsWithHiddenFields, + InterpolatableRobotFileCompressInstructionsWithHiddenFieldsInput, +} from './file-compress.ts' +export type { + InterpolatableRobotFileDecompressInstructions, + InterpolatableRobotFileDecompressInstructionsInput, + InterpolatableRobotFileDecompressInstructionsWithHiddenFields, + InterpolatableRobotFileDecompressInstructionsWithHiddenFieldsInput, +} from './file-decompress.ts' +export type { + InterpolatableRobotFileFilterInstructions, + InterpolatableRobotFileFilterInstructionsInput, + InterpolatableRobotFileFilterInstructionsWithHiddenFields, + InterpolatableRobotFileFilterInstructionsWithHiddenFieldsInput, +} from './file-filter.ts' +export type { + InterpolatableRobotFileHashInstructions, + InterpolatableRobotFileHashInstructionsInput, + InterpolatableRobotFileHashInstructionsWithHiddenFields, + InterpolatableRobotFileHashInstructionsWithHiddenFieldsInput, +} from './file-hash.ts' +export type { + InterpolatableRobotFilePreviewInstructions, + InterpolatableRobotFilePreviewInstructionsInput, + InterpolatableRobotFilePreviewInstructionsWithHiddenFields, + InterpolatableRobotFilePreviewInstructionsWithHiddenFieldsInput, +} from './file-preview.ts' +export type { + InterpolatableRobotFileReadInstructions, + InterpolatableRobotFileReadInstructionsInput, + InterpolatableRobotFileReadInstructionsWithHiddenFields, + InterpolatableRobotFileReadInstructionsWithHiddenFieldsInput, +} from './file-read.ts' +export type { + InterpolatableRobotFileServeInstructions, + InterpolatableRobotFileServeInstructionsInput, + InterpolatableRobotFileServeInstructionsWithHiddenFields, + InterpolatableRobotFileServeInstructionsWithHiddenFieldsInput, +} from './file-serve.ts' +export type { + InterpolatableRobotFileVerifyInstructions, + InterpolatableRobotFileVerifyInstructionsInput, + InterpolatableRobotFileVerifyInstructionsWithHiddenFields, + InterpolatableRobotFileVerifyInstructionsWithHiddenFieldsInput, +} from './file-verify.ts' +export type { + InterpolatableRobotFileVirusscanInstructions, + InterpolatableRobotFileVirusscanInstructionsInput, + InterpolatableRobotFileVirusscanInstructionsWithHiddenFields, + InterpolatableRobotFileVirusscanInstructionsWithHiddenFieldsInput, +} from './file-virusscan.ts' +export type { + InterpolatableRobotFileWatermarkInstructions, + InterpolatableRobotFileWatermarkInstructionsInput, + InterpolatableRobotFileWatermarkInstructionsWithHiddenFields, + InterpolatableRobotFileWatermarkInstructionsWithHiddenFieldsInput, +} from './file-watermark.ts' +export type { + InterpolatableRobotFtpImportInstructions, + InterpolatableRobotFtpImportInstructionsInput, + InterpolatableRobotFtpImportInstructionsWithHiddenFields, + InterpolatableRobotFtpImportInstructionsWithHiddenFieldsInput, +} from './ftp-import.ts' +export type { + InterpolatableRobotFtpStoreInstructions, + InterpolatableRobotFtpStoreInstructionsInput, + InterpolatableRobotFtpStoreInstructionsWithHiddenFields, + InterpolatableRobotFtpStoreInstructionsWithHiddenFieldsInput, +} from './ftp-store.ts' +export type { + InterpolatableRobotGoogleImportInstructions, + InterpolatableRobotGoogleImportInstructionsInput, + InterpolatableRobotGoogleImportInstructionsWithHiddenFields, + InterpolatableRobotGoogleImportInstructionsWithHiddenFieldsInput, +} from './google-import.ts' +export type { + InterpolatableRobotGoogleStoreInstructions, + InterpolatableRobotGoogleStoreInstructionsInput, + InterpolatableRobotGoogleStoreInstructionsWithHiddenFields, + InterpolatableRobotGoogleStoreInstructionsWithHiddenFieldsInput, +} from './google-store.ts' +export type { + InterpolatableRobotHtmlConvertInstructions, + InterpolatableRobotHtmlConvertInstructionsInput, + InterpolatableRobotHtmlConvertInstructionsWithHiddenFields, + InterpolatableRobotHtmlConvertInstructionsWithHiddenFieldsInput, +} from './html-convert.ts' +export type { + InterpolatableRobotHttpImportInstructions, + InterpolatableRobotHttpImportInstructionsInput, + InterpolatableRobotHttpImportInstructionsWithHiddenFields, + InterpolatableRobotHttpImportInstructionsWithHiddenFieldsInput, +} from './http-import.ts' +export type { + InterpolatableRobotImageBgremoveInstructions, + InterpolatableRobotImageBgremoveInstructionsInput, + InterpolatableRobotImageBgremoveInstructionsWithHiddenFields, + InterpolatableRobotImageBgremoveInstructionsWithHiddenFieldsInput, +} from './image-bgremove.ts' +export type { + InterpolatableRobotImageDescribeInstructions, + InterpolatableRobotImageDescribeInstructionsInput, + InterpolatableRobotImageDescribeInstructionsWithHiddenFields, + InterpolatableRobotImageDescribeInstructionsWithHiddenFieldsInput, +} from './image-describe.ts' +export type { + InterpolatableRobotImageFacedetectInstructions, + InterpolatableRobotImageFacedetectInstructionsInput, + InterpolatableRobotImageFacedetectInstructionsWithHiddenFields, + InterpolatableRobotImageFacedetectInstructionsWithHiddenFieldsInput, +} from './image-facedetect.ts' +export type { + InterpolatableRobotImageGenerateInstructions, + InterpolatableRobotImageGenerateInstructionsInput, + InterpolatableRobotImageGenerateInstructionsWithHiddenFields, + InterpolatableRobotImageGenerateInstructionsWithHiddenFieldsInput, +} from './image-generate.ts' +export type { + InterpolatableRobotImageMergeInstructions, + InterpolatableRobotImageMergeInstructionsInput, + InterpolatableRobotImageMergeInstructionsWithHiddenFields, + InterpolatableRobotImageMergeInstructionsWithHiddenFieldsInput, +} from './image-merge.ts' +export type { + InterpolatableRobotImageOcrInstructions, + InterpolatableRobotImageOcrInstructionsInput, + InterpolatableRobotImageOcrInstructionsWithHiddenFields, + InterpolatableRobotImageOcrInstructionsWithHiddenFieldsInput, +} from './image-ocr.ts' +export type { + InterpolatableRobotImageOptimizeInstructions, + InterpolatableRobotImageOptimizeInstructionsInput, + InterpolatableRobotImageOptimizeInstructionsWithHiddenFields, + InterpolatableRobotImageOptimizeInstructionsWithHiddenFieldsInput, +} from './image-optimize.ts' +export type { + InterpolatableRobotImageResizeInstructions, + InterpolatableRobotImageResizeInstructionsInput, + InterpolatableRobotImageResizeInstructionsWithHiddenFields, + InterpolatableRobotImageResizeInstructionsWithHiddenFieldsInput, +} from './image-resize.ts' +export type { + InterpolatableRobotMetaReadInstructions, + InterpolatableRobotMetaReadInstructionsInput, + InterpolatableRobotMetaReadInstructionsWithHiddenFields, +} from './meta-read.ts' +export type { + InterpolatableRobotMetaWriteInstructions, + InterpolatableRobotMetaWriteInstructionsInput, + InterpolatableRobotMetaWriteInstructionsWithHiddenFields, + InterpolatableRobotMetaWriteInstructionsWithHiddenFieldsInput, +} from './meta-write.ts' +export type { + InterpolatableRobotMinioImportInstructions, + InterpolatableRobotMinioImportInstructionsInput, + InterpolatableRobotMinioImportInstructionsWithHiddenFields, + InterpolatableRobotMinioImportInstructionsWithHiddenFieldsInput, +} from './minio-import.ts' +export type { + InterpolatableRobotMinioStoreInstructions, + InterpolatableRobotMinioStoreInstructionsInput, + InterpolatableRobotMinioStoreInstructionsWithHiddenFields, + InterpolatableRobotMinioStoreInstructionsWithHiddenFieldsInput, +} from './minio-store.ts' +export type { + InterpolatableRobotProgressSimulateInstructions, + InterpolatableRobotProgressSimulateInstructionsInput, +} from './progress-simulate.ts' +export type { + InterpolatableRobotS3ImportInstructions, + InterpolatableRobotS3ImportInstructionsInput, + InterpolatableRobotS3ImportInstructionsWithHiddenFields, + InterpolatableRobotS3ImportInstructionsWithHiddenFieldsInput, +} from './s3-import.ts' +export type { + InterpolatableRobotS3StoreInstructions, + InterpolatableRobotS3StoreInstructionsInput, + InterpolatableRobotS3StoreInstructionsWithHiddenFields, + InterpolatableRobotS3StoreInstructionsWithHiddenFieldsInput, +} from './s3-store.ts' +export type { + InterpolatableRobotScriptRunInstructions, + InterpolatableRobotScriptRunInstructionsInput, + InterpolatableRobotScriptRunInstructionsWithHiddenFields, + InterpolatableRobotScriptRunInstructionsWithHiddenFieldsInput, +} from './script-run.ts' +export type { + InterpolatableRobotSftpImportInstructions, + InterpolatableRobotSftpImportInstructionsInput, + InterpolatableRobotSftpImportInstructionsWithHiddenFields, + InterpolatableRobotSftpImportInstructionsWithHiddenFieldsInput, +} from './sftp-import.ts' +export type { + InterpolatableRobotSftpStoreInstructions, + InterpolatableRobotSftpStoreInstructionsInput, + InterpolatableRobotSftpStoreInstructionsWithHiddenFields, + InterpolatableRobotSftpStoreInstructionsWithHiddenFieldsInput, +} from './sftp-store.ts' +export type { + InterpolatableRobotSpeechTranscribeInstructions, + InterpolatableRobotSpeechTranscribeInstructionsInput, + InterpolatableRobotSpeechTranscribeInstructionsWithHiddenFields, + InterpolatableRobotSpeechTranscribeInstructionsWithHiddenFieldsInput, +} from './speech-transcribe.ts' +export type { + InterpolatableRobotSupabaseImportInstructions, + InterpolatableRobotSupabaseImportInstructionsInput, + InterpolatableRobotSupabaseImportInstructionsWithHiddenFields, + InterpolatableRobotSupabaseImportInstructionsWithHiddenFieldsInput, +} from './supabase-import.ts' +export type { + InterpolatableRobotSupabaseStoreInstructions, + InterpolatableRobotSupabaseStoreInstructionsInput, + InterpolatableRobotSupabaseStoreInstructionsWithHiddenFields, + InterpolatableRobotSupabaseStoreInstructionsWithHiddenFieldsInput, +} from './supabase-store.ts' +export type { + InterpolatableRobotSwiftImportInstructions, + InterpolatableRobotSwiftImportInstructionsInput, + InterpolatableRobotSwiftImportInstructionsWithHiddenFields, + InterpolatableRobotSwiftImportInstructionsWithHiddenFieldsInput, +} from './swift-import.ts' +export type { + InterpolatableRobotSwiftStoreInstructions, + InterpolatableRobotSwiftStoreInstructionsInput, + InterpolatableRobotSwiftStoreInstructionsWithHiddenFields, + InterpolatableRobotSwiftStoreInstructionsWithHiddenFieldsInput, +} from './swift-store.ts' +export type { + InterpolatableRobotTextSpeakInstructions, + InterpolatableRobotTextSpeakInstructionsInput, + InterpolatableRobotTextSpeakInstructionsWithHiddenFields, + InterpolatableRobotTextSpeakInstructionsWithHiddenFieldsInput, +} from './text-speak.ts' +export type { + InterpolatableRobotTextTranslateInstructions, + InterpolatableRobotTextTranslateInstructionsInput, + InterpolatableRobotTextTranslateInstructionsWithHiddenFields, + InterpolatableRobotTextTranslateInstructionsWithHiddenFieldsInput, +} from './text-translate.ts' +export type { + InterpolatableRobotTigrisImportInstructions, + InterpolatableRobotTigrisImportInstructionsInput, + InterpolatableRobotTigrisImportInstructionsWithHiddenFields, + InterpolatableRobotTigrisImportInstructionsWithHiddenFieldsInput, +} from './tigris-import.ts' +export type { + InterpolatableRobotTigrisStoreInstructions, + InterpolatableRobotTigrisStoreInstructionsInput, + InterpolatableRobotTigrisStoreInstructionsWithHiddenFields, + InterpolatableRobotTigrisStoreInstructionsWithHiddenFieldsInput, +} from './tigris-store.ts' +export type { + InterpolatableRobotTlcdnDeliverInstructions, + InterpolatableRobotTlcdnDeliverInstructionsInput, + InterpolatableRobotTlcdnDeliverInstructionsWithHiddenFields, + InterpolatableRobotTlcdnDeliverInstructionsWithHiddenFieldsInput, +} from './tlcdn-deliver.ts' +export type { + InterpolatableRobotTusStoreInstructions, + InterpolatableRobotTusStoreInstructionsInput, + InterpolatableRobotTusStoreInstructionsWithHiddenFields, + InterpolatableRobotTusStoreInstructionsWithHiddenFieldsInput, +} from './tus-store.ts' +export type { + InterpolatableRobotUploadHandleInstructions, + InterpolatableRobotUploadHandleInstructionsInput, + InterpolatableRobotUploadHandleInstructionsWithHiddenFields, + InterpolatableRobotUploadHandleInstructionsWithHiddenFieldsInput, +} from './upload-handle.ts' +export type { + InterpolatableRobotVideoAdaptiveInstructions, + InterpolatableRobotVideoAdaptiveInstructionsInput, + InterpolatableRobotVideoAdaptiveInstructionsWithHiddenFields, + InterpolatableRobotVideoAdaptiveInstructionsWithHiddenFieldsInput, +} from './video-adaptive.ts' +export type { + InterpolatableRobotVideoConcatInstructions, + InterpolatableRobotVideoConcatInstructionsInput, + InterpolatableRobotVideoConcatInstructionsWithHiddenFields, + InterpolatableRobotVideoConcatInstructionsWithHiddenFieldsInput, +} from './video-concat.ts' +export type { + InterpolatableRobotVideoEncodeInstructions, + InterpolatableRobotVideoEncodeInstructionsInput, + InterpolatableRobotVideoEncodeInstructionsWithHiddenFields, + InterpolatableRobotVideoEncodeInstructionsWithHiddenFieldsInput, +} from './video-encode.ts' +export type { + InterpolatableRobotVideoMergeInstructions, + InterpolatableRobotVideoMergeInstructionsInput, + InterpolatableRobotVideoMergeInstructionsWithHiddenFields, + InterpolatableRobotVideoMergeInstructionsWithHiddenFieldsInput, +} from './video-merge.ts' +export type { + InterpolatableRobotVideoOndemandInstructions, + InterpolatableRobotVideoOndemandInstructionsInput, + InterpolatableRobotVideoOndemandInstructionsWithHiddenFields, + InterpolatableRobotVideoOndemandInstructionsWithHiddenFieldsInput, +} from './video-ondemand.ts' +export type { + InterpolatableRobotVideoSubtitleInstructions, + InterpolatableRobotVideoSubtitleInstructionsInput, + InterpolatableRobotVideoSubtitleInstructionsWithHiddenFields, + InterpolatableRobotVideoSubtitleInstructionsWithHiddenFieldsInput, +} from './video-subtitle.ts' +export type { + InterpolatableRobotVideoThumbsInstructions, + InterpolatableRobotVideoThumbsInstructionsInput, + InterpolatableRobotVideoThumbsInstructionsWithHiddenFields, + InterpolatableRobotVideoThumbsInstructionsWithHiddenFieldsInput, +} from './video-thumbs.ts' +export type { + InterpolatableRobotVimeoImportInstructions, + InterpolatableRobotVimeoImportInstructionsInput, + InterpolatableRobotVimeoImportInstructionsWithHiddenFields, + InterpolatableRobotVimeoImportInstructionsWithHiddenFieldsInput, +} from './vimeo-import.ts' +export type { + InterpolatableRobotVimeoStoreInstructions, + InterpolatableRobotVimeoStoreInstructionsInput, + InterpolatableRobotVimeoStoreInstructionsWithHiddenFields, + InterpolatableRobotVimeoStoreInstructionsWithHiddenFieldsInput, +} from './vimeo-store.ts' +export type { + InterpolatableRobotWasabiImportInstructions, + InterpolatableRobotWasabiImportInstructionsInput, + InterpolatableRobotWasabiImportInstructionsWithHiddenFields, + InterpolatableRobotWasabiImportInstructionsWithHiddenFieldsInput, +} from './wasabi-import.ts' +export type { + InterpolatableRobotWasabiStoreInstructions, + InterpolatableRobotWasabiStoreInstructionsInput, + InterpolatableRobotWasabiStoreInstructionsWithHiddenFields, + InterpolatableRobotWasabiStoreInstructionsWithHiddenFieldsInput, +} from './wasabi-store.ts' +export type { + InterpolatableRobotYoutubeStoreInstructions, + InterpolatableRobotYoutubeStoreInstructionsInput, + InterpolatableRobotYoutubeStoreInstructionsWithHiddenFields, + InterpolatableRobotYoutubeStoreInstructionsWithHiddenFieldsInput, +} from './youtube-store.ts' diff --git a/packages/transloadit/src/alphalib/types/robots/_instructions-primitives.ts b/packages/transloadit/src/alphalib/types/robots/_instructions-primitives.ts new file mode 100644 index 00000000..50058199 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/_instructions-primitives.ts @@ -0,0 +1,1770 @@ +import type { Replace } from 'type-fest' +import { z } from 'zod' + +import { stackVersions } from '../stackVersions.ts' + +export const robotNames = z.enum([ + 'AiChatRobot', + 'UploadHandleRobot', + 'FileServeRobot', + 'FileWatermarkRobot', + 'FileVerifyRobot', + 'EdglyDeliverRobot', + 'TlcdnDeliverRobot', + 'VideoSubtitleRobot', + 'VideoEncodeRobot', + 'VideoAdaptiveRobot', + 'VideoMergeRobot', + 'VideoConcatRobot', + 'AudioWaveformRobot', + 'AudioEncodeRobot', + 'AudioLoopRobot', + 'AudioConcatRobot', + 'AudioMergeRobot', + 'AudioArtworkRobot', + 'ImageFacedetectRobot', + 'ImageDescribeRobot', + 'ImageOcrRobot', + 'ImageBgremoveRobot', + 'ImageGenerateRobot', + 'DocumentOcrRobot', + 'SpeechTranscribeRobot', + 'VideoThumbsRobot', + 'FileVirusscanRobot', + 'ImageOptimizeRobot', + 'FileCompressRobot', + 'MetaReadRobot', + 'FileDecompressRobot', + 'MetaWriteRobot', + 'DocumentThumbsRobot', + 'DocumentConvertRobot', + 'DocumentMergeRobot', + 'DocumentSplitRobot', + 'DocumentAutorotateRobot', + 'HtmlConvertRobot', + 'ImageResizeRobot', + 'ImageMergeRobot', + 'S3ImportRobot', + 'S3StoreRobot', + 'DigitalOceanImportRobot', + 'DigitalOceanStoreRobot', + 'BackblazeImportRobot', + 'BackblazeStoreRobot', + 'MinioImportRobot', + 'TigrisImportRobot', + 'CloudflareImportRobot', + 'SupabaseImportRobot', + 'MinioStoreRobot', + 'TigrisStoreRobot', + 'CloudflareStoreRobot', + 'SupabaseStoreRobot', + 'WasabiImportRobot', + 'WasabiStoreRobot', + 'SwiftImportRobot', + 'SwiftStoreRobot', + 'GoogleImportRobot', + 'GoogleStoreRobot', + 'DropboxImportRobot', + 'DropboxStoreRobot', + 'HttpImportRobot', + 'SftpImportRobot', + 'SftpStoreRobot', + 'FtpImportRobot', + 'FtpStoreRobot', + 'CloudfilesImportRobot', + 'CloudfilesStoreRobot', + 'AzureImportRobot', + 'AzureStoreRobot', + 'YoutubeStoreRobot', + 'VimeoImportRobot', + 'VimeoStoreRobot', + 'AssemblySavejsonRobot', + 'ScriptRunRobot', + 'FileHashRobot', + 'FileReadRobot', + 'VideoOndemandRobot', + 'FileFilterRobot', + 'TextSpeakRobot', + 'TextTranslateRobot', + 'FilePreviewRobot', + 'TusStoreRobot', + 'ProgressSimulateRobot', +]) + +export const robotMetaSchema = z.object({ + // Added keys from api2/lib/config.ts: + name: robotNames, + priceFactor: z.number(), + queueSlotCount: z.number(), + downloadInputFiles: z.boolean().optional(), + preserveInputFileUrls: z.boolean().optional(), + minimumCharge: z.number().optional(), + minimumChargeUsd: z.number().optional(), + minimumChargeUsdPerSpeechTranscribeMinute: z + .object({ + aws: z.number(), + gcp: z.number(), + }) + .optional(), + minimumChargeUsdPerDocumentOcrPage: z + .object({ + aws: z.number(), + gcp: z.number(), + }) + .optional(), + isAllowedForUrlTransform: z.boolean(), + removeJobResultFilesFromDiskRightAfterStoringOnS3: z.boolean(), + lazyLoad: z.boolean().optional(), + installVersionFile: z.string().optional(), + trackOutputFileSize: z.boolean().optional(), + isInternal: z.boolean(), + numDaemons: z.number().optional(), + stage: z.enum(['alpha', 'beta', 'ga', 'deprecated', 'removed']), + importRanges: z.array(z.string()).optional(), + extraChargeForImageResize: z.number().optional(), + + // Original keys from content repo: + allowed_for_url_transform: z.boolean(), + bytescount: z.number(), + description: z.string().optional(), + discount_factor: z.number(), + discount_pct: z.number(), + // To avoid a cycling dependency back to template.ts, we'll use any for now: + // example_code: assemblyInstructionsSchema.optional(), + example_code: z.any().optional(), + example_code_description: z.string().optional(), + extended_description: z.string().optional(), + has_small_icon: z.literal(true).optional(), + minimum_charge: z.number(), + minimum_charge_usd: z.union([z.number(), z.record(z.string(), z.number())]).optional(), + minimum_charge_usd_note: z.string().optional(), + ogimage: z.string().optional(), + marketing_intro: z.string().optional(), + output_factor: z.number(), + override_lvl1: z.string().optional(), + purpose_sentence: z.string(), + purpose_verb: z.enum([ + 'auto-rotate', + 'cache & deliver', + 'compress', + 'concatenate', + 'convert', + 'decompress', + 'detect', + 'encode', + 'export', + 'extract', + 'filter', + 'generate', + 'handle', + 'hash', + 'import', + 'loop', + 'merge', + 'optimize', + 'read', + 'recognize', + 'run', + 'scan', + 'serve', + 'speak', + 'subtitle', + 'take', + 'transcode', + 'transcribe', + 'translate', + 'verify', + 'remove', + 'write', + 'stream', + ]), + purpose_word: z.string(), + purpose_words: z.string(), + requires_credentials: z.literal(true).optional(), + service_slug: z.enum([ + 'artificial-intelligence', + 'audio-encoding', + 'code-evaluation', + 'content-delivery', + 'document-processing', + 'file-compressing', + 'file-exporting', + 'file-filtering', + 'file-importing', + 'handling-uploads', + 'image-manipulation', + 'media-cataloging', + 'video-encoding', + ]), + slot_count: z.number(), + title: z.string(), + typical_file_size_mb: z.number(), + typical_file_type: z.enum([ + 'audio file', + 'audio or video file', + 'document', + 'file', + 'image', + 'video', + 'webpage', + ]), + uses_tools: z.array(z.enum(['ffmpeg', 'imagemagick'])).optional(), +}) + +export type RobotMetaInput = z.input + +// These schemas can be reproduced with z.string().regex(). However, this causes some issues. +// We use this in combination with unions. Internally Zod normalizes unions. A string schema and +// enums merged in some way. Both are validated. Normally, if a Zod union has errors, all of them +// are surfaced. However, if the regex isn’t match, and none of the enum values overlap, then the +// regex error is raised instead of the union error. As a result, the best error we could give back +// to the user, is that there’s a problem with the interpolation syntax. But really the other error +// is more useful in pretty much every case. To work around this, we use z.custom() instead, as Zod +// can’t normalize that. +const interpolationRegexFull = /^\${.+}$/ +export const interpolationSchemaFull = z.custom<`\${${string}}`>( + (input) => typeof input === 'string' && interpolationRegexFull.test(input), + 'Must be a full interpolation string', +) +const interpolationRegexPartial = /\${.+}/ +export const interpolationSchemaPartial = z.custom( + (input) => typeof input === 'string' && interpolationRegexPartial.test(input), + 'Must be a partially interpolatable string', +) + +export const booleanStringSchema = z.enum(['true', 'false']) + +type InterpolatableTuple = Schemas extends readonly [ + infer Head extends z.ZodTypeAny, + ...infer Rest extends z.ZodTypeAny[], +] + ? [InterpolatableSchema, ...InterpolatableTuple] + : Schemas + +type InterpolatableSchema = Schema extends z.ZodString + ? Schema + : Schema extends + | z.ZodBoolean + | z.ZodEffects + | z.ZodEnum<[string, ...string[]]> + | z.ZodLiteral + | z.ZodNumber + ? z.ZodUnion<[z.ZodString, Schema]> + : Schema extends z.ZodArray + ? z.ZodUnion<[z.ZodString, z.ZodArray, Cardinality>]> + : Schema extends z.ZodDefault + ? z.ZodDefault> + : Schema extends z.ZodNullable + ? z.ZodNullable> + : Schema extends z.ZodOptional + ? z.ZodOptional> + : Schema extends z.ZodRecord + ? z.ZodRecord> + : Schema extends z.ZodTuple + ? z.ZodUnion< + [ + z.ZodString, + z.ZodTuple< + InterpolatableTuple, + Rest extends z.ZodTypeAny ? InterpolatableSchema : null + >, + ] + > + : Schema extends z.ZodObject + ? z.ZodUnion< + [ + z.ZodString, + z.ZodObject< + { [Key in keyof T]: InterpolatableSchema }, + UnknownKeys, + Catchall + >, + ] + > + : Schema extends z.ZodUnion + ? z.ZodUnion<[z.ZodString, ...InterpolatableTuple]> + : Schema + +export function interpolateRecursive( + schema: Schema, +): InterpolatableSchema { + const def = schema._def + + switch (def.typeName) { + case z.ZodFirstPartyTypeKind.ZodBoolean: + return z.union([ + interpolationSchemaFull, + z + .union([schema, booleanStringSchema]) + .transform((value) => value === true || value === false), + ]) as InterpolatableSchema + case z.ZodFirstPartyTypeKind.ZodArray: { + let replacement = z.array(interpolateRecursive(def.type), def) + + if (def.exactLength != null) { + replacement = replacement.min(def.exactLength.value, def.exactLength.message) + } + + if (def.maxLength != null) { + replacement = replacement.min(def.maxLength.value, def.maxLength.message) + } + + if (def.minLength != null) { + replacement = replacement.min(def.minLength.value, def.minLength.message) + } + + return z.union([interpolationSchemaFull, replacement]) as InterpolatableSchema + } + case z.ZodFirstPartyTypeKind.ZodDefault: { + const replacement = ( + interpolateRecursive(def.innerType) as InterpolatableSchema + ).default(def.defaultValue()) + + return ( + def.description ? replacement.describe(def.description) : replacement + ) as InterpolatableSchema + } + case z.ZodFirstPartyTypeKind.ZodEffects: + case z.ZodFirstPartyTypeKind.ZodEnum: + case z.ZodFirstPartyTypeKind.ZodLiteral: + return z.union([interpolationSchemaFull, schema], def) as InterpolatableSchema + case z.ZodFirstPartyTypeKind.ZodNumber: + return z.union( + [ + z + .string() + .regex(/^\d+(\.\d+)?$/) + .transform((value) => Number(value)), + interpolationSchemaFull, + schema, + ], + def, + ) as InterpolatableSchema + case z.ZodFirstPartyTypeKind.ZodNullable: + return interpolateRecursive(def.innerType) + .nullable() + .describe(def.description) as InterpolatableSchema + case z.ZodFirstPartyTypeKind.ZodObject: { + const replacement = z.object( + Object.fromEntries( + Object.entries(def.shape()).map(([key, nested]) => [ + key, + interpolateRecursive(nested as z.ZodFirstPartySchemaTypes), + ]), + ), + def, + ) + return z.union([ + interpolationSchemaFull, + def.unknownKeys === 'strict' + ? replacement.strict() + : def.unknownKeys === 'passthrough' + ? replacement.passthrough() + : replacement, + ]) as InterpolatableSchema + } + case z.ZodFirstPartyTypeKind.ZodOptional: + return z.optional(interpolateRecursive(def.innerType), def) as InterpolatableSchema + case z.ZodFirstPartyTypeKind.ZodRecord: + return z.record( + def.keyType, + interpolateRecursive(def.valueType), + def, + ) as InterpolatableSchema + case z.ZodFirstPartyTypeKind.ZodString: + return z.union([interpolationSchemaPartial, schema], def) as InterpolatableSchema + case z.ZodFirstPartyTypeKind.ZodTuple: { + const tuple = z.tuple(def.items.map(interpolateRecursive), def) + + return z.union([ + interpolationSchemaFull, + def.rest ? tuple.rest(def.rest) : tuple, + ]) as InterpolatableSchema + } + case z.ZodFirstPartyTypeKind.ZodUnion: + return z.union( + [interpolationSchemaFull, ...(def.options.map(interpolateRecursive) as z.ZodUnionOptions)], + def, + ) as InterpolatableSchema + default: + return schema as InterpolatableSchema + } +} + +/** + * The robot keys specified in this array can’t be interpolated. + */ +const uninterpolatableKeys = ['robot', 'use'] as const + +type InterpolatableRobot> = + Schema extends z.ZodObject + ? z.ZodObject< + { + [Key in keyof T]: Key extends (typeof uninterpolatableKeys)[number] + ? T[Key] + : InterpolatableSchema + }, + UnknownKeys, + Catchall + > + : never + +export function interpolateRobot>( + schema: Schema, +): InterpolatableRobot { + const def = schema._def + return z + .object( + Object.fromEntries( + Object.entries(def.shape()).map(([key, nested]) => [ + key, + (uninterpolatableKeys as readonly string[]).includes(key) + ? nested + : interpolateRecursive(nested as z.ZodFirstPartySchemaTypes), + ]), + ), + def, + ) + .strict() as InterpolatableRobot +} + +/** + * Fields that are shared by all Transloadit robots. + */ +export type RobotBase = z.infer +export const robotBase = z + .object({ + output_meta: z + .union([z.record(z.boolean()), z.boolean(), z.array(z.string())]) + .optional() + .describe(` +Allows you to specify a set of metadata that is more expensive on CPU power to calculate, and thus is disabled by default to keep your Assemblies processing fast. + +For images, you can add \`"has_transparency": true\` in this object to extract if the image contains transparent parts and \`"dominant_colors": true\` to extract an array of hexadecimal color codes from the image. + +For videos, you can add the \`"colorspace: true"\` parameter to extract the colorspace of the output video. + +For audio, you can add \`"mean_volume": true\` to get a single value representing the mean average volume of the audio file. + +You can also set this to \`false\` to skip metadata extraction and speed up transcoding. +`), + + result: z + .boolean() + .default(false) + .describe('Whether the results of this Step should be present in the Assembly Status JSON'), + + queue: z + .enum(['batch']) + .optional() + .describe( + `Setting the queue to 'batch', manually downgrades the priority of jobs for this step to avoid consuming Priority job slots for jobs that don't need zero queue waiting times`, + ), + + force_accept: z + .boolean() + .default(false) + .describe(`Force a Robot to accept a file type it would have ignored. + +By default, Robots ignore files they are not familiar with. +[🤖/video/encode](/docs/robots/video-encode/), for +example, will happily ignore input images. + +With the \`force_accept\` parameter set to \`true\`, you can force Robots to accept all files thrown at them. +This will typically lead to errors and should only be used for debugging or combatting edge cases. +`), + + ignore_errors: z + .union([z.boolean(), z.array(z.enum(['meta', 'execute']))]) + .transform((value) => (value === true ? ['meta', 'execute'] : value === false ? [] : value)) + .default([]) + .describe(` +Ignore errors during specific phases of processing. + +Setting this to \`["meta"]\` will cause the Robot to ignore errors during metadata extraction. + +Setting this to \`["execute"]\` will cause the Robot to ignore errors during the main execution phase. + +Setting this to \`true\` is equivalent to \`["meta", "execute"]\` and will ignore errors in both phases. +`), + }) + .strict() + +export const useParamObjectSchema = z + .object({ + name: z.string(), + fields: z.string().optional(), + as: z.string().optional(), + }) + .strict() + +export const useParamStringSchema = z.string() +export const useParamArrayOfStringsSchema = z.array(useParamStringSchema) +export const useParamArrayOfUseParamObjectSchema = z.array(useParamObjectSchema) +export const useParamStepsSchema = z.union([ + useParamStringSchema, + useParamArrayOfStringsSchema, + useParamArrayOfUseParamObjectSchema, +]) +export const useParamObjectOfStepsSchema = z + .object({ + steps: useParamStepsSchema, + bundle_steps: z.boolean().optional(), + group_by_original: z.boolean().optional(), + fields: z + .array(z.string()) + .optional() + .describe(` +Array of field names to filter input files by when using steps. +`), + }) + .strict() + +// Hidden fields variants for use parameters +export const useParamObjectWithHiddenFieldsSchema = useParamObjectSchema.extend({ + result: z.union([z.literal('debug'), z.boolean()]).optional(), +}) + +export const useParamArrayOfUseParamObjectWithHiddenFieldsSchema = z.array( + useParamObjectWithHiddenFieldsSchema, +) +export const useParamStepsWithHiddenFieldsSchema = z.union([ + useParamStringSchema, + useParamArrayOfStringsSchema, + useParamArrayOfUseParamObjectWithHiddenFieldsSchema, +]) +export const useParamObjectOfStepsWithHiddenFieldsSchema = z + .object({ + steps: useParamStepsWithHiddenFieldsSchema, + bundle_steps: z.boolean().optional(), + group_by_original: z.boolean().optional(), + fields: z + .array(z.string()) + .optional() + .describe(` +Array of field names to filter input files by when using steps. +`), + }) + .strict() + +/** + * A robot that uses another robot’s output as input. + */ +export type RobotUse = z.infer +export const robotUse = z + .object({ + use: z + .union([useParamStepsSchema, useParamObjectOfStepsSchema]) + .describe( + ` +Specifies which Step(s) to use as input. + +- You can pick any names for Steps except \`":original"\` (reserved for user uploads handled by Transloadit) +- You can provide several Steps as input with arrays: + \`\`\`json + { + "use": [ + ":original", + "encoded", + "resized" + ] + } + \`\`\` + +> [!Tip] +> That's likely all you need to know about \`use\`, but you can view [Advanced use cases](/docs/topics/use-parameter/). +`, + ) + .optional(), + }) + .strict() + +export type RobotUseWithHiddenFields = z.infer +export const robotUseWithHiddenFields = z + .object({ + use: z + .union([useParamStepsWithHiddenFieldsSchema, useParamObjectOfStepsWithHiddenFieldsSchema]) + .describe( + ` +Specifies which Step(s) to use as input. + +- You can pick any names for Steps except \`":original"\` (reserved for user uploads handled by Transloadit) +- You can provide several Steps as input with arrays: + \`\`\`json + { + "use": [ + ":original", + "encoded", + "resized" + ] + } + \`\`\` + +> [!Tip] +> That's likely all you need to know about \`use\`, but you can view [Advanced use cases](/docs/topics/use-parameter/). +`, + ) + .optional(), + }) + .strict() + +export const complexWidthSchema = z.preprocess((val) => { + if (typeof val === 'string' && val.startsWith('${')) { + return val + } + if (typeof val === 'string') { + const num = Number.parseInt(val, 10) + if (Number.isNaN(num) || val.includes('x')) { + return val + } + return num + } + return val +}, z.number().int().min(1).max(7680)) + +export const complexHeightSchema = z.preprocess((val) => { + if (typeof val === 'string' && val.startsWith('${')) { + return val + } + if (typeof val === 'string') { + const num = Number.parseInt(val, 10) + if (Number.isNaN(num) || val.includes('x')) { + return val + } + return num + } + return val +}, z.number().int().min(1).max(4320)) + +/** + * A robot that uses FFmpeg. + */ +export type FFmpeg = z.infer +export const robotFFmpeg = z.object({ + ffmpeg: z + .object({ + af: z.string().optional(), + 'b:a': z.union([z.string(), z.number()]).optional(), + 'b:v': z.union([z.string(), z.number()]).optional(), + 'c:a': z.string().optional(), + 'c:v': z.string().optional(), + 'codec:a': z.string().optional(), + 'codec:v': z.string().optional(), + 'filter:v': z.string().optional(), + 'filter:a': z.string().optional(), + bits_per_mb: z.union([z.string(), z.number()]).optional(), + ss: z.union([z.string(), z.number()]).optional(), + t: z.union([z.string(), z.number()]).optional(), + to: z.union([z.string(), z.number()]).optional(), + vendor: z.string().optional(), + shortest: z.boolean().nullish(), + filter_complex: z.union([z.string(), z.record(z.string())]).optional(), + 'level:v': z.union([z.string(), z.number()]).optional(), + 'profile:v': z.union([z.number(), z.enum(['baseline', 'main', 'high', 'main10'])]).optional(), + 'qscale:a': z.number().optional(), + 'qscale:v': z.number().optional(), + 'x264-params': z.string().optional(), + 'overshoot-pct': z.number().optional(), + deadline: z.string().optional(), + 'cpu-used': z.string().optional(), + 'undershoot-pct': z.number().optional(), + 'row-mt': z.number().optional(), + 'x265-params': z + .object({ + 'vbv-maxrate': z.number().optional(), + 'vbv-bufsize': z.number().optional(), + 'rc-lookahead': z.number().optional(), + 'b-adapt': z.number().optional(), + }) + .strict() + .optional(), + 'svtav1-params': z + .object({ + tune: z.number().optional(), + 'enable-qm': z.number().optional(), + 'fast-decode': z.number().optional(), + 'film-grain-denoise': z.number().optional(), + }) + .strict() + .optional(), + ac: z.number().optional(), + an: z.boolean().optional(), + ar: z.number().optional(), + async: z.number().optional(), + b: z + .union([ + z + .object({ + v: z.number().optional(), + a: z.number().optional(), + }) + .strict(), + z.string(), + ]) + .optional(), + bt: z.union([z.number(), z.string()]).optional(), + bufsize: z.union([z.string(), z.number()]).optional(), + c: z.string().optional(), + codec: z + .object({ + v: z.string().optional(), + a: z.string().optional(), + }) + .strict() + .optional(), + coder: z.number().optional(), + crf: z.number().optional(), + f: z.string().optional(), + flags: z.string().optional(), + g: z.number().optional(), + i_qfactor: z.union([z.string(), z.number()]).optional(), + keyint_min: z.number().optional(), + level: z.union([z.string(), z.number()]).optional(), + map: z.union([z.string(), z.array(z.string())]).optional(), + maxrate: z.union([z.string(), z.number()]).optional(), + me_range: z.number().optional(), + movflags: z.string().optional(), + partitions: z.string().optional(), + pix_fmt: z.string().optional(), + preset: z.union([z.string(), z.number()]).optional(), + profile: z.string().optional(), + 'q:a': z.number().optional(), + qcomp: z.union([z.string(), z.number()]).optional(), + qdiff: z.number().optional(), + qmax: z.number().optional(), + qmin: z.number().optional(), + r: z.union([z.number(), z.string()]).nullable().optional(), + rc_eq: z.string().optional(), + refs: z.number().optional(), + s: z.string().optional(), + sc_threshold: z.number().optional(), + sws_flags: z.string().optional(), + threads: z.number().optional(), + trellis: z.number().optional(), + transloaditffpreset: z.literal('empty').optional(), + vn: z.boolean().optional(), + vf: z.string().optional(), + x264opts: z.string().optional(), + vbr: z.union([z.string(), z.number()]).optional(), + }) + .passthrough() + .optional() + .describe(` +A parameter object to be passed to FFmpeg. If a preset is used, the options specified are merged on top of the ones from the preset. For available options, see the [FFmpeg documentation](https://ffmpeg.org/ffmpeg-doc.html). Options specified here take precedence over the preset options. +`), + + ffmpeg_stack: z + // Any semver in range is allowed and normalized. The enum is used for editor completions. + .union([z.enum(['v5', 'v6', 'v7']), z.string().regex(/^v?[567](\.\d+)?(\.\d+)?$/)]) + .default('v5.0.0') + .describe(` +Selects the FFmpeg stack version to use for encoding. These versions reflect real FFmpeg versions. We currently recommend to use "v6.0.0". +`), +}) + +/** + * Replace all underscores with hyphens. + * + * @param preset + * The input preset which may contain underscores. + * @returns + * The hyphenated preset. + */ +function transformPreset(preset: T): Replace { + return preset.replaceAll('_', '-') as Replace +} + +/** + * Convert a preset with hyphens to any underscore/hyphen combination. + * + * @template T + * The preset to process. + */ +type ReplacePreset = T extends `${infer T0}-${infer Tail}` + ? T | `${T0}-${ReplacePreset}` | `${T0}_${ReplacePreset}` + : T + +/** + * Generate all possible underscore/hyphen combinations of a preset. + * + * @param chunks + * A normalized preset split on hyphens. + * @returns + * An iterable that yields all possible combinations. + */ +function* generateCombinations(chunks: string[]): Iterable { + if (chunks.length === 0) { + return + } + + if (chunks.length === 1) { + yield chunks[0] + } + + const [head, ...remaining] = chunks + for (const result of generateCombinations(remaining)) { + yield `${head}-${result}` + yield `${head}_${result}` + } +} + +/** + * Create all possible preset combinations from a list of normalized presets. + * + * @param inputs + * The hyphenated presets. + * @returns + * An array of all possible combinations. + */ +function createPresets( + inputs: T[], +): readonly [ReplacePreset, ...ReplacePreset[]] { + const results: string[] = [] + for (const input of inputs) { + results.push(...generateCombinations(input.split('-'))) + } + + return [...results].sort() as [ReplacePreset, ...ReplacePreset[]] +} + +const audioPresets = createPresets([ + 'aac', + 'alac', + 'audio/aac', + 'audio/alac', + 'audio/flac', + 'audio/mp3', + 'audio/ogg', + 'dash-32k-audio', + 'dash-64k-audio', + 'dash-128k-audio', + 'dash-256k-audio', + 'dash/32k-audio', + 'dash/64k-audio', + 'dash/128k-audio', + 'dash/256k-audio', + 'empty', + 'flac', + 'hg-transformers-audio', + 'mp3', + 'ogg', + 'opus', + 'speech', + 'wav', +]) + +/** + * A robot that uses FFmpeg to **output** audio. + */ +export type FFmpegAudio = z.infer +export const robotFFmpegAudio = robotFFmpeg + .extend({ + preset: z + .enum(audioPresets) + .transform(transformPreset) + .optional() + .describe(` +Performs conversion using pre-configured settings. + +If you specify your own FFmpeg parameters using the Robot's \`ffmpeg\` parameter and you have not specified a preset, then the default \`mp3\` preset is not applied. This is to prevent you from having to override each of the MP3 preset's values manually. + +For a list of audio presets, see [audio presets](/docs/presets/audio/). +`), + }) + .strict() + +/** + * A robot that uses FFmpeg to **output** video. + */ +export type FFmpegVideo = z.infer +export const robotFFmpegVideo = robotFFmpeg + .extend({ + width: z + .number() + .int() + .min(1) + .nullish() + .describe(` +Width of the new video, in pixels. + +If the value is not specified and the \`preset\` parameter is available, the \`preset\`'s [supplied width](/docs/presets/video/) will be implemented. +`), + height: z + .number() + .int() + .min(1) + .nullish() + .describe(` +Height of the new video, in pixels. + +If the value is not specified and the \`preset\` parameter is available, the \`preset\`'s [supplied height](/docs/presets/video/) will be implemented. +`), + preset: z + .enum([ + ...createPresets([ + 'android', + 'android-high', + 'android-low', + 'dash-270p-video', + 'dash-360p-video', + 'dash-480p-video', + 'dash-540p-video', + 'dash-576p-video', + 'dash-720p-video', + 'dash-1080p-video', + 'dash/270p-video', + 'dash/360p-video', + 'dash/480p-video', + 'dash/540p-video', + 'dash/576p-video', + 'dash/720p-video', + 'dash/1080p-video', + 'flash', + 'gif', + 'hevc', + 'hls-270p', + 'hls-360p', + 'hls-480p', + 'hls-540p', + 'hls-576p', + 'hls-720p', + 'hls-1080p', + 'hls/270p', + 'hls/360p', + 'hls/480p', + 'hls/540p', + 'hls/720p', + 'hls/1080p', + 'hls/4k', + 'ipad', + 'ipad-high', + 'ipad-low', + 'iphone', + 'iphone-high', + 'iphone-low', + 'ogv', + 'vod/270p', + 'vod/480p', + 'vod/720p', + 'vod/1080p', + 'vp9', + 'vp9-270p', + 'vp9-360p', + 'vp9-480p', + 'vp9-540p', + 'vp9-576p', + 'vp9-720p', + 'vp9-1080p', + 'web/mp4-x265/240p', + 'web/mp4-x265/360p', + 'web/mp4-x265/480p', + 'web/mp4-x265/720p', + 'web/mp4-x265/1080p', + 'web/mp4-x265/4k', + 'web/mp4-x265/8k', + 'web/mp4/240p', + 'web/mp4/360p', + 'web/mp4/480p', + 'web/mp4/540p', + 'web/mp4/720p', + 'web/mp4/1080p', + 'web/mp4/4k', + 'web/mp4/8k', + 'web/webm-av1/240p', + 'web/webm-av1/360p', + 'web/webm-av1/480p', + 'web/webm-av1/720p', + 'web/webm-av1/1080p', + 'web/webm-av1/4k', + 'web/webm-av1/8k', + 'web/webm/240p', + 'web/webm/360p', + 'web/webm/480p', + 'web/webm/720p', + 'web/webm/1080p', + 'web/webm/4k', + 'web/webm/8k', + 'webm', + 'webm-270p', + 'webm-360p', + 'webm-480p', + 'webm-540p', + 'webm-576p', + 'webm-720p', + 'webm-1080p', + 'wmv', + ]), + ...audioPresets, + ]) + .transform(transformPreset) + .optional() + .describe(` +Converts a video according to [pre-configured settings](/docs/presets/video/). + +If you specify your own FFmpeg parameters using the Robot's and/or do not not want Transloadit to set any encoding setting, starting \`ffmpeg_stack: "${stackVersions.ffmpeg.recommendedVersion}"\`, you can use the value \`'empty'\` here. +`), + }) + .strict() + +export const unsafeCoordinatesSchema = z + .union([ + z + .object({ + x1: z.union([z.string(), z.number()]).nullish(), + y1: z.union([z.string(), z.number()]).nullish(), + x2: z.union([z.string(), z.number()]).nullish(), + y2: z.union([z.string(), z.number()]).nullish(), + }) + .strict(), + z.string(), + ]) + .describe(` +Coordinates for watermarking. +`) +export type UnsafeCoordinates = z.infer + +export const parsedCoordinatesSchema = z + .object({ + x1: z.number(), + y1: z.number(), + x2: z.number(), + y2: z.number(), + }) + .strict() +export type ParsedCoordinates = z.infer + +export const path = z.union([z.string(), z.array(z.string())]) + +export const next_page_token = z.string().default('') + +export const files_per_page = z.number().int().default(1000) + +export const page_number = z.number().int().default(1) + +export const recursive = z.boolean().default(false) + +export const return_file_stubs = z + .boolean() + .describe( + ` +If set to \`true\`, the Robot will not yet import the actual files but instead return an empty file stub that includes a URL from where the file can be imported by subsequent Robots. This is useful for cases where subsequent Steps need more control over the import process, such as with 🤖/video/ondemand. This parameter should only be set if all subsequent Steps use Robots that support file stubs. +`, + ) + .default(false) + +export const port = z.number().int().min(1).max(65535) + +// TODO: Use an enum. +export const preset = z.string() + +export const resize_strategy = z + .enum(['crop', 'fit', 'fillcrop', 'min_fit', 'pad', 'stretch']) + .default('pad') + +export const positionSchema = z.enum([ + 'bottom', + 'bottom-left', + 'bottom-right', + 'center', + 'left', + 'right', + 'top', + 'top-left', + 'top-right', +]) + +export const percentageSchema = z.string().regex(/^\d+%$/) + +export const color_with_alpha = z.string().regex(/^#?[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/) + +export const color_without_alpha = z.string().regex(/^#?[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$/) + +// Extended color schemas that also support named colors (for robots that support them) +export const color_with_alpha_with_named = z.union([ + color_with_alpha, // Extend the base hex color schema + z.enum([ + 'transparent', + 'none', + 'black', + 'white', + 'red', + 'green', + 'blue', + 'yellow', + 'cyan', + 'magenta', + 'gray', + 'grey', + 'opaque', + ]), // Named colors +]) + +export const color_without_alpha_with_named = z.union([ + color_without_alpha, // Extend the base hex color schema + z.enum([ + 'transparent', + 'none', + 'black', + 'white', + 'red', + 'green', + 'blue', + 'yellow', + 'cyan', + 'magenta', + 'gray', + 'grey', + 'opaque', + ]), // Named colors +]) + +export const bitrateSchema = z.number().int().min(1) + +export const sampleRateSchema = z.number().int().min(1) + +export const optimize_priority = z + .enum(['compression-ratio', 'conversion-speed']) + .default('conversion-speed') + +export type ImagemagickRobot = z.infer +export const robotImagemagick = z + .object({ + imagemagick_stack: z + // Any semver in range is allowed and normalized. The enum is used for editor completions. + .union([z.enum(['v3']), z.string().regex(/^v?[23](\.\d+)?(\.\d+)?$/)]) + .default('v3'), + }) + .strict() + +export const colorspaceSchema = z.enum([ + 'CMY', + 'CMYK', + 'Gray', + 'HCL', + 'HCLp', + 'HSB', + 'HSI', + 'HSL', + 'HSV', + 'HWB', + 'Jzazbz', + 'Lab', + 'LCHab', + 'LCHuv', + 'LMS', + 'Log', + 'Luv', + 'OHTA', + 'OkLab', + 'OkLCH', + 'Rec601YCbCr', + 'Rec709YCbCr', + 'RGB', + 'scRGB', + 'sRGB', + 'Transparent', + 'Undefined', + 'xyY', + 'XYZ', + 'YCbCr', + 'YCC', + 'YDbDr', + 'YIQ', + 'YPbPr', + 'YUV', +]) + +// TODO: add before and after images to the description. +export const imageQualitySchema = z + .number() + .int() + .min(1) + .max(100) + .default(92) + .describe(` +Controls the image compression for JPG and PNG images. Please also take a look at [🤖/image/optimize](/docs/robots/image-optimize/). +`) + +export const aiProviderSchema = z.enum(['aws', 'gcp', 'replicate', 'fal', 'transloadit']) + +export const granularitySchema = z.enum(['full', 'list']).default('full') + +/** + * A robot that imports data from a source. + */ +export type RobotImport = z.infer +export const robotImport = z + .object({ + force_name: z + .union([z.string(), z.array(z.string())]) + .nullable() + .default(null) + .describe( + 'Custom name for the imported file(s). By default file names are derived from the source.', + ), + ignore_errors: z + .union([z.boolean(), z.array(z.enum(['meta', 'import', 'execute']))]) + .transform((value) => + value === true ? ['meta', 'import', 'execute'] : value === false ? [] : value, + ) + .default([]), + }) + .strict() + +export type AzureBase = z.infer +export const azureBase = z + .object({ + credentials: z + .string() + .optional() + .describe(` +Please create your associated Template Credentials in your Transloadit account and use the name of your [Template Credentials](/c/template-credentials/) as this parameter's value. They will contain the values for your Azure Container, Account and Key. + +While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"account"\`, \`"key"\`, \`"container"\`. +`), + account: z.string().optional(), + container: z.string().optional(), + key: z.string().optional(), + }) + .strict() + +export type BackblazeBase = z.infer +export const backblazeBase = z + .object({ + credentials: z + .string() + .optional() + .describe(` +Please create your associated Template Credentials in your Transloadit account and use the name of your Template Credentials as this parameter's value. They will contain the values for your Backblaze Bucket Name, App Key ID, and App Key. + +To create your credential information, head over to Backblaze, sign in to your account, and select "Create a Bucket". Save the name of your bucket, and click on the "App Keys" tab, scroll to the bottom of the page then select “Add a New Application Key”. Allow access to your recently created bucket, select “Read and Write” as your type of access, and tick the “Allow List All Bucket Names” option. + +Now that everything is in place, create your key, and take note of the information you are given so you can input the information into your Template Credentials. + +⚠️ Your App Key will only be viewable once, so make sure you note this down. + +While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"bucket"\`, \`"app_key_id"\`, \`"app_key"\`. +`), + bucket: z.string().optional(), + app_key_id: z.string().optional(), + app_key: z.string().optional(), + }) + .strict() + +export type CloudfilesBase = z.infer +export const cloudfilesBase = z + .object({ + credentials: z + .string() + .optional() + .describe(` +Please create your associated Template Credentials in your Transloadit account and use the name of your [Template Credentials](/c/template-credentials/) as this parameter's value. They will contain the values for your Cloud Files Container, User, Key, Account type and Data center. + +While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"account_type"\` ("us" or "uk"), \`"data_center"\` ("dfw" for Dallas or "ord" for Chicago for example), \`"user"\`, \`"key"\`, \`"container"\`. +`), + account_type: z.enum(['uk', 'us']).optional(), + data_center: z.string().optional(), + user: z.string().optional(), + key: z.string().optional(), + container: z.string().optional(), + }) + .strict() + +export type CloudflareBase = z.infer +export const cloudflareBase = z + .object({ + credentials: z + .string() + .optional() + .describe(` +Please create your associated Template Credentials in your Transloadit account and use the name of your Template Credentials as this parameter's value. They will contain the values for your cloudflare bucket, Key, Secret and Bucket region. + +While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"bucket"\`, \`"host"\`, \`"key"\`, \`"secret"\`. +`), + bucket: z.string().optional(), + host: z.string().optional(), + key: z.string().optional(), + secret: z.string().optional(), + }) + .strict() + +export type DigitalOceanBase = z.infer +export const digitalOceanBase = z + .object({ + credentials: z + .string() + .optional() + .describe(` +Please create your associated Template Credentials in your Transloadit account and use the name of your [Template Credentials](/c/template-credentials/) as this parameter's value. They will contain the values for your DigitalOcean Space, Key, Secret and Region. + +While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"space"\`, \`"region"\` (for example: \`"fra1"\` or \`"nyc3"\`), \`"key"\`, \`"secret"\`. +`), + space: z.string().optional(), + region: z.string().optional(), + key: z.string().optional(), + secret: z.string().optional(), + }) + .strict() + +export type DropboxBase = z.infer +export const dropboxBase = z + .object({ + credentials: z + .string() + .optional() + .describe(` +Please create your associated Template Credentials in your Transloadit account and use the name of your Template Credentials as this parameter's value. They will contain the values for your Dropbox access token. +`), + }) + .strict() + +export type VimeoBase = z.infer +export const vimeoBase = z + .object({ + credentials: z + .string() + .optional() + .describe(` +Please create your associated Template Credentials in your Transloadit account and use the name of your Template Credentials as this parameter's value. They will contain the values for your Vimeo access token. +`), + }) + .strict() + +export type FtpBase = z.infer +export const ftpBase = z + .object({ + credentials: z + .string() + .optional() + .describe(` +Please create your associated Template Credentials in your Transloadit account and use the name of your Template Credentials as this parameter's value. They will contain the values for your FTP host, user and password. + +While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials with their static nature is too unwieldy. If you have this requirement, feel free to use the following parameters instead: \`"host"\`, \`"user"\`, \`"password"\`. +`), + host: z.string().optional(), + port: port.default(21).describe('The port to use for the FTP connection.'), + user: z.string().optional(), + password: z.string().optional(), + }) + .strict() + +export type GoogleBase = z.infer +export const googleBase = z + .object({ + credentials: z + .string() + .optional() + .describe(` +Create a new [Google service account](https://cloud.google.com/storage/docs/authentication). Set its role to "Storage Object Creator". Choose "JSON" for the key file format and download it to your computer. You will need to upload this file when creating your Template Credentials. + +Go back to your Google credentials project and enable the "Google Cloud Storage JSON API" for it. Wait around ten minutes for the action to propagate through the Google network. Grab the project ID from the dropdown menu in the header bar on the Google site. You will also need it later on. + +Now you can set up the \`storage.objects.create\` and \`storage.objects.delete\` permissions. The latter is optional and only required if you intend to overwrite existing paths. + +To do this from the Google Cloud console, navigate to "IAM & Admin" and select "Roles". From here, click "Create Role", enter a name, set the role launch stage to _General availability,_ and set the permissions stated above. + +Next, go to Storage browser and select the ellipsis on your bucket to edit bucket permissions. From here, select "Add Member", enter your service account as a new member, and select your newly created role. + +Then, create your associated [Template Credentials](/c/template-credentials/) in your Transloadit account and use the name of your Template Credentials as this parameter's value. +`), + }) + .strict() + +export type MinioBase = z.infer +export const minioBase = z + .object({ + credentials: z + .string() + .optional() + .describe(` +Please create your associated Template Credentials in your Transloadit account and use the name of your Template Credentials as this parameter's value. They will contain the values for your MinIO bucket, Key, Secret and Bucket region. + +While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"bucket"\`, \`"host"\`, \`"key"\`, \`"secret"\`. +`), + bucket: z.string().optional(), + host: z.string().optional(), + key: z.string().optional(), + secret: z.string().optional(), + }) + .strict() + +export type S3Base = z.infer +export const s3Base = z + .object({ + credentials: z + .string() + .optional() + .describe(` +Please create your associated Template Credentials in your Transloadit account and use the name of your Template Credentials as this parameter's value. They will contain the values for your S3 bucket, Key, Secret and Bucket region. + +While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"bucket"\`, \`"bucket_region"\` (for example: \`"us-east-1"\` or \`"eu-west-2"\`), \`"key"\`, \`"secret"\`. +`), + bucket: z.string().optional(), + bucket_region: z.string().optional(), + key: z.string().optional(), + secret: z.string().optional(), + }) + .strict() + +export type SftpBase = z.infer +export const sftpBase = z + .object({ + credentials: z + .string() + .optional() + .describe(` +Please create your associated Template Credentials in your Transloadit account and use the name of your Template Credentials as this parameter's value. They will contain the values for your SFTP host, user and optional custom public key. + +While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"host"\`, \`"port"\`, \`"user"\`, \`"public_key"\` (optional). +`), + host: z.string().optional(), + port: port.default(21).describe('The port to use for the FTP connection.'), + user: z.string().optional(), + public_key: z.string().optional(), + }) + .strict() + +export type SupabaseBase = z.infer +export const supabaseBase = z + .object({ + credentials: z + .string() + .optional() + .describe(` +Please create your associated Template Credentials in your Transloadit account and use the name of your Template Credentials as this parameter's value. They will contain the values for your Supabase bucket, Key, Secret and Bucket region. + +While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"bucket"\`, \`"host"\`, \`"key"\`, \`"secret"\`. + +If you do use these parameters, make sure to use the **Endpoint** value under \`Storage > S3 Connection\` in the Supabase console for the \`"host"\` value, and the values under **S3 Access Keys** on the same page for your \`"key"\` and \`"secret"\`. +`), + bucket: z.string().optional(), + bucket_region: z + .string() + .optional() + .describe(` +The region where the bucket is located. +`), + host: z.string().optional(), + key: z.string().optional(), + secret: z.string().optional(), + }) + .strict() + +export type SwiftBase = z.infer +export const swiftBase = z + .object({ + credentials: z + .string() + .optional() + .describe(` + Please create your associated Template Credentials in your Transloadit account and use the name of your Template Credentials as this parameter's value. They will contain the values for your Swift bucket, Key, Secret and Bucket region. + + While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"bucket"\`, \`"host"\`, \`"key"\`, \`"secret"\`. + `), + bucket: z.string().optional(), + bucket_region: z + .string() + .optional() + .describe(` +The region where the bucket is located. +`), + host: z.string().optional(), + key: z.string().optional(), + secret: z.string().optional(), + }) + .strict() + +export type TigrisBase = z.infer +export const tigrisBase = z + .object({ + credentials: z + .string() + .optional() + .describe(` +Please create your associated Template Credentials in your Transloadit account and use the name of your Template Credentials as this parameter's value. They will contain the values for your MinIO bucket, Key, Secret and Bucket region. + +While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"bucket"\`, \`"host"\`, \`"key"\`, \`"secret"\`. +`), + bucket: z.string().optional(), + bucket_region: z + .string() + .optional() + .describe(` +The region where the bucket is located. +`), + host: z.string().optional(), + key: z.string().optional(), + secret: z.string().optional(), + }) + .strict() + +export type WasabiBase = z.infer +export const wasabiBase = z + .object({ + credentials: z + .string() + .optional() + .describe(` +Please create your associated Template Credentials in your Transloadit account and use the name of your Template Credentials as this parameter's value. They will contain the values for your Wasabi bucket, Key, Secret and Bucket region. + +While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"bucket"\`, \`"host"\`, \`"key"\`, \`"secret"\`. +`), + bucket: z.string().optional(), + bucket_region: z + .string() + .optional() + .describe(` +The region where the bucket is located. +`), + host: z.string().optional(), + key: z.string().optional(), + secret: z.string().optional(), + }) + .strict() + +export type FilterExpression = z.infer +export const filterExpression = z.union([ + z.string(), + z.number(), + z.null(), + z.array(z.union([z.string(), z.number(), z.null()])), +]) + +export type FilterCondition = z.infer +export const filterCondition = z.union([ + z.null(), + z.string(), + z.array( + z.tuple([ + filterExpression, + z.union([ + z.literal('=').describe('Equals without type check'), + z.literal('==').describe('Equals without type check'), + z.literal('===').describe('Strict equals with type check'), + z.literal('<').describe('Less than'), + z.literal('>').describe('Greater than'), + z.literal('<=').describe('Less or equal'), + z.literal('>=').describe('Greater or equal'), + z.literal('!=').describe('Simple inequality check without type check'), + z.literal('!==').describe('Strict inequality check with type check'), + z + .literal('regex') + .describe( + 'Case-insensitive regular expression based on [RE2](https://github.com/google/re2) `.match()`', + ), + z + .literal('!regex') + .describe( + 'Case-insensitive regular expression based on [RE2](https://github.com/google/re2) `!.match()`', + ), + z + .literal('includes') + .describe( + 'Check if the right element is included in the array, which is represented by the left element', + ), + z + .literal('!includes') + .describe( + 'Check if the right element is not included in the array, which is represented by the left element', + ), + z + .literal('empty') + .describe( + 'Check if the left element is an empty array, an object without properties, an empty string, the number zero or the boolean false. Leave the third element of the array to be an empty string. It won’t be evaluated.', + ), + z + .literal('!empty') + .describe( + 'Check if the left element is an array with members, an object with at least one property, a non-empty string, a number that does not equal zero or the boolean true. Leave the third element of the array to be an empty string. It won’t be evaluated.', + ), + ]), + filterExpression, + ]), + ), +]) + +/** + * Parameters specific to the /video/encode robot. Useful for typing robots that pass files to /video/encode. + */ +export const videoEncodeSpecificInstructionsSchema = robotFFmpegVideo + .extend({ + resize_strategy: resize_strategy.describe(` +See the [available resize strategies](/docs/topics/resize-strategies/). +`), + zoom: z + .boolean() + .default(true) + .describe(` +If this is set to \`false\`, smaller videos will not be stretched to the desired width and height. For details about the impact of zooming for your preferred resize strategy, see the list of available [resize strategies](/docs/topics/resize-strategies/). +`), + crop: unsafeCoordinatesSchema.optional().describe(` +Specify an object containing coordinates for the top left and bottom right corners of the rectangle to be cropped from the original video(s). Values can be integers for absolute pixel values or strings for percentage based values. + +For example: + +\`\`\`json +{ + "x1": 80, + "y1": 100, + "x2": "60%", + "y2": "80%" +} +\`\`\` + +This will crop the area from \`(80, 100)\` to \`(600, 800)\` from a 1000×1000 pixels video, which is a square whose width is 520px and height is 700px. If \`crop\` is set, the width and height parameters are ignored, and the \`resize_strategy\` is set to \`crop\` automatically. + +You can also use a JSON string of such an object with coordinates in similar fashion: + +\`\`\`json +"{\\"x1\\": , \\"y1\\": , \\"x2\\": , \\"y2\\": }" +\`\`\` +`), + background: color_with_alpha.default('#00000000').describe(` +The background color of the resulting video the \`"rrggbbaa"\` format (red, green, blue, alpha) when used with the \`"pad"\` resize strategy. The default color is black. +`), + rotate: z + // We can’t use enum. + // See https://github.com/colinhacks/zod/issues/2686 + .union([ + z.literal(0), + z.literal(90), + z.literal(180), + z.literal(270), + z.literal(360), + z.literal(false), + ]) + .optional() + .describe(` +Forces the video to be rotated by the specified degree integer. Currently, only multiples of \`90\` are supported. We automatically correct the orientation of many videos when the orientation is provided by the camera. This option is only useful for videos requiring rotation because it was not detected by the camera. If you set \`rotate\` to \`false\` no rotation is performed, even if the metadata contains such instructions. +`), + hint: z + .boolean() + .default(false) + .describe(` +Enables hinting for mp4 files, for RTP/RTSP streaming. +`), + turbo: z + .boolean() + .default(false) + .describe(` +Splits the video into multiple chunks so that each chunk can be encoded in parallel before all encoded chunks are stitched back together to form the result video. This comes at the expense of extra Priority Job Slots and may prove to be counter-productive for very small video files. +`), + chunk_duration: z + .number() + .int() + .min(1) + .optional() + .describe(` +Allows you to specify the duration of each chunk when \`turbo\` is set to \`true\`. This means you can take advantage of that feature while using fewer Priority Job Slots. For instance, the longer each chunk is, the fewer Encoding Jobs will need to be used. +`), + watermark_url: z + .string() + .default('') + .describe(` +A URL indicating a PNG image to be overlaid above this image. You can also [supply the watermark via another Assembly Step](/docs/topics/use-parameter/#supplying-the-watermark-via-an-assembly-step). +`), + watermark_position: z + .union([positionSchema, z.array(positionSchema)]) + .default('center') + .describe(` +The position at which the watermark is placed. + +An array of possible values can also be specified, in which case one value will be selected at random, such as \`[ "center", "left", "bottom-left", "bottom-right" ]\`. + +This setting puts the watermark in the specified corner. To use a specific pixel offset for the watermark, you will need to add the padding to the image itself. +`), + watermark_x_offset: z + .number() + .int() + .default(0) + .describe(` +The x-offset in number of pixels at which the watermark will be placed in relation to the position it has due to \`watermark_position\`. + +Values can be both positive and negative and yield different results depending on the \`watermark_position\` parameter. Positive values move the watermark closer to the image's center point, whereas negative values move the watermark further away from the image's center point. +`), + watermark_y_offset: z + .number() + .int() + .default(0) + .describe(` +The y-offset in number of pixels at which the watermark will be placed in relation to the position it has due to \`watermark_position\`. + +Values can be both positive and negative and yield different results depending on the \`watermark_position\` parameter. Positive values move the watermark closer to the image's center point, whereas negative values move the watermark further away from the image's center point. +`), + watermark_size: percentageSchema.optional().describe(` +The size of the watermark, as a percentage, such as \`"50%"\`. How the watermark is resized greatly depends on the \`watermark_resize_strategy\`. +`), + watermark_resize_strategy: z + .enum(['area', 'fit', 'stretch']) + .default('fit') + .describe(` +To explain how the resize strategies work, let's assume our target video size is 800×800 pixels and our watermark image is 400×300 pixels. Let's also assume, the \`watermark_size\` parameter is set to \`"25%"\`. + +For the \`"fit"\` resize strategy, the watermark is scaled so that the longer side of the watermark takes up 25% of the corresponding video side. And the other side is scaled according to the aspect ratio of the watermark image. So with our watermark, the width is the longer side, and 25% of the video size would be 200px. Hence, the watermark would be resized to 200×150 pixels. If the \`watermark_size\` was set to \`"50%"\`", it would be resized to 400×300 pixels (so just left at its original size). + +For the \`"stretch"\` resize strategy, the watermark image is stretched (meaning, it is resized without keeping its aspect ratio in mind) so that both sides take up 25% of the corresponding video side. Since our video is 800×800 pixels, for a watermark size of 25% the watermark would be resized to 200×200 pixels. Its height would appear stretched, because keeping the aspect ratio in mind it would be resized to 200×150 pixels instead. + +For the \`"area"\` resize strategy, the watermark is resized (keeping its aspect ratio in check) so that it covers \`"xx%"\` of the video's surface area. The value from \`watermark_size\` is used for the percentage area size. +`), + watermark_start_time: z + .number() + .default(0) + .describe(` +The delay in seconds from the start of the video for the watermark to appear. By default the watermark is immediately shown. +`), + watermark_duration: z + .number() + .default(-1) + .describe(` +The duration in seconds for the watermark to be shown. Can be used together with \`watermark_start_time\` to create nice effects. The default value is \`-1.0\`, which means that the watermark is shown for the entire duration of the video. +`), + watermark_opacity: z + .number() + .min(0) + .max(1) + .default(1) + .describe(` +The opacity of the watermark. Valid values are between \`0\` (invisible) and \`1.0\` (full visibility). +`), + segment: z + .boolean() + .default(false) + .describe(` +Splits the file into multiple parts, to be used for Apple's [HTTP Live Streaming](https://developer.apple.com/resources/http-streaming/). +`), + segment_duration: z + .number() + .int() + .min(1) + .default(10) + .describe(` +Specifies the length of each HTTP segment. This is optional, and the default value as recommended by Apple is \`10\`. Do not change this value unless you have a good reason. +`), + segment_prefix: z + .string() + .default('') + .describe(` +The prefix used for the naming. For example, a prefix of \`"segment_"\` would produce files named \`"segment_0.ts"\`, \`"segment_1.ts"\` and so on. This is optional, and defaults to the base name of the input file. Also see the related \`segment_name\` parameter. +`), + segment_name: z + .string() + .default('') + .describe(` +The name used for the final segment. Available variables are \`\${segment_prefix}\`, \`\${segment_number}\` and \`\${segment_id}\` (which is a UUIDv4 without dashes). +`), + segment_time_delta: z + .number() + .optional() + .describe(` +Delta to apply to segment duration. This is optional and allows fine-tuning of segment boundaries. +`), + }) + .strict() + +/** + * Type for the normalized use parameter from AssemblyNormalizer + * The steps array can contain either strings or objects with name property + */ +export interface NormalizedUse { + steps: Array<{ name: string; as?: string; fields?: string }> +} diff --git a/packages/transloadit/src/alphalib/types/robots/ai-chat.ts b/packages/transloadit/src/alphalib/types/robots/ai-chat.ts new file mode 100644 index 00000000..da94e545 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/ai-chat.ts @@ -0,0 +1,278 @@ +import { z } from 'zod' +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +// We duplicate coreMessageSchema (and its related types) from structuredAiVercel.ts here +// so that we do not need to distribute structuredAiVercel.ts to for instance +// the node-sdk, which does rely on this ai-chat file to determine +// support Robot parameters. + +// Define JSONValue schema for proper type matching with AI SDK +const jsonValueSchema: z.ZodType = z.lazy(() => + z.union([ + z.string(), + z.number(), + z.boolean(), + z.null(), + z.array(jsonValueSchema), + z.record(jsonValueSchema), + ]), +) + +// Define provider metadata schema to match the AI SDK v5 +const providerMetadataSchema = z.record(z.record(jsonValueSchema)).optional() + +const textPartSchema = z.object({ + type: z.literal('text'), + text: z.string(), + experimental_providerMetadata: providerMetadataSchema, +}) +const imagePartSchema = z.object({ + type: z.literal('image'), + image: z.union([ + z.string(), + z.instanceof(Uint8Array), + z.instanceof(ArrayBuffer), + // Note: Buffer is not included here since it's Node.js-only and this code runs in browsers. + // Node.js Buffer extends Uint8Array, so Uint8Array validation handles Buffer values too. + z.instanceof(URL), + ]), + mimeType: z.string().optional(), + experimental_providerMetadata: providerMetadataSchema, +}) +const filePartSchema = z.object({ + type: z.literal('file'), + data: z.union([ + z.string(), + z.instanceof(Uint8Array), + z.instanceof(ArrayBuffer), + // Note: Buffer is not included here since it's Node.js-only and this code runs in browsers. + // Node.js Buffer extends Uint8Array, so Uint8Array validation handles Buffer values too. + z.instanceof(URL), + ]), + mediaType: z.string(), + experimental_providerMetadata: providerMetadataSchema, +}) +const toolCallPartSchema = z.object({ + type: z.literal('tool-call'), + toolCallId: z.string(), + toolName: z.string(), + args: z.record(jsonValueSchema), + experimental_providerMetadata: providerMetadataSchema, +}) +const toolResultPartSchema = z.object({ + type: z.literal('tool-result'), + toolCallId: z.string(), + toolName: z.string(), + result: z.unknown(), + experimental_content: z + .array( + z.union([ + z.object({ + type: z.literal('text'), + text: z.string(), + }), + z.object({ + type: z.literal('image'), + data: z.string(), + mimeType: z.string().optional(), + }), + ]), + ) + .optional(), + isError: z.boolean().optional(), + experimental_providerMetadata: providerMetadataSchema, +}) +const coreSystemMessageSchema = z.object({ + role: z.literal('system'), + content: z.string(), + experimental_providerMetadata: providerMetadataSchema, +}) +const coreUserMessageSchema = z.object({ + role: z.literal('user'), + content: z.union([ + z.string(), + z.array(z.union([textPartSchema, imagePartSchema, filePartSchema])), + ]), + experimental_providerMetadata: providerMetadataSchema, +}) +const coreAssistantMessageSchema = z.object({ + role: z.literal('assistant'), + content: z.union([z.string(), z.array(z.union([textPartSchema, toolCallPartSchema]))]), + experimental_providerMetadata: providerMetadataSchema, +}) +const coreToolMessageSchema = z.object({ + role: z.literal('tool'), + content: z.array(toolResultPartSchema), + experimental_providerMetadata: providerMetadataSchema, +}) +const coreMessageSchema = z.discriminatedUnion('role', [ + coreSystemMessageSchema, + coreUserMessageSchema, + coreAssistantMessageSchema, + coreToolMessageSchema, +]) + +export const meta: RobotMetaInput = { + name: 'AiChatRobot', + allowed_for_url_transform: true, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + minimum_charge: 0, + output_factor: 0.6, + purpose_sentence: 'generates AI chat responses from prompts', + purpose_verb: 'generate', + purpose_word: 'generate', + purpose_words: 'Generate AI chat responses', + service_slug: 'artificial-intelligence', + slot_count: 10, + title: 'Generate AI chat responses', + typical_file_size_mb: 0.01, + typical_file_type: 'document', + priceFactor: 1, + queueSlotCount: 10, + // Is this a sensbile minimum charge? What if the customer supplies their own keys? Is it low enough for these cases? + minimumChargeUsd: 0.06, + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'alpha', +} + +export const vendorModelSchema = z + .string() + .regex(/^[a-z]+\/[a-z0-9.-]+$/, 'Must be in format "vendor/model"') + .refine( + (val) => { + const [vendor, model] = val.split('/') + if (vendor === 'anthropic') { + return model === 'claude-4-sonnet-20250514' || model === 'claude-4-opus-20250514' + } + if (vendor === 'openai') { + return ( + model === 'gpt-4.1-2025-04-14' || + model === 'chatgpt-4o-latest' || + model === 'o3-2025-04-16' || + model === 'gpt-4o-audio-preview' + ) + } + if (vendor === 'google') { + return model === 'gemini-2.5-pro' + } + if (vendor === 'moonshot') { + return model === 'kimi-k2' + } + return false + }, + { + message: + 'Invalid vendor/model combination. Supported: anthropic/claude-4-sonnet-20250514, anthropic/claude-4-opus-20250514, openai/gpt-4.1-2025-04-14, openai/chatgpt-4o-latest, openai/o3-2025-04-16, openai/gpt-4o-audio-preview, google/gemini-2.5-pro, moonshot/kimi-k2', + }, + ) + +export type VendorModel = z.infer + +/** + * Model capabilities for /ai/chat. This centralizes which models support which input types. + * Key format: 'vendor/model' + */ +export const MODEL_CAPABILITIES: Record = { + 'anthropic/claude-4-sonnet-20250514': { pdf: true, image: true }, + 'anthropic/claude-4-opus-20250514': { pdf: true, image: true }, + 'google/gemini-2.5-pro': { pdf: true, image: true }, + 'openai/gpt-4.1-2025-04-14': { pdf: false, image: true }, + 'openai/chatgpt-4o-latest': { pdf: false, image: true }, + 'openai/o3-2025-04-16': { pdf: false, image: true }, + 'openai/gpt-4o-audio-preview': { pdf: false, image: false }, + 'moonshot/kimi-k2': { pdf: false, image: false }, +} + +export const robotAiChatInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/ai/chat'), + // TODO: Is the auto mode yet implemented? + model: z + .union([vendorModelSchema, z.literal('auto')]) + .default('auto') + .describe( + 'The model to use. Transloadit can pick the best model for the job if you set this to "auto".', + ), + format: z.enum(['json', 'text', 'meta']).default('json'), + return_messages: z.enum(['all', 'last']).default('last'), + schema: z.string().optional().describe('The JSON Schema that the LLM should output'), + messages: z + .union([z.string(), z.array(coreMessageSchema)]) + .describe('The prompt, or message history to send to the LLM.'), + system_message: z + .string() + .optional() + .describe('Set the system/developer prompt, if the model allows it'), + credentials: z + .union([z.string(), z.array(z.string())]) + .optional() + .describe('Names of template credentials to make available to the robot.'), + mcp_servers: z + .array( + z.object({ + type: z.enum(['sse', 'http']), + url: z.string(), + headers: z.record(z.string()).optional(), + }), + ) + .optional() + .describe('The MCP servers to use. This is used to call tools from the LLM.'), + }) + .strict() + +export const robotAiChatInstructionsWithHiddenFieldsSchema = robotAiChatInstructionsSchema.extend({ + result: z.union([z.literal('debug'), robotAiChatInstructionsSchema.shape.result]), + provider: z + .string() + .optional() + .describe( + 'Where to run the model. By the default, it is the vendor. For instance, anthropic:claude* runs on the Anthropic API. But, Claude could also be run on AWS Bedrock. This is a hidden placeholder for now, but will be used in the future to allow for more flexibility in where to run models. ', + ), + // These are listed here because we don't have these properties in the public documentation. + // They should set these keys using template credentials. + openai_api_key: z.string().optional().describe('The API key to use for the OpenAI API.'), + anthropic_api_key: z.string().optional().describe('The API key to use for the Anthropic API.'), + deepseek_api_key: z.string().optional().describe('The API key to use for the DeepSeek API.'), + google_generative_ai_api_key: z + .string() + .optional() + .describe('The API key to use for the Google Generative AI API.'), + xai_api_key: z.string().optional().describe('The API key to use for the xAI API.'), +}) + +export type RobotAiChatInstructions = z.infer + +export type RobotAiChatInstructionsWithHiddenFields = z.infer< + typeof robotAiChatInstructionsWithHiddenFieldsSchema +> + +export type RobotAiChatInstructionsWithHiddenFieldsInput = z.input< + typeof robotAiChatInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotAiChatInstructionsSchema = interpolateRobot( + robotAiChatInstructionsSchema, +) +export type InterpolatableRobotAiChatInstructions = z.infer< + typeof interpolatableRobotAiChatInstructionsSchema +> +export type InterpolatableRobotAiChatInstructionsInput = z.input< + typeof interpolatableRobotAiChatInstructionsSchema +> + +export const interpolatableRobotAiChatInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotAiChatInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotAiChatInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotAiChatInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotAiChatInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotAiChatInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/assembly-savejson.ts b/packages/transloadit/src/alphalib/types/robots/assembly-savejson.ts new file mode 100644 index 00000000..99428241 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/assembly-savejson.ts @@ -0,0 +1,37 @@ +import { z } from 'zod' +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase } from './_instructions-primitives.ts' + +// @ts-expect-error - AssemblySavejsonRobot is not ready yet @TODO please supply missing keys +export const meta: RobotMetaInput = { + name: 'AssemblySavejsonRobot', + priceFactor: 0, + queueSlotCount: 5, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: true, + stage: 'ga', + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, +} + +export const robotAssemblySavejsonInstructionsSchema = robotBase + .extend({ + robot: z.literal('/assembly/savejson').describe(` +TODO: Add robot description here +`), + }) + .strict() + +export type RobotAssemblySavejsonInstructions = z.infer< + typeof robotAssemblySavejsonInstructionsSchema +> + +export const interpolatableRobotAssemblySavejsonInstructionsSchema = interpolateRobot( + robotAssemblySavejsonInstructionsSchema, +) +export type InterpolatableRobotAssemblySavejsonInstructions = + InterpolatableRobotAssemblySavejsonInstructionsInput + +export type InterpolatableRobotAssemblySavejsonInstructionsInput = z.input< + typeof interpolatableRobotAssemblySavejsonInstructionsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/audio-artwork.ts b/packages/transloadit/src/alphalib/types/robots/audio-artwork.ts new file mode 100644 index 00000000..e57096f5 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/audio-artwork.ts @@ -0,0 +1,107 @@ +import { z } from 'zod' + +import { stackVersions } from '../stackVersions.ts' +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + interpolateRobot, + robotBase, + robotFFmpegAudio, + robotUse, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + example_code: { + steps: { + artwork_extracted: { + robot: '/audio/artwork', + use: ':original', + ffmpeg_stack: stackVersions.ffmpeg.recommendedVersion, + }, + }, + }, + example_code_description: 'Extract embedded cover artwork from uploaded audio files:', + minimum_charge: 0, + output_factor: 0.8, + override_lvl1: 'Audio Encoding', + purpose_sentence: + 'extracts the embedded cover artwork from audio files and allows you to pipe it into other Steps, for example into /image/resize Steps. It can also insert images into audio files as cover artwork', + purpose_verb: 'extract', + purpose_word: 'extract/insert artwork', + purpose_words: 'Extract or insert audio artwork', + service_slug: 'audio-encoding', + slot_count: 20, + title: 'Extract or insert audio artwork', + typical_file_size_mb: 3.8, + typical_file_type: 'audio file', + uses_tools: ['ffmpeg'], + name: 'AudioArtworkRobot', + priceFactor: 1, + queueSlotCount: 20, + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotAudioArtworkInstructionsSchema = robotBase + .merge(robotUse) + .merge(robotFFmpegAudio) + .extend({ + robot: z.literal('/audio/artwork').describe(` +For extraction, this Robot uses the image format embedded within the audio file — most often, this is JPEG. + +If you need the image in a different format, pipe the result of this Robot into [🤖/image/resize](/docs/robots/image-resize/). + +The \`method\` parameter determines whether to extract or insert. +`), + method: z + .enum(['extract', 'insert']) + .default('extract') + .describe(` +What should be done with the audio file. A value of \`"extract"\` means audio artwork will be extracted. A value of \`"insert"\` means the provided image will be inserted as audio artwork. +`), + change_format_if_necessary: z + .boolean() + .default(false) + .describe(` +Whether the original file should be transcoded into a new format if there is an issue with the original file. +`), + }) + .strict() + +export const robotAudioArtworkInstructionsWithHiddenFieldsSchema = + robotAudioArtworkInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotAudioArtworkInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotAudioArtworkInstructions = z.infer +export type RobotAudioArtworkInstructionsWithHiddenFields = z.infer< + typeof robotAudioArtworkInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotAudioArtworkInstructionsSchema = interpolateRobot( + robotAudioArtworkInstructionsSchema, +) +export type InterpolatableRobotAudioArtworkInstructions = + InterpolatableRobotAudioArtworkInstructionsInput + +export type InterpolatableRobotAudioArtworkInstructionsInput = z.input< + typeof interpolatableRobotAudioArtworkInstructionsSchema +> + +export const interpolatableRobotAudioArtworkInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotAudioArtworkInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotAudioArtworkInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotAudioArtworkInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotAudioArtworkInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotAudioArtworkInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/audio-concat.ts b/packages/transloadit/src/alphalib/types/robots/audio-concat.ts new file mode 100644 index 00000000..2d223372 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/audio-concat.ts @@ -0,0 +1,142 @@ +import { z } from 'zod' + +import { stackVersions } from '../stackVersions.ts' +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + bitrateSchema, + interpolateRobot, + robotBase, + robotFFmpegAudio, + robotUse, + robotUseWithHiddenFields, + sampleRateSchema, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: false, + bytescount: 4, + discount_factor: 0.25, + discount_pct: 75, + example_code: { + steps: { + concatenated: { + robot: '/audio/concat', + use: { + steps: [ + { + name: ':original', + fields: 'first_audio_file', + as: 'audio_1', + }, + { + name: ':original', + fields: 'second_audio_file', + as: 'audio_2', + }, + { + name: ':original', + fields: 'third_audio_file', + as: 'audio_3', + }, + ], + }, + ffmpeg_stack: stackVersions.ffmpeg.recommendedVersion, + }, + }, + }, + example_code_description: + 'If you have a form with 3 file input fields and want to concatenate the uploaded audios in a specific order, instruct Transloadit using the `name` attribute of each input field. Use this attribute as the value for the `fields` key in the JSON, and set `as` to `audio_[[index]]`. Transloadit will concatenate the files based on the ascending index order:', + minimum_charge: 0, + output_factor: 0.8, + override_lvl1: 'Audio Encoding', + purpose_sentence: 'concatenates several audio files together', + purpose_verb: 'concatenate', + purpose_word: 'concatenate', + purpose_words: 'Concatenate audio', + service_slug: 'audio-encoding', + slot_count: 20, + title: 'Concatenate audio', + typical_file_size_mb: 3.8, + typical_file_type: 'audio file', + uses_tools: ['ffmpeg'], + name: 'AudioConcatRobot', + priceFactor: 4, + queueSlotCount: 20, + isAllowedForUrlTransform: false, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotAudioConcatInstructionsSchema = robotBase + .merge(robotUse) + .merge(robotFFmpegAudio) + .extend({ + result: z + .boolean() + .optional() + .describe('Whether the results of this Step should be present in the Assembly Status JSON'), + robot: z.literal('/audio/concat').describe(` +This Robot can concatenate an almost infinite number of audio files. +`), + bitrate: bitrateSchema.optional().describe(` +Bit rate of the resulting audio file, in bits per second. If not specified will default to the bit rate of the input audio file. +`), + sample_rate: sampleRateSchema.optional().describe(` +Sample rate of the resulting audio file, in Hertz. If not specified will default to the sample rate of the input audio file. +`), + audio_fade_seconds: z + .number() + .default(1) + .describe(` +When used this adds an audio fade in and out effect between each section of your concatenated audio file. The float value is used, so if you want an audio delay effect of 500 milliseconds between each video section, you would select 0.5. Integer values can also be represented. + +This parameter does not add an audio fade effect at the beginning or end of your result audio file. If you want to do so, create an additional [🤖/audio/encode](/docs/robots/audio-encode/) Step and use our \`ffmpeg\` parameter as shown in this [demo](/demos/audio-encoding/ffmpeg-fade-in-and-out/). +`), + crossfade: z + .boolean() + .default(false) + .describe(` +When set to \`true\`, this parameter enables crossfading between concatenated audio files using FFmpeg's \`acrossfade\` filter. This creates a smooth transition where the end of one audio file overlaps and blends with the beginning of the next file. + +The duration of the crossfade is controlled by the \`audio_fade_seconds\` parameter (defaults to 1 second if \`audio_fade_seconds\` is 0). + +Note: This parameter requires at least 2 audio files to concatenate and only works with audio files, not video files. +`), + }) + .strict() + +export const robotAudioConcatInstructionsWithHiddenFieldsSchema = robotAudioConcatInstructionsSchema + .omit({ use: true }) + .merge(robotUseWithHiddenFields) + .extend({ + result: z + .union([z.literal('debug'), robotAudioConcatInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotAudioConcatInstructions = z.infer +export type RobotAudioConcatInstructionsWithHiddenFields = z.infer< + typeof robotAudioConcatInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotAudioConcatInstructionsSchema = interpolateRobot( + robotAudioConcatInstructionsSchema, +) +export type InterpolatableRobotAudioConcatInstructions = + InterpolatableRobotAudioConcatInstructionsInput + +export type InterpolatableRobotAudioConcatInstructionsInput = z.input< + typeof interpolatableRobotAudioConcatInstructionsSchema +> + +export const interpolatableRobotAudioConcatInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotAudioConcatInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotAudioConcatInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotAudioConcatInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotAudioConcatInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotAudioConcatInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/audio-encode.ts b/packages/transloadit/src/alphalib/types/robots/audio-encode.ts new file mode 100644 index 00000000..2476cee8 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/audio-encode.ts @@ -0,0 +1,103 @@ +import { z } from 'zod' + +import { stackVersions } from '../stackVersions.ts' +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + bitrateSchema, + interpolateRobot, + robotBase, + robotFFmpegAudio, + robotUse, + sampleRateSchema, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: false, + bytescount: 4, + discount_factor: 0.25, + discount_pct: 75, + example_code: { + steps: { + mp3_encoded: { + robot: '/audio/encode', + use: ':original', + preset: 'mp3', + bitrate: 256000, + ffmpeg_stack: stackVersions.ffmpeg.recommendedVersion, + }, + }, + }, + example_code_description: 'Encode uploaded audio to MP3 format at a 256 kbps bitrate:', + minimum_charge: 0, + output_factor: 0.8, + override_lvl1: 'Audio Encoding', + purpose_sentence: + 'converts audio files into all kinds of formats for you. We provide encoding presets for the most common formats', + purpose_verb: 'encode', + purpose_word: 'encode', + purpose_words: 'Encode audio', + service_slug: 'audio-encoding', + slot_count: 20, + title: 'Encode audio', + typical_file_size_mb: 3.8, + typical_file_type: 'audio file', + uses_tools: ['ffmpeg'], + name: 'AudioEncodeRobot', + priceFactor: 4, + queueSlotCount: 20, + isAllowedForUrlTransform: false, + trackOutputFileSize: true, + isInternal: false, + stage: 'ga', + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, +} + +export const robotAudioEncodeInstructionsSchema = robotBase + .merge(robotUse) + .merge(robotFFmpegAudio) + .extend({ + result: z + .boolean() + .optional() + .describe('Whether the results of this Step should be present in the Assembly Status JSON'), + robot: z.literal('/audio/encode'), + bitrate: bitrateSchema.optional().describe(` +Bit rate of the resulting audio file, in bits per second. If not specified will default to the bit rate of the input audio file. +`), + sample_rate: sampleRateSchema.optional().describe(` +Sample rate of the resulting audio file, in Hertz. If not specified will default to the sample rate of the input audio file. +`), + }) + .strict() + +export const robotAudioEncodeInstructionsWithHiddenFieldsSchema = + robotAudioEncodeInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotAudioEncodeInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotAudioEncodeInstructions = z.infer +export type RobotAudioEncodeInstructionsWithHiddenFields = z.infer< + typeof robotAudioEncodeInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotAudioEncodeInstructionsSchema = interpolateRobot( + robotAudioEncodeInstructionsSchema, +) +export type InterpolatableRobotAudioEncodeInstructions = + InterpolatableRobotAudioEncodeInstructionsInput + +export type InterpolatableRobotAudioEncodeInstructionsInput = z.input< + typeof interpolatableRobotAudioEncodeInstructionsSchema +> + +export const interpolatableRobotAudioEncodeInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotAudioEncodeInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotAudioEncodeInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotAudioEncodeInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotAudioEncodeInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotAudioEncodeInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/audio-loop.ts b/packages/transloadit/src/alphalib/types/robots/audio-loop.ts new file mode 100644 index 00000000..d0f4b872 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/audio-loop.ts @@ -0,0 +1,102 @@ +import { z } from 'zod' + +import { stackVersions } from '../stackVersions.ts' +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + bitrateSchema, + interpolateRobot, + robotBase, + robotFFmpegAudio, + robotUse, + sampleRateSchema, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: false, + bytescount: 4, + discount_factor: 0.25, + discount_pct: 75, + example_code: { + steps: { + looped: { + robot: '/audio/loop', + use: ':original', + duration: 300, + ffmpeg_stack: stackVersions.ffmpeg.recommendedVersion, + }, + }, + }, + example_code_description: 'Loop uploaded audio to achieve a target duration of 300 seconds:', + marketing_intro: + 'Whether you’re producing beats, white-noise, or just empty segments as fillers between audio tracks that you’re to stringing together with [🤖/audio/concat](/docs/robots/audio-concat/), [🤖/audio/loop](/docs/robots/audio-loop/) has got your back.', + minimum_charge: 0, + output_factor: 0.8, + override_lvl1: 'Audio Encoding', + purpose_sentence: 'loops one audio file as often as is required to match a given duration', + purpose_verb: 'loop', + purpose_word: 'loop', + purpose_words: 'Loop audio', + service_slug: 'audio-encoding', + slot_count: 20, + title: 'Loop audio', + typical_file_size_mb: 3.8, + typical_file_type: 'audio file', + uses_tools: ['ffmpeg'], + name: 'AudioLoopRobot', + priceFactor: 4, + queueSlotCount: 20, + isAllowedForUrlTransform: false, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotAudioLoopInstructionsSchema = robotBase + .merge(robotUse) + .merge(robotFFmpegAudio) + .extend({ + robot: z.literal('/audio/loop'), + bitrate: bitrateSchema.optional().describe(` +Bit rate of the resulting audio file, in bits per second. If not specified will default to the bit rate of the input audio file. +`), + sample_rate: sampleRateSchema.optional().describe(` +Sample rate of the resulting audio file, in Hertz. If not specified will default to the sample rate of the input audio file. +`), + duration: z + .number() + .default(60) + .describe(` +Target duration for the whole process in seconds. The Robot will loop the input audio file for as long as this target duration is not reached yet. +`), + }) + .strict() + +export const robotAudioLoopInstructionsWithHiddenFieldsSchema = + robotAudioLoopInstructionsSchema.extend({ + result: z.union([z.literal('debug'), robotAudioLoopInstructionsSchema.shape.result]).optional(), + }) + +export type RobotAudioLoopInstructions = z.infer +export type RobotAudioLoopInstructionsWithHiddenFields = z.infer< + typeof robotAudioLoopInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotAudioLoopInstructionsSchema = interpolateRobot( + robotAudioLoopInstructionsSchema, +) +export type InterpolatableRobotAudioLoopInstructions = InterpolatableRobotAudioLoopInstructionsInput + +export type InterpolatableRobotAudioLoopInstructionsInput = z.input< + typeof interpolatableRobotAudioLoopInstructionsSchema +> + +export const interpolatableRobotAudioLoopInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotAudioLoopInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotAudioLoopInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotAudioLoopInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotAudioLoopInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotAudioLoopInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/audio-merge.ts b/packages/transloadit/src/alphalib/types/robots/audio-merge.ts new file mode 100644 index 00000000..e1059540 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/audio-merge.ts @@ -0,0 +1,137 @@ +import { z } from 'zod' + +import { stackVersions } from '../stackVersions.ts' +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + bitrateSchema, + interpolateRobot, + robotBase, + robotFFmpegAudio, + robotUse, + robotUseWithHiddenFields, + sampleRateSchema, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: false, + bytescount: 4, + discount_factor: 0.25, + discount_pct: 75, + example_code: { + steps: { + merged: { + robot: '/audio/merge', + preset: 'mp3', + use: { + steps: [ + { + name: ':original', + fields: 'first_audio_file', + as: 'audio', + }, + { + name: ':original', + fields: 'second_audio_file', + as: 'audio', + }, + { + name: ':original', + fields: 'third_audio_file', + as: 'audio', + }, + ], + }, + ffmpeg_stack: stackVersions.ffmpeg.recommendedVersion, + }, + }, + }, + example_code_description: + 'If you have a form with 3 file input fields and wish to overlay the uploaded audios, instruct Transloadit using the `name` attribute of each input field. Use this attribute as the value for the `fields` key in the JSON, and set `as` to `audio`:', + minimum_charge: 0, + output_factor: 0.8, + override_lvl1: 'Audio Encoding', + purpose_sentence: 'overlays several audio files on top of each other', + purpose_verb: 'merge', + purpose_word: 'merge', + purpose_words: 'Merge audio files into one', + service_slug: 'audio-encoding', + slot_count: 20, + title: 'Merge audio files into one', + typical_file_size_mb: 3.8, + typical_file_type: 'audio file', + uses_tools: ['ffmpeg'], + name: 'AudioMergeRobot', + priceFactor: 4, + queueSlotCount: 20, + isAllowedForUrlTransform: false, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotAudioMergeInstructionsSchema = robotBase + .merge(robotUse) + .merge(robotFFmpegAudio) + .extend({ + robot: z.literal('/audio/merge'), + bitrate: bitrateSchema.optional().describe(` +Bit rate of the resulting audio file, in bits per second. If not specified will default to the bit rate of the input audio file. +`), + sample_rate: sampleRateSchema.optional().describe(` +Sample rate of the resulting audio file, in Hertz. If not specified will default to the sample rate of the input audio file. +`), + duration: z + .enum(['first', 'longest', 'shortest']) + .default('longest') + .describe(` +Duration of the output file compared to the duration of all merged audio files. Can be \`"first"\` (duration of the first input file), \`"shortest"\` (duration of the shortest audio file) or \`"longest"\` for the duration of the longest input file. +`), + loop: z + .boolean() + .default(false) + .describe(` +Specifies if any input files that do not match the target duration should be looped to match it. Useful for audio merging where your overlay file is typically much shorter than the main audio file. +`), + volume: z + .enum(['average', 'sum']) + .default('average') + .describe(` +Valid values are \`"average"\` and \`"sum"\` here. \`"average"\` means each input is scaled 1/n (n is the number of inputs) or \`"sum"\` which means each individual audio stays on the same volume, but since we merge tracks 'on top' of each other, this could result in very loud output. +`), + }) + .strict() + +export const robotAudioMergeInstructionsWithHiddenFieldsSchema = robotAudioMergeInstructionsSchema + .omit({ use: true }) + .merge(robotUseWithHiddenFields) + .extend({ + result: z + .union([z.literal('debug'), robotAudioMergeInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotAudioMergeInstructions = z.infer +export type RobotAudioMergeInstructionsWithHiddenFields = z.infer< + typeof robotAudioMergeInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotAudioMergeInstructionsSchema = interpolateRobot( + robotAudioMergeInstructionsSchema, +) +export type InterpolatableRobotAudioMergeInstructions = + InterpolatableRobotAudioMergeInstructionsInput + +export type InterpolatableRobotAudioMergeInstructionsInput = z.input< + typeof interpolatableRobotAudioMergeInstructionsSchema +> + +export const interpolatableRobotAudioMergeInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotAudioMergeInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotAudioMergeInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotAudioMergeInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotAudioMergeInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotAudioMergeInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/audio-waveform.ts b/packages/transloadit/src/alphalib/types/robots/audio-waveform.ts new file mode 100644 index 00000000..a48c8795 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/audio-waveform.ts @@ -0,0 +1,280 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + color_with_alpha, + interpolateRobot, + robotBase, + robotFFmpeg, + robotUse, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: false, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + example_code: { + steps: { + waveformed: { + robot: '/audio/waveform', + use: ':original', + width: 400, + height: 200, + outer_color: '0099ccff', + center_color: '0099ccff', + }, + }, + }, + example_code_description: + 'Generate a 400×200 waveform in `#0099cc` color from an uploaded audio file:', + extended_description: ` +Here is an example waveform image: + +{% assign hotDemo = collections.demos |find: "url", "/demos/audio-encoding/generate-a-waveform-image-from-an-audio-file/" %} + +Example waveform image +`, + minimum_charge: 1048576, + output_factor: 0.07, + override_lvl1: 'Audio Encoding', + purpose_sentence: + 'generates waveform images for your audio files and allows you to change their colors and dimensions', + purpose_verb: 'generate', + purpose_word: 'generate waveforms', + purpose_words: 'Generate waveform images from audio', + service_slug: 'audio-encoding', + slot_count: 20, + title: 'Generate waveform images from audio', + typical_file_size_mb: 3.8, + typical_file_type: 'audio file', + name: 'AudioWaveformRobot', + priceFactor: 1, + queueSlotCount: 20, + minimumCharge: 1048576, + isAllowedForUrlTransform: false, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +// Base schema with common fields +const robotAudioWaveformInstructionsBaseSchema = robotBase + .merge(robotUse) + .merge(robotFFmpeg) + .extend({ + robot: z.literal('/audio/waveform').describe(` +We recommend that you use an [🤖/audio/encode](/docs/robots/audio-encode/) Step prior to your waveform Step to convert audio files to MP3. This way it is guaranteed that [🤖/audio/waveform](/docs/robots/audio-waveform/) accepts your audio file and you can also down-sample large audio files and save some money. + +Similarly, if you need the output image in a different format, please pipe the result of this Robot into [🤖/image/resize](/docs/robots/image-resize/). +`), + format: z + .enum(['image', 'json']) + .default('image') + .describe(` +The format of the result file. Can be \`"image"\` or \`"json"\`. If \`"image"\` is supplied, a PNG image will be created, otherwise a JSON file. +`), + width: z + .number() + .int() + .min(1) + .default(256) + .describe(` +The width of the resulting image if the format \`"image"\` was selected. +`), + height: z + .number() + .int() + .min(1) + .default(64) + .describe(` +The height of the resulting image if the format \`"image"\` was selected. +`), + antialiasing: z + .union([z.literal(0), z.literal(1), z.boolean()]) + .default(0) + .describe(` +Either a value of \`0\` or \`1\`, or \`true\`/\`false\`, corresponding to if you want to enable antialiasing to achieve smoother edges in the waveform graph or not. +`), + background_color: color_with_alpha.default('#00000000').describe(` +The background color of the resulting image in the "rrggbbaa" format (red, green, blue, alpha), if the format \`"image"\` was selected. +`), + center_color: color_with_alpha.default('000000ff').describe(` +The color used in the center of the gradient. The format is "rrggbbaa" (red, green, blue, alpha). +`), + outer_color: color_with_alpha.default('000000ff').describe(` +The color used in the outer parts of the gradient. The format is "rrggbbaa" (red, green, blue, alpha). +`), + }) + +const styleSchema = z.preprocess( + (val) => { + // Backwards compatibility: historically this robot used numeric styles 0/1/2. + // The new API is `style: "v0" | "v1"`. Old v2 values are mapped to v1. + if (val === 'v1' || val === 1 || val === '1') return 'v1' + if (val === 'v0' || val === 0 || val === '0') return 'v0' + return val + }, + z.enum(['v0', 'v1']).default('v0'), +) + +// Unified schema: all parameters exist for both styles, but v1-only parameters only apply when +// `style` is `v1` (they are accepted for v0 but have no effect). +export const robotAudioWaveformInstructionsSchema = robotAudioWaveformInstructionsBaseSchema + .extend({ + style: styleSchema.describe(` +Waveform style version. + +- \`"v0"\`: Legacy waveform generation (default). +- \`"v1"\`: Advanced waveform generation with additional parameters. + +For backwards compatibility, numeric values \`0\`, \`1\`, \`2\` are also accepted and mapped to \`"v0"\` (0) and \`"v1"\` (1/2). +`), + + // v1-only parameters (accepted for v0 but have no effect) + split_channels: z + .boolean() + .optional() + .describe(` +Available when style is \`"v1"\`. If set to \`true\`, outputs multi-channel waveform data or image files, one per channel. +`), + zoom: z + .number() + .int() + .min(1) + .optional() + .describe(` +Available when style is \`"v1"\`. Zoom level in samples per pixel. This parameter cannot be used together with \`pixels_per_second\`. +`), + pixels_per_second: z + .number() + .positive() + .optional() + .describe(` +Available when style is \`"v1"\`. Zoom level in pixels per second. This parameter cannot be used together with \`zoom\`. +`), + bits: z + .union([z.literal(8), z.literal(16)]) + .optional() + .describe(` +Available when style is \`"v1"\`. Bit depth for waveform data. Can be 8 or 16. +`), + start: z + .number() + .min(0) + .optional() + .describe(` +Available when style is \`"v1"\`. Start time in seconds. +`), + end: z + .number() + .min(0) + .optional() + .describe(` +Available when style is \`"v1"\`. End time in seconds (0 means end of audio). +`), + colors: z + .enum(['audition', 'audacity']) + .optional() + .describe(` +Available when style is \`"v1"\`. Color scheme to use. Can be "audition" or "audacity". +`), + border_color: color_with_alpha.optional().describe(` +Available when style is \`"v1"\`. Border color in "rrggbbaa" format. +`), + waveform_style: z + .enum(['normal', 'bars']) + .optional() + .describe(` +Available when style is \`"v1"\`. Waveform style. Can be "normal" or "bars". +`), + bar_width: z + .number() + .int() + .positive() + .optional() + .describe(` +Available when style is \`"v1"\`. Width of bars in pixels when waveform_style is "bars". +`), + bar_gap: z + .number() + .int() + .min(0) + .optional() + .describe(` +Available when style is \`"v1"\`. Gap between bars in pixels when waveform_style is "bars". +`), + bar_style: z + .enum(['square', 'rounded']) + .optional() + .describe(` +Available when style is \`"v1"\`. Bar style when waveform_style is "bars". +`), + axis_label_color: color_with_alpha.optional().describe(` +Available when style is \`"v1"\`. Color for axis labels in "rrggbbaa" format. +`), + no_axis_labels: z + .boolean() + .optional() + .describe(` +Available when style is \`"v1"\`. If set to \`true\`, renders waveform image without axis labels. +`), + with_axis_labels: z + .boolean() + .optional() + .describe(` +Available when style is \`"v1"\`. If set to \`true\`, renders waveform image with axis labels. +`), + amplitude_scale: z + .number() + .positive() + .optional() + .describe(` +Available when style is \`"v1"\`. Amplitude scale factor. +`), + compression: z + .number() + .int() + .min(-1) + .max(9) + .optional() + .describe(` +Available when style is \`"v1"\`. PNG compression level: 0 (none) to 9 (best), or -1 (default). Only applicable when format is "image". +`), + }) + .strict() + +export const robotAudioWaveformInstructionsWithHiddenFieldsSchema = + robotAudioWaveformInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotAudioWaveformInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotAudioWaveformInstructions = z.infer +export type RobotAudioWaveformInstructionsWithHiddenFields = z.infer< + typeof robotAudioWaveformInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotAudioWaveformInstructionsSchema = interpolateRobot( + robotAudioWaveformInstructionsSchema, +) + +export type InterpolatableRobotAudioWaveformInstructions = z.input< + typeof interpolatableRobotAudioWaveformInstructionsSchema +> +export type InterpolatableRobotAudioWaveformInstructionsInput = z.input< + typeof interpolatableRobotAudioWaveformInstructionsSchema +> + +export const interpolatableRobotAudioWaveformInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotAudioWaveformInstructionsWithHiddenFieldsSchema, +) + +export type InterpolatableRobotAudioWaveformInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotAudioWaveformInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotAudioWaveformInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotAudioWaveformInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/azure-import.ts b/packages/transloadit/src/alphalib/types/robots/azure-import.ts new file mode 100644 index 00000000..806277d2 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/azure-import.ts @@ -0,0 +1,111 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + azureBase, + files_per_page, + interpolateRobot, + next_page_token, + path, + recursive, + robotBase, + robotImport, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 10, + discount_factor: 0.1, + discount_pct: 90, + example_code: { + steps: { + imported: { + robot: '/azure/import', + credentials: 'YOUR_AZURE_CREDENTIALS', + path: 'path/to/files/', + }, + }, + }, + example_code_description: + 'Import files from the `path/to/files` directory and its subdirectories:', + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Importing', + purpose_sentence: 'imports whole directories of files from your Azure container', + purpose_verb: 'import', + purpose_word: 'Azure', + purpose_words: 'Import files from Azure', + service_slug: 'file-importing', + requires_credentials: true, + slot_count: 20, + title: 'Import files from Azure', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'AzureImportRobot', + priceFactor: 6.6666, + queueSlotCount: 20, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: true, + stage: 'ga', +} + +export const robotAzureImportInstructionsSchema = robotBase + .merge(robotImport) + .merge(azureBase) + .extend({ + robot: z.literal('/azure/import'), + path: path.describe(` +The path in your container to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. + +If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are descendants of this directory are recursively imported. For example: \`images/\`. + +If you want to import all files from the root directory, please use \`/\` as the value here. + +You can also use an array of path strings here to import multiple paths in the same Robot's Step. +`), + recursive: recursive.describe(` + Setting this to \`true\` will enable importing files from subdirectories and sub-subdirectories (etc.) of the given path. + `), + next_page_token: next_page_token.describe(` +A string token used for pagination. The returned files of one paginated call have the next page token inside of their meta data, which needs to be used for the subsequent paging call. +`), + files_per_page: files_per_page.describe(` +The pagination page size. +`), + }) + .strict() + +export const robotAzureImportInstructionsWithHiddenFieldsSchema = + robotAzureImportInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotAzureImportInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotAzureImportInstructions = z.infer +export type RobotAzureImportInstructionsWithHiddenFields = z.infer< + typeof robotAzureImportInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotAzureImportInstructionsSchema = interpolateRobot( + robotAzureImportInstructionsSchema, +) +export type InterpolatableRobotAzureImportInstructions = + InterpolatableRobotAzureImportInstructionsInput + +export type InterpolatableRobotAzureImportInstructionsInput = z.input< + typeof interpolatableRobotAzureImportInstructionsSchema +> + +export const interpolatableRobotAzureImportInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotAzureImportInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotAzureImportInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotAzureImportInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotAzureImportInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotAzureImportInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/azure-store.ts b/packages/transloadit/src/alphalib/types/robots/azure-store.ts new file mode 100644 index 00000000..dcb1e678 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/azure-store.ts @@ -0,0 +1,143 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { azureBase, interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 6, + discount_factor: 0.15000150001500018, + discount_pct: 84.99984999849998, + example_code: { + steps: { + exported: { + robot: '/azure/store', + use: ':original', + credentials: 'YOUR_AZURE_CREDENTIALS', + path: 'my_target_folder/${unique_prefix}/${file.url_name}', + }, + }, + }, + example_code_description: 'Export uploaded files to `my_target_folder` on Azure:', + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Exporting', + purpose_sentence: 'exports encoding results to Microsoft Azure', + purpose_verb: 'export', + purpose_word: 'Azure', + purpose_words: 'Export files to Microsoft Azure', + service_slug: 'file-exporting', + slot_count: 10, + title: 'Export files to Microsoft Azure', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'AzureStoreRobot', + priceFactor: 6.6666, + queueSlotCount: 10, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotAzureStoreInstructionsSchema = robotBase + .merge(robotUse) + .merge(azureBase) + .extend({ + robot: z.literal('/azure/store'), + path: z + .string() + .default('${unique_prefix}/${file.url_name}') + .describe(` +The path at which the file is to be stored. This may include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). +`), + content_type: z + .string() + .optional() + .describe(` +The content type with which to store the file. By default this will be guessed by Azure. +`), + content_encoding: z + .string() + .optional() + .describe(` +The content encoding with which to store the file. By default this will be guessed by Azure. +`), + content_language: z + .string() + .optional() + .describe(` +The content language with which to store the file. By default this will be guessed by Azure. +`), + content_disposition: z + .string() + .optional() + .describe(` +The content disposition with which to store the file. By default this will be guessed by Azure. +`), + cache_control: z + .string() + .optional() + .describe(` +The cache control header with which to store the file. +`), + // TODO: verify if this is correct. + metadata: z + .record(z.string()) + .default({}) + .describe(` +A JavaScript object containing a list of metadata to be set for this file on Azure, such as \`{ FileURL: "\${file.url_name}" }\`. This can also include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). +`), + sas_expires_in: z + .number() + .int() + .min(0) + .optional() + .describe(` +Set this to a number to enable shared access signatures for your stored object. This reflects the number of seconds that the signature will be valid for once the object is stored. Enabling this will attach the shared access signature (SAS) to the result URL of your object. +`), + sas_permissions: z + .string() + .regex(/^[rdw]+$/) + .min(0) + .max(3) + .optional() + .describe(` +Set this to a combination of \`r\` (read), \`w\` (write) and \`d\` (delete) for your shared access signatures (SAS) permissions. +`), + }) + .strict() + +export const robotAzureStoreInstructionsWithHiddenFieldsSchema = + robotAzureStoreInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotAzureStoreInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotAzureStoreInstructions = z.infer +export type RobotAzureStoreInstructionsWithHiddenFields = z.infer< + typeof robotAzureStoreInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotAzureStoreInstructionsSchema = interpolateRobot( + robotAzureStoreInstructionsSchema, +) +export type InterpolatableRobotAzureStoreInstructions = + InterpolatableRobotAzureStoreInstructionsInput + +export type InterpolatableRobotAzureStoreInstructionsInput = z.input< + typeof interpolatableRobotAzureStoreInstructionsSchema +> + +export const interpolatableRobotAzureStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotAzureStoreInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotAzureStoreInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotAzureStoreInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotAzureStoreInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotAzureStoreInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/backblaze-import.ts b/packages/transloadit/src/alphalib/types/robots/backblaze-import.ts new file mode 100644 index 00000000..1062f462 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/backblaze-import.ts @@ -0,0 +1,119 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + backblazeBase, + files_per_page, + interpolateRobot, + path, + recursive, + robotBase, + robotImport, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 10, + discount_factor: 0.1, + discount_pct: 90, + example_code: { + steps: { + imported: { + robot: '/backblaze/import', + credentials: 'YOUR_BACKBLAZE_CREDENTIALS', + path: 'path/to/files/', + recursive: true, + }, + }, + }, + example_code_description: + 'Import files from the `path/to/files` directory and its subdirectories:', + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Importing', + purpose_sentence: 'imports whole directories of files from your Backblaze bucket', + purpose_verb: 'import', + purpose_word: 'Backblaze', + purpose_words: 'Import files from Backblaze', + requires_credentials: true, + service_slug: 'file-importing', + slot_count: 20, + title: 'Import files from Backblaze', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'BackblazeImportRobot', + priceFactor: 6.6666, + queueSlotCount: 20, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: true, + stage: 'ga', +} + +export const robotBackblazeImportInstructionsSchema = robotBase + .merge(robotImport) + .merge(backblazeBase) + .extend({ + robot: z.literal('/backblaze/import'), + path: path.describe(` +The path in your bucket to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. + +If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are direct descendants of this directory will be imported. For example: \`images/\`. + +Directories are **not** imported recursively. If you want to import files from subdirectories and sub-subdirectories, enable the \`recursive\` parameter. + +If you want to import all files from the root directory, please use \`/\` as the value here. In this case, make sure all your objects belong to a path. If you have objects in the root of your bucket that aren't prefixed with \`/\`, you'll receive a 404 \`BACKBLAZE_IMPORT_NOT_FOUND\` error. + +You can also use an array of path strings here to import multiple paths in the same Robot's Step. +`), + recursive: recursive.describe(` +Setting this to \`true\` will enable importing files from subdirectories and sub-subdirectories (etc.) of the given path. + +Please use the pagination parameters \`start_file_name\` and \`files_per_page\` wisely here. +`), + start_file_name: z + .string() + .default('') + .describe(` +The name of the last file from the previous paging call. This tells the Robot to ignore all files up to and including this file. +`), + files_per_page: files_per_page.describe(` +The pagination page size. This only works when recursive is \`true\` for now, in order to not break backwards compatibility in non-recursive imports. +`), + }) + .strict() + +export const robotBackblazeImportInstructionsWithHiddenFieldsSchema = + robotBackblazeImportInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotBackblazeImportInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotBackblazeImportInstructions = z.infer< + typeof robotBackblazeImportInstructionsSchema +> +export type RobotBackblazeImportInstructionsWithHiddenFields = z.infer< + typeof robotBackblazeImportInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotBackblazeImportInstructionsSchema = interpolateRobot( + robotBackblazeImportInstructionsSchema, +) +export type InterpolatableRobotBackblazeImportInstructions = + InterpolatableRobotBackblazeImportInstructionsInput + +export type InterpolatableRobotBackblazeImportInstructionsInput = z.input< + typeof interpolatableRobotBackblazeImportInstructionsSchema +> + +export const interpolatableRobotBackblazeImportInstructionsWithHiddenFieldsSchema = + interpolateRobot(robotBackblazeImportInstructionsWithHiddenFieldsSchema) +export type InterpolatableRobotBackblazeImportInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotBackblazeImportInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotBackblazeImportInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotBackblazeImportInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/backblaze-store.ts b/packages/transloadit/src/alphalib/types/robots/backblaze-store.ts new file mode 100644 index 00000000..84bbd13e --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/backblaze-store.ts @@ -0,0 +1,104 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { backblazeBase, interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 6, + discount_factor: 0.15000150001500018, + discount_pct: 84.99984999849998, + example_code: { + steps: { + exported: { + robot: '/backblaze/store', + use: ':original', + credentials: 'YOUR_BACKBLAZE_CREDENTIALS', + path: 'my_target_folder/${unique_prefix}/${file.url_name}', + }, + }, + }, + example_code_description: 'Export uploaded files to `my_target_folder` on Backblaze:', + extended_description: ` +## Access + +Your Backblaze buckets need to have the \`listBuckets\` (to obtain a bucket ID from a bucket name), \`writeFiles\` and \`listFiles\` permissions. +`, + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Exporting', + purpose_sentence: 'exports encoding results to Backblaze', + purpose_verb: 'export', + purpose_word: 'Backblaze', + purpose_words: 'Export files to Backblaze', + service_slug: 'file-exporting', + slot_count: 10, + title: 'Export files to Backblaze', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'BackblazeStoreRobot', + priceFactor: 6.6666, + queueSlotCount: 10, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotBackblazeStoreInstructionsSchema = robotBase + .merge(robotUse) + .merge(backblazeBase) + .extend({ + robot: z.literal('/backblaze/store'), + path: z + .string() + .default('${unique_prefix}/${file.url_name}') + .describe(` +The path at which the file is to be stored. This may include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). +`), + headers: z + .record(z.string()) + .default({}) + .describe(` +An object containing a list of headers to be set for this file on backblaze, such as \`{ FileURL: "\${file.url_name}" }\`. This can also include any available [Assembly Variables](/docs/topics/assembly-instructions/#assembly-variables). + +[Here](https://www.backblaze.com/b2/docs/b2_upload_file.html) you can find a list of available headers. + +Object Metadata can be specified using \`X-Bz-Info-*\` headers. +`), + }) + .strict() + +export const robotBackblazeStoreInstructionsWithHiddenFieldsSchema = + robotBackblazeStoreInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotBackblazeStoreInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotBackblazeStoreInstructions = z.infer +export type RobotBackblazeStoreInstructionsWithHiddenFields = z.infer< + typeof robotBackblazeStoreInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotBackblazeStoreInstructionsSchema = interpolateRobot( + robotBackblazeStoreInstructionsSchema, +) +export type InterpolatableRobotBackblazeStoreInstructions = + InterpolatableRobotBackblazeStoreInstructionsInput + +export type InterpolatableRobotBackblazeStoreInstructionsInput = z.input< + typeof interpolatableRobotBackblazeStoreInstructionsSchema +> + +export const interpolatableRobotBackblazeStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotBackblazeStoreInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotBackblazeStoreInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotBackblazeStoreInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotBackblazeStoreInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotBackblazeStoreInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/cloudfiles-import.ts b/packages/transloadit/src/alphalib/types/robots/cloudfiles-import.ts new file mode 100644 index 00000000..3f380511 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/cloudfiles-import.ts @@ -0,0 +1,118 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + cloudfilesBase, + files_per_page, + interpolateRobot, + page_number, + path, + robotBase, + robotImport, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 10, + discount_factor: 0.1, + discount_pct: 90, + example_code: { + steps: { + imported: { + robot: '/cloudfiles/import', + credentials: 'YOUR_CLOUDFILES_CREDENTIALS', + path: 'path/to/files/', + recursive: true, + }, + }, + }, + example_code_description: + 'Import files from the `path/to/files` directory and its subdirectories:', + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Importing', + purpose_sentence: 'imports whole directories of files from your Rackspace Cloud Files container', + purpose_verb: 'import', + purpose_word: 'Rackspace Cloud Files', + purpose_words: 'Import files from Rackspace Cloud Files', + requires_credentials: true, + service_slug: 'file-importing', + slot_count: 20, + title: 'Import files from Rackspace Cloud Files', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'CloudfilesImportRobot', + priceFactor: 6.6666, + queueSlotCount: 20, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: true, + stage: 'ga', +} + +export const robotCloudfilesImportInstructionsSchema = robotBase + .merge(robotImport) + .merge(cloudfilesBase) + .extend({ + robot: z.literal('/cloudfiles/import'), + path: path.describe(` +The path in your bucket to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. + +If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are direct descendants of this directory will be imported. For example: \`images/\`. + +Directories are **not** imported recursively. If you want to import files from subdirectories and sub-subdirectories, enable the \`recursive\` parameter. + +You can also use an array of path strings here to import multiple paths in the same Robot's Step. +`), + recursive: z + .boolean() + .default(false) + .describe(` +Setting this to \`true\` will enable importing files from subdirectories and sub-subdirectories (etc.) of the given path. + +Please use the pagination parameters \`page_number\` and \`files_per_page\`wisely here. +`), + page_number: page_number.describe(` +The pagination page number. For now, in order to not break backwards compatibility in non-recursive imports, this only works when recursive is set to \`true\`. + +When doing big imports, make sure no files are added or removed from other scripts within your path, otherwise you might get weird results with the pagination. +`), + files_per_page: files_per_page.describe(` +The pagination page size. This only works when recursive is \`true\` for now, in order to not break backwards compatibility in non-recursive imports. +`), + }) + .strict() + +export const robotCloudfilesImportInstructionsWithHiddenFieldsSchema = + robotCloudfilesImportInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotCloudfilesImportInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotCloudfilesImportInstructions = z.infer< + typeof robotCloudfilesImportInstructionsSchema +> +export type RobotCloudfilesImportInstructionsWithHiddenFields = z.infer< + typeof robotCloudfilesImportInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotCloudfilesImportInstructionsSchema = interpolateRobot( + robotCloudfilesImportInstructionsSchema, +) +export type InterpolatableRobotCloudfilesImportInstructions = + InterpolatableRobotCloudfilesImportInstructionsInput + +export type InterpolatableRobotCloudfilesImportInstructionsInput = z.input< + typeof interpolatableRobotCloudfilesImportInstructionsSchema +> + +export const interpolatableRobotCloudfilesImportInstructionsWithHiddenFieldsSchema = + interpolateRobot(robotCloudfilesImportInstructionsWithHiddenFieldsSchema) +export type InterpolatableRobotCloudfilesImportInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotCloudfilesImportInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotCloudfilesImportInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotCloudfilesImportInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/cloudfiles-store.ts b/packages/transloadit/src/alphalib/types/robots/cloudfiles-store.ts new file mode 100644 index 00000000..eec34b63 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/cloudfiles-store.ts @@ -0,0 +1,104 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + cloudfilesBase, + interpolateRobot, + robotBase, + robotUse, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 6, + discount_factor: 0.15000150001500018, + discount_pct: 84.99984999849998, + example_code: { + steps: { + exported: { + robot: '/cloudfiles/store', + use: ':original', + credentials: 'YOUR_CLOUDFILES_CREDENTIALS', + path: 'my_target_folder/${unique_prefix}/${file.url_name}', + }, + }, + }, + example_code_description: 'Export uploaded files to `my_target_folder` on Rackspace Cloud Files:', + extended_description: ` + + +## A note about URLs + +If your container is CDN-enabled, the resulting \`file.url\` indicates the path to the file in your +CDN container, or is \`null\` otherwise. + +The storage container URL for this file is always available via \`file.meta.storage_url\`. +`, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Exporting', + purpose_sentence: 'exports encoding results to Rackspace Cloud Files', + purpose_verb: 'export', + purpose_word: 'Rackspace Cloud Files', + purpose_words: 'Export files to Rackspace Cloud Files', + service_slug: 'file-exporting', + slot_count: 10, + title: 'Export files to Rackspace Cloud Files', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'CloudfilesStoreRobot', + priceFactor: 6.6666, + queueSlotCount: 10, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotCloudfilesStoreInstructionsSchema = robotBase + .merge(robotUse) + .merge(cloudfilesBase) + .extend({ + robot: z.literal('/cloudfiles/store'), + path: z + .string() + .default('${unique_prefix}/${file.url_name}') + .describe(` +The path at which to store the file. This value can also contain [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). +`), + }) + .strict() + +export const robotCloudfilesStoreInstructionsWithHiddenFieldsSchema = + robotCloudfilesStoreInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotCloudfilesStoreInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotCloudfilesStoreInstructions = z.infer< + typeof robotCloudfilesStoreInstructionsSchema +> +export type RobotCloudfilesStoreInstructionsWithHiddenFields = z.infer< + typeof robotCloudfilesStoreInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotCloudfilesStoreInstructionsSchema = interpolateRobot( + robotCloudfilesStoreInstructionsSchema, +) +export type InterpolatableRobotCloudfilesStoreInstructions = + InterpolatableRobotCloudfilesStoreInstructionsInput + +export type InterpolatableRobotCloudfilesStoreInstructionsInput = z.input< + typeof interpolatableRobotCloudfilesStoreInstructionsSchema +> + +export const interpolatableRobotCloudfilesStoreInstructionsWithHiddenFieldsSchema = + interpolateRobot(robotCloudfilesStoreInstructionsWithHiddenFieldsSchema) +export type InterpolatableRobotCloudfilesStoreInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotCloudfilesStoreInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotCloudfilesStoreInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotCloudfilesStoreInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/cloudflare-import.ts b/packages/transloadit/src/alphalib/types/robots/cloudflare-import.ts new file mode 100644 index 00000000..3ac9036c --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/cloudflare-import.ts @@ -0,0 +1,121 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + cloudflareBase, + files_per_page, + interpolateRobot, + page_number, + path, + recursive, + return_file_stubs, + robotBase, + robotImport, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 10, + discount_factor: 0.1, + discount_pct: 90, + example_code: { + steps: { + imported: { + robot: '/cloudflare/import', + credentials: 'YOUR_CLOUDFLARE_CREDENTIALS', + path: 'path/to/files/', + recursive: true, + }, + }, + }, + example_code_description: + 'Import files from the `path/to/files` directory and its subdirectories:', + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Importing', + purpose_sentence: 'imports whole directories of files from your cloudflare r2 bucket', + purpose_verb: 'import', + purpose_word: 'cloudflare', + purpose_words: 'Import files from Cloudflare R2', + requires_credentials: true, + service_slug: 'file-importing', + slot_count: 20, + title: 'Import files from Cloudflare R2', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'CloudflareImportRobot', + priceFactor: 6.6666, + queueSlotCount: 20, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: true, + stage: 'ga', +} + +export const robotCloudflareImportInstructionsSchema = robotBase + .merge(robotImport) + .merge(cloudflareBase) + .extend({ + robot: z.literal('/cloudflare/import'), + path: path.describe(` +The path in your bucket to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. + +If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are direct descendants of this directory will be imported. For example: \`images/\`. + +Directories are **not** imported recursively. If you want to import files from subdirectories and sub-subdirectories, enable the \`recursive\` parameter. + +If you want to import all files from the root directory, please use \`/\` as the value here. In this case, make sure all your objects belong to a path. If you have objects in the root of your bucket that aren't prefixed with \`/\`, you'll receive an error: \`A client error (NoSuchKey) occurred when calling the GetObject operation: The specified key does not exist.\` + +You can also use an array of path strings here to import multiple paths in the same Robot's Step. +`), + recursive: recursive.describe(` +Setting this to \`true\` will enable importing files from subfolders and sub-subfolders, etc. of the given path. + +Please use the pagination parameters \`page_number\` and \`files_per_page\` wisely here. +`), + page_number: page_number.describe(` +The pagination page number. For now, in order to not break backwards compatibility in non-recursive imports, this only works when recursive is set to \`true\`. + +When doing big imports, make sure no files are added or removed from other scripts within your path, otherwise you might get weird results with the pagination. +`), + files_per_page: files_per_page.describe(` +The pagination page size. This only works when recursive is \`true\` for now, in order to not break backwards compatibility in non-recursive imports. +`), + return_file_stubs, + }) + .strict() + +export const robotCloudflareImportInstructionsWithHiddenFieldsSchema = + robotCloudflareImportInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotCloudflareImportInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotCloudflareImportInstructions = z.infer< + typeof robotCloudflareImportInstructionsSchema +> +export type RobotCloudflareImportInstructionsWithHiddenFields = z.infer< + typeof robotCloudflareImportInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotCloudflareImportInstructionsSchema = interpolateRobot( + robotCloudflareImportInstructionsSchema, +) +export type InterpolatableRobotCloudflareImportInstructions = + InterpolatableRobotCloudflareImportInstructionsInput + +export type InterpolatableRobotCloudflareImportInstructionsInput = z.input< + typeof interpolatableRobotCloudflareImportInstructionsSchema +> + +export const interpolatableRobotCloudflareImportInstructionsWithHiddenFieldsSchema = + interpolateRobot(robotCloudflareImportInstructionsWithHiddenFieldsSchema) +export type InterpolatableRobotCloudflareImportInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotCloudflareImportInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotCloudflareImportInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotCloudflareImportInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/cloudflare-store.ts b/packages/transloadit/src/alphalib/types/robots/cloudflare-store.ts new file mode 100644 index 00000000..905ba306 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/cloudflare-store.ts @@ -0,0 +1,120 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + cloudflareBase, + interpolateRobot, + robotBase, + robotUse, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 6, + discount_factor: 0.15000150001500018, + discount_pct: 84.99984999849998, + example_code: { + steps: { + exported: { + robot: '/cloudflare/store', + use: ':original', + credentials: 'YOUR_CLOUDFLARE_CREDENTIALS', + path: 'my_target_folder/${unique_prefix}/${file.url_name}', + }, + }, + }, + example_code_description: 'Export uploaded files to `my_target_folder` on cloudflare R2:', + extended_description: ` +The URL to the result file will be returned in the Assembly Status JSON. +`, + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Exporting', + purpose_sentence: 'exports encoding results to cloudflare r2 buckets', + purpose_verb: 'export', + purpose_word: 'cloudflare', + purpose_words: 'Export files to Cloudflare R2', + service_slug: 'file-exporting', + slot_count: 10, + title: 'Export files to Cloudflare R2', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'CloudflareStoreRobot', + priceFactor: 6.6666, + queueSlotCount: 10, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotCloudflareStoreInstructionsSchema = robotBase + .merge(robotUse) + .merge(cloudflareBase) + .extend({ + robot: z.literal('/cloudflare/store'), + path: z + .string() + .default('${unique_prefix}/${file.url_name}') + .describe(` +The path at which the file is to be stored. This may include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). The path must not be a directory. +`), + headers: z + .record(z.string()) + .default({ 'Content-Type': '${file.mime}' }) + .describe(` +An object containing a list of headers to be set for this file on cloudflare Spaces, such as \`{ FileURL: "\${file.url_name}" }\`. This can also include any available [Assembly Variables](/docs/topics/assembly-instructions/#assembly-variables). + +Object Metadata can be specified using \`x-amz-meta-*\` headers. Note that these headers [do not support non-ASCII metadata values](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html#UserMetadata). +`), + sign_urls_for: z + .number() + .int() + .min(0) + .optional() + .describe(` +This parameter provides signed URLs in the result JSON (in the \`signed_ssl_url\` property). The number that you set this parameter to is the URL expiry time in seconds. If this parameter is not used, no URL signing is done. +`), + url_prefix: z + .string() + .optional() + .describe(` +The URL prefix used for accessing files from your Cloudflare R2 bucket. This is typically the custom public URL access host set up in your Cloudflare account. +`), + }) + .strict() + +export const robotCloudflareStoreInstructionsWithHiddenFieldsSchema = + robotCloudflareStoreInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotCloudflareStoreInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotCloudflareStoreInstructions = z.infer< + typeof robotCloudflareStoreInstructionsSchema +> +export type RobotCloudflareStoreInstructionsWithHiddenFields = z.infer< + typeof robotCloudflareStoreInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotCloudflareStoreInstructionsSchema = interpolateRobot( + robotCloudflareStoreInstructionsSchema, +) +export type InterpolatableRobotCloudflareStoreInstructions = + InterpolatableRobotCloudflareStoreInstructionsInput + +export type InterpolatableRobotCloudflareStoreInstructionsInput = z.input< + typeof interpolatableRobotCloudflareStoreInstructionsSchema +> + +export const interpolatableRobotCloudflareStoreInstructionsWithHiddenFieldsSchema = + interpolateRobot(robotCloudflareStoreInstructionsWithHiddenFieldsSchema) +export type InterpolatableRobotCloudflareStoreInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotCloudflareStoreInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotCloudflareStoreInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotCloudflareStoreInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/digitalocean-import.ts b/packages/transloadit/src/alphalib/types/robots/digitalocean-import.ts new file mode 100644 index 00000000..a63a7dc9 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/digitalocean-import.ts @@ -0,0 +1,118 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + digitalOceanBase, + files_per_page, + interpolateRobot, + page_number, + path, + recursive, + return_file_stubs, + robotBase, + robotImport, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 10, + discount_factor: 0.1, + discount_pct: 90, + example_code: { + steps: { + imported: { + robot: '/digitalocean/import', + credentials: 'YOUR_DIGITALOCEAN_CREDENTIALS', + path: 'path/to/files/', + recursive: true, + }, + }, + }, + example_code_description: + 'Import files from the `path/to/files` directory and its subdirectories:', + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Importing', + purpose_sentence: 'imports whole directories of files from DigitalOcean Spaces', + purpose_verb: 'import', + purpose_word: 'DigitalOcean Spaces', + purpose_words: 'Import files from DigitalOcean Spaces', + service_slug: 'file-importing', + slot_count: 20, + title: 'Import files from DigitalOcean Spaces', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'DigitalOceanImportRobot', + priceFactor: 6.6666, + queueSlotCount: 20, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: true, + stage: 'ga', +} + +export const robotDigitaloceanImportInstructionsSchema = robotBase + .merge(robotImport) + .merge(digitalOceanBase) + .extend({ + robot: z.literal('/digitalocean/import'), + path: path.describe(` +The path in your bucket to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. + +If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are direct descendants of this directory will be imported. For example: \`images/\`. + +Directories are **not** imported recursively. If you want to import files from subdirectories and sub-subdirectories, enable the \`recursive\` parameter. + +You can also use an array of path strings here to import multiple paths in the same Robot's Step. +`), + recursive: recursive.describe(` +Setting this to \`true\` will enable importing files from subdirectories and sub-subdirectories (etc.) of the given path. + +Please use the pagination parameters \`page_number\` and \`files_per_page\` wisely here. +`), + page_number: page_number.describe(` +The pagination page number. For now, in order to not break backwards compatibility in non-recursive imports, this only works when recursive is set to \`true\`. + +When doing big imports, make sure no files are added or removed from other scripts within your path, otherwise you might get weird results with the pagination. +`), + files_per_page: files_per_page.describe(` +The pagination page size. This only works when recursive is \`true\` for now, in order to not break backwards compatibility in non-recursive imports. +`), + return_file_stubs, + }) + .strict() + +export const robotDigitaloceanImportInstructionsWithHiddenFieldsSchema = + robotDigitaloceanImportInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotDigitaloceanImportInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotDigitaloceanImportInstructions = z.infer< + typeof robotDigitaloceanImportInstructionsSchema +> +export type RobotDigitaloceanImportInstructionsWithHiddenFields = z.infer< + typeof robotDigitaloceanImportInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotDigitaloceanImportInstructionsSchema = interpolateRobot( + robotDigitaloceanImportInstructionsSchema, +) +export type InterpolatableRobotDigitaloceanImportInstructions = + InterpolatableRobotDigitaloceanImportInstructionsInput + +export type InterpolatableRobotDigitaloceanImportInstructionsInput = z.input< + typeof interpolatableRobotDigitaloceanImportInstructionsSchema +> + +export const interpolatableRobotDigitaloceanImportInstructionsWithHiddenFieldsSchema = + interpolateRobot(robotDigitaloceanImportInstructionsWithHiddenFieldsSchema) +export type InterpolatableRobotDigitaloceanImportInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotDigitaloceanImportInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotDigitaloceanImportInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotDigitaloceanImportInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/digitalocean-store.ts b/packages/transloadit/src/alphalib/types/robots/digitalocean-store.ts new file mode 100644 index 00000000..99aeedb4 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/digitalocean-store.ts @@ -0,0 +1,125 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + digitalOceanBase, + interpolateRobot, + robotBase, + robotUse, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 6, + discount_factor: 0.15000150001500018, + discount_pct: 84.99984999849998, + example_code: { + steps: { + exported: { + robot: '/digitalocean/store', + use: ':original', + credentials: 'YOUR_DIGITALOCEAN_CREDENTIALS', + path: 'my_target_folder/${unique_prefix}/${file.url_name}', + }, + }, + }, + example_code_description: 'Export uploaded files to `my_target_folder` on DigitalOcean Spaces:', + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Exporting', + purpose_sentence: 'exports encoding results to DigitalOcean Spaces', + purpose_verb: 'export', + purpose_word: 'DigitalOcean Spaces', + purpose_words: 'Export files to DigitalOcean Spaces', + service_slug: 'file-exporting', + slot_count: 10, + title: 'Export files to DigitalOcean Spaces', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'DigitalOceanStoreRobot', + priceFactor: 6.6666, + queueSlotCount: 10, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotDigitaloceanStoreInstructionsSchema = robotBase + .merge(robotUse) + .merge(digitalOceanBase) + .extend({ + robot: z.literal('/digitalocean/store'), + path: z + .string() + .default('${unique_prefix}/${file.url_name}') + .describe(` +The path at which the file is to be stored. This may include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). The path must not be a directory. +`), + url_prefix: z + .string() + .default('https://{space}.{region}.digitaloceanspaces.com/') + .describe(` +The URL prefix used for the returned URL, such as \`"https://my.cdn.com/some/path"\`. +`), + acl: z + .enum(['private', 'public-read']) + .default('public-read') + .describe(` +The permissions used for this file. +`), + headers: z + .record(z.string()) + .default({ 'Content-Type': '${file.mime}' }) + .describe(` +An object containing a list of headers to be set for this file on DigitalOcean Spaces, such as \`{ FileURL: "\${file.url_name}" }\`. This can also include any available [Assembly Variables](/docs/topics/assembly-instructions/#assembly-variables). + +[Here](https://developers.digitalocean.com/documentation/spaces/#object) you can find a list of available headers. + +Object Metadata can be specified using \`x-amz-meta-*\` headers. Note that these headers [do not support non-ASCII metadata values](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html#UserMetadata). +`), + sign_urls_for: z + .number() + .int() + .min(0) + .optional() + .describe(` +This parameter provides signed URLs in the result JSON (in the \`signed_ssl_url\` property). The number that you set this parameter to is the URL expiry time in seconds. If this parameter is not used, no URL signing is done. +`), + }) + .strict() + +export const robotDigitaloceanStoreInstructionsWithHiddenFieldsSchema = + robotDigitaloceanStoreInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotDigitaloceanStoreInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotDigitaloceanStoreInstructions = z.infer< + typeof robotDigitaloceanStoreInstructionsSchema +> +export type RobotDigitaloceanStoreInstructionsWithHiddenFields = z.infer< + typeof robotDigitaloceanStoreInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotDigitaloceanStoreInstructionsSchema = interpolateRobot( + robotDigitaloceanStoreInstructionsSchema, +) +export type InterpolatableRobotDigitaloceanStoreInstructions = + InterpolatableRobotDigitaloceanStoreInstructionsInput + +export type InterpolatableRobotDigitaloceanStoreInstructionsInput = z.input< + typeof interpolatableRobotDigitaloceanStoreInstructionsSchema +> + +export const interpolatableRobotDigitaloceanStoreInstructionsWithHiddenFieldsSchema = + interpolateRobot(robotDigitaloceanStoreInstructionsWithHiddenFieldsSchema) +export type InterpolatableRobotDigitaloceanStoreInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotDigitaloceanStoreInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotDigitaloceanStoreInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotDigitaloceanStoreInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/document-autorotate.ts b/packages/transloadit/src/alphalib/types/robots/document-autorotate.ts new file mode 100644 index 00000000..f1c4ab83 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/document-autorotate.ts @@ -0,0 +1,74 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + example_code_description: + 'Auto-rotate individual pages of a documents to the correction orientation:', + minimum_charge: 2097152, + output_factor: 1, + override_lvl1: 'Document Processing', + purpose_sentence: 'corrects the orientation of documents', + purpose_verb: 'auto-rotate', + purpose_word: 'auto-rotate documents', + purpose_words: 'Auto-rotate documents', + service_slug: 'document-processing', + slot_count: 10, + title: 'Auto-rotate documents to the correct orientation', + typical_file_size_mb: 0.8, + typical_file_type: 'document', + name: 'DocumentAutorotateRobot', + priceFactor: 1, + queueSlotCount: 10, + minimumCharge: 2097152, + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotDocumentAutorotateInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/document/autorotate'), + }) + .strict() + +export const robotDocumentAutorotateInstructionsWithHiddenFieldsSchema = + robotDocumentAutorotateInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotDocumentAutorotateInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotDocumentAutorotateInstructions = z.infer< + typeof robotDocumentAutorotateInstructionsSchema +> +export type RobotDocumentAutorotateInstructionsWithHiddenFields = z.infer< + typeof robotDocumentAutorotateInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotDocumentAutorotateInstructionsSchema = interpolateRobot( + robotDocumentAutorotateInstructionsSchema, +) +export type InterpolatableRobotDocumentAutorotateInstructions = + InterpolatableRobotDocumentAutorotateInstructionsInput + +export type InterpolatableRobotDocumentAutorotateInstructionsInput = z.input< + typeof interpolatableRobotDocumentAutorotateInstructionsSchema +> + +export const interpolatableRobotDocumentAutorotateInstructionsWithHiddenFieldsSchema = + interpolateRobot(robotDocumentAutorotateInstructionsWithHiddenFieldsSchema) +export type InterpolatableRobotDocumentAutorotateInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotDocumentAutorotateInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotDocumentAutorotateInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotDocumentAutorotateInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/document-convert.ts b/packages/transloadit/src/alphalib/types/robots/document-convert.ts new file mode 100644 index 00000000..94ac9507 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/document-convert.ts @@ -0,0 +1,302 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + example_code: { + steps: { + converted: { + robot: '/document/convert', + use: ':original', + format: 'pdf', + }, + }, + }, + example_code_description: 'Convert uploaded files to PDF documents:', + extended_description: ` +> [!Note] +> This Robot can convert files to PDF, but cannot convert PDFs to different formats. If you want to convert PDFs to say, JPEG or TIFF, use [🤖/image/resize](/docs/robots/image-resize/). If you want to turn them into text files or recognize (OCR) them to make them searchable, reach out, as we have a new Robot in the works for this. + +Sometimes, a certain file type might not support what you are trying to accomplish. Perhaps your company is trying to automate document formatting, but it only works with docx, so all your docs need to be converted. Or maybe your stored jpg files are taking up too much space and you want a lighter format. Whatever the case, we have you covered. + +Using this Robot, you can bypass the issues that certain file types may bring, by converting your file into the most suitable format. This also works in conjunction with our other Robots, allowing for even greater versatility when using our services. + +> ![Warning] +> A general rule of this Robot is that converting files into an alien format category will result in an error. For example, SRT files can be converted into the VTT format, but not to an image. + +The following file formats can be converted from: + +- \`ai\` +- \`csv\` +- \`doc\` +- \`docx\` +- \`eps\` +- \`gif\` +- \`html\` +- \`jpg\` +- \`latex\` +- \`md\` +- \`oda\` +- \`odd\` +- \`odt\` +- \`ott\` +- \`png\` +- \`pot\` +- \`pps\` +- \`ppt\` +- \`pptx\` +- \`ppz\` +- \`ps\` +- \`rtf\` +- \`rtx\` +- \`svg\` +- \`text\` +- \`txt\` +- \`xhtml\` +- \`xla\` +- \`xls\` +- \`xlsx\` +- \`xml\` +`, + minimum_charge: 1048576, + output_factor: 1, + override_lvl1: 'Document Processing', + purpose_sentence: 'converts documents into different formats', + purpose_verb: 'convert', + purpose_word: 'convert', + purpose_words: 'Convert documents into different formats', + service_slug: 'document-processing', + slot_count: 12, + title: 'Convert documents into different formats', + typical_file_size_mb: 0.8, + typical_file_type: 'document', + name: 'DocumentConvertRobot', + priceFactor: 1, + // This slot count needs to be unique, because unoconv can only process one document at a time, + // and is also only included in WorkerSlotCalculator::slotsThatFit() when + // we have enough idle unoconv daemons. + // We do not want a queue of this Robot to block any other Robot's jobs. + queueSlotCount: 32, + minimumCharge: 1048576, + lazyLoad: true, + installVersionFile: process.env.API2_UNOCONV_INSTALL_VERSION_FILE || '', + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + // we cannot use coreConfig.numUnoconvDaemons, because it does not live in alphalib + numDaemons: 8, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotDocumentConvertInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/document/convert').describe(` +> [!Note] +> This Robot can convert files to PDF, but cannot convert PDFs to different formats. If you want to convert PDFs to say, JPEG or TIFF, use [🤖/image/resize](/docs/robots/image-resize/). If you want to turn them into text files or recognize (OCR) them to make them searchable, reach out, as we have a new Robot in the works for this. + +Sometimes, a certain file type might not support what you are trying to accomplish. Perhaps your company is trying to automate document formatting, but it only works with docx, so all your docs need to be converted. Or maybe your stored jpg files are taking up too much space and you want a lighter format. Whatever the case, we have you covered. + +Using this Robot, you can bypass the issues that certain file types may bring, by converting your file into the most suitable format. This also works in conjunction with our other Robots, allowing for even greater versatility when using our services. + +> [!Warning] +> A general rule of this Robot is that converting files into an alien format category will result in an error. For example, SRT files can be converted into the VTT format, but not to an image. + +The following file formats can be converted from: + +- \`ai\` +- \`csv\` +- \`doc\` +- \`docx\` +- \`eps\` +- \`gif\` +- \`html\` +- \`jpg\` +- \`latex\` +- \`md\` +- \`oda\` +- \`odd\` +- \`odt\` +- \`ott\` +- \`png\` +- \`pot\` +- \`pps\` +- \`ppt\` +- \`pptx\` +- \`ppz\` +- \`ps\` +- \`rtf\` +- \`rtx\` +- \`svg\` +- \`text\` +- \`txt\` +- \`xhtml\` +- \`xla\` +- \`xls\` +- \`xlsx\` +- \`xml\` +`), + format: z + .enum([ + 'ai', + 'csv', + 'doc', + 'docx', + 'eps', + 'gif', + 'html', + 'jpeg', + 'jpg', + 'latex', + 'oda', + 'odd', + 'odt', + 'ott', + 'pdf', + 'png', + 'pot', + 'pps', + 'ppt', + 'pptx', + 'ppz', + 'ps', + 'rtf', + 'rtx', + 'srt', + 'svg', + 'text', + 'txt', + 'vtt', + 'xhtml', + 'xla', + 'xls', + 'xlsx', + 'xml', + ]) + .describe(` +The desired format for document conversion. +`), + markdown_format: z + .enum(['commonmark', 'gfm']) + .default('gfm') + .describe(` +Markdown can be represented in several [variants](https://www.iana.org/assignments/markdown-variants/markdown-variants.xhtml), so when using this Robot to transform Markdown into HTML please specify which revision is being used. +`), + markdown_theme: z + .enum(['bare', 'github']) + .default('github') + .describe(` +This parameter overhauls your Markdown files styling based on several canned presets. +`), + pdf_margin: z + .string() + .default('6.25mm,6.25mm,14.11mm,6.25mm') + .describe(` +PDF Paper margins, separated by \`,\` and with units. + +We support the following unit values: \`px\`, \`in\`, \`cm\`, \`mm\`. + +Currently this parameter is only supported when converting from \`html\`. +`), + pdf_print_background: z + .boolean() + .default(true) + .describe(` +Print PDF background graphics. + +Currently this parameter is only supported when converting from \`html\`. +`), + pdf_format: z + .enum(['A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'Ledger', 'Legal', 'Letter', 'Tabloid']) + .default('Letter') + .describe(` +PDF paper format. + +Currently this parameter is only supported when converting from \`html\`. +`), + pdf_display_header_footer: z + .boolean() + .default(false) + .describe(` +Display PDF header and footer. + +Currently this parameter is only supported when converting from \`html\`. +`), + pdf_header_template: z + .string() + .optional() + .describe(` +HTML template for the PDF print header. + +Should be valid HTML markup with following classes used to inject printing values into them: +- \`date\` formatted print date +- \`title\` document title +- \`url\` document location +- \`pageNumber\` current page number +- \`totalPages\` total pages in the document + +Currently this parameter is only supported when converting from \`html\`, and requires \`pdf_display_header_footer\` to be enabled. + +To change the formatting of the HTML element, the \`font-size\` must be specified in a wrapper. For example, to center the page number at the top of a page you'd use the following HTML for the header template: + +\`\`\`html +
+\`\`\` +`), + pdf_footer_template: z + .string() + .optional() + .describe(` +HTML template for the PDF print footer. + +Should use the same format as the \`pdf_header_template\`. + +Currently this parameter is only supported when converting from \`html\`, and requires \`pdf_display_header_footer\` to be enabled. + +To change the formatting of the HTML element, the \`font-size\` must be specified in a wrapper. For example, to center the page number in the footer you'd use the following HTML for the footer template: + +\`\`\`html +
+\`\`\` +`), + }) + .strict() + +export const robotDocumentConvertInstructionsWithHiddenFieldsSchema = + robotDocumentConvertInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotDocumentConvertInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotDocumentConvertInstructions = z.infer< + typeof robotDocumentConvertInstructionsSchema +> +export type RobotDocumentConvertInstructionsWithHiddenFields = z.infer< + typeof robotDocumentConvertInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotDocumentConvertInstructionsSchema = interpolateRobot( + robotDocumentConvertInstructionsSchema, +) +export type InterpolatableRobotDocumentConvertInstructions = + InterpolatableRobotDocumentConvertInstructionsInput + +export type InterpolatableRobotDocumentConvertInstructionsInput = z.input< + typeof interpolatableRobotDocumentConvertInstructionsSchema +> + +export const interpolatableRobotDocumentConvertInstructionsWithHiddenFieldsSchema = + interpolateRobot(robotDocumentConvertInstructionsWithHiddenFieldsSchema) +export type InterpolatableRobotDocumentConvertInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotDocumentConvertInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotDocumentConvertInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotDocumentConvertInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/document-merge.ts b/packages/transloadit/src/alphalib/types/robots/document-merge.ts new file mode 100644 index 00000000..0a8340b3 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/document-merge.ts @@ -0,0 +1,114 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + example_code: { + steps: { + merged: { + robot: '/document/merge', + use: { + steps: [':original'], + bundle_steps: true, + }, + }, + }, + }, + example_code_description: 'Merge all uploaded PDF documents into one:', + extended_description: ` +> ![Note] +> This Robot can merge PDF files only at the moment. + +Input files are sorted alphanumerically unless you provide the as-syntax in the "use" parameter. For example: + +\`\`\`json +{ + "use": [ + { "name": "my_step_name", "as": "document_2" }, + { "name": "my_other_step_name", "as": "document_1" } + ] +} +\`\`\` +`, + minimum_charge: 1048576, + output_factor: 1, + override_lvl1: 'Document Processing', + purpose_sentence: 'concatenates several PDF documents into a single file', + purpose_verb: 'convert', + purpose_word: 'convert', + purpose_words: 'Merge documents into one', + service_slug: 'document-processing', + slot_count: 10, + title: 'Merge documents into one', + typical_file_size_mb: 0.8, + typical_file_type: 'document', + name: 'DocumentMergeRobot', + priceFactor: 1, + queueSlotCount: 10, + minimumCharge: 1048576, + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotDocumentMergeInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/document/merge'), + input_passwords: z + .array(z.string()) + .default([]) + .describe(` +An array of passwords for the input documents, in case they are encrypted. The order of passwords must match the order of the documents as they are passed to the /document/merge step. + +This can be achieved via our as-syntax using "document_1", "document_2", etc if provided. See the demos below. + +If the as-syntax is not used in the "use" parameter, the documents are sorted alphanumerically based on their filename, and in that order input passwords should be provided. +`), + output_password: z + .string() + .optional() + .describe(` +If not empty, encrypts the output file and makes it accessible only by typing in this password. +`), + }) + .strict() + +export const robotDocumentMergeInstructionsWithHiddenFieldsSchema = + robotDocumentMergeInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotDocumentMergeInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotDocumentMergeInstructions = z.infer +export type RobotDocumentMergeInstructionsWithHiddenFields = z.infer< + typeof robotDocumentMergeInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotDocumentMergeInstructionsSchema = interpolateRobot( + robotDocumentMergeInstructionsSchema, +) +export type InterpolatableRobotDocumentMergeInstructions = + InterpolatableRobotDocumentMergeInstructionsInput + +export type InterpolatableRobotDocumentMergeInstructionsInput = z.input< + typeof interpolatableRobotDocumentMergeInstructionsSchema +> + +export const interpolatableRobotDocumentMergeInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotDocumentMergeInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotDocumentMergeInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotDocumentMergeInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotDocumentMergeInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotDocumentMergeInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/document-ocr.ts b/packages/transloadit/src/alphalib/types/robots/document-ocr.ts new file mode 100644 index 00000000..5a443ea3 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/document-ocr.ts @@ -0,0 +1,120 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + aiProviderSchema, + granularitySchema, + interpolateRobot, + robotBase, + robotUse, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + example_code: { + steps: { + recognized: { + robot: '/document/ocr', + use: ':original', + provider: 'gcp', + }, + }, + }, + example_code_description: 'Recognize text in an uploaded document and save it to a JSON file:', + extended_description: ` +> [!Warning] +> Transloadit aims to be deterministic, but this Robot uses third-party AI services. The providers (AWS, GCP) will evolve their models over time, giving different responses for the same input PDFs. Avoid relying on exact responses in your tests and application. + +> [!Note] +> Currently, this Robot only supports character recognition for PDFs. To use this Robot with other document formats, use [/document/convert](/docs/robots/document-convert/) first to convert the document into a PDF. +`, + minimum_charge: 1048576, + output_factor: 1, + override_lvl1: 'Artificial Intelligence', + purpose_sentence: 'recognizes text in documents and returns it in a machine-readable format', + purpose_verb: 'recognize', + purpose_word: 'recognize text', + purpose_words: 'Recognize text in documents (OCR)', + service_slug: 'artificial-intelligence', + slot_count: 10, + title: 'Recognize text in documents', + typical_file_size_mb: 0.8, + typical_file_type: 'document', + name: 'DocumentOcrRobot', + priceFactor: 1, + queueSlotCount: 10, + minimumChargeUsdPerDocumentOcrPage: { + aws: 0.02, + gcp: 0.015, + }, + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotDocumentOcrInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/document/ocr').describe(` +With this Robot, you can detect and extract text from PDFs using optical character recognition (OCR). + +For example, you can use the results to obtain the content of invoices, legal documents or restaurant menus. You can also pass the text down to other Robots to filter documents that contain (or do not contain) certain phrases. +`), + provider: aiProviderSchema.describe(` +Which AI provider to leverage. Valid values are \`"aws"\` and \`"gcp"\`. + +Transloadit outsources this task and abstracts the interface so you can expect the same data structures, but different latencies and information being returned. Different cloud vendors have different areas they shine in, and we recommend to try out and see what yields the best results for your use case. + +AWS supports detection for the following languages: English, Arabic, Russian, German, French, Italian, Portuguese and Spanish. GCP allows for a wider range of languages, with varying levels of support which can be found on the [official documentation](https://cloud.google.com/vision/docs/languages/). +`), + granularity: granularitySchema.describe(` +Whether to return a full response including coordinates for the text (\`"full"\`), or a flat list of the extracted phrases (\`"list"\`). This parameter has no effect if the \`format\` parameter is set to \`"text"\`. +`), + format: z + .enum(['json', 'meta', 'text']) + .default('json') + .describe(` +In what format to return the extracted text. +- \`"json"\` returns a JSON file. +- \`"meta"\` does not return a file, but stores the data inside Transloadit's file object (under \`\${file.meta.recognized_text}\`, which is an array of strings) that's passed around between encoding Steps, so that you can use the values to burn the data into videos, filter on them, etc. +- \`"text"\` returns the recognized text as a plain UTF-8 encoded text file. +`), + }) + .strict() + +export const robotDocumentOcrInstructionsWithHiddenFieldsSchema = + robotDocumentOcrInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotDocumentOcrInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotDocumentOcrInstructions = z.infer +export type RobotDocumentOcrInstructionsWithHiddenFields = z.infer< + typeof robotDocumentOcrInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotDocumentOcrInstructionsSchema = interpolateRobot( + robotDocumentOcrInstructionsSchema, +) +export type InterpolatableRobotDocumentOcrInstructions = + InterpolatableRobotDocumentOcrInstructionsInput + +export type InterpolatableRobotDocumentOcrInstructionsInput = z.input< + typeof interpolatableRobotDocumentOcrInstructionsSchema +> + +export const interpolatableRobotDocumentOcrInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotDocumentOcrInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotDocumentOcrInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotDocumentOcrInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotDocumentOcrInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotDocumentOcrInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/document-split.ts b/packages/transloadit/src/alphalib/types/robots/document-split.ts new file mode 100644 index 00000000..a76b836d --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/document-split.ts @@ -0,0 +1,78 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + example_code_description: 'Extract single or multiple pages from a PDF document:', + minimum_charge: 2097152, + output_factor: 1, + override_lvl1: 'Document Processing', + purpose_sentence: 'extracts pages from documents', + purpose_verb: 'extract', + purpose_word: 'extracts pages', + purpose_words: 'Extracts pages', + service_slug: 'document-processing', + slot_count: 10, + title: 'Extract pages from a document', + typical_file_size_mb: 0.8, + typical_file_type: 'document', + name: 'DocumentSplitRobot', + priceFactor: 1, + queueSlotCount: 10, + minimumCharge: 1048576, + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotDocumentSplitInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/document/split'), + pages: z + .union([z.string(), z.array(z.string())]) + .describe( + 'The pages to select from the input PDF and to be included in the output PDF. Each entry can be a single page number (e.g. 5), or a range (e.g. `5-10`). Page numbers start at 1. By default all pages are extracted.', + ) + .optional(), + }) + .strict() + +export const robotDocumentSplitInstructionsWithHiddenFieldsSchema = + robotDocumentSplitInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotDocumentSplitInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotDocumentSplitInstructions = z.infer +export type RobotDocumentSplitInstructionsWithHiddenFields = z.infer< + typeof robotDocumentSplitInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotDocumentSplitInstructionsSchema = interpolateRobot( + robotDocumentSplitInstructionsSchema, +) +export type InterpolatableRobotDocumentSplitInstructions = + InterpolatableRobotDocumentSplitInstructionsInput + +export type InterpolatableRobotDocumentSplitInstructionsInput = z.input< + typeof interpolatableRobotDocumentSplitInstructionsSchema +> + +export const interpolatableRobotDocumentSplitInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotDocumentSplitInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotDocumentSplitInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotDocumentSplitInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotDocumentSplitInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotDocumentSplitInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/document-thumbs.ts b/packages/transloadit/src/alphalib/types/robots/document-thumbs.ts new file mode 100644 index 00000000..2aff4c71 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/document-thumbs.ts @@ -0,0 +1,231 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + colorspaceSchema, + interpolateRobot, + robotBase, + robotImagemagick, + robotUse, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + example_code: { + steps: { + thumbnailed: { + use: ':original', + robot: '/document/thumbs', + width: 200, + resize_strategy: 'fit', + trim_whitespace: false, + }, + }, + }, + example_code_description: 'Convert all pages of a PDF document into separate 200px-wide images:', + minimum_charge: 524288, + output_factor: 1, + override_lvl1: 'Document Processing', + purpose_sentence: + 'generates an image for each page in a PDF file or an animated GIF file that loops through all pages', + purpose_verb: 'extract', + purpose_word: 'thumbnail', + purpose_words: 'Extract thumbnail images from documents', + service_slug: 'document-processing', + slot_count: 10, + title: 'Extract thumbnail images from documents', + typical_file_size_mb: 0.8, + typical_file_type: 'document', + uses_tools: ['imagemagick'], + name: 'DocumentThumbsRobot', + priceFactor: 1, + queueSlotCount: 60, + minimumCharge: 524288, + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotDocumentThumbsInstructionsSchema = robotBase + .merge(robotUse) + .merge(robotImagemagick) + .extend({ + robot: z.literal('/document/thumbs').describe(` +## Things to keep in mind + +- If you convert a multi-page PDF file into several images, all result images will be sorted with the first image being the thumbnail of the first document page, etc. +- You can also check the \`meta.thumb_index\` key of each result image to find out which page it corresponds to. Keep in mind that these thumb indices **start at 0,** not at 1. +`), + page: z + .number() + .int() + .nullable() + .default(null) + .describe(` +The PDF page that you want to convert to an image. By default the value is \`null\` which means that all pages will be converted into images. +`), + format: z + .enum(['gif', 'jpeg', 'jpg', 'png']) + .default('png') + .describe(` +The format of the extracted image(s). + +If you specify the value \`"gif"\`, then an animated gif cycling through all pages is created. Please check out [this demo](/demos/document-processing/convert-all-pages-of-a-document-into-an-animated-gif/) to learn more about this. +`), + delay: z + .number() + .int() + .min(0) + .optional() + .describe(` +If your output format is \`"gif"\` then this parameter sets the number of 100th seconds to pass before the next frame is shown in the animation. Set this to \`100\` for example to allow 1 second to pass between the frames of the animated gif. + +If your output format is not \`"gif"\`, then this parameter does not have any effect. +`), + width: z + .number() + .int() + .min(1) + .max(5000) + .optional() + .describe(` +Width of the new image, in pixels. If not specified, will default to the width of the input image +`), + height: z + .number() + .int() + .min(1) + .max(5000) + .optional() + .describe(` +Height of the new image, in pixels. If not specified, will default to the height of the input image +`), + resize_strategy: z + .enum(['crop', 'fillcrop', 'fit', 'min_fit', 'pad', 'stretch']) + .default('pad') + .describe(` +One of the [available resize strategies](/docs/topics/resize-strategies/). +`), + // TODO: Determine the allowed colors + background: z + .string() + .default('#FFFFFF') + .describe(` +Either the hexadecimal code or [name](https://www.imagemagick.org/script/color.php#color_names) of the color used to fill the background (only used for the pad resize strategy). + +By default, the background of transparent images is changed to white. For details about how to preserve transparency across all image types, see [this demo](/demos/image-manipulation/properly-preserve-transparency-across-all-image-types/). +`), + // TODO: Update options list. Why are they capitalized? They are lowercase in th ImageMagick docs. + alpha: z + .enum(['Remove', 'Set']) + .optional() + .describe(` +Change how the alpha channel of the resulting image should work. Valid values are \`"Set"\` to enable transparency and \`"Remove"\` to remove transparency. + +For a list of all valid values please check the ImageMagick documentation [here](http://www.imagemagick.org/script/command-line-options.php#alpha). +`), + density: z + .string() + .regex(/\d+(x\d+)?/) + .optional() + .describe(` +While in-memory quality and file format depth specifies the color resolution, the density of an image is the spatial (space) resolution of the image. That is the density (in pixels per inch) of an image and defines how far apart (or how big) the individual pixels are. It defines the size of the image in real world terms when displayed on devices or printed. + +You can set this value to a specific \`width\` or in the format \`width\`x\`height\`. + +If your converted image has a low resolution, please try using the density parameter to resolve that. +`), + antialiasing: z + .boolean() + .default(false) + .describe(` +Controls whether or not antialiasing is used to remove jagged edges from text or images in a document. +`), + colorspace: colorspaceSchema.optional().describe(` +Sets the image colorspace. For details about the available values, see the [ImageMagick documentation](https://www.imagemagick.org/script/command-line-options.php#colorspace). + +Please note that if you were using \`"RGB"\`, we recommend using \`"sRGB"\`. ImageMagick might try to find the most efficient \`colorspace\` based on the color of an image, and default to e.g. \`"Gray"\`. To force colors, you might then have to use this parameter. +`), + trim_whitespace: z + .boolean() + .default(true) + .describe(` +This determines if additional whitespace around the PDF should first be trimmed away before it is converted to an image. If you set this to \`true\` only the real PDF page contents will be shown in the image. + +If you need to reflect the PDF's dimensions in your image, it is generally a good idea to set this to \`false\`. +`), + pdf_use_cropbox: z + .boolean() + .default(true) + .describe(` +Some PDF documents lie about their dimensions. For instance they'll say they are landscape, but when opened in decent Desktop readers, it's really in portrait mode. This can happen if the document has a cropbox defined. When this option is enabled (by default), the cropbox is leading in determining the dimensions of the resulting thumbnails. +`), + turbo: z + .boolean() + .default(true) + .describe(` +If you set this to \`false\`, the robot will not emit files as they become available. This is useful if you are only interested in the final result and not in the intermediate steps. + +Also, extracted pages will be resized a lot faster as they are sent off to other machines for the resizing. This is especially useful for large documents with many pages to get up to 20 times faster processing. + +Turbo Mode increases pricing, though, in that the input document's file size is added for every extracted page. There are no performance benefits nor increased charges for single-page documents. +`), + }) + .strict() + +export const robotDocumentThumbsInstructionsWithHiddenFieldsSchema = + robotDocumentThumbsInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotDocumentThumbsInstructionsSchema.shape.result]) + .optional(), + stack: z + .string() + .optional() + .describe(` +The image processing stack to use. Defaults to the robot's preferred stack (ImageMagick). +`), + // Override to support lowercase for BC: + alpha: z + .enum(['Remove', 'Set', 'remove', 'set']) + .optional() + .describe(` +Change how the alpha channel of the resulting image should work. Valid values are \`"Set"\` to enable transparency and \`"Remove"\` to remove transparency. Lowercase values are also accepted for backwards compatibility. +`), + // Override to support 'none' for BC + resize_strategy: z + .enum(['crop', 'fillcrop', 'fit', 'min_fit', 'pad', 'stretch', 'none']) + .optional() + .describe(` +One of the [available resize strategies](/docs/transcoding/image-manipulation/image-resize/#resize-strategies). The 'none' value is supported for backwards compatibility. +`), + }) + +export type RobotDocumentThumbsInstructions = z.infer +export type RobotDocumentThumbsInstructionsWithHiddenFields = z.infer< + typeof robotDocumentThumbsInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotDocumentThumbsInstructionsSchema = interpolateRobot( + robotDocumentThumbsInstructionsSchema, +) +export type InterpolatableRobotDocumentThumbsInstructions = + InterpolatableRobotDocumentThumbsInstructionsInput + +export type InterpolatableRobotDocumentThumbsInstructionsInput = z.input< + typeof interpolatableRobotDocumentThumbsInstructionsSchema +> + +export const interpolatableRobotDocumentThumbsInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotDocumentThumbsInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotDocumentThumbsInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotDocumentThumbsInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotDocumentThumbsInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotDocumentThumbsInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/dropbox-import.ts b/packages/transloadit/src/alphalib/types/robots/dropbox-import.ts new file mode 100644 index 00000000..b18d0d9e --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/dropbox-import.ts @@ -0,0 +1,100 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + dropboxBase, + interpolateRobot, + path, + robotBase, + robotImport, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 10, + discount_factor: 0.1, + discount_pct: 90, + example_code: { + steps: { + imported: { + robot: '/dropbox/import', + credentials: 'YOUR_DROPBOX_CREDENTIALS', + path: 'path/to/files/', + }, + }, + }, + example_code_description: + 'Import files from the `path/to/files` directory and its subdirectories:', + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Importing', + purpose_sentence: 'imports whole directories of files from your Dropbox', + purpose_verb: 'import', + purpose_word: 'Dropbox', + purpose_words: 'Import files from Dropbox', + requires_credentials: true, + service_slug: 'file-importing', + slot_count: 20, + title: 'Import files from Dropbox', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'DropboxImportRobot', + priceFactor: 6.6666, + queueSlotCount: 20, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: true, + stage: 'ga', +} + +export const robotDropboxImportInstructionsSchema = robotBase + .merge(robotImport) + .merge(dropboxBase) + .extend({ + robot: z.literal('/dropbox/import'), + path: path.describe(` +The path in your Dropbox to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. + +If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are descendants of this directory are recursively imported. For example: \`images/\`. + +If you want to import all files from the root directory, please use \`/\` as the value here. + +You can also use an array of path strings here to import multiple paths in the same Robot's Step. +`), + }) + .strict() + +export const robotDropboxImportInstructionsWithHiddenFieldsSchema = + robotDropboxImportInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotDropboxImportInstructionsSchema.shape.result]) + .optional(), + access_token: z.string().optional(), // Legacy field for backward compatibility + }) + +export type RobotDropboxImportInstructions = z.infer +export type RobotDropboxImportInstructionsWithHiddenFields = z.infer< + typeof robotDropboxImportInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotDropboxImportInstructionsSchema = interpolateRobot( + robotDropboxImportInstructionsSchema, +) +export type InterpolatableRobotDropboxImportInstructions = + InterpolatableRobotDropboxImportInstructionsInput + +export type InterpolatableRobotDropboxImportInstructionsInput = z.input< + typeof interpolatableRobotDropboxImportInstructionsSchema +> + +export const interpolatableRobotDropboxImportInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotDropboxImportInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotDropboxImportInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotDropboxImportInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotDropboxImportInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotDropboxImportInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/dropbox-store.ts b/packages/transloadit/src/alphalib/types/robots/dropbox-store.ts new file mode 100644 index 00000000..95c1b992 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/dropbox-store.ts @@ -0,0 +1,97 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { dropboxBase, interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 6, + discount_factor: 0.15000150001500018, + discount_pct: 84.99984999849998, + example_code: { + steps: { + exported: { + robot: '/dropbox/store', + use: ':original', + credentials: 'YOUR_DROPBOX_CREDENTIALS', + path: 'my_target_folder/${unique_prefix}/${file.url_name}', + }, + }, + }, + example_code_description: 'Export uploaded files to `my_target_folder` on Dropbox:', + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Exporting', + purpose_sentence: 'exports encoding results to Dropbox', + purpose_verb: 'export', + purpose_word: 'Dropbox', + purpose_words: 'Export files to Dropbox', + service_slug: 'file-exporting', + slot_count: 10, + title: 'Export files to Dropbox', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'DropboxStoreRobot', + priceFactor: 6.6666, + queueSlotCount: 10, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotDropboxStoreInstructionsSchema = robotBase + .merge(robotUse) + .merge(dropboxBase) + .extend({ + robot: z.literal('/dropbox/store'), + path: z + .string() + .default('${unique_prefix}/${file.url_name}') + .describe(` +The path at which the file is to be stored. This may include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). +`), + create_sharing_link: z + .boolean() + .default(false) + .describe(` +Whether to create a URL to this file for sharing with other people. This will overwrite the file's \`"url"\` property. +`), + }) + .strict() + +export const robotDropboxStoreInstructionsWithHiddenFieldsSchema = + robotDropboxStoreInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotDropboxStoreInstructionsSchema.shape.result]) + .optional(), + access_token: z.string().optional(), // Legacy field for backward compatibility + }) + +export type RobotDropboxStoreInstructions = z.infer +export type RobotDropboxStoreInstructionsInput = z.input +export type RobotDropboxStoreInstructionsWithHiddenFields = z.infer< + typeof robotDropboxStoreInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotDropboxStoreInstructionsSchema = interpolateRobot( + robotDropboxStoreInstructionsSchema, +) +export type InterpolatableRobotDropboxStoreInstructions = + InterpolatableRobotDropboxStoreInstructionsInput + +export type InterpolatableRobotDropboxStoreInstructionsInput = z.input< + typeof interpolatableRobotDropboxStoreInstructionsSchema +> + +export const interpolatableRobotDropboxStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotDropboxStoreInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotDropboxStoreInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotDropboxStoreInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotDropboxStoreInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotDropboxStoreInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/edgly-deliver.ts b/packages/transloadit/src/alphalib/types/robots/edgly-deliver.ts new file mode 100644 index 00000000..cfd19d16 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/edgly-deliver.ts @@ -0,0 +1,73 @@ +import { z } from 'zod' +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: false, + bytescount: 20, + discount_factor: 0.05, + discount_pct: 95, + minimum_charge: 102400, + output_factor: 1, + override_lvl1: 'Content Delivery', + purpose_sentence: 'caches and delivers files globally', + purpose_verb: 'cache & deliver', + purpose_word: 'Cache and deliver files', + purpose_words: 'Cache and deliver files globally', + service_slug: 'content-delivery', + slot_count: 0, + title: 'Cache and deliver files globally', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'EdglyDeliverRobot', + priceFactor: 20, + queueSlotCount: 0, + minimumCharge: 102400, + downloadInputFiles: false, + preserveInputFileUrls: true, + isAllowedForUrlTransform: false, + trackOutputFileSize: false, + isInternal: true, + stage: 'removed', + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, +} + +export const robotEdglyDeliverInstructionsSchema = robotBase + .extend({ + robot: z.literal('/edgly/deliver').describe(` +When you want Transloadit to tranform files on the fly, this Robot can cache and deliver the results close to your end-user, saving on latency and encoding volume. The use of this Robot is implicit when you use the edgly.net domain. +`), + }) + .strict() + +export const robotEdglyDeliverInstructionsWithHiddenFieldsSchema = + robotEdglyDeliverInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotEdglyDeliverInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotEdglyDeliverInstructions = z.infer +export type RobotEdglyDeliverInstructionsWithHiddenFields = z.infer< + typeof robotEdglyDeliverInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotEdglyDeliverInstructionsSchema = interpolateRobot( + robotEdglyDeliverInstructionsSchema, +) +export type InterpolatableRobotEdglyDeliverInstructions = + InterpolatableRobotEdglyDeliverInstructionsInput + +export type InterpolatableRobotEdglyDeliverInstructionsInput = z.input< + typeof interpolatableRobotEdglyDeliverInstructionsSchema +> + +export const interpolatableRobotEdglyDeliverInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotEdglyDeliverInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotEdglyDeliverInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotEdglyDeliverInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotEdglyDeliverInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotEdglyDeliverInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/file-compress.ts b/packages/transloadit/src/alphalib/types/robots/file-compress.ts new file mode 100644 index 00000000..f329ba6e --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/file-compress.ts @@ -0,0 +1,167 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: false, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + example_code: { + steps: { + compressed: { + robot: '/file/compress', + use: { + steps: [':original'], + bundle_steps: true, + }, + format: 'zip', + }, + }, + }, + example_code_description: 'Compress uploaded files into a ZIP archive:', + extended_description: ` +### Archive structure for the \`"advanced"\` file layout. + +There are a few things that we kept in mind when designing the \`"advanced"\` archive structure: + +- There could be naming collisions. +- You want to know which Step a result file belongs to. +- You want to know from which originally uploaded file a result file was generated. +- Ideally, you want subfolders for a better structure of files. + +To achieve all this, we have created the following archive file structure. + +- There is a subfolder for each Step name that has result files in the archive. +- Files are named according to the first two letters of the unique original prefix + "_" + the first two letters of the unique prefix + "_" + the original file name. If you do not know what the original prefixes are, please check [our available Assembly variables](/docs/topics/assembly-instructions/#assembly-variables) and look for \`\${unique_original_prefix}\` and \`\${unique_prefix}\`. +- Files that belong to the \`:original\` Step (originally uploaded files) do **not** include the first two letters of the \`unique_original_prefix\`. +- If you are dealing with thumbnails from [🤖/video/thumbs](/docs/robots/video-thumbs/), there is an additional digit representing the order in the file name. + +Here is an example: + +\`\`\`yaml +":original": + - gh_a.mov # "gh" are the first 2 letters of the unique prefix. + # "a.mov" was the file name of the uploaded file. + - ff_b.mov +"thumbed": + - gh_e8_thumb_1.jpg # "gh" is the unique original prefix, meaning it's a result of a.mov. + # "e8" is the file's unique prefix. + # The "1" shows the thumbnail order. + - gh_cv_thumb_2.jpg + - ff_9b_thumb_3.jpg +"resized": + - gh_ll_thumb.jpg + - gh_df_thumb.jpg + - ff_jk_thumb.jpg # is a child of b.mov, as it starts with "ff" +\`\`\` +`, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Compressing', + purpose_sentence: 'creates archives of files or file conversion results', + purpose_verb: 'compress', + purpose_word: 'compress', + purpose_words: 'Compress files', + service_slug: 'file-compressing', + slot_count: 15, + title: 'Compress files', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'FileCompressRobot', + priceFactor: 1, + queueSlotCount: 15, + isAllowedForUrlTransform: false, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotFileCompressInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/file/compress'), + format: z + .enum(['tar', 'zip']) + .default('tar') + .describe(` +The format of the archive to be created. Supported values are \`"tar"\` and \`"zip"\`. + +Note that \`"tar"\` without setting \`gzip\` to \`true\` results in an archive that's not compressed in any way. +`), + gzip: z + .boolean() + .default(false) + .describe(` +Determines if the result archive should also be gzipped. Gzip compression is only applied if you use the \`"tar"\` format. +`), + password: z + .string() + .nullable() + .default(null) + .describe(` +This allows you to encrypt all archive contents with a password and thereby protect it against unauthorized use. To unzip the archive, the user will need to provide the password in a text input field prompt. + +This parameter has no effect if the format parameter is anything other than \`"zip"\`. +`), + compression_level: z + .number() + .int() + .min(-9) + .max(0) + .default(-6) + .describe(` +Determines how fiercely to try to compress the archive. \`-0\` is compressionless, which is suitable for media that is already compressed. \`-1\` is fastest with lowest compression. \`-9\` is slowest with the highest compression. + +If you are using \`-0\` in combination with the \`tar\` format with \`gzip\` enabled, consider setting \`gzip: false\` instead. This results in a plain Tar archive, meaning it already has no compression. +`), + file_layout: z + .enum(['advanced', 'simple', 'relative-path']) + .default('advanced') + .describe(` +Determines if the result archive should contain all files in one directory (value for this is \`"simple"\`) or in subfolders according to the explanation below (value for this is \`"advanced"\`). The \`"relative-path"\` option preserves the relative directory structure of the input files. + +Files with same names are numbered in the \`"simple"\` file layout to avoid naming collisions. +`), + archive_name: z + .string() + .optional() + .describe(` +The name of the archive file to be created (without the file extension). +`), + }) + .strict() + +export const robotFileCompressInstructionsWithHiddenFieldsSchema = + robotFileCompressInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotFileCompressInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotFileCompressInstructions = z.infer +export type RobotFileCompressInstructionsWithHiddenFields = z.infer< + typeof robotFileCompressInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotFileCompressInstructionsSchema = interpolateRobot( + robotFileCompressInstructionsSchema, +) +export type InterpolatableRobotFileCompressInstructions = + InterpolatableRobotFileCompressInstructionsInput + +export type InterpolatableRobotFileCompressInstructionsInput = z.input< + typeof interpolatableRobotFileCompressInstructionsSchema +> + +export const interpolatableRobotFileCompressInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotFileCompressInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotFileCompressInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotFileCompressInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotFileCompressInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotFileCompressInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/file-decompress.ts b/packages/transloadit/src/alphalib/types/robots/file-decompress.ts new file mode 100644 index 00000000..17ee0e23 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/file-decompress.ts @@ -0,0 +1,125 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 1, + discount_factor: 0.8, + discount_pct: 20, + example_code: { + steps: { + decompressed: { + robot: '/file/decompress', + use: ':original', + }, + }, + }, + example_code_description: 'Decompress an uploaded archive:', + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Compressing', + purpose_sentence: + 'extracts entire archives of files to be consumed by other Robots or exported as individual files', + purpose_verb: 'decompress', + purpose_word: 'decompress', + purpose_words: 'Decompress archives', + service_slug: 'file-compressing', + slot_count: 10, + title: 'Decompress archives', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'FileDecompressRobot', + priceFactor: 1.25, + queueSlotCount: 10, + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: true, + stage: 'ga', +} + +export const robotFileDecompressInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/file/decompress').describe(` +This Robot supports the following archive formats: + +- ZIP archives (with uncompressed or "deflate"-compressed entries) +- 7-Zip archives +- RAR archives +- GNU tar format (including GNU long filenames, long link names, and sparse files) +- Solaris 9 extended tar format (including ACLs) +- Old V7 tar archives +- POSIX ustar +- POSIX pax interchange format +- POSIX octet-oriented cpio +- SVR4 ASCII cpio +- POSIX octet-oriented cpio +- Binary cpio (big-endian or little-endian) +- ISO9660 CD-ROM images (with optional Rockridge or Joliet extensions) +- GNU and BSD "ar" archives +- "mtree" format +- Microsoft CAB format +- LHA and LZH archives +- XAR archives + +This Robot also detects and handles any of the following before evaluating the archive file: + +- uuencoded files +- Files with RPM wrapper +- gzip compression +- bzip2 compression +- compress/LZW compression +- lzma, lzip, and xz compression + +For security reasons, archives that contain symlinks to outside the archived dir, will error out the Assembly. Decompressing password-protected archives (encrypted archives) is currently not fully supported but will not cause an Assembly to fail. +`), + ignore_errors: z + .union([z.boolean(), z.array(z.enum(['meta', 'execute']))]) + .transform((ignoreErrors): ('meta' | 'execute')[] => + ignoreErrors === true ? ['meta', 'execute'] : ignoreErrors === false ? [] : ignoreErrors, + ) + .default([]) + .describe(` +A possible array member is only \`"meta"\`. + +You might see an error when trying to extract metadata from the files inside your archive. This happens, for example, for files with a size of zero bytes. Setting this to \`true\` will cause the Robot to not stop the file decompression (and the entire Assembly) when that happens. + +To keep backwards compatibility, setting this parameter to \`true\` will set it to \`["meta"]\` internally. +`), + }) + .strict() + +export const robotFileDecompressInstructionsWithHiddenFieldsSchema = + robotFileDecompressInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotFileDecompressInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotFileDecompressInstructions = z.infer +export type RobotFileDecompressInstructionsWithHiddenFields = z.infer< + typeof robotFileDecompressInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotFileDecompressInstructionsSchema = interpolateRobot( + robotFileDecompressInstructionsSchema, +) +export type InterpolatableRobotFileDecompressInstructions = + InterpolatableRobotFileDecompressInstructionsInput + +export type InterpolatableRobotFileDecompressInstructionsInput = z.input< + typeof interpolatableRobotFileDecompressInstructionsSchema +> + +export const interpolatableRobotFileDecompressInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotFileDecompressInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotFileDecompressInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotFileDecompressInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotFileDecompressInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotFileDecompressInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/file-filter.ts b/packages/transloadit/src/alphalib/types/robots/file-filter.ts new file mode 100644 index 00000000..0bf6f8c9 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/file-filter.ts @@ -0,0 +1,173 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + filterCondition, + interpolateRobot, + robotBase, + robotUse, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 0, + discount_factor: 0, + discount_pct: 100, + example_code: { + steps: { + filtered: { + robot: '/file/filter', + use: ':original', + declines: [['${file.size}', '>', '20971520']], + error_on_decline: true, + error_msg: 'File size must not exceed 20 MB', + }, + }, + }, + example_code_description: 'Reject files that are larger than 20 MB:', + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Filtering', + purpose_sentence: 'directs files to different encoding Steps based on your conditions', + purpose_verb: 'filter', + purpose_word: 'filter', + purpose_words: 'Filter files', + service_slug: 'file-filtering', + slot_count: 0, + title: 'Filter files', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'FileFilterRobot', + priceFactor: 100, + queueSlotCount: 0, + downloadInputFiles: false, + preserveInputFileUrls: true, + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotFileFilterInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/file/filter').describe(` +Think of this Robot as an \`if/else\` condition for building advanced file conversion workflows. With it, you can filter and direct certain uploaded files depending on their metadata. + +The Robot has two modes of operation: + +- Constructing conditions out of arrays with 3 members each. For example, \`["\${file.size}", "<=", "720"]\` +- Writing conditions in JavaScript. For example, \`\${file.size <= 720}\`. See also [Dynamic Evaluation](/docs/topics/dynamic-evaluation/). + +Passing JavaScript allows you to implement logic as complex as you wish, however it’s slower than combining arrays of conditions, and will be charged for per invocation via [🤖/script/run](/docs/robots/script-run/). + +### Conditions as arrays + +The \`accepts\` and \`declines\` parameters can each be set to an array of arrays with three members: + +1. A value or job variable, such as \`\${file.mime}\` +2. One of the following operators: \`==\`, \`===\`, \`<\`, \`>\`, \`<=\`, \`>=\`, \`!=\`, \`!==\`, \`regex\`, \`!regex\` +3. A value or job variable, such as \`50\` or \`"foo"\` + +Examples: + +- \`[["\${file.meta.width}", ">", "\${file.meta.height}"]]\` +- \`[["\${file.size}", "<=", "720"]]\` +- \`[["720", ">=", "\${file.size}"]]\` +- \`[["\${file.mime}", "regex", "image"]]\` + +> [!Warning] +> If you would like to match against a \`null\` value or a value that is not present (like an audio file does not have a \`video_codec\` property in its metadata), match against \`""\` (an empty string) instead. We’ll support proper matching against \`null\` in the future, but we cannot easily do so right now without breaking backwards compatibility. + +### Conditions as JavaScript + +The \`accepts\` and \`declines\` parameters can each be set to strings of JavaScript, which return a boolean value. + +Examples: + +- \`\${file.meta.width > file.meta.height}\` +- \`\${file.size <= 720}\` +- \`\${/image/.test(file.mime)}\` +- \`\${Math.max(file.meta.width, file.meta.height) > 100}\` + +As indicated, we charge for this via [🤖/script/run](/docs/robots/script-run/). See also [Dynamic Evaluation](/docs/topics/dynamic-evaluation/) for more details on allowed syntax and behavior. +`), + accepts: filterCondition + .describe( + ` +Files that match at least one requirement will be accepted, or declined otherwise. If the value is \`null\`, all files will be accepted. If the array is empty, no files will be accepted. Example: + +\`[["\${file.mime}", "==", "image/gif"]]\`. + +If the \`condition_type\` parameter is set to \`"and"\`, then all requirements must match for the file to be accepted. + +If \`accepts\` and \`declines\` are both provided, the requirements in \`accepts\` will be evaluated first, before the conditions in \`declines\`. +`, + ) + .optional(), + declines: filterCondition + .describe( + ` +Files that match at least one requirement will be declined, or accepted otherwise. If the value is \`null\` or an empty array, no files will be declined. Example: + +\`[["\${file.size}",">","1024"]]\`. + +If the \`condition_type\` parameter is set to \`"and"\`, then all requirements must match for the file to be declined. + +If \`accepts\` and \`declines\` are both provided, the requirements in \`accepts\` will be evaluated first, before the conditions in \`declines\`. +`, + ) + .optional(), + condition_type: z + .enum(['and', 'or']) + .default('or') + .describe(` +Specifies the condition type according to which the members of the \`accepts\` or \`declines\` arrays should be evaluated. Can be \`"or"\` or \`"and"\`. +`), + error_on_decline: z + .boolean() + .default(false) + .describe(` +If this is set to \`true\` and one or more files are declined, the Assembly will be stopped and marked with an error. +`), + error_msg: z + .string() + .default('One of your files was declined') + .describe(` +The error message shown to your users (such as by Uppy) when a file is declined and \`error_on_decline\` is set to \`true\`. +`), + }) + .strict() + +export const robotFileFilterInstructionsWithHiddenFieldsSchema = + robotFileFilterInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotFileFilterInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotFileFilterInstructions = z.infer +export type RobotFileFilterInstructionsWithHiddenFields = z.infer< + typeof robotFileFilterInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotFileFilterInstructionsSchema = interpolateRobot( + robotFileFilterInstructionsSchema, +) +export type InterpolatableRobotFileFilterInstructions = + InterpolatableRobotFileFilterInstructionsInput + +export type InterpolatableRobotFileFilterInstructionsInput = z.input< + typeof interpolatableRobotFileFilterInstructionsSchema +> + +export const interpolatableRobotFileFilterInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotFileFilterInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotFileFilterInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotFileFilterInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotFileFilterInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotFileFilterInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/file-hash.ts b/packages/transloadit/src/alphalib/types/robots/file-hash.ts new file mode 100644 index 00000000..50058e66 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/file-hash.ts @@ -0,0 +1,86 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: false, + bytescount: 5, + discount_factor: 0.2, + discount_pct: 80, + example_code: { + steps: { + hashed: { + robot: '/file/hash', + use: ':original', + algorithm: 'sha1', + }, + }, + }, + example_code_description: 'Hash each uploaded file using the SHA-1 algorithm:', + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'Media Cataloging', + purpose_sentence: 'hashes files in Assemblies', + purpose_verb: 'hash', + purpose_word: 'file', + purpose_words: 'Hash files', + service_slug: 'media-cataloging', + slot_count: 60, + title: 'Hash Files', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'FileHashRobot', + priceFactor: 5, + queueSlotCount: 60, + isAllowedForUrlTransform: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotFileHashInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/file/hash').describe(` +This Robot allows you to hash any file as part of the Assembly execution process. This can be useful for verifying the integrity of a file for example. +`), + algorithm: z + .enum(['b2', 'md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512']) + .default('sha256') + .describe(` +The hashing algorithm to use. + +The file hash is exported as \`file.meta.hash\`. +`), + }) + .strict() + +export const robotFileHashInstructionsWithHiddenFieldsSchema = + robotFileHashInstructionsSchema.extend({ + result: z.union([z.literal('debug'), robotFileHashInstructionsSchema.shape.result]).optional(), + }) + +export type RobotFileHashInstructions = z.infer +export type RobotFileHashInstructionsWithHiddenFields = z.infer< + typeof robotFileHashInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotFileHashInstructionsSchema = interpolateRobot( + robotFileHashInstructionsSchema, +) +export type InterpolatableRobotFileHashInstructions = InterpolatableRobotFileHashInstructionsInput + +export type InterpolatableRobotFileHashInstructionsInput = z.input< + typeof interpolatableRobotFileHashInstructionsSchema +> + +export const interpolatableRobotFileHashInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotFileHashInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotFileHashInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotFileHashInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotFileHashInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotFileHashInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/file-preview.ts b/packages/transloadit/src/alphalib/types/robots/file-preview.ts new file mode 100644 index 00000000..a4307225 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/file-preview.ts @@ -0,0 +1,260 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + color_with_alpha, + complexHeightSchema, + complexWidthSchema, + interpolateRobot, + optimize_priority, + resize_strategy, + robotBase, + robotUse, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + example_code: { + steps: { + previewed: { + robot: '/file/preview', + use: ':original', + height: 400, + width: 300, + format: 'png', + }, + }, + }, + example_code_description: 'Generate a preview thumbnail for any uploaded file:', + minimum_charge: 1048576, + output_factor: 1, + override_lvl1: 'Media Cataloging', + purpose_sentence: + 'generates a thumbnail for any uploaded file to preview its content, similar to the thumbnails in desktop file managers', + purpose_verb: 'generate', + purpose_word: 'generate', + purpose_words: 'Generate a preview thumbnail', + service_slug: 'media-cataloging', + slot_count: 15, + title: 'Generate a preview thumbnail', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'FilePreviewRobot', + priceFactor: 1, + queueSlotCount: 15, + minimumCharge: 1048576, + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + importRanges: ['0-19999999', '-1000000'], + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'beta', +} + +export const robotFilePreviewInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/file/preview').describe(` +This Robot's purpose is to generate a meaningful preview image for any file, in such a way that the resulting thumbnail highlights the file's content. The goal is not to losslessly present the original media in a smaller way. Instead, it is to maximize the chance of a person recognizing the media at a glance, while being visually pleasing and consistent with other previews. The generation process depends on the file type. For example, the Robot can extract artwork from media files, frames from videos, generate a waveform for audio files, and preview the content of documents and images. The details of all available strategies are provided in the next section. + +If no file-specific thumbnail can be generated because the file type is not supported, a generic icon containing the file extension will be generated. + +The default parameters ensure that the Robot always generates a preview image with the predefined dimensions and formats, to allow an easy integration into your application's UI. In addition, the generated preview images are optimized by default to reduce their file size while keeping their quality. +`), + format: z + .enum(['gif', 'jpg', 'png']) + .default('png') + .describe(` +The output format for the generated thumbnail image. If a short video clip is generated using the \`clip\` strategy, its format is defined by \`clip_format\`. +`), + width: complexWidthSchema.default(300).describe(` +Width of the thumbnail, in pixels. +`), + height: complexHeightSchema.default(200).describe(` +Height of the thumbnail, in pixels. +`), + resize_strategy: resize_strategy.describe(` +To achieve the desired dimensions of the preview thumbnail, the Robot might have to resize the generated image. This happens, for example, when the dimensions of a frame extracted from a video do not match the chosen \`width\` and \`height\` parameters. + +See the list of available [resize strategies](/docs/topics/resize-strategies/) for more details. +`), + background: color_with_alpha.default('#ffffffff').describe(` +The hexadecimal code of the color used to fill the background (only used for the pad resize strategy). The format is \`#rrggbb[aa]\` (red, green, blue, alpha). Use \`#00000000\` for a transparent padding. +`), + strategy: z + .object({ + archive: z.array(z.string()).default(['icon']), + audio: z.array(z.string()).default(['artwork', 'waveform', 'icon']), + document: z.array(z.string()).default(['page', 'icon']), + image: z.array(z.string()).default(['image', 'icon']), + unknown: z.array(z.string()).default(['icon']), + video: z.array(z.string()).default(['artwork', 'frame', 'icon']), + webpage: z.array(z.string()).default(['render', 'icon']), + }) + .optional() + .describe(` +Definition of the thumbnail generation process per file category. The parameter must be an object whose keys can be one of the file categories: \`audio\`, \`video\`, \`image\`, \`document\`, \`archive\`, \`webpage\`, and \`unknown\`. The corresponding value is an array of strategies for the specific file category. See the above section for a list of all available strategies. + +For each file, the Robot will attempt to use the first strategy to generate the thumbnail. If this process fails (e.g., because no artwork is available in a video file), the next strategy is attempted. This is repeated until either a thumbnail is generated or the list is exhausted. Selecting the \`icon\` strategy as the last entry provides a fallback mechanism to ensure that an appropriate strategy is always available. + +The parameter defaults to the following definition: + +\`\`\`json +{ + "audio": ["artwork", "waveform", "icon"], + "video": ["artwork", "frame", "icon"], + "document": ["page", "icon"], + "image": ["image", "icon"], + "webpage": ["render", "icon"], + "archive": ["icon"], + "unknown": ["icon"] +} +\`\`\` +`), + artwork_outer_color: color_with_alpha.optional().describe(` + The color used in the outer parts of the artwork's gradient. + `), + artwork_center_color: color_with_alpha.optional().describe(` + The color used in the center of the artwork's gradient. + `), + waveform_center_color: color_with_alpha.default('#000000ff').describe(` +The color used in the center of the waveform's gradient. The format is \`#rrggbb[aa]\` (red, green, blue, alpha). Only used if the \`waveform\` strategy for audio files is applied. +`), + waveform_outer_color: color_with_alpha.default('#000000ff').describe(` +The color used in the outer parts of the waveform's gradient. The format is \`#rrggbb[aa]\` (red, green, blue, alpha). Only used if the \`waveform\` strategy for audio files is applied. +`), + waveform_height: z + .number() + .int() + .min(1) + .max(5000) + .default(100) + .describe(` +Height of the waveform, in pixels. Only used if the \`waveform\` strategy for audio files is applied. It can be utilized to ensure that the waveform only takes up a section of the preview thumbnail. +`), + waveform_width: z + .number() + .int() + .min(1) + .max(5000) + .default(300) + .describe(` +Width of the waveform, in pixels. Only used if the \`waveform\` strategy for audio files is applied. It can be utilized to ensure that the waveform only takes up a section of the preview thumbnail. +`), + icon_style: z + .enum(['square', 'with-text']) + .default('with-text') + .describe(` +The style of the icon generated if the \`icon\` strategy is applied. The default style, \`with-text\`, includes an icon showing the file type and a text box below it, whose content can be controlled by the \`icon_text_content\` parameter and defaults to the file extension (e.g. MP4, JPEG). The \`square\` style only includes a square variant of the icon showing the file type. Below are exemplary previews generated for a text file utilizing the different styles: + +

\`with-text\` style:
+![Image with text style]({{site.asset_cdn}}/assets/images/file-preview/icon-with-text.png) +

\`square\` style:
+![Image with square style]({{site.asset_cdn}}/assets/images/file-preview/icon-square.png) +`), + icon_text_color: color_with_alpha.default('#a2a2a2').describe(` +The color of the text used in the icon. The format is \`#rrggbb[aa]\`. Only used if the \`icon\` strategy is applied. +`), + // TODO: Determine the font enum. + icon_text_font: z + .string() + .default('Roboto') + .describe(` +The font family of the text used in the icon. Only used if the \`icon\` strategy is applied. [Here](/docs/supported-formats/fonts/) is a list of all supported fonts. +`), + icon_text_content: z + .enum(['extension', 'none']) + .default('extension') + .describe(` +The content of the text box in generated icons. Only used if the \`icon_style\` parameter is set to \`with-text\`. The default value, \`extension\`, adds the file extension (e.g. MP4, JPEG) to the icon. The value \`none\` can be used to render an empty text box, which is useful if no text should not be included in the raster image, but some place should be reserved in the image for later overlaying custom text over the image using HTML etc. +`), + optimize: z + .boolean() + .default(true) + .describe(` +Specifies whether the generated preview image should be optimized to reduce the image's file size while keeping their quaility. If enabled, the images will be optimized using [🤖/image/optimize](/docs/robots/image-optimize/). +`), + optimize_priority: optimize_priority.describe(` +Specifies whether conversion speed or compression ratio is prioritized when optimizing images. Only used if \`optimize\` is enabled. Please see the [🤖/image/optimize documentation](/docs/robots/image-optimize/#param-priority) for more details. +`), + optimize_progressive: z + .boolean() + .default(false) + .describe(` +Specifies whether images should be interlaced, which makes the result image load progressively in browsers. Only used if \`optimize\` is enabled. Please see the [🤖/image/optimize documentation](/docs/robots/image-optimize/#param-progressive) for more details. +`), + clip_format: z + .enum(['apng', 'avif', 'gif', 'webp']) + .default('webp') + .describe(` +The animated image format for the generated video clip. Only used if the \`clip\` strategy for video files is applied. + +Please consult the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types) for detailed information about the image formats and their characteristics. GIF enjoys the broadest support in software, but only supports a limit color palette. APNG supports a variety of color depths, but its lossless compression produces large images for videos. AVIF is a modern image format that offers great compression, but proper support for animations is still lacking in some browsers. WebP on the other hand, enjoys broad support while offering a great balance between small file sizes and good visual quality, making it the default clip format. +`), + clip_offset: z + .number() + .min(0) + .default(1) + .describe(` +The start position in seconds of where the clip is cut. Only used if the \`clip\` strategy for video files is applied. Be aware that for larger video only the first few MBs of the file may be imported to improve speed. Larger offsets may seek to a position outside of the imported part and thus fail to generate a clip. +`), + clip_duration: z + .number() + .min(0) + .default(5) + .describe(` +The duration in seconds of the generated video clip. Only used if the \`clip\` strategy for video files is applied. Be aware that a longer clip duration also results in a larger file size, which might be undesirable for previews. +`), + clip_framerate: z + .number() + .int() + .min(1) + .max(60) + .default(5) + .describe(` +The framerate of the generated video clip. Only used if the \`clip\` strategy for video files is applied. Be aware that a higher framerate appears smoother but also results in a larger file size, which might be undesirable for previews. +`), + clip_loop: z + .boolean() + .default(true) + .describe(` +Specifies whether the generated animated image should loop forever (\`true\`) or stop after playing the animation once (\`false\`). Only used if the \`clip\` strategy for video files is applied. +`), + }) + .strict() + +export const robotFilePreviewInstructionsWithHiddenFieldsSchema = + robotFilePreviewInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotFilePreviewInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotFilePreviewInstructions = z.infer +export type RobotFilePreviewInstructionsInput = z.input +export type RobotFilePreviewInstructionsWithHiddenFields = z.infer< + typeof robotFilePreviewInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotFilePreviewInstructionsSchema = interpolateRobot( + robotFilePreviewInstructionsSchema, +) +export type InterpolatableRobotFilePreviewInstructions = + InterpolatableRobotFilePreviewInstructionsInput + +export type InterpolatableRobotFilePreviewInstructionsInput = z.input< + typeof interpolatableRobotFilePreviewInstructionsSchema +> + +export const interpolatableRobotFilePreviewInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotFilePreviewInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotFilePreviewInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotFilePreviewInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotFilePreviewInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotFilePreviewInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/file-read.ts b/packages/transloadit/src/alphalib/types/robots/file-read.ts new file mode 100644 index 00000000..4fabd6c7 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/file-read.ts @@ -0,0 +1,71 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 5, + discount_factor: 0.2, + discount_pct: 80, + minimum_charge: 512000, + output_factor: 1, + override_lvl1: 'Document Processing', + purpose_sentence: 'reads file contents from supported file-types', + purpose_verb: 'read', + purpose_word: 'read files', + purpose_words: 'Read file contents', + service_slug: 'document-processing', + slot_count: 5, + title: 'Read file contents', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'FileReadRobot', + priceFactor: 5, + queueSlotCount: 5, + minimumCharge: 512000, + isAllowedForUrlTransform: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotFileReadInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/file/read').describe(` +This Robot accepts any file, and will read the file using UTF-8 encoding. The result is outputted to \`file.meta.content\` to be accessed in later Steps. + +The Robot currently only accepts files under 500KB. +`), + }) + .strict() + +export const robotFileReadInstructionsWithHiddenFieldsSchema = + robotFileReadInstructionsSchema.extend({ + result: z.union([z.literal('debug'), robotFileReadInstructionsSchema.shape.result]).optional(), + }) + +export type RobotFileReadInstructions = z.infer +export type RobotFileReadInstructionsWithHiddenFields = z.infer< + typeof robotFileReadInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotFileReadInstructionsSchema = interpolateRobot( + robotFileReadInstructionsSchema, +) +export type InterpolatableRobotFileReadInstructions = InterpolatableRobotFileReadInstructionsInput + +export type InterpolatableRobotFileReadInstructionsInput = z.input< + typeof interpolatableRobotFileReadInstructionsSchema +> + +export const interpolatableRobotFileReadInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotFileReadInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotFileReadInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotFileReadInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotFileReadInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotFileReadInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/file-serve.ts b/packages/transloadit/src/alphalib/types/robots/file-serve.ts new file mode 100644 index 00000000..7c4e2a63 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/file-serve.ts @@ -0,0 +1,128 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 4, + discount_factor: 0.25, + discount_pct: 75, + minimum_charge: 0, + output_factor: 1, + purpose_sentence: 'serves files to web browsers', + purpose_verb: 'serve', + purpose_word: 'Serve files', + purpose_words: 'Serve files to web browsers', + service_slug: 'content-delivery', + slot_count: 0, + title: 'Serve files to web browsers', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'FileServeRobot', + priceFactor: 4, + queueSlotCount: 0, + downloadInputFiles: false, + preserveInputFileUrls: true, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + stage: 'ga', + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, +} + +export const robotFileServeInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/file/serve').describe(` +When you want Transloadit to tranform files on the fly, you can use this Robot to determine which Step of a Template should be served to the end-user (via a CDN), as well as set extra information on the served files, such as headers. This way you can for instance suggest the CDN for how long to keep cached copies of the result around. By default, as you can see in the \`headers\` parameter, we instruct browsers to cache the result for 72h (\`259200\` seconds) and CDNs to cache the content for 24h (\`86400\` seconds). These values should be adjusted to suit your use case. + +🤖/file/serve merely acts as the glue layer between our Assembly engine and serving files over HTTP. It let's you pick the proper result of a series of Steps via the \`use\` parameter and configure headers on the original content. That is where its responsibilies end, and 🤖/tlcdn/deliver, then takes over to globally distribute this original content across the globe, and make sure that is cached close to your end-users, when they make requests such as , another. 🤖/tlcdn/deliver is not a part of your Assembly Instructions, but it may appear on your invoices as bandwidth charges incur when distributing the cached copies. 🤖/file/serve only charges when the CDN does not have a cached copy and requests to regenerate the original content, which depending on your caching settings could be just once a month, or year, per file/transformation. + +While theoretically possible, you could use [🤖/file/serve](/docs/robots/file-serve/) directly in HTML files, but we strongly recommend against this, because if your site gets popular and the media URL that /file/serve is handling gets hit one million times, that is one million new image resizes. Wrapping it with a CDN (and thanks to the caching that comes with it) makes sure encoding charges stay low, as well as latencies. + +Also consider configuring caching headers and cache-control directives to control how content is cached and invalidated on the CDN edge servers, balancing between freshness and efficiency. + +## Smart CDN Security with Signature Authentication + +You can leverage [Signature Authentication](/docs/api/authentication/#smart-cdn) to avoid abuse of our encoding platform. Below is a quick Node.js example using our Node SDK, but there are [examples for other languages and SDKs](/docs/api/authentication/#example-code) as well. + +\`\`\`javascript +// yarn add transloadit +// or +// npm install --save transloadit + +import { Transloadit } from 'transloadit' + +const transloadit = new Transloadit({ + authKey: 'YOUR_TRANSLOADIT_KEY', + authSecret: 'YOUR_TRANSLOADIT_SECRET', +}) + +const url = transloadit.getSignedSmartCDNUrl({ + workspace: 'YOUR_WORKSPACE', + template: 'YOUR_TEMPLATE', + input: 'image.png', + urlParams: { height: 100, width: 100 }, +}) + +console.log(url) +\`\`\` + +This will generate a signed Smart CDN URL that includes authentication parameters, preventing unauthorized access to your transformation endpoints. + +## More information + +- [Content Delivery](/services/content-delivery/) +- [🤖/file/serve](/docs/robots/file-serve/) pricing +- [🤖/tlcdn/deliver](/docs/robots/tlcdn-deliver/) pricing +- [File Preview Feature](/blog/2024/06/file-preview-with-smart-cdn/) blog post +`), + headers: z + .record(z.string()) + .default({ + 'Access-Control-Allow-Headers': + 'X-Requested-With, Content-Type, Cache-Control, Accept, Content-Length, Transloadit-Client, Authorization', + 'Access-Control-Allow-Methods': 'POST, GET, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Origin': '*', + 'Cache-Control': 'public, max-age=259200, s-max-age=86400', + 'Content-Type': '${file.mime}; charset=utf-8', + 'Transfer-Encoding': 'chunked', + 'Transloadit-Assembly': '…', + 'Transloadit-RequestID': '…', + }) + .describe(` +An object containing a list of headers to be set for a file as we serve it to a CDN/web browser, such as \`{ FileURL: "\${file.url_name}" }\` which will be merged over the defaults, and can include any available [Assembly Variable](/docs/topics/assembly-instructions/#assembly-variables). +`), + }) + .strict() + +export const robotFileServeInstructionsWithHiddenFieldsSchema = + robotFileServeInstructionsSchema.extend({ + result: z.union([z.literal('debug'), robotFileServeInstructionsSchema.shape.result]).optional(), + }) + +export type RobotFileServeInstructions = z.infer +export type RobotFileServeInstructionsInput = z.input +export type RobotFileServeInstructionsWithHiddenFields = z.infer< + typeof robotFileServeInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotFileServeInstructionsSchema = interpolateRobot( + robotFileServeInstructionsSchema, +) +export type InterpolatableRobotFileServeInstructions = InterpolatableRobotFileServeInstructionsInput + +export type InterpolatableRobotFileServeInstructionsInput = z.input< + typeof interpolatableRobotFileServeInstructionsSchema +> + +export const interpolatableRobotFileServeInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotFileServeInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotFileServeInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotFileServeInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotFileServeInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotFileServeInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/file-verify.ts b/packages/transloadit/src/alphalib/types/robots/file-verify.ts new file mode 100644 index 00000000..15b55908 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/file-verify.ts @@ -0,0 +1,102 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 4, + description: + '/file/verify is a simple Robot that helps ensure that the files you upload are of the type you initially intended. This is especially useful when handling user-generated content, where you may not want to run certain Steps in your Template if the user hasn’t uploaded a file of the correct type. Another use case for /file/verify is when a user uploads a ZIP file, but we find that it has a few damaged files inside when we extract it. Perhaps you don’t want to error out, but only send the good files to a next processing step. With /file/verify, you can do exactly that (assuming the default of `error_on_decline`: `true`).', + discount_factor: 0.25, + discount_pct: 75, + example_code: { + steps: { + scanned: { + robot: '/file/verify', + use: ':original', + error_on_decline: true, + error_msg: 'At least one of the uploaded files was not the desired type', + verify_to_be: 'image', + }, + }, + }, + example_code_description: 'Scan the uploaded files and throw an error if they are not images:', + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Filtering', + purpose_sentence: 'verifies your files are the type that you want', + purpose_verb: 'verify', + purpose_word: 'verify the file type', + purpose_words: 'Verify the file type', + service_slug: 'file-filtering', + slot_count: 10, + title: 'Verify the file type', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'FileVerifyRobot', + priceFactor: 4, + queueSlotCount: 10, + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotFileVerifyInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/file/verify'), + error_on_decline: z + .boolean() + .default(false) + .describe(` +If this is set to \`true\` and one or more files are declined, the Assembly will be stopped and marked with an error. +`), + error_msg: z + .string() + .default('One of your files was declined') + .describe(` +The error message shown to your users (such as by Uppy) when a file is declined and \`error_on_decline\` is set to \`true\`. +`), + verify_to_be: z + .string() + .default('pdf') + .describe(` +The type that you want to match against to ensure your file is of this type. For example, \`image\` will verify whether uploaded files are images. This also works against file media types, in this case \`image/png\` would also work to match against specifically \`png\` files. +`), + }) + .strict() + +export const robotFileVerifyInstructionsWithHiddenFieldsSchema = + robotFileVerifyInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotFileVerifyInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotFileVerifyInstructions = z.infer +export type RobotFileVerifyInstructionsWithHiddenFields = z.infer< + typeof robotFileVerifyInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotFileVerifyInstructionsSchema = interpolateRobot( + robotFileVerifyInstructionsSchema, +) +export type InterpolatableRobotFileVerifyInstructions = + InterpolatableRobotFileVerifyInstructionsInput + +export type InterpolatableRobotFileVerifyInstructionsInput = z.input< + typeof interpolatableRobotFileVerifyInstructionsSchema +> + +export const interpolatableRobotFileVerifyInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotFileVerifyInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotFileVerifyInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotFileVerifyInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotFileVerifyInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotFileVerifyInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/file-virusscan.ts b/packages/transloadit/src/alphalib/types/robots/file-virusscan.ts new file mode 100644 index 00000000..11e7d886 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/file-virusscan.ts @@ -0,0 +1,113 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: false, + bytescount: 1, + description: + 'While 100% security is a myth, having /file/virusscan as a gatekeeper bot helps reject millions of trojans, viruses, malware & other malicious threats before they reach your platform.', + discount_factor: 1, + discount_pct: 0, + example_code: { + steps: { + scanned: { + robot: '/file/virusscan', + use: ':original', + error_on_decline: true, + error_msg: 'At least one of the uploaded files is malicious and was declined', + }, + }, + }, + example_code_description: + 'Scan uploaded files and throw an error if a malicious file is detected:', + minimum_charge: 1048576, + ogimage: '/assets/images/robots/ogimages/file-virusscan.jpg', + output_factor: 1, + override_lvl1: 'File Filtering', + purpose_sentence: + 'rejects millions of trojans, viruses, malware & other malicious threats before they reach your platform', + purpose_verb: 'scan', + purpose_word: 'scan for viruses and reject malware', + purpose_words: 'Scan files for viruses', + service_slug: 'file-filtering', + slot_count: 38, + title: 'Scan files for viruses', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'FileVirusscanRobot', + priceFactor: 1, + queueSlotCount: 38, + minimumCharge: 1048576, + lazyLoad: true, + installVersionFile: process.env.API2_CLAMD_INSTALL_VERSION_FILE || '', + isAllowedForUrlTransform: false, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotFileVirusscanInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/file/virusscan').describe(` + This Robot is built on top of [ClamAV](https://www.clamav.net/), the best open source antivirus engine available. We update its signatures on a daily basis. + +By default, this Robot excludes all malicious files from further processing without any additional notification. This behavior can be changed by setting \`error_on_decline\` to \`true\`, which will stop Assemblies as soon as malicious files are found. Such Assemblies will then be marked with an error. + +We allow the use of industry standard [EICAR files](https://www.eicar.org/download-anti-malware-testfile/) for integration testing without needing to use potentially dangerous live virus samples. +`), + error_on_decline: z + .boolean() + .default(false) + .describe(` +If this is set to \`true\` and one or more files are declined, the Assembly will be stopped and marked with an error. +`), + error_msg: z + .string() + .default('One of your files was declined') + .describe(` +The error message shown to your users (such as by Uppy) when a file is declined and \`error_on_decline\` is set to \`true\`. +`), + }) + .strict() + +export const robotFileVirusscanInstructionsWithHiddenFieldsSchema = + robotFileVirusscanInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotFileVirusscanInstructionsSchema.shape.result]) + .optional(), + can_use_daemon_fallback: z + .boolean() + .optional() + .describe(` +Allow the robot to use a daemon fallback mechanism if the primary scanning method fails. +`), + }) + +export type RobotFileVirusscanInstructions = z.infer +export type RobotFileVirusscanInstructionsWithHiddenFields = z.infer< + typeof robotFileVirusscanInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotFileVirusscanInstructionsSchema = interpolateRobot( + robotFileVirusscanInstructionsSchema, +) +export type InterpolatableRobotFileVirusscanInstructions = + InterpolatableRobotFileVirusscanInstructionsInput + +export type InterpolatableRobotFileVirusscanInstructionsInput = z.input< + typeof interpolatableRobotFileVirusscanInstructionsSchema +> + +export const interpolatableRobotFileVirusscanInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotFileVirusscanInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotFileVirusscanInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotFileVirusscanInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotFileVirusscanInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotFileVirusscanInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/file-watermark.ts b/packages/transloadit/src/alphalib/types/robots/file-watermark.ts new file mode 100644 index 00000000..70e03dd8 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/file-watermark.ts @@ -0,0 +1,56 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +// @ts-expect-error - FileWatermarkRobot is not ready yet @TODO please supply missing keys +export const meta: RobotMetaInput = { + name: 'FileWatermarkRobot', + priceFactor: 4, + queueSlotCount: 20, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotFileWatermarkInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/file/watermark'), + randomize: z.boolean().optional(), + }) + .strict() + +export const robotFileWatermarkInstructionsWithHiddenFieldsSchema = + robotFileWatermarkInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotFileWatermarkInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotFileWatermarkInstructions = z.infer +export type RobotFileWatermarkInstructionsWithHiddenFields = z.infer< + typeof robotFileWatermarkInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotFileWatermarkInstructionsSchema = interpolateRobot( + robotFileWatermarkInstructionsSchema, +) +export type InterpolatableRobotFileWatermarkInstructions = + InterpolatableRobotFileWatermarkInstructionsInput + +export type InterpolatableRobotFileWatermarkInstructionsInput = z.input< + typeof interpolatableRobotFileWatermarkInstructionsSchema +> + +export const interpolatableRobotFileWatermarkInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotFileWatermarkInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotFileWatermarkInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotFileWatermarkInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotFileWatermarkInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotFileWatermarkInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/ftp-import.ts b/packages/transloadit/src/alphalib/types/robots/ftp-import.ts new file mode 100644 index 00000000..5791c338 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/ftp-import.ts @@ -0,0 +1,95 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + ftpBase, + interpolateRobot, + path, + robotBase, + robotImport, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 10, + discount_factor: 0.1, + discount_pct: 90, + example_code: { + steps: { + imported: { + robot: '/ftp/import', + credentials: 'YOUR_FTP_CREDENTIALS', + path: 'path/to/files/', + }, + }, + }, + example_code_description: + 'Import files from the `path/to/files` directory and its subdirectories:', + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Importing', + purpose_sentence: + 'imports whole libraries of files from your FTP servers into Transloadit. This Robot relies on password access. For more security, consider our /sftp/import Robot', + purpose_verb: 'import', + purpose_word: 'FTP servers', + purpose_words: 'Import files from FTP servers', + service_slug: 'file-importing', + slot_count: 20, + title: 'Import files from FTP servers', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'FtpImportRobot', + priceFactor: 6.6666, + queueSlotCount: 20, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: true, + stage: 'ga', +} + +export const robotFtpImportInstructionsSchema = robotBase + .merge(robotImport) + .merge(ftpBase) + .extend({ + robot: z.literal('/ftp/import'), + path: path.describe(` +The path on your FTP server where to search for files. Files are imported recursively from all sub-directories and sub-sub-directories (and so on) from this path. +`), + passive_mode: z + .boolean() + .default(true) + .describe(` +Determines if passive mode should be used for the FTP connection. +`), + }) + .strict() + +export const robotFtpImportInstructionsWithHiddenFieldsSchema = + robotFtpImportInstructionsSchema.extend({ + result: z.union([z.literal('debug'), robotFtpImportInstructionsSchema.shape.result]).optional(), + }) + +export type RobotFtpImportInstructions = z.infer +export type RobotFtpImportInstructionsWithHiddenFields = z.infer< + typeof robotFtpImportInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotFtpImportInstructionsSchema = interpolateRobot( + robotFtpImportInstructionsSchema, +) +export type InterpolatableRobotFtpImportInstructions = InterpolatableRobotFtpImportInstructionsInput + +export type InterpolatableRobotFtpImportInstructionsInput = z.input< + typeof interpolatableRobotFtpImportInstructionsSchema +> + +export const interpolatableRobotFtpImportInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotFtpImportInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotFtpImportInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotFtpImportInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotFtpImportInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotFtpImportInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/ftp-store.ts b/packages/transloadit/src/alphalib/types/robots/ftp-store.ts new file mode 100644 index 00000000..85c33773 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/ftp-store.ts @@ -0,0 +1,119 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { ftpBase, interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 6, + discount_factor: 0.15000150001500018, + discount_pct: 84.99984999849998, + example_code: { + steps: { + exported: { + robot: '/ftp/store', + use: ':original', + credentials: 'YOUR_FTP_CREDENTIALS', + path: 'my_target_folder/${unique_prefix}/${file.url_name}', + }, + }, + }, + example_code_description: 'Export uploaded files to `my_target_folder` on an FTP server:', + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Exporting', + purpose_sentence: + 'exports encoding results to your FTP servers. This Robot relies on password access. For more security, consider our /sftp/store Robot', + purpose_verb: 'export', + purpose_word: 'FTP servers', + purpose_words: 'Export files to FTP servers', + service_slug: 'file-exporting', + slot_count: 10, + title: 'Export files to FTP servers', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'FtpStoreRobot', + priceFactor: 6.6666, + queueSlotCount: 10, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotFtpStoreInstructionsSchema = robotBase + .merge(robotUse) + .merge(ftpBase) + .extend({ + robot: z.literal('/ftp/store'), + path: z + .string() + .default('${unique_prefix}/${file.url_name}') + .describe(` +The path at which the file is to be stored. This can contain any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). + +Please note that you might need to include your homedir at the beginning of the path. +`), + url_template: z + .string() + .default('https://{HOST}/{PATH}') + .describe(` +The URL of the file in the result JSON. The following [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables) are supported. +`), + ssl_url_template: z + .string() + .default('https://{HOST}/{PATH}') + .describe(` +The SSL URL of the file in the result JSON. The following [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables) are supported. +`), + secure: z + .boolean() + .default(false) + .describe(` +Determines whether to establish a secure connection to the FTP server using SSL. +`), + }) + .strict() + +export const robotFtpStoreInstructionsWithHiddenFieldsSchema = + robotFtpStoreInstructionsSchema.extend({ + result: z.union([z.literal('debug'), robotFtpStoreInstructionsSchema.shape.result]).optional(), + use_remote_utime: z + .boolean() + .optional() + .describe(` +Use the remote file's modification time instead of the current time when storing the file. +`), + version: z + .union([z.string(), z.number()]) + .optional() + .describe(` +Version identifier for the underlying tool used (2 is ncftp, 1 is ftp). +`), + allowNetwork: z.string().optional(), // For internal test purposes + }) + +export type RobotFtpStoreInstructions = z.infer +export type RobotFtpStoreInstructionsWithHiddenFields = z.infer< + typeof robotFtpStoreInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotFtpStoreInstructionsSchema = interpolateRobot( + robotFtpStoreInstructionsSchema, +) +export type InterpolatableRobotFtpStoreInstructions = InterpolatableRobotFtpStoreInstructionsInput + +export type InterpolatableRobotFtpStoreInstructionsInput = z.input< + typeof interpolatableRobotFtpStoreInstructionsSchema +> + +export const interpolatableRobotFtpStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotFtpStoreInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotFtpStoreInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotFtpStoreInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotFtpStoreInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotFtpStoreInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/google-import.ts b/packages/transloadit/src/alphalib/types/robots/google-import.ts new file mode 100644 index 00000000..b031fbd3 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/google-import.ts @@ -0,0 +1,115 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + files_per_page, + googleBase, + interpolateRobot, + next_page_token, + path, + recursive, + robotBase, + robotImport, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 10, + discount_factor: 0.1, + discount_pct: 90, + example_code: { + steps: { + imported: { + robot: '/google/import', + credentials: 'YOUR_GOOGLE_CREDENTIALS', + path: 'path/to/files/', + recursive: true, + }, + }, + }, + example_code_description: + 'Import files from the `path/to/files` directory and its subdirectories:', + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Importing', + purpose_sentence: 'imports whole directories of files from Google Storage', + purpose_verb: 'import', + purpose_word: 'Google Storage', + purpose_words: 'Import files from Google Storage', + service_slug: 'file-importing', + slot_count: 20, + title: 'Import files from Google Storage', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'GoogleImportRobot', + priceFactor: 6.6666, + queueSlotCount: 20, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: true, + stage: 'ga', +} + +export const robotGoogleImportInstructionsSchema = robotBase + .merge(robotImport) + .merge(googleBase) + .extend({ + robot: z.literal('/google/import'), + path: path.describe(` +The path in your bucket to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. + +If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are direct descendants of this directory will be imported. For example: \`images/\`. + +Directories are **not** imported recursively. If you want to import files from subdirectories and sub-subdirectories, enable the \`recursive\` parameter. + +If you want to import all files from the root directory, please use \`/\` as the value here. In this case, make sure all your objects belong to a path. If you have objects in the root of your bucket that aren't prefixed with \`/\`, you'll receive a 404 \`GOOGLE_IMPORT_NOT_FOUND\` error. + +You can also use an array of path strings here to import multiple paths in the same Robot's Step. +`), + recursive: recursive.describe(` +Setting this to \`true\` will enable importing files from subdirectories and sub-subdirectories (etc.) of the given path. + +Please use the pagination parameters \`start_file_name\` and \`files_per_page\` wisely here. +`), + next_page_token: next_page_token.describe(` +A string token used for pagination. The returned files of one paginated call have the next page token inside of their meta data, which needs to be used for the subsequent paging call. +`), + files_per_page: files_per_page.describe(` +The pagination page size. This only works when recursive is \`true\` for now, in order to not break backwards compatibility in non-recursive imports. +`), + }) + .strict() + +export const robotGoogleImportInstructionsWithHiddenFieldsSchema = + robotGoogleImportInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotGoogleImportInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotGoogleImportInstructions = z.infer +export type RobotGoogleImportInstructionsWithHiddenFields = z.infer< + typeof robotGoogleImportInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotGoogleImportInstructionsSchema = interpolateRobot( + robotGoogleImportInstructionsSchema, +) +export type InterpolatableRobotGoogleImportInstructions = + InterpolatableRobotGoogleImportInstructionsInput + +export type InterpolatableRobotGoogleImportInstructionsInput = z.input< + typeof interpolatableRobotGoogleImportInstructionsSchema +> + +export const interpolatableRobotGoogleImportInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotGoogleImportInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotGoogleImportInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotGoogleImportInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotGoogleImportInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotGoogleImportInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/google-store.ts b/packages/transloadit/src/alphalib/types/robots/google-store.ts new file mode 100644 index 00000000..9101626b --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/google-store.ts @@ -0,0 +1,139 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { googleBase, interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 6, + discount_factor: 0.15000150001500018, + discount_pct: 84.99984999849998, + example_code: { + steps: { + exported: { + robot: '/google/store', + use: ':original', + credentials: 'YOUR_GOOGLE_CREDENTIALS', + path: 'my_target_folder/${unique_prefix}/${file.url_name}', + }, + }, + }, + example_code_description: 'Export uploaded files to `my_target_folder` on Google Storage:', + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Exporting', + purpose_sentence: 'exports encoding results to Google Storage', + purpose_verb: 'export', + purpose_word: 'Google Storage', + purpose_words: 'Export files to Google Storage', + service_slug: 'file-exporting', + slot_count: 10, + title: 'Export files to Google Storage', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'GoogleStoreRobot', + priceFactor: 6.6666, + queueSlotCount: 10, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotGoogleStoreInstructionsSchema = robotBase + .merge(robotUse) + .merge(googleBase) + .extend({ + robot: z.literal('/google/store').describe(` +The URL to the exported file in your Google bucket will be presented in the Transloadit Assembly Status JSON. This Robot can also be used to export encoded files to Google's Firebase as demonstrated in [this blogpost](/blog/2018/12/2h-youtube-clone/). +`), + result: z + .boolean() + .optional() + .describe('Whether the results of this Step should be present in the Assembly Status JSON'), + credentials: z.string().describe(` +Create a new [Google service account](https://cloud.google.com/storage/docs/authentication). Set its role to "Storage Object Creator". Choose "JSON" for the key file format and download it to your computer. You will need to upload this file when creating your Template Credentials. + +Go back to your Google credentials project and enable the "Google Cloud Storage JSON API" for it. Wait around ten minutes for the action to propagate through the Google network. Grab the project ID from the dropdown menu in the header bar on the Google site. You will also need it later on. + +Now you can set up the \`storage.objects.create\` and \`storage.objects.delete\` permissions. The latter is optional and only required if you intend to overwrite existing paths. + +To do this from the Google Cloud console, navigate to "IAM & Admin" and select "Roles". From here, click "Create Role", enter a name, set the role launch stage to _General availability,_ and set the permissions stated above. + +Next, go to Storage browser and select the ellipsis on your bucket to edit bucket permissions. From here, select "Add Member", enter your service account as a new member, and select your newly created role. + +Then, create your associated [Template Credentials](/c/template-credentials/) in your Transloadit account and use the name of your Template Credentials as this parameter's value. +`), + path: z + .string() + .default('${unique_prefix}/${file.url_name}') + .describe(` +The path at which the file is to be stored. This may include any available [Assembly Variables](/docs/topics/assembly-instructions/#assembly-variables). +`), + acl: z + .enum([ + 'authenticated-read', + 'bucket-owner-full-control', + 'private', + 'project-private', + 'public-read', + ]) + .nullable() + .default('public-read') + .describe(` +The permissions used for this file. +`), + cache_control: z + .string() + .optional() + .describe(` +The \`Cache-Control\` header determines how long browsers are allowed to cache your object for. Values specified with this parameter will be added to the object's metadata under the \`Cache-Control\` header. For more information on valid values, take a look at the [official Google documentation](https://cloud.google.com/storage/docs/metadata#cache-control). +`), + url_template: z + .string() + .default('https://{HOST}/{PATH}') + .describe(` +The URL of the file in the result JSON. This may include any of the following supported [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). +`), + ssl_url_template: z + .string() + .default('https://{HOST}/{PATH}') + .describe(` +The SSL URL of the file in the result JSON. The following [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables) are supported. +`), + }) + .strict() + +export const robotGoogleStoreInstructionsWithHiddenFieldsSchema = + robotGoogleStoreInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotGoogleStoreInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotGoogleStoreInstructions = z.infer +export type RobotGoogleStoreInstructionsWithHiddenFields = z.infer< + typeof robotGoogleStoreInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotGoogleStoreInstructionsSchema = interpolateRobot( + robotGoogleStoreInstructionsSchema, +) +export type InterpolatableRobotGoogleStoreInstructions = + InterpolatableRobotGoogleStoreInstructionsInput + +export type InterpolatableRobotGoogleStoreInstructionsInput = z.input< + typeof interpolatableRobotGoogleStoreInstructionsSchema +> + +export const interpolatableRobotGoogleStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotGoogleStoreInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotGoogleStoreInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotGoogleStoreInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotGoogleStoreInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotGoogleStoreInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/html-convert.ts b/packages/transloadit/src/alphalib/types/robots/html-convert.ts new file mode 100644 index 00000000..2960a07f --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/html-convert.ts @@ -0,0 +1,165 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + example_code: { + steps: { + captured: { + robot: '/html/convert', + url: 'https://transloadit.com', + }, + }, + }, + example_code_description: 'Take a full screenshot of the Transloadit homepage:', + extended_description: ` +> [!Warning] +> A validation error will occur if neither an HTML file is uploaded nor a URL parameter is given. + +> [!Note] +> Any files imported within the HTML page will be included in the cost. +`, + minimum_charge: 1048576, + output_factor: 0.5, + override_lvl1: 'Document Processing', + purpose_sentence: 'takes screenshots of web pages or uploaded HTML pages', + purpose_verb: 'take', + purpose_word: 'take screenshots of a webpage', + purpose_words: 'Take screenshots of webpages or HTML files', + service_slug: 'document-processing', + slot_count: 10, + title: 'Take screenshots of webpages or uploaded HTML files', + typical_file_size_mb: 0.6, + typical_file_type: 'webpage', + name: 'HtmlConvertRobot', + priceFactor: 1, + queueSlotCount: 30, + minimumCharge: 1048576, + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotHtmlConvertInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/html/convert').describe(` +A URL can be provided instead of an input HTML file, to capture a screenshot from the website referenced by the URL. + +Use [🤖/image/resize](/docs/robots/image-resize/) to resize or crop the screenshot as needed. +`), + url: z + .string() + .nullable() + .default(null) + .describe(` +The URL of the web page to be converted. Optional, as you can also upload/import HTML files and pass it to this Robot. +`), + format: z + .enum(['jpeg', 'jpg', 'pdf', 'png']) + .default('png') + .describe(` +The format of the resulting image. +`), + fullpage: z + .boolean() + .default(true) + .describe(` +Determines if a screenshot of the full page should be taken or not. + +If set to \`true\`, the \`height\` parameter will not have any effect, as heights of websites vary. You can control the size of the resulting image somewhat, though, by setting the \`width\` parameter. + +If set to \`false\`, an image will be cropped from the top of the webpage according to your \`width\` and \`height\` parameters. +`), + omit_background: z + .boolean() + .default(false) + .describe(` +Determines whether to preserve a transparent background in HTML pages. Useful if you're generating artwork in HTML that you want to overlay on e.g. a video. + +The default of \`false\` fills transparent areas with a white background, for easier reading/printing. + +This parameter is only used when \`format\` is not \`pdf\`. +`), + width: z + .number() + .int() + .min(1) + .default(1024) + .describe(` +The screen width that will be used, in pixels. Change this to change the dimensions of the resulting image. +`), + height: z + .number() + .int() + .min(1) + .optional() + .describe(` +The screen height that will be used, in pixels. By default this equals the length of the web page in pixels if \`fullpage\` is set to \`true\`. If \`fullpage\` is set to \`false\`, the height parameter takes effect and defaults to the value \`768\`. +`), + delay: z + .number() + .int() + .min(0) + .default(0) + .describe(` +The delay (in milliseconds) applied to allow the page and all of its JavaScript to render before taking the screenshot. +`), + headers: z + .record(z.string()) + .optional() + .describe(` +An object containing optional headers that will be passed along with the original request to the website. For example, this parameter can be used to pass along an authorization token along with the request. +`), + wait_until: z + .enum(['domcontentloaded', 'load', 'networkidle', 'commit']) + .default('networkidle') + .describe(` +The event to wait for before taking the screenshot. Used for loading Javascript, and images. + +See [Playwright's documentation](https://playwright.dev/docs/api/class-page#page-wait-for-load-state) for more information. +`), + }) + .strict() + +export const robotHtmlConvertInstructionsWithHiddenFieldsSchema = + robotHtmlConvertInstructionsSchema.extend({ + debuginfo: z.boolean().optional(), + timeouts: z.record(z.unknown()).optional(), + actions: z.array(z.record(z.unknown())).optional(), + result: z + .union([z.literal('debug'), robotHtmlConvertInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotHtmlConvertInstructions = z.infer +export type RobotHtmlConvertInstructionsWithHiddenFields = z.infer< + typeof robotHtmlConvertInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotHtmlConvertInstructionsSchema = interpolateRobot( + robotHtmlConvertInstructionsSchema, +) +export type InterpolatableRobotHtmlConvertInstructions = + InterpolatableRobotHtmlConvertInstructionsInput + +export type InterpolatableRobotHtmlConvertInstructionsInput = z.input< + typeof interpolatableRobotHtmlConvertInstructionsSchema +> + +export const interpolatableRobotHtmlConvertInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotHtmlConvertInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotHtmlConvertInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotHtmlConvertInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotHtmlConvertInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotHtmlConvertInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/http-import.ts b/packages/transloadit/src/alphalib/types/robots/http-import.ts new file mode 100644 index 00000000..1dec3319 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/http-import.ts @@ -0,0 +1,168 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + interpolateRobot, + return_file_stubs, + robotBase, + robotImport, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 10, + discount_factor: 0.1, + discount_pct: 90, + example_code: { + steps: { + imported: { + robot: '/http/import', + url: 'https://demos.transloadit.com/inputs/chameleon.jpg', + }, + }, + }, + example_code_description: 'Import an image from a specific URL:', + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Importing', + purpose_sentence: 'imports any file that is publicly available via a web URL into Transloadit', + purpose_verb: 'import', + purpose_word: 'Webservers', + purpose_words: 'Import files from web servers', + service_slug: 'file-importing', + slot_count: 10, + title: 'Import files from web servers', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'HttpImportRobot', + priceFactor: 10, + queueSlotCount: 10, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: true, + stage: 'ga', +} + +export const robotHttpImportInstructionsSchema = robotBase + .merge(robotImport) + .extend({ + robot: z.literal('/http/import').describe(` +The result of this Robot will carry a field \`import_url\` in their metadata, which references the URL from which they were imported. Further conversion results that use this file will also carry this \`import_url\` field. This allows you to to match conversion results with the original import URL that you used. + +This Robot knows to interpret links to files on these services: + +- Dropbox +- Google Drive +- Google Docs +- OneDrive + +Instead of downloading the HTML page previewing the file, the actual file itself will be imported. +`), + url: z.union([z.string().url(), z.array(z.string().url())]).describe(` +The URL from which the file to be imported can be retrieved. + +You can also specify an array of URLs or a string of \`|\` delimited URLs to import several files at once. Please also check the \`url_delimiter\` parameter for that. +`), + url_delimiter: z + .string() + .default('|') + .describe(` +Provides the delimiter that is used to split the URLs in your \`url\` parameter value. +`), + headers: z + .union([ + z.array(z.string()), + z.array(z.record(z.string())), + z.string(), // For JSON strings like '{"X-Database":"volt"}' + ]) + .default([]) + .describe(` +Custom headers to be sent for file import. + +This is an empty array by default, such that no additional headers except the necessary ones (e.g. Host) are sent. + +Headers can be specified as: +- An array of strings in the format "Header-Name: value" +- An array of objects with header names as keys and values as values +- A JSON string that will be parsed into an object +`), + import_on_errors: z + .array(z.string()) + .default([]) + .describe(` +Setting this to \`"meta"\` will still import the file on metadata extraction errors. \`ignore_errors\` is similar, it also ignores the error and makes sure the Robot doesn't stop, but it doesn't import the file. +`), + fail_fast: z + .boolean() + .default(false) + .describe(` +Disable the internal retry mechanism, and fail immediately if a resource can't be imported. This can be useful for performance critical applications. +`), + return_file_stubs, + range: z + .union([z.string(), z.array(z.string())]) + .optional() + .describe(` +Allows you to specify one or more byte ranges to import from the file. The server must support range requests for this to work. + +**Single range**: Use a string like \`"0-99"\` to import bytes 0-99 (the first 100 bytes). + +**Multiple ranges**: Use an array like \`["0-99", "200-299"]\` to import multiple separate ranges. The resulting file will contain all requested ranges concatenated together, with zero bytes (\\0) filling any gaps between non-contiguous ranges. + +**Range formats**: +- \`"0-99"\`: Bytes 0 through 99 (inclusive) +- \`"100-199"\`: Bytes 100 through 199 (inclusive) +- \`"-100"\`: The last 100 bytes of the file + +**Important notes**: +- The server must support HTTP range requests (respond with 206 Partial Content) +- If the server doesn't support range requests, the entire file will be imported instead +- Overlapping ranges are allowed and will be included as requested +- The resulting file size will be the highest byte position requested, with gaps filled with zero bytes +`), + }) + .strict() + +export const robotHttpImportInstructionsWithHiddenFieldsSchema = + robotHttpImportInstructionsSchema.extend({ + force_original_id: z.string().optional(), + force_name: z + .union([z.string(), z.record(z.string())]) + .optional() + .describe(` +Force a specific filename for imported files. Can be a string to apply to all imports, or an object mapping URLs to filenames. +`), + result: z + .union([z.literal('debug'), robotHttpImportInstructionsSchema.shape.result]) + .optional(), + credentials: z.string().optional(), // For test purposes + bucket: z.string().optional(), // For test purposes + // Override url to support relative URLs in tests with bucket + url: z.union([z.string(), z.array(z.string())]).optional(), + }) + +export type RobotHttpImportInstructions = z.infer +export type RobotHttpImportInstructionsWithHiddenFields = z.infer< + typeof robotHttpImportInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotHttpImportInstructionsSchema = interpolateRobot( + robotHttpImportInstructionsSchema, +) +export type InterpolatableRobotHttpImportInstructions = + InterpolatableRobotHttpImportInstructionsInput + +export type InterpolatableRobotHttpImportInstructionsInput = z.input< + typeof interpolatableRobotHttpImportInstructionsSchema +> + +export const interpolatableRobotHttpImportInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotHttpImportInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotHttpImportInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotHttpImportInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotHttpImportInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotHttpImportInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/image-bgremove.ts b/packages/transloadit/src/alphalib/types/robots/image-bgremove.ts new file mode 100644 index 00000000..178a052c --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/image-bgremove.ts @@ -0,0 +1,95 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + discount_factor: 1, + bytescount: 1, + discount_pct: 0, + example_code: { + steps: { + remove_background: { + robot: '/image/bgremove', + use: ':original', + }, + }, + }, + example_code_description: 'Remove the background from the uploaded image:', + minimum_charge: 0, + output_factor: 0.6, + override_lvl1: 'Image Manipulation', + purpose_sentence: 'removes the background from images', + purpose_verb: 'remove', + purpose_word: 'remove', + purpose_words: 'Remove the background from images', + service_slug: 'image-manipulation', + slot_count: 10, + title: 'Remove the background from images', + typical_file_size_mb: 0.8, + typical_file_type: 'image', + name: 'ImageBgremoveRobot', + priceFactor: 1, + queueSlotCount: 10, + minimumChargeUsd: 0.006, + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotImageBgremoveInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/image/bgremove'), + select: z + .enum(['foreground', 'background']) + .optional() + .describe('Region to select and keep in the image. The other region is removed.'), + format: z.enum(['png', 'gif', 'webp']).optional().describe('Format of the generated image.'), + provider: z + .enum(['transloadit', 'replicate', 'fal']) + .optional() + .describe('Provider to use for removing the background.'), + model: z + .string() + .optional() + .describe( + 'Provider-specific model to use for removing the background. Mostly intended for testing and evaluation.', + ), + }) + .strict() + +export const robotImageBgremoveInstructionsWithHiddenFieldsSchema = + robotImageBgremoveInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotImageBgremoveInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotImageBgremoveInstructions = z.infer +export type RobotImageBgremoveInstructionsWithHiddenFields = z.infer< + typeof robotImageBgremoveInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotImageBgremoveInstructionsSchema = interpolateRobot( + robotImageBgremoveInstructionsSchema, +) +export type InterpolatableRobotImageBgremoveInstructions = + InterpolatableRobotImageBgremoveInstructionsInput + +export type InterpolatableRobotImageBgremoveInstructionsInput = z.input< + typeof interpolatableRobotImageBgremoveInstructionsSchema +> + +export const interpolatableRobotImageBgremoveInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotImageBgremoveInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotImageBgremoveInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotImageBgremoveInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotImageBgremoveInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotImageBgremoveInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/image-describe.ts b/packages/transloadit/src/alphalib/types/robots/image-describe.ts new file mode 100644 index 00000000..d6a618c8 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/image-describe.ts @@ -0,0 +1,121 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + aiProviderSchema, + granularitySchema, + interpolateRobot, + robotBase, + robotUse, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + example_code: { + steps: { + described: { + robot: '/image/describe', + use: ':original', + provider: 'aws', + }, + }, + }, + example_code_description: + 'Recognize objects in an uploaded image and store the labels in a JSON file:', + extended_description: ` +> [!Warning] +> Transloadit aims to be deterministic, but this Robot uses third-party AI services. The providers (AWS, GCP) will evolve their models over time, giving different responses for the same input images. Avoid relying on exact responses in your tests and application. +`, + minimum_charge: 1572864, + output_factor: 0.05, + override_lvl1: 'Artificial Intelligence', + purpose_sentence: 'recognizes objects in images and returns them as English words', + purpose_verb: 'recognize', + purpose_word: 'recognize objects', + purpose_words: 'Recognize objects in images', + service_slug: 'artificial-intelligence', + slot_count: 10, + title: 'Recognize objects in images', + typical_file_size_mb: 0.8, + typical_file_type: 'image', + name: 'ImageDescribeRobot', + priceFactor: 1, + queueSlotCount: 10, + minimumChargeUsd: 0.0013, + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotImageDescribeInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/image/describe').describe(` +You can use the labels that we return in your application to automatically classify images. You can also pass the labels down to other Robots to filter images that contain (or do not contain) certain content. +`), + provider: aiProviderSchema.optional().describe(` +Which AI provider to leverage. + +Transloadit outsources this task and abstracts the interface so you can expect the same data structures, but different latencies and information being returned. Different cloud vendors have different areas they shine in, and we recommend to try out and see what yields the best results for your use case. +`), + granularity: granularitySchema.describe(` +Whether to return a full response (\`"full"\`) including confidence percentages for each found label, or just a flat list of labels (\`"list"\`). +`), + format: z + .enum(['json', 'meta', 'text']) + .default('json') + .describe(` +In what format to return the descriptions. + +- \`"json"\` returns a JSON file. +- \`"meta"\` does not return a file, but stores the data inside Transloadit's file object (under \`\${file.meta.descriptions}\`) that's passed around between encoding Steps, so that you can use the values to burn the data into videos, filter on them, etc. +`), + explicit_descriptions: z + .boolean() + .default(false) + .describe(` +Whether to return only explicit or only non-explicit descriptions of the provided image. Explicit descriptions include labels for NSFW content (nudity, violence, etc). If set to \`false\`, only non-explicit descriptions (such as human or chair) will be returned. If set to \`true\`, only explicit descriptions will be returned. + +The possible descriptions depend on the chosen provider. The list of labels from AWS can be found [in their documentation](https://docs.aws.amazon.com/rekognition/latest/dg/moderation.html#moderation-api). GCP labels the image based on five categories, as described [in their documentation](https://cloud.google.com/vision/docs/detecting-safe-search). + +For an example of how to automatically reject NSFW content and malware, please check out this [blog post](/blog/2022/07/deny-image-uploads/). +`), + }) + .strict() + +export const robotImageDescribeInstructionsWithHiddenFieldsSchema = + robotImageDescribeInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotImageDescribeInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotImageDescribeInstructions = z.infer +export type RobotImageDescribeInstructionsWithHiddenFields = z.infer< + typeof robotImageDescribeInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotImageDescribeInstructionsSchema = interpolateRobot( + robotImageDescribeInstructionsSchema, +) +export type InterpolatableRobotImageDescribeInstructions = + InterpolatableRobotImageDescribeInstructionsInput + +export type InterpolatableRobotImageDescribeInstructionsInput = z.input< + typeof interpolatableRobotImageDescribeInstructionsSchema +> + +export const interpolatableRobotImageDescribeInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotImageDescribeInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotImageDescribeInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotImageDescribeInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotImageDescribeInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotImageDescribeInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/image-facedetect.ts b/packages/transloadit/src/alphalib/types/robots/image-facedetect.ts new file mode 100644 index 00000000..fad9a026 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/image-facedetect.ts @@ -0,0 +1,187 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + aiProviderSchema, + interpolateRobot, + robotBase, + robotUse, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + example_code: { + steps: { + faces_detected: { + robot: '/image/facedetect', + use: ':original', + crop: true, + faces: 'each', + crop_padding: '10px', + }, + }, + }, + example_code_description: + 'Detect all faces in uploaded images, crop them, and save as separate images:', + minimum_charge: 5242880, + output_factor: 0.2, + override_lvl1: 'Artificial Intelligence', + purpose_sentence: + 'detects faces in images and can return either their coordinates or the faces themselves as new images', + purpose_verb: 'detect', + purpose_word: 'detect faces', + purpose_words: 'Detect faces in images', + service_slug: 'artificial-intelligence', + slot_count: 20, + title: 'Detect faces in images', + typical_file_size_mb: 0.8, + typical_file_type: 'image', + name: 'ImageFacedetectRobot', + priceFactor: 1, + queueSlotCount: 20, + minimumChargeUsd: 0.0013, + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotImageFacedetectInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/image/facedetect').describe(` +You can specify padding around the extracted faces, tailoring the output for your needs. + +This Robot works well together with [🤖/image/resize](/docs/robots/image-resize/) to bring the full power of resized and optimized images to your website or app. + +
+ +**How to improve the accuracy:** + +- Ensure that your pictures have the correct orientation. This Robot achieves the best performance when the faces in the image are oriented upright and not rotated. +- If the Robot detects objects other than a face, you can use \`"faces": "max-confidence"\` within your Template for selecting only the detection with the highest confidence. +- The number of returned detections can also be controlled using the \`min_confidence\` parameter. Increasing its value will yield less results but each with a higher confidence. Decreasing the value, on the other hand, will provide more results but may also include objects other than faces. + +
+`), + provider: aiProviderSchema.optional().describe(` +Which AI provider to leverage. + +Transloadit outsources this task and abstracts the interface so you can expect the same data structures, but different latencies and information being returned. Different cloud vendors have different areas they shine in, and we recommend to try out and see what yields the best results for your use case. +`), + crop: z + .boolean() + .default(false) + .describe(` +Determine if the detected faces should be extracted. If this option is set to \`false\`, then the Robot returns the input image again, but with the coordinates of all detected faces attached to \`file.meta.faces\` in the result JSON. If this parameter is set to \`true\`, the Robot will output all detected faces as images. +`), + crop_padding: z + .string() + .regex(/^\d+(px|%)$/) + .default('5px') + .describe(` +Specifies how much padding is added to the extracted face images if \`crop\` is set to \`true\`. Values can be in \`px\` (pixels) or \`%\` (percentage of the width and height of the particular face image). +`), + format: z + .enum(['jpg', 'png', 'preserve', 'tiff']) + .default('preserve') + .describe(` +Determines the output format of the extracted face images if \`crop\` is set to \`true\`. + +The default value \`"preserve"\` means that the input image format is re-used. +`), + min_confidence: z + .number() + .int() + .min(0) + .max(100) + .default(70) + .describe(` +Specifies the minimum confidence that a detected face must have. Only faces which have a higher confidence value than this threshold will be included in the result. +`), + faces: z + .union([z.enum(['each', 'group', 'max-confidence', 'max-size']), z.number().int()]) + .default('each') + .describe(` +Determines which of the detected faces should be returned. Valid values are: + +- \`"each"\` — each face is returned individually. +- \`"max-confidence"\` — only the face with the highest confidence value is returned. +- \`"max-size"\` — only the face with the largest area is returned. +- \`"group"\` — all detected faces are grouped together into one rectangle that contains all faces. +- any integer — the faces are sorted by their top-left corner and the integer determines the index of the returned face. Be aware the values are zero-indexed, meaning that \`faces: 0\` will return the first face. If no face for a given index exists, no output is produced. + +For the following examples, the input image is: + +![](/assets/images/abbas-malek-hosseini-22NnY93qaOk-unsplash.jpg) + +
+ +\`faces: "each"\` applied: + +![](/assets/images/abbas-malek-hosseini-22NnY93qaOk-face-0.jpg) +![](/assets/images/abbas-malek-hosseini-22NnY93qaOk-face-1.jpg) + +
+ +\`faces: "max-confidence"\` applied: + +![](/assets/images/abbas-malek-hosseini-22NnY93qaOk-face-1.jpg) + +
+ +\`faces: "max-size"\` applied: + +![](/assets/images/abbas-malek-hosseini-22NnY93qaOk-face-1.jpg) + +
+ +\`faces: "group"\` applied: + +![](/assets/images/abbas-malek-hosseini-22NnY93qaOk-face-group.jpg) + +
+ +\`faces: 0\` applied: + +![](/assets/images/abbas-malek-hosseini-22NnY93qaOk-face-0.jpg) +`), + }) + .strict() + +export const robotImageFacedetectInstructionsWithHiddenFieldsSchema = + robotImageFacedetectInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotImageFacedetectInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotImageFacedetectInstructions = z.infer< + typeof robotImageFacedetectInstructionsSchema +> +export type RobotImageFacedetectInstructionsWithHiddenFields = z.infer< + typeof robotImageFacedetectInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotImageFacedetectInstructionsSchema = interpolateRobot( + robotImageFacedetectInstructionsSchema, +) +export type InterpolatableRobotImageFacedetectInstructions = + InterpolatableRobotImageFacedetectInstructionsInput + +export type InterpolatableRobotImageFacedetectInstructionsInput = z.input< + typeof interpolatableRobotImageFacedetectInstructionsSchema +> + +export const interpolatableRobotImageFacedetectInstructionsWithHiddenFieldsSchema = + interpolateRobot(robotImageFacedetectInstructionsWithHiddenFieldsSchema) +export type InterpolatableRobotImageFacedetectInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotImageFacedetectInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotImageFacedetectInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotImageFacedetectInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/image-generate.ts b/packages/transloadit/src/alphalib/types/robots/image-generate.ts new file mode 100644 index 00000000..2b120099 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/image-generate.ts @@ -0,0 +1,92 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + minimum_charge: 0, + output_factor: 0.6, + purpose_sentence: 'generates images from text prompts using AI', + purpose_verb: 'generate', + purpose_word: 'generate', + purpose_words: 'Generate images from text prompts', + service_slug: 'artificial-intelligence', + slot_count: 10, + title: 'Generate images from text prompts', + typical_file_size_mb: 1.2, + typical_file_type: 'image', + name: 'ImageGenerateRobot', + priceFactor: 1, + queueSlotCount: 10, + minimumChargeUsd: 0.06, + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotImageGenerateInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/image/generate'), + model: z + .string() + .optional() + .describe('The AI model to use for image generation. Defaults to google/nano-banana.'), + prompt: z.string().describe('The prompt describing the desired image content.'), + format: z + .enum(['jpeg', 'jpg', 'png', 'gif', 'webp', 'svg']) + .optional() + .describe('Format of the generated image.'), + seed: z.number().optional().describe('Seed for the random number generator.'), + aspect_ratio: z.string().optional().describe('Aspect ratio of the generated image.'), + height: z.number().optional().describe('Height of the generated image.'), + width: z.number().optional().describe('Width of the generated image.'), + style: z.string().optional().describe('Style of the generated image.'), + num_outputs: z + .number() + .int() + .min(1) + .max(10) + .optional() + .describe('Number of image variants to generate.'), + }) + .strict() + +export const robotImageGenerateInstructionsWithHiddenFieldsSchema = + robotImageGenerateInstructionsSchema.extend({ + provider: z.string().optional().describe('Provider for generating the image.'), + result: z + .union([z.literal('debug'), robotImageGenerateInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotImageGenerateInstructions = z.infer +export type RobotImageGenerateInstructionsWithHiddenFields = z.infer< + typeof robotImageGenerateInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotImageGenerateInstructionsSchema = interpolateRobot( + robotImageGenerateInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotImageGenerateInstructions = + InterpolatableRobotImageGenerateInstructionsInput + +export type InterpolatableRobotImageGenerateInstructionsInput = z.input< + typeof interpolatableRobotImageGenerateInstructionsSchema +> + +export const interpolatableRobotImageGenerateInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotImageGenerateInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotImageGenerateInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotImageGenerateInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotImageGenerateInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotImageGenerateInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/image-merge.ts b/packages/transloadit/src/alphalib/types/robots/image-merge.ts new file mode 100644 index 00000000..535d05ba --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/image-merge.ts @@ -0,0 +1,127 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + color_without_alpha, + imageQualitySchema, + interpolateRobot, + robotBase, + robotUse, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + example_code: { + steps: { + merged: { + robot: '/image/merge', + use: { + steps: [':original'], + bundle_steps: true, + }, + border: 5, + }, + }, + }, + example_code_description: + 'Merge uploaded images into one, with a 5px gap between them on the spritesheet:', + minimum_charge: 0, + output_factor: 0.6, + override_lvl1: 'Image Manipulation', + purpose_sentence: 'merges several images into a single spritesheet', + purpose_verb: 'merge', + purpose_word: 'merge', + purpose_words: 'Merge several images into one image', + service_slug: 'image-manipulation', + slot_count: 10, + title: 'Merge several images into a single image', + typical_file_size_mb: 0.8, + typical_file_type: 'image', + name: 'ImageMergeRobot', + priceFactor: 1, + queueSlotCount: 10, + isAllowedForUrlTransform: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotImageMergeInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/image/merge').describe(` +The final result will be a spritesheet, with the images displayed horizontally or vertically. + +It's recommended to use this Robot with +[🤖/image/resize](/docs/robots/image-resize/) so your images are of a +similar size before merging them. +`), + format: z + .enum(['jpg', 'png']) + .default('png') + .describe('The output format for the modified image.'), + direction: z + .enum(['horizontal', 'vertical']) + .default('horizontal') + .describe('Specifies the direction which the images are displayed.'), + // TODO: default is not between 1 and 10 + border: z + .number() + .int() + .default(0) + .describe(` +An integer value which defines the gap between images on the spritesheet. + +A value of \`10\` would cause the images to have the largest gap between them, while a value of \`1\` would place the images side-by-side. +`), + background: color_without_alpha.default('#FFFFFF').describe(` +Either the hexadecimal code or [name](https://www.imagemagick.org/script/color.php#color_names) of the color used to fill the background (only shown with a border > 1). + +By default, the background of transparent images is changed to white. + +For details about how to preserve transparency across all image types, see [this demo](/demos/image-manipulation/properly-preserve-transparency-across-all-image-types/). +`), + adaptive_filtering: z + .boolean() + .default(false) + .describe(` +Controls the image compression for PNG images. Setting to \`true\` results in smaller file size, while increasing processing time. It is encouraged to keep this option disabled. +`), + quality: imageQualitySchema, + }) + .strict() + +export const robotImageMergeInstructionsWithHiddenFieldsSchema = + robotImageMergeInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotImageMergeInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotImageMergeInstructions = z.infer +export type RobotImageMergeInstructionsWithHiddenFields = z.infer< + typeof robotImageMergeInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotImageMergeInstructionsSchema = interpolateRobot( + robotImageMergeInstructionsSchema, +) +export type InterpolatableRobotImageMergeInstructions = + InterpolatableRobotImageMergeInstructionsInput + +export type InterpolatableRobotImageMergeInstructionsInput = z.input< + typeof interpolatableRobotImageMergeInstructionsSchema +> + +export const interpolatableRobotImageMergeInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotImageMergeInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotImageMergeInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotImageMergeInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotImageMergeInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotImageMergeInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/image-ocr.ts b/packages/transloadit/src/alphalib/types/robots/image-ocr.ts new file mode 100644 index 00000000..e4d4a862 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/image-ocr.ts @@ -0,0 +1,112 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + aiProviderSchema, + granularitySchema, + interpolateRobot, + robotBase, + robotUse, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + example_code: { + steps: { + recognized: { + robot: '/image/ocr', + use: ':original', + provider: 'gcp', + format: 'text', + }, + }, + }, + example_code_description: 'Recognize text in an uploaded image and save it to a text file:', + extended_description: ` +> [!Warning] +> Transloadit aims to be deterministic, but this Robot uses third-party AI services. The providers (AWS, GCP) will evolve their models over time, giving different responses for the same input images. Avoid relying on exact responses in your tests and application. +`, + minimum_charge: 1048576, + output_factor: 0.6, + override_lvl1: 'Artificial Intelligence', + purpose_sentence: 'recognizes text in images and returns it in a machine-readable format', + purpose_verb: 'recognize', + purpose_word: 'recognize text', + purpose_words: 'Recognize text in images (OCR)', + service_slug: 'artificial-intelligence', + slot_count: 10, + title: 'Recognize text in images', + typical_file_size_mb: 0.8, + typical_file_type: 'image', + name: 'ImageOcrRobot', + priceFactor: 1, + queueSlotCount: 10, + minimumChargeUsd: 0.0013, + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotImageOcrInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/image/ocr').describe(` +With this Robot you can detect and extract text from images using optical character recognition (OCR). + +For example, you can use the results to obtain the content of traffic signs, name tags, package labels and many more. You can also pass the text down to other Robots to filter images that contain (or do not contain) certain phrases. For images of dense documents, results may vary and be less accurate than for small pieces of text in photos. +`), + provider: aiProviderSchema.describe(` +Which AI provider to leverage. + +Transloadit outsources this task and abstracts the interface so you can expect the same data structures, but different latencies and information being returned. Different cloud vendors have different areas they shine in, and we recommend to try out and see what yields the best results for your use case. + +AWS supports detection for the following languages: English, Arabic, Russian, German, French, Italian, Portuguese and Spanish. GCP allows for a wider range of languages, with varying levels of support which can be found on the [official documentation](https://cloud.google.com/vision/docs/languages/). +`), + granularity: granularitySchema.describe(` +Whether to return a full response including coordinates for the text (\`"full"\`), or a flat list of the extracted phrases (\`"list"\`). This parameter has no effect if the \`format\` parameter is set to \`"text"\`. +`), + format: z + .enum(['json', 'meta', 'text']) + .default('json') + .describe(` +In what format to return the extracted text. +- \`"json"\` returns a JSON file. +- \`"meta"\` does not return a file, but stores the data inside Transloadit's file object (under \`\${file.meta.recognized_text}\`, which is an array of strings) that's passed around between encoding Steps, so that you can use the values to burn the data into videos, filter on them, etc. +- \`"text"\` returns the recognized text as a plain UTF-8 encoded text file. +`), + }) + .strict() + +export const robotImageOcrInstructionsWithHiddenFieldsSchema = + robotImageOcrInstructionsSchema.extend({ + result: z.union([z.literal('debug'), robotImageOcrInstructionsSchema.shape.result]).optional(), + }) + +export type RobotImageOcrInstructions = z.infer +export type RobotImageOcrInstructionsWithHiddenFields = z.infer< + typeof robotImageOcrInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotImageOcrInstructionsSchema = interpolateRobot( + robotImageOcrInstructionsSchema, +) +export type InterpolatableRobotImageOcrInstructions = InterpolatableRobotImageOcrInstructionsInput + +export type InterpolatableRobotImageOcrInstructionsInput = z.input< + typeof interpolatableRobotImageOcrInstructionsSchema +> + +export const interpolatableRobotImageOcrInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotImageOcrInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotImageOcrInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotImageOcrInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotImageOcrInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotImageOcrInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/image-optimize.ts b/packages/transloadit/src/alphalib/types/robots/image-optimize.ts new file mode 100644 index 00000000..f4d027c5 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/image-optimize.ts @@ -0,0 +1,114 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + interpolateRobot, + optimize_priority, + robotBase, + robotUse, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + example_code: { + steps: { + optimized: { + robot: '/image/optimize', + use: ':original', + }, + }, + }, + example_code_description: 'Optimize uploaded images:', + minimum_charge: 0, + output_factor: 0.6, + override_lvl1: 'Image Manipulation', + purpose_sentence: 'reduces the size of images while maintaining the same visual quality', + purpose_verb: 'optimize', + purpose_word: 'optimize', + purpose_words: 'Optimize images without quality loss', + service_slug: 'image-manipulation', + slot_count: 15, + title: 'Optimize images without quality loss', + typical_file_size_mb: 0.8, + typical_file_type: 'image', + name: 'ImageOptimizeRobot', + priceFactor: 1, + queueSlotCount: 15, + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotImageOptimizeInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/image/optimize').describe(` +With this Robot it's possible to reduce the file size of your JPEG, PNG, GIF, WEBP and SVG images by up to 80% for big images and 65% for small to medium sized ones — while keeping their original quality! + +This Robot enables you to lower your storage and bandwidth costs, and improves your user experience and monetization by reducing the load time of image-intensive web pages. + +It works well together with [🤖/image/resize](/docs/robots/image-resize/) to bring the full power of resized and optimized images to your website or app. + +> [!Note] +> This Robot accepts all image types and will just pass on unsupported image types unoptimized. Hence, there is no need to set up [🤖/file/filter](/docs/robots/file-filter/) workflows for this. +`), + priority: optimize_priority.describe(` +Provides different algorithms for better or worse compression for your images, but that run slower or faster. The value \`"conversion-speed"\` will result in an average compression ratio of 18%. \`"compression-ratio"\` will result in an average compression ratio of 31%. +`), + progressive: z + .boolean() + .default(false) + .describe(` +Interlaces the image if set to \`true\`, which makes the result image load progressively in browsers. Instead of rendering the image from top to bottom, the browser will first show a low-res blurry version of the image which is then quickly replaced with the actual image as the data arrives. This greatly increases the user experience, but comes at a loss of about 10% of the file size reduction. +`), + preserve_meta_data: z + .boolean() + .default(true) + .describe(` +Specifies if the image's metadata should be preserved during the optimization, or not. If it is not preserved, the file size is even further reduced. But be aware that this could strip a photographer's copyright information, which for obvious reasons can be frowned upon. +`), + fix_breaking_images: z + .boolean() + .default(true) + .describe(` +If set to \`true\` this parameter tries to fix images that would otherwise make the underlying tool error out and thereby break your Assemblies. This can sometimes result in a larger file size, though. +`), + }) + .strict() + +export const robotImageOptimizeInstructionsWithHiddenFieldsSchema = + robotImageOptimizeInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotImageOptimizeInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotImageOptimizeInstructions = z.infer +export type RobotImageOptimizeInstructionsWithHiddenFields = z.infer< + typeof robotImageOptimizeInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotImageOptimizeInstructionsSchema = interpolateRobot( + robotImageOptimizeInstructionsSchema, +) +export type InterpolatableRobotImageOptimizeInstructions = + InterpolatableRobotImageOptimizeInstructionsInput + +export type InterpolatableRobotImageOptimizeInstructionsInput = z.input< + typeof interpolatableRobotImageOptimizeInstructionsSchema +> + +export const interpolatableRobotImageOptimizeInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotImageOptimizeInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotImageOptimizeInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotImageOptimizeInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotImageOptimizeInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotImageOptimizeInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/image-resize.ts b/packages/transloadit/src/alphalib/types/robots/image-resize.ts new file mode 100644 index 00000000..ff632d2f --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/image-resize.ts @@ -0,0 +1,653 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + color_without_alpha_with_named, + colorspaceSchema, + complexHeightSchema, + complexWidthSchema, + imageQualitySchema, + interpolateRobot, + percentageSchema, + positionSchema, + robotBase, + robotImagemagick, + robotUse, + unsafeCoordinatesSchema, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + example_code: { + steps: { + resized: { + robot: '/image/resize', + use: ':original', + width: 200, + }, + }, + }, + example_code_description: + 'Resize uploaded images to a width of 200px while keeping their original aspect ratio:', + minimum_charge: 0, + output_factor: 0.6, + override_lvl1: 'Image Manipulation', + purpose_sentence: + 'resizes, crops, changes colorization, rotation, and applies text and watermarks to images', + purpose_verb: 'convert', + purpose_word: 'convert/resize/watermark', + purpose_words: 'Convert, resize, or watermark images', + service_slug: 'image-manipulation', + slot_count: 5, + title: 'Convert, resize, or watermark images', + typical_file_size_mb: 0.8, + typical_file_type: 'image', + uses_tools: ['imagemagick'], + name: 'ImageResizeRobot', + priceFactor: 1, + queueSlotCount: 5, + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const oneTextSchema = z.object({ + // TODO: Determine valid fonts + text: z.string(), + font: z + .string() + .default('Arial') + .describe(` +The font family to use. Also includes boldness and style of the font. + +[Here](/docs/supported-formats/fonts/) is a list of all +supported fonts. +`), + size: z + .number() + .int() + .min(1) + .default(12) + .describe(` +The text size in pixels. +`), + rotate: z + .number() + .int() + .default(0) + .describe(` +The rotation angle in degrees. +`), + color: color_without_alpha_with_named.default('#000000').describe(` +The text color. All hex colors in the form \`"#xxxxxx"\` are supported, where each x can be \`0-9\` or \`a-f\`. Named colors like \`"black"\`, \`"white"\`, \`"transparent"\` etc. are also supported. If you want a transparent text color, use "stroke" instead, otherwise your text will not be visible. +`), + background_color: color_without_alpha_with_named.default('transparent').describe(` +The background color behind the text. All hex colors in the form \`"#xxxxxx"\` are supported, where each x can be \`0-9\` or \`a-f\`. Named colors like \`"black"\`, \`"white"\`, \`"transparent"\` etc. are also supported. +`), + stroke_width: z + .number() + .int() + .min(0) + .default(0) + .describe(` +The stroke's width in pixels. +`), + stroke_color: color_without_alpha_with_named.default('transparent').describe(` +The stroke's color. All hex colors in the form \`"#xxxxxx"\` are supported, where each x can be \`0-9\` or \`a-f\`. Named colors like \`"black"\`, \`"white"\`, \`"transparent"\` etc. are also supported. +`), + align: z + .enum(['center', 'left', 'right']) + .default('center') + .describe(` +The horizontal text alignment. Can be \`"left"\`, \`"center"\` and \`"right"\`. +`), + valign: z + .enum(['bottom', 'center', 'top']) + .default('center') + .describe(` +The vertical text alignment. Can be \`"top"\`, \`"center"\` and \`"bottom"\`. +`), + x_offset: z + .number() + .int() + .default(0) + .describe(` +The horizontal offset for the text in pixels that is added (positive integer) or removed (negative integer) from the horizontal alignment. +`), + y_offset: z + .number() + .int() + .default(0) + .describe(` +The vertical offset for the text in pixels that is added (positive integer) or removed (negative integer) from the vertical alignment. +`), +}) + +const TEXT_DESCRIPTION = ` +Text overlays to be applied to the image. Can be either a single text object or an array of text objects. Each text object contains text rules. The following text parameters are intended to be used as properties for your text overlays. Here is an example: + +\`\`\`json +"watermarked": { + "use": "resized", + "robot": "/image/resize", + "text": [ + { + "text": "© 2018 Transloadit.com", + "size": 12, + "font": "Ubuntu", + "color": "#eeeeee", + "valign": "bottom", + "align": "right", + "x_offset": 16, + "y_offset": -10 + } + ] +} +\`\`\`` + +export const robotImageResizeInstructionsSchema = robotBase + .merge(robotUse) + .merge(robotImagemagick) + .extend({ + robot: z.literal('/image/resize'), + // TODO: Use an enum + format: z + .string() + .nullable() + .default(null) + .describe(` +The output format for the modified image. + +Some of the most important available formats are \`"jpg"\`, \`"png"\`, \`"gif"\`, and \`"tiff"\`. For a complete lists of all formats that we can write to please check [our supported image formats list](/docs/supported-formats/image-formats/). + +If \`null\` (default), then the input image's format will be used as the output format. + +If you wish to convert to \`"pdf"\`, please consider [🤖/document/convert](/docs/robots/document-convert/) instead. +`), + width: complexWidthSchema.optional().describe(` +Width of the result in pixels. If not specified, will default to the width of the original. +`), + height: complexHeightSchema.optional().describe(` +Height of the new image, in pixels. If not specified, will default to the height of the input image. +`), + resize_strategy: z + .union([ + z + .literal('crop') + .describe(`Cuts an area out of an image, discarding any overlapping parts. If the source image is smaller than the crop frame, it will be zoomed. This strategy is implied when you specify coordinates in the \`crop\` parameter, and cannot be used without it. + +To crop around human faces, see [🤖/image/facedetect](https://transloadit.com/docs/robots/image-facedetect/) instead.`), + z + .literal('fillcrop') + .describe(`Scales the image to fit into our 100×100 target while preserving aspect ratio, while trimming away any excess surface. This means both sides will become exactly 100 pixels, at the tradeoff of destroying parts of the image. + +By default the resulting image is horizontally/vertically centered to fill the target rectangle. Use the \`gravity\` parameter to change where to crop the image, such as \`"bottom\`" or \`"left\`".`), + z + .literal('fit') + .describe(`Uses the larger side of the original image as a base for the resize. Aspect ratio is preserved. Either side will become at most 100 pixels. + +For example: resizing a 400×300 image into 100×100, would produce a 100×75 image.`), + z + .literal('min_fit') + .describe(`Uses the **smaller** side of the original image as a base for the resize. After resizing, the larger side will have a larger value than specified. Aspect ratio is preserved. Either side will become at least 100 pixels. + +For example: resizing a 400×300 image into 100×100, would produce a 133×100 image.`), + z + .literal('pad') + .describe(`Scales the image to fit while preserving aspect ratio. Both sides of the resized image become exactly 100 pixels, and any remaining surface is filled with a background color. + +In this example, the background color is determined by the [Assembly Variable](https://transloadit.com/docs/topics/assembly-instructions/#assembly-variables) \`\${file.meta.average_color}\`. If you set \`zoom\` to \`false\` (default is \`true\`), smaller images will be centered horizontally and vertically, and have the background padding all around them.`), + z + .literal('stretch') + .describe( + 'Ignores aspect ratio, resizing the image to the exact width and height specified. This may result in a stretched or distorted image.', + ), + ]) + .default('fit') + .describe(` +See the list of available [resize strategies](/docs/topics/resize-strategies/). +`), + zoom: z + .boolean() + .default(true) + .describe(` +If this is set to \`false\`, smaller images will not be stretched to the desired width and height. For details about the impact of zooming for your preferred resize strategy, see the list of available [resize strategies](/docs/topics/resize-strategies/). +`), + crop: unsafeCoordinatesSchema.optional().describe(` +Specify an object containing coordinates for the top left and bottom right corners of the rectangle to be cropped from the original image(s). The coordinate system is rooted in the top left corner of the image. Values can be integers for absolute pixel values or strings for percentage based values. + +For example: + +\`\`\`json +{ + "x1": 80, + "y1": 100, + "x2": "60%", + "y2": "80%" +} +\`\`\` + +This will crop the area from \`(80, 100)\` to \`(600, 800)\` from a 1000×1000 pixels image, which is a square whose width is 520px and height is 700px. If \`crop\` is set, the width and height parameters are ignored, and the \`resize_strategy\` is set to \`crop\` automatically. + +You can also use a JSON string of such an object with coordinates in similar fashion: + +\`\`\`json +"{\\"x1\\": , \\"y1\\": , \\"x2\\": , \\"y2\\": }" +\`\`\` + +To crop around human faces, see [🤖/image/facedetect](/docs/robots/image-facedetect/). +`), + gravity: positionSchema.default('center').describe(` +The direction from which the image is to be cropped, when \`"resize_strategy"\` is set to \`"crop"\`, but no crop coordinates are defined. +`), + strip: z + .boolean() + .default(false) + .describe(` +Strips all metadata from the image. This is useful to keep thumbnails as small as possible. +`), + alpha: z + .enum([ + 'Activate', + 'Background', + 'Copy', + 'Deactivate', + 'Extract', + 'Off', + 'On', + 'Opaque', + 'Remove', + 'Set', + 'Shape', + 'Transparent', + ]) + .optional() + .describe(` +Gives control of the alpha/matte channel of an image. +`), + preclip_alpha: z + .enum([ + 'Activate', + 'Background', + 'Copy', + 'Deactivate', + 'Extract', + 'Off', + 'On', + 'Opaque', + 'Remove', + 'Set', + 'Shape', + 'Transparent', + ]) + .optional() + .describe(` +Gives control of the alpha/matte channel of an image before applying the clipping path via \`clip: true\`. +`), + flatten: z + .boolean() + .default(true) + .describe(` +Flattens all layers onto the specified background to achieve better results from transparent formats to non-transparent formats, as explained in the [ImageMagick documentation](https://www.imagemagick.org/script/command-line-options.php#layers). + +To preserve animations, GIF files are not flattened when this is set to \`true\`. To flatten GIF animations, use the \`frame\` parameter. +`), + correct_gamma: z + .boolean() + .default(false) + .describe(` +Prevents gamma errors [common in many image scaling algorithms](https://www.4p8.com/eric.brasseur/gamma.html). +`), + quality: imageQualitySchema, + adaptive_filtering: z + .boolean() + .default(false) + .describe(` +Controls the image compression for PNG images. Setting to \`true\` results in smaller file size, while increasing processing time. It is encouraged to keep this option disabled. +`), + background: color_without_alpha_with_named.default('#FFFFFF').describe(` +Either the hexadecimal code or [name](https://www.imagemagick.org/script/color.php#color_names) of the color used to fill the background (used for the \`pad\` resize strategy). + +**Note:** By default, the background of transparent images is changed to white. To preserve transparency, set \`"background"\` to \`"none"\`. +`), + frame: z + .number() + .int() + .min(1) + .nullable() + .default(null) + .describe(` +Use this parameter when dealing with animated GIF files to specify which frame of the GIF is used for the operation. Specify \`1\` to use the first frame, \`2\` to use the second, and so on. \`null\` means all frames. +`), + colorspace: colorspaceSchema.optional().describe(` +Sets the image colorspace. For details about the available values, see the [ImageMagick documentation](https://www.imagemagick.org/script/command-line-options.php#colorspace). Please note that if you were using \`"RGB"\`, we recommend using \`"sRGB"\` instead as of 2014-02-04. ImageMagick might try to find the most efficient \`colorspace\` based on the color of an image, and default to e.g. \`"Gray"\`. To force colors, you might have to use this parameter in combination with \`type: "TrueColor"\`. +`), + type: z + .enum([ + 'Bilevel', + 'ColorSeparation', + 'ColorSeparationAlpha', + 'Grayscale', + 'GrayscaleAlpha', + 'Palette', + 'PaletteAlpha', + 'TrueColor', + 'TrueColorAlpha', + ]) + .optional() + .describe(` +Sets the image color type. For details about the available values, see the [ImageMagick documentation](https://www.imagemagick.org/script/command-line-options.php#type). If you're using \`colorspace\`, ImageMagick might try to find the most efficient based on the color of an image, and default to e.g. \`"Gray"\`. To force colors, you could e.g. set this parameter to \`"TrueColor"\` +`), + sepia: z + .number() + .int() + .min(0) + .max(99) + .nullable() + .default(null) + .describe(` +Applies a sepia tone effect in percent. +`), + rotation: z + .union([ + z.number(), // Support any numeric rotation value (including precise angles like 2.9) + z.boolean(), + z.literal('auto'), // Support 'auto' string value + ]) + .default(true) + .describe(` +Determines whether the image should be rotated. Use any number to specify the rotation angle in degrees (e.g., \`90\`, \`180\`, \`270\`, \`360\`, or precise values like \`2.9\`). Use the value \`true\` or \`"auto"\` to auto-rotate images that are rotated incorrectly or depend on EXIF rotation settings. Otherwise, use \`false\` to disable auto-fixing altogether. +`), + compress: z + .enum(['BZip', 'Fax', 'Group4', 'JPEG', 'JPEG2000', 'Lossless', 'LZW', 'None', 'RLE', 'Zip']) + .nullable() + .default(null) + .describe(` +Specifies pixel compression for when the image is written. Compression is disabled by default. + +Please also take a look at [🤖/image/optimize](/docs/robots/image-optimize/). +`), + blur: z + .string() + .regex(/^\d+(\.\d+)?x\d+(\.\d+)?$/) + .nullable() + .default(null) + .describe(` +Specifies gaussian blur, using a value with the form \`{radius}x{sigma}\`. The radius value specifies the size of area the operator should look at when spreading pixels, and should typically be either \`"0"\` or at least two times the sigma value. The sigma value is an approximation of how many pixels the image is "spread"; think of it as the size of the brush used to blur the image. This number is a floating point value, enabling small values like \`"0.5"\` to be used. +`), + blur_regions: z + .array( + z.object({ + // TODO: These types are not documented. + x: complexWidthSchema, + y: complexHeightSchema, + width: complexWidthSchema, + height: complexHeightSchema, + }), + ) + .nullable() + .default(null) + .describe(` +Specifies an array of ellipse objects that should be blurred on the image. Each object has the following keys: \`x\`, \`y\`, \`width\`, \`height\`. If \`blur_regions\` has a value, then the \`blur\` parameter is used as the strength of the blur for each region. +`), + // TODO: An int according to the docs, a float in the example + brightness: z + .number() + .min(0) + .default(1) + .describe(` +Increases or decreases the brightness of the image by using a multiplier. For example \`1.5\` would increase the brightness by 50%, and \`0.75\` would decrease the brightness by 25%. +`), + // TODO: An int according to the docs, a float in the example + saturation: z + .number() + .min(0) + .default(1) + .describe(` +Increases or decreases the saturation of the image by using a multiplier. For example \`1.5\` would increase the saturation by 50%, and \`0.75\` would decrease the saturation by 25%. +`), + hue: z + .number() + .min(0) + .default(100) + .describe(` +Changes the hue by rotating the color of the image. The value \`100\` would produce no change whereas \`0\` and \`200\` will negate the colors in the image. +`), + contrast: z + .number() + .min(0) + .max(2) + .default(1) + .describe(` +Adjusts the contrast of the image. A value of \`1\` produces no change. Values below \`1\` decrease contrast (with \`0\` being minimum contrast), and values above \`1\` increase contrast (with \`2\` being maximum contrast). This works like the \`brightness\` parameter. +`), + watermark_url: z + .string() + .optional() + .describe(` +A URL indicating a PNG image to be overlaid above this image. Please note that you can also [supply the watermark via another Assembly Step](/docs/topics/use-parameter/#supplying-the-watermark-via-an-assembly-step). With watermarking you can add an image onto another image. This is usually used for logos. +`), + watermark_position: z + .union([positionSchema, z.array(positionSchema)]) + .default('center') + .describe(` +The position at which the watermark is placed. The available options are \`"center"\`, \`"top"\`, \`"bottom"\`, \`"left"\`, and \`"right"\`. You can also combine options, such as \`"bottom-right"\`. + +An array of possible values can also be specified, in which case one value will be selected at random, such as \`[ "center", "left", "bottom-left", "bottom-right" ]\`. + +This setting puts the watermark in the specified corner. To use a specific pixel offset for the watermark, you will need to add the padding to the image itself. +`), + watermark_x_offset: z + .number() + .int() + .default(0) + .describe(` +The x-offset in number of pixels at which the watermark will be placed in relation to the position it has due to \`watermark_position\`. + +Values can be both positive and negative and yield different results depending on the \`watermark_position\` parameter. Positive values move the watermark closer to the image's center point, whereas negative values move the watermark further away from the image's center point. +`), + watermark_y_offset: z + .number() + .int() + .default(0) + .describe(` +The y-offset in number of pixels at which the watermark will be placed in relation to the position it has due to \`watermark_position\`. + +Values can be both positive and negative and yield different results depending on the \`watermark_position\` parameter. Positive values move the watermark closer to the image's center point, whereas negative values move the watermark further away from the image's center point. +`), + watermark_size: percentageSchema.optional().describe(` +The size of the watermark, as a percentage. + +For example, a value of \`"50%"\` means that size of the watermark will be 50% of the size of image on which it is placed. The exact sizing depends on \`watermark_resize_strategy\`, too. +`), + watermark_resize_strategy: z + .enum(['area', 'fit', 'min_fit', 'stretch']) + .default('fit') + .describe(` +Available values are \`"fit"\`, \`"min_fit"\`, \`"stretch"\` and \`"area"\`. + +To explain how the resize strategies work, let's assume our target image size is 800×800 pixels and our watermark image is 400×300 pixels. Let's also assume, the \`watermark_size\` parameter is set to \`"25%"\`. + +For the \`"fit"\` resize strategy, the watermark is scaled so that the longer side of the watermark takes up 25% of the corresponding image side. And the other side is scaled according to the aspect ratio of the watermark image. So with our watermark, the width is the longer side, and 25% of the image size would be 200px. Hence, the watermark would be resized to 200×150 pixels. If the \`watermark_size\` was set to \`"50%"\`, it would be resized to 400×300 pixels (so just left at its original size). + +For the \`"min_fit"\` resize strategy, the watermark is scaled so that the shorter side of the watermark takes up 25% of the corresponding image side. And the other side is scaled according to the aspect ratio of the watermark image. So with our watermark, the height is the shorter side, and 25% of the image size would be 200px. Hence, the watermark would be resized to 267×200 pixels. If the \`watermark_size\` was set to \`"50%"\`, it would be resized to 533×400 pixels (so larger than its original size). + +For the \`"stretch"\` resize strategy, the watermark is stretched (meaning, it is resized without keeping its aspect ratio in mind) so that both sides take up 25% of the corresponding image side. Since our image is 800×800 pixels, for a watermark size of 25% the watermark would be resized to 200×200 pixels. Its height would appear stretched, because keeping the aspect ratio in mind it would be resized to 200×150 pixels instead. + +For the \`"area"\` resize strategy, the watermark is resized (keeping its aspect ratio in check) so that it covers \`"xx%"\` of the image's surface area. The value from \`watermark_size\` is used for the percentage area size. +`), + watermark_opacity: z + .number() + .min(0) + .max(1) + .default(1.0) + .describe(` +The opacity of the watermark, where \`0.0\` is fully transparent and \`1.0\` is fully opaque. + +For example, a value of \`0.5\` means the watermark will be 50% transparent, allowing the underlying image to show through. This is useful for subtle branding or when you want the watermark to be less obtrusive. +`), + watermark_repeat_x: z + .boolean() + .default(false) + .describe(` +When set to \`true\`, the watermark will be repeated horizontally across the entire width of the image. + +This is useful for creating tiled watermark patterns that cover the full image and make it more difficult to crop out the watermark. +`), + watermark_repeat_y: z + .boolean() + .default(false) + .describe(` +When set to \`true\`, the watermark will be repeated vertically across the entire height of the image. + +This is useful for creating tiled watermark patterns that cover the full image. Can be combined with \`watermark_repeat_x\` to tile in both directions. +`), + text: z + .union([ + // Support single text object (backward compatibility) + oneTextSchema, + // Support array of text objects (current schema) + z.array(oneTextSchema), + ]) + .optional() + .describe(TEXT_DESCRIPTION), + progressive: z + .boolean() + .default(false) + .describe(` +Interlaces the image if set to \`true\`, which makes the image load progressively in browsers. Instead of rendering the image from top to bottom, the browser will first show a low-res blurry version of the images which is then quickly replaced with the actual image as the data arrives. This greatly increases the user experience, but comes at a cost of a file size increase by around 10%. +`), + transparent: z + .union([color_without_alpha_with_named, z.string().regex(/^\d+,\d+,\d+$/)]) + .optional() + .describe(` +Make this color transparent within the image. Example: \`"255,255,255"\`. +`), + trim_whitespace: z + .boolean() + .default(false) + .describe(` +This determines if additional whitespace around the image should first be trimmed away. If you set this to \`true\` this parameter removes any edges that are exactly the same color as the corner pixels. +`), + clip: z + .union([z.string(), z.boolean()]) + .default(false) + .describe(` +Apply the clipping path to other operations in the resize job, if one is present. If set to \`true\`, it will automatically take the first clipping path. If set to a String it finds a clipping path by that name. +`), + negate: z + .boolean() + .default(false) + .describe(` +Replace each pixel with its complementary color, effectively negating the image. Especially useful when testing clipping. +`), + density: z + .string() + .regex(/\d+(x\d+)?/) + .nullable() + .default(null) + .describe(` +While in-memory quality and file format depth specifies the color resolution, the density of an image is the spatial (space) resolution of the image. That is the density (in pixels per inch) of an image and defines how far apart (or how big) the individual pixels are. It defines the size of the image in real world terms when displayed on devices or printed. + +You can set this value to a specific \`width\` or in the format \`width\`x\`height\`. + +If your converted image is unsharp, please try increasing density. +`), + monochrome: z + .boolean() + .default(false) + .describe(` +Transform the image to black and white. This is a shortcut for setting the colorspace to Gray and type to Bilevel. +`), + shave: z + .union([ + z.string().regex(/^\d+(x\d+)?$/), + z + .number() + .int() + .min(0) + .transform(String), // Accept numbers and convert to string + ]) + .optional() + .describe(` +Shave pixels from the image edges. The value should be in the format \`width\` or \`width\`x\`height\` to specify the number of pixels to remove from each side. +`), + }) + .strict() + +export const robotImageResizeInstructionsWithHiddenFieldsSchema = + robotImageResizeInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotImageResizeInstructionsSchema.shape.result]) + .optional(), + stack: z.string().optional().describe('Legacy parameter, use imagemagick_stack instead'), + text: z + .union([ + // Support single text object (backward compatibility) + oneTextSchema.extend({ + gravity: positionSchema + .default('top-left') + .optional() + .describe(` + Legacy. The direction from which to start the offsets. + `), + }), + // Support array of text objects (current schema) + z.array( + oneTextSchema.extend({ + gravity: positionSchema + .default('top-left') + .optional() + .describe(` + Legacy. The direction from which to start the offsets. + `), + }), + ), + ]) + .optional() + .describe(TEXT_DESCRIPTION), + watermark_position_x: z + .number() + .int() + .optional() + .describe(` + Legacy alias for \`watermark_x_offset\`. The x-offset in number of pixels at which the watermark will be placed. + `), + watermark_position_y: z + .number() + .int() + .optional() + .describe(` + Legacy alias for \`watermark_y_offset\`. The y-offset in number of pixels at which the watermark will be placed. + `), + }) + +export type RobotImageResizeInstructions = z.infer +export type RobotImageResizeInstructionsWithHiddenFields = z.infer< + typeof robotImageResizeInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotImageResizeInstructionsSchema = interpolateRobot( + robotImageResizeInstructionsSchema, +) +export type InterpolatableRobotImageResizeInstructions = + InterpolatableRobotImageResizeInstructionsInput + +export type InterpolatableRobotImageResizeInstructionsInput = z.input< + typeof interpolatableRobotImageResizeInstructionsSchema +> + +export const interpolatableRobotImageResizeInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotImageResizeInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotImageResizeInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotImageResizeInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotImageResizeInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotImageResizeInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/meta-read.ts b/packages/transloadit/src/alphalib/types/robots/meta-read.ts new file mode 100644 index 00000000..544c578a --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/meta-read.ts @@ -0,0 +1,44 @@ +import { z } from 'zod' +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase } from './_instructions-primitives.ts' + +// @ts-expect-error - MetaReadRobot is not ready yet @TODO please supply missing keys +export const meta: RobotMetaInput = { + name: 'MetaReadRobot', + priceFactor: 0, + queueSlotCount: 15, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: true, + stage: 'ga', + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, +} + +export const robotMetaReadInstructionsSchema = robotBase + .extend({ + robot: z.literal('/meta/read').describe('Reads metadata from a file.'), + }) + .strict() + +export type RobotMetaReadInstructions = z.infer + +export const robotMetaReadInstructionsWithHiddenFieldsSchema = + robotMetaReadInstructionsSchema.extend({ + result: z.union([z.literal('debug'), robotMetaReadInstructionsSchema.shape.result]).optional(), + }) + +export const interpolatableRobotMetaReadInstructionsSchema = interpolateRobot( + robotMetaReadInstructionsSchema, +) +export type InterpolatableRobotMetaReadInstructions = InterpolatableRobotMetaReadInstructionsInput + +export type InterpolatableRobotMetaReadInstructionsInput = z.input< + typeof interpolatableRobotMetaReadInstructionsSchema +> + +export const interpolatableRobotMetaReadInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotMetaReadInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotMetaReadInstructionsWithHiddenFields = z.input< + typeof interpolatableRobotMetaReadInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/meta-write.ts b/packages/transloadit/src/alphalib/types/robots/meta-write.ts new file mode 100644 index 00000000..46852e8d --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/meta-write.ts @@ -0,0 +1,93 @@ +import { z } from 'zod' + +import { stackVersions } from '../stackVersions.ts' +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotFFmpeg, robotUse } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + example_code: { + steps: { + attributed: { + robot: '/meta/write', + use: ':original', + data_to_write: { + copyright: '© Transloadit', + }, + ffmpeg_stack: stackVersions.ffmpeg.recommendedVersion, + }, + }, + }, + example_code_description: 'Add a copyright notice to uploaded images:', + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'Media Cataloging', + purpose_sentence: 'writes metadata into files', + purpose_verb: 'write', + purpose_word: 'write metadata', + purpose_words: 'Write metadata to media', + service_slug: 'media-cataloging', + slot_count: 10, + title: 'Write metadata to media', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + uses_tools: ['ffmpeg'], + name: 'MetaWriteRobot', + priceFactor: 1, + queueSlotCount: 10, + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotMetaWriteInstructionsSchema = robotBase + .merge(robotUse) + .merge(robotFFmpeg) + .extend({ + robot: z.literal('/meta/write').describe(` +**Note:** This Robot currently accepts images, videos and audio files. +`), + data_to_write: z + .record(z.unknown()) + .default({}) + .describe(` +A key/value map defining the metadata to write into the file. + +Valid metadata keys can be found [here](https://exiftool.org/TagNames/EXIF.html). For example: \`ProcessingSoftware\`. +`), + }) + .strict() + +export const robotMetaWriteInstructionsWithHiddenFieldsSchema = + robotMetaWriteInstructionsSchema.extend({ + result: z.union([z.literal('debug'), robotMetaWriteInstructionsSchema.shape.result]).optional(), + }) + +export type RobotMetaWriteInstructions = z.infer +export type RobotMetaWriteInstructionsWithHiddenFields = z.infer< + typeof robotMetaWriteInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotMetaWriteInstructionsSchema = interpolateRobot( + robotMetaWriteInstructionsSchema, +) +export type InterpolatableRobotMetaWriteInstructions = InterpolatableRobotMetaWriteInstructionsInput + +export type InterpolatableRobotMetaWriteInstructionsInput = z.input< + typeof interpolatableRobotMetaWriteInstructionsSchema +> + +export const interpolatableRobotMetaWriteInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotMetaWriteInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotMetaWriteInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotMetaWriteInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotMetaWriteInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotMetaWriteInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/minio-import.ts b/packages/transloadit/src/alphalib/types/robots/minio-import.ts new file mode 100644 index 00000000..617f6990 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/minio-import.ts @@ -0,0 +1,120 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + files_per_page, + interpolateRobot, + minioBase, + page_number, + path, + recursive, + return_file_stubs, + robotBase, + robotImport, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 10, + discount_factor: 0.1, + discount_pct: 90, + example_code: { + steps: { + imported: { + robot: '/minio/import', + credentials: 'YOUR_MINIO_CREDENTIALS', + path: 'path/to/files/', + recursive: true, + }, + }, + }, + example_code_description: + 'Import files from the `path/to/files` directory and its subdirectories:', + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Importing', + purpose_sentence: 'imports whole directories of files from your MinIO bucket', + purpose_verb: 'import', + purpose_word: 'MinIO', + purpose_words: 'Import files from MinIO', + requires_credentials: true, + service_slug: 'file-importing', + slot_count: 20, + title: 'Import files from MinIO', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'MinioImportRobot', + priceFactor: 6.6666, + queueSlotCount: 20, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: true, + stage: 'ga', +} + +export const robotMinioImportInstructionsSchema = robotBase + .merge(robotImport) + .merge(minioBase) + .extend({ + robot: z.literal('/minio/import'), + path: path.describe(` +The path in your bucket to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. + +If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are direct descendants of this directory will be imported. For example: \`images/\`. + +Directories are **not** imported recursively. If you want to import files from subdirectories and sub-subdirectories, enable the \`recursive\` parameter. + +If you want to import all files from the root directory, please use \`/\` as the value here. In this case, make sure all your objects belong to a path. If you have objects in the root of your bucket that aren't prefixed with \`/\`, you'll receive an error: \`A client error (NoSuchKey) occurred when calling the GetObject operation: The specified key does not exist.\` + +You can also use an array of path strings here to import multiple paths in the same Robot's Step. +`), + recursive: recursive.describe(` +Setting this to \`true\` will enable importing files from subdirectories and sub-subdirectories (etc.) of the given path. + +Please use the pagination parameters \`page_number\` and \`files_per_page\` wisely here. +`), + page_number: page_number.describe(` +The pagination page number. For now, in order to not break backwards compatibility in non-recursive imports, this only works when recursive is set to \`true\`. + +When doing big imports, make sure no files are added or removed from other scripts within your path, otherwise you might get weird results with the pagination. +`), + files_per_page: files_per_page.describe(` +The pagination page size. This only works when recursive is \`true\` for now, in order to not break backwards compatibility in non-recursive imports. +`), + return_file_stubs, + }) + .strict() + +export const robotMinioImportInstructionsWithHiddenFieldsSchema = + robotMinioImportInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotMinioImportInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotMinioImportInstructions = z.infer +export type RobotMinioImportInstructionsWithHiddenFields = z.infer< + typeof robotMinioImportInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotMinioImportInstructionsSchema = interpolateRobot( + robotMinioImportInstructionsSchema, +) +export type InterpolatableRobotMinioImportInstructions = + InterpolatableRobotMinioImportInstructionsInput + +export type InterpolatableRobotMinioImportInstructionsInput = z.input< + typeof interpolatableRobotMinioImportInstructionsSchema +> + +export const interpolatableRobotMinioImportInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotMinioImportInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotMinioImportInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotMinioImportInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotMinioImportInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotMinioImportInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/minio-store.ts b/packages/transloadit/src/alphalib/types/robots/minio-store.ts new file mode 100644 index 00000000..6003f609 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/minio-store.ts @@ -0,0 +1,115 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, minioBase, robotBase, robotUse } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 6, + discount_factor: 0.15000150001500018, + discount_pct: 84.99984999849998, + example_code: { + steps: { + exported: { + robot: '/minio/store', + use: ':original', + credentials: 'YOUR_MINIO_CREDENTIALS', + path: 'my_target_folder/${unique_prefix}/${file.url_name}', + }, + }, + }, + example_code_description: 'Export uploaded files to `my_target_folder` on MinIO:', + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Exporting', + purpose_sentence: 'exports encoding results to MinIO buckets', + purpose_verb: 'export', + purpose_word: 'MinIO', + purpose_words: 'Export files to MinIO', + service_slug: 'file-exporting', + slot_count: 10, + title: 'Export files to MinIO', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'MinioStoreRobot', + priceFactor: 6.6666, + queueSlotCount: 10, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotMinioStoreInstructionsSchema = robotBase + .merge(robotUse) + .merge(minioBase) + .extend({ + robot: z.literal('/minio/store').describe(` +The URL to the result file will be returned in the Assembly Status JSON. +`), + path: z + .string() + .default('${unique_prefix}/${file.url_name}') + .describe(` +The path at which the file is to be stored. This may include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). The path must not be a directory. +`), + acl: z + .enum(['private', 'public-read']) + .default('public-read') + .describe(` +The permissions used for this file. +`), + headers: z + .record(z.string()) + .default({ 'Content-Type': '${file.mime}' }) + .describe(` +An object containing a list of headers to be set for this file on MinIO Spaces, such as \`{ FileURL: "\${file.url_name}" }\`. This can also include any available [Assembly Variables](/docs/topics/assembly-instructions/#assembly-variables). + +Object Metadata can be specified using \`x-amz-meta-*\` headers. Note that these headers [do not support non-ASCII metadata values](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html#UserMetadata). +`), + sign_urls_for: z + .number() + .int() + .min(0) + .optional() + .describe(` +This parameter provides signed URLs in the result JSON (in the \`signed_ssl_url\` property). The number that you set this parameter to is the URL expiry time in seconds. + +If this parameter is not used, no URL signing is done. +`), + }) + .strict() + +export const robotMinioStoreInstructionsWithHiddenFieldsSchema = + robotMinioStoreInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotMinioStoreInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotMinioStoreInstructions = z.infer +export type RobotMinioStoreInstructionsWithHiddenFields = z.infer< + typeof robotMinioStoreInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotMinioStoreInstructionsSchema = interpolateRobot( + robotMinioStoreInstructionsSchema, +) +export type InterpolatableRobotMinioStoreInstructions = + InterpolatableRobotMinioStoreInstructionsInput + +export type InterpolatableRobotMinioStoreInstructionsInput = z.input< + typeof interpolatableRobotMinioStoreInstructionsSchema +> + +export const interpolatableRobotMinioStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotMinioStoreInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotMinioStoreInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotMinioStoreInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotMinioStoreInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotMinioStoreInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/progress-simulate.ts b/packages/transloadit/src/alphalib/types/robots/progress-simulate.ts new file mode 100644 index 00000000..350f2bc5 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/progress-simulate.ts @@ -0,0 +1,40 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +// @ts-expect-error - ProgressSimulateRobot is not ready yet @TODO please supply missing keys +export const meta: RobotMetaInput = { + name: 'ProgressSimulateRobot', + priceFactor: 1, + queueSlotCount: 20, + isAllowedForUrlTransform: false, + trackOutputFileSize: true, + isInternal: true, + stage: 'ga', + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, +} + +export const robotProgressSimulateInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/progress/simulate'), + duration: z.number(), + output_files: z.number(), + emit_progress: z.boolean(), + predict_output: z.boolean(), + }) + .strict() +export type RobotProgressSimulateInstructions = z.infer< + typeof robotProgressSimulateInstructionsSchema +> + +export const interpolatableRobotProgressSimulateInstructionsSchema = interpolateRobot( + robotProgressSimulateInstructionsSchema, +) +export type InterpolatableRobotProgressSimulateInstructions = + InterpolatableRobotProgressSimulateInstructionsInput + +export type InterpolatableRobotProgressSimulateInstructionsInput = z.input< + typeof interpolatableRobotProgressSimulateInstructionsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/s3-import.ts b/packages/transloadit/src/alphalib/types/robots/s3-import.ts new file mode 100644 index 00000000..5d435821 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/s3-import.ts @@ -0,0 +1,175 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + files_per_page, + interpolateRobot, + page_number, + path, + recursive, + return_file_stubs, + robotBase, + robotImport, + s3Base, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 10, + discount_factor: 0.1, + discount_pct: 90, + example_code: { + steps: { + imported: { + robot: '/s3/import', + credentials: 'YOUR_AWS_CREDENTIALS', + path: 'path/to/files/', + recursive: true, + }, + }, + }, + example_code_description: + 'Import files from the `path/to/files` directory and its subdirectories:', + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Importing', + purpose_sentence: 'imports whole directories of files from your S3 bucket', + purpose_verb: 'import', + purpose_word: 'Amazon S3', + purpose_words: 'Import files from Amazon S3', + requires_credentials: true, + service_slug: 'file-importing', + slot_count: 10, + title: 'Import files from Amazon S3', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'S3ImportRobot', + priceFactor: 10, + queueSlotCount: 10, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: true, + stage: 'ga', +} + +export const robotS3ImportInstructionsSchema = robotBase + .merge(robotImport) + .merge(s3Base) + .extend({ + robot: z.literal('/s3/import').describe(` +If you are new to Amazon S3, see our tutorial on [using your own S3 bucket](/docs/faq/how-to-set-up-an-amazon-s3-bucket/). + +The URL to the result file in your S3 bucket will be returned in the Assembly Status JSON. + +> [!Warning] +> **Use DNS-compliant bucket names**. Your bucket name [must be DNS-compliant](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html) and must not contain uppercase letters. Any non-alphanumeric characters in the file names will be replaced with an underscore, and spaces will be replaced with dashes. If your existing S3 bucket contains uppercase letters or is otherwise not DNS-compliant, rewrite the result URLs using the Robot’s \`url_prefix\` parameter. + + + +## Limit access + +You will also need to add permissions to your bucket so that Transloadit can access it properly. Here is an example IAM policy that you can use. Following the [principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege), it contains the **minimum required permissions** to export a file to your S3 bucket using Transloadit. You may require more permissions (especially viewing permissions) depending on your application. + +Please change \`{BUCKET_NAME}\` in the values for \`Sid\` and \`Resource\` accordingly. Also, this policy will grant the minimum required permissions to all your users. We advise you to create a separate Amazon IAM user, and use its User ARN (can be found in the "Summary" tab of a user [here](https://console.aws.amazon.com/iam/home#users)) for the \`Principal\` value. More information about this can be found [here](https://docs.aws.amazon.com/AmazonS3/latest/dev/AccessPolicyLanguage_UseCases_s3_a.html). + +\`\`\`json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AllowTransloaditToImportFilesIn{BUCKET_NAME}Bucket", + "Effect": "Allow", + "Action": ["s3:GetBucketLocation", "s3:ListBucket"], + "Resource": ["arn:aws:s3:::{BUCKET_NAME}", "arn:aws:s3:::{BUCKET_NAME}/*"] + } + ] +} +\`\`\` + +The \`Sid\` value is just an identifier for you to recognize the rule later. You can name it anything you like. + +The policy needs to be separated into two parts, because the \`ListBucket\` action requires permissions on the bucket while the other actions require permissions on the objects in the bucket. When targeting the objects there's a trailing slash and an asterisk in the \`Resource\` parameter, whereas when the policy targets the bucket, the slash and the asterisk are omitted. + +In order to build proper result URLs we need to know the region in which your S3 bucket resides. For this we require the \`GetBucketLocation\` permission. Figuring out your bucket's region this way will also slow down your Assemblies. To make this much faster and to also not require the \`GetBucketLocation\` permission, we have added the \`bucket_region\` parameter to the /s3/store and /s3/import Robots. We recommend using them at all times. + +Please keep in mind that if you use bucket encryption you may also need to add \`"sts:*"\` and \`"kms:*"\` to the bucket policy. Please read [here](https://docs.aws.amazon.com/kms/latest/developerguide/kms-api-permissions-reference.html) and [here](https://aws.amazon.com/blogs/security/how-to-restrict-amazon-s3-bucket-access-to-a-specific-iam-role/) in case you run into trouble with our example bucket policy. +`), + path: path.describe(` +The path in your bucket to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. + +If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are direct descendants to this directory will be imported. For example: \`images/\`. + +Directories are **not** imported recursively. If you want to import files from subdirectories and sub-subdirectories, enable the \`recursive\` parameter. + +If you want to import all files from the root directory, please use \`/\` as the value here. In this case, make sure all your objects belong to a path. If you have objects in the root of your bucket that aren't prefixed with \`/\`, you'll receive an error: \`A client error (NoSuchKey) occurred when calling the GetObject operation: The specified key does not exist.\` + +You can also use an array of path strings here to import multiple paths in the same Robot's Step. +`), + recursive: recursive.describe(` +Setting this to \`true\` will enable importing files from subdirectories and sub-subdirectories (etc.) of the given path. + +Please use the pagination parameters \`page_number\` and \`files_per_page\` wisely here. +`), + page_number: page_number.optional().describe(` +The pagination page number. For now, in order to not break backwards compatibility in non-recursive imports, this only works when recursive is set to \`true\`. + +When doing big imports, make sure no files are added or removed from other scripts within your path, otherwise you might get weird results with the pagination. +`), + files_per_page: files_per_page.optional().describe(` +The pagination page size. This only works when recursive is \`true\` for now, in order to not break backwards compatibility in non-recursive imports. +`), + return_file_stubs, + range: z + .union([z.string(), z.array(z.string())]) + .optional() + .describe(` +Allows you to specify one or more byte ranges to import from the file. S3 must support range requests for this to work. + +**Single range**: Use a string like \`"0-99"\` to import bytes 0-99 (the first 100 bytes). + +**Multiple ranges**: Use an array like \`["0-99", "200-299"]\` to import multiple separate ranges. The resulting file will contain all requested ranges concatenated together, with zero bytes (\\0) filling any gaps between non-contiguous ranges. + +**Range formats**: +- \`"0-99"\`: Bytes 0 through 99 (inclusive) +- \`"100-199"\`: Bytes 100 through 199 (inclusive) +- \`"-100"\`: The last 100 bytes of the file + +**Important notes**: +- S3 supports range requests by default +- Overlapping ranges are allowed and will be included as requested +- The resulting file size will be the highest byte position requested, with gaps filled with zero bytes +- Each range is fetched in a separate request to ensure compatibility with S3 +`), + }) + .strict() + +export const robotS3ImportInstructionsWithHiddenFieldsSchema = + robotS3ImportInstructionsSchema.extend({ + result: z.union([z.literal('debug'), robotS3ImportInstructionsSchema.shape.result]).optional(), + }) + +export type RobotS3ImportInstructions = z.infer +export type RobotS3ImportInstructionsWithHiddenFields = z.infer< + typeof robotS3ImportInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotS3ImportInstructionsSchema = interpolateRobot( + robotS3ImportInstructionsSchema, +) +export type InterpolatableRobotS3ImportInstructions = InterpolatableRobotS3ImportInstructionsInput + +export type InterpolatableRobotS3ImportInstructionsInput = z.input< + typeof interpolatableRobotS3ImportInstructionsSchema +> + +export const interpolatableRobotS3ImportInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotS3ImportInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotS3ImportInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotS3ImportInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotS3ImportInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotS3ImportInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/s3-store.ts b/packages/transloadit/src/alphalib/types/robots/s3-store.ts new file mode 100644 index 00000000..69946f32 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/s3-store.ts @@ -0,0 +1,198 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse, s3Base } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 10, + discount_factor: 0.1, + discount_pct: 90, + example_code: { + steps: { + exported: { + robot: '/s3/store', + use: ':original', + credentials: 'YOUR_AWS_CREDENTIALS', + path: 'my_target_folder/${unique_prefix}/${file.url_name}', + }, + }, + }, + example_code_description: 'Export uploaded files to `my_target_folder` in an S3 bucket:', + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Exporting', + purpose_sentence: 'exports encoding results to Amazon S3', + purpose_verb: 'export', + purpose_word: 'Amazon S3', + purpose_words: 'Export files to Amazon S3', + service_slug: 'file-exporting', + slot_count: 2, + title: 'Export files to Amazon S3', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'S3StoreRobot', + priceFactor: 10, + queueSlotCount: 2, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + stage: 'ga', + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, +} + +export const robotS3StoreInstructionsSchema = robotBase + .merge(robotUse) + .merge(s3Base) + .extend({ + robot: z.literal('/s3/store').describe(` +If you are new to Amazon S3, see our tutorial on [using your own S3 bucket](/docs/faq/how-to-set-up-an-amazon-s3-bucket/). + +The URL to the result file in your S3 bucket will be returned in the Assembly Status JSON. If your S3 bucket has versioning enabled, the version ID of the file will be returned within \`meta.version_id\` + +> [!Warning] +> **Avoid permission errors.** By default, \`acl\` is set to \`"public"\`. AWS S3 has a bucket setting called "Block new public ACLs and uploading public objects". Set this to False in your bucket if you intend to leave \`acl\` as \`"public"\`. Otherwise, you’ll receive permission errors in your Assemblies despite your S3 credentials being configured correctly. + +> [!Warning] +> **Use DNS-compliant bucket names.** Your bucket name [must be DNS-compliant](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html) and must not contain uppercase letters. Any non-alphanumeric characters in the file names will be replaced with an underscore, and spaces will be replaced with dashes. If your existing S3 bucket contains uppercase letters or is otherwise not DNS-compliant, rewrite the result URLs using the Robot’s \`url_prefix\` parameter. + + + +## Limit access + +You will also need to add permissions to your bucket so that Transloadit can access it properly. Here is an example IAM policy that you can use. Following the [principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege), it contains the **minimum required permissions** to export a file to your S3 bucket using Transloadit. You may require more permissions (especially viewing permissions) depending on your application. + +Please change \`{BUCKET_NAME}\` in the values for \`Sid\` and \`Resource\` accordingly. Also, this policy will grant the minimum required permissions to all your users. We advise you to create a separate Amazon IAM user, and use its User ARN (can be found in the "Summary" tab of a user [here](https://console.aws.amazon.com/iam/home#users)) for the \`Principal\` value. More information about this can be found [here](https://docs.aws.amazon.com/AmazonS3/latest/dev/AccessPolicyLanguage_UseCases_s3_a.html). + +\`\`\`json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AllowTransloaditToStoreFilesIn{BUCKET_NAME}Bucket", + "Effect": "Allow", + "Action": ["s3:GetBucketLocation", "s3:ListBucket", "s3:PutObject", "s3:PutObjectAcl"], + "Resource": ["arn:aws:s3:::{BUCKET_NAME}", "arn:aws:s3:::{BUCKET_NAME}/*"] + } + ] +} +\`\`\` + +The \`Sid\` value is just an identifier for you to recognize the rule later. You can name it anything you like. + +The policy needs to be separated into two parts, because the \`ListBucket\` action requires permissions on the bucket while the other actions require permissions on the objects in the bucket. When targeting the objects there's a trailing slash and an asterisk in the \`Resource\` parameter, whereas when the policy targets the bucket, the slash and the asterisk are omitted. + +Please note that if you give the Robot's \`acl\` parameter a value of \`"bucket-default"\`, then you do not need the \`"s3:PutObjectAcl"\` permission in your bucket policy. + +In order to build proper result URLs we need to know the region in which your S3 bucket resides. For this we require the \`GetBucketLocation\` permission. Figuring out your bucket's region this way will also slow down your Assemblies. To make this much faster and to also not require the \`GetBucketLocation\` permission, we have added the \`bucket_region\` parameter to the /s3/store and /s3/import Robots. We recommend using them at all times. + +Please keep in mind that if you use bucket encryption you may also need to add \`"sts:*"\` and \`"kms:*"\` to the bucket policy. Please read [here](https://docs.aws.amazon.com/kms/latest/developerguide/kms-api-permissions-reference.html) and [here](https://aws.amazon.com/blogs/security/how-to-restrict-amazon-s3-bucket-access-to-a-specific-iam-role/) in case you run into trouble with our example bucket policy. +`), + path: z + .string() + .default('${unique_prefix}/${file.url_name}') + .describe(` +The path at which the file is to be stored. This may include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). The path must not be a directory. +`), + url_prefix: z + .string() + .default('http://{bucket}.s3.amazonaws.com/') + .describe(` +The URL prefix used for the returned URL, such as \`"http://my.cdn.com/some/path/"\`. +`), + acl: z + .enum(['bucket-default', 'private', 'public', 'public-read']) + .default('public-read') + .describe(` +The permissions used for this file. + +Please keep in mind that the default value \`"public-read"\` can lead to permission errors due to the \`"Block all public access"\` checkbox that is checked by default when creating a new Amazon S3 Bucket in the AWS console. +`), + check_integrity: z + .boolean() + .default(false) + .describe(` +Calculate and submit the file's checksum in order for S3 to verify its integrity after uploading, which can help with occasional file corruption issues. + +Enabling this option adds to the overall execution time, as integrity checking can be CPU intensive, especially for larger files. +`), + headers: z + .record(z.string()) + .default({ 'Content-Type': '${file.mime}' }) + .describe(` +An object containing a list of headers to be set for this file on S3, such as \`{ FileURL: "\${file.url_name}" }\`. This can also include any available [Assembly Variables](/docs/topics/assembly-instructions/#assembly-variables). You can find a list of available headers [here](https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUT.html). + +Object Metadata can be specified using \`x-amz-meta-*\` headers. Note that these headers [do not support non-ASCII metadata values](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html#UserMetadata). +`), + tags: z + .record(z.string()) + .default({}) + .describe(` +Object tagging allows you to categorize storage. You can associate up to 10 tags with an object. Tags that are associated with an object must have unique tag keys. +`), + host: z + .string() + .default('s3.amazonaws.com') + .describe(` +The host of the storage service used. This only needs to be set when the storage service used is not Amazon S3, but has a compatible API (such as hosteurope.de). The default protocol used is HTTP, for anything else the protocol needs to be explicitly specified. For example, prefix the host with \`https://\` or \`s3://\` to use either respective protocol. +`), + no_vhost: z + .boolean() + .default(false) + .describe(` +Set to \`true\` if you use a custom host and run into access denied errors. +`), + sign_urls_for: z + .number() + .int() + .min(0) + .optional() + .describe(` +This parameter provides signed URLs in the result JSON (in the \`signed_url\` and \`signed_ssl_url\` properties). The number that you set this parameter to is the URL expiry time in seconds. If this parameter is not used, no URL signing is done. +`), + session_token: z + .string() + .optional() + .describe(` +The session token to use for the S3 store. This is only used if the credentials are from an IAM user with the \`sts:AssumeRole\` permission. +`), + }) + .strict() + +export const robotS3StoreInstructionsWithHiddenFieldsSchema = robotS3StoreInstructionsSchema.extend( + { + result: z.union([z.literal('debug'), robotS3StoreInstructionsSchema.shape.result]).optional(), + skip_region_lookup: z + .boolean() + .optional() + .describe(` +Internal parameter to skip region lookup for testing purposes. +`), + }, +) + +export type RobotS3StoreInstructions = z.infer +export type RobotS3StoreInstructionsInput = z.input +export type RobotS3StoreInstructionsWithHiddenFields = z.infer< + typeof robotS3StoreInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotS3StoreInstructionsSchema = interpolateRobot( + robotS3StoreInstructionsSchema, +) +export type InterpolatableRobotS3StoreInstructions = InterpolatableRobotS3StoreInstructionsInput + +export type InterpolatableRobotS3StoreInstructionsInput = z.input< + typeof interpolatableRobotS3StoreInstructionsSchema +> + +export const interpolatableRobotS3StoreInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotS3StoreInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotS3StoreInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotS3StoreInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotS3StoreInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotS3StoreInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/script-run.ts b/packages/transloadit/src/alphalib/types/robots/script-run.ts new file mode 100644 index 00000000..1cabe774 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/script-run.ts @@ -0,0 +1,122 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 10, + discount_factor: 0.1, + discount_pct: 90, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'Code Evaluation', + purpose_sentence: 'runs scripts in Assemblies', + purpose_verb: 'run', + purpose_word: 'script', + purpose_words: 'Run scripts in Assemblies', + service_slug: 'code-evaluation', + slot_count: 5, + title: 'Run Scripts', + typical_file_size_mb: 0.0001, + typical_file_type: 'file', + name: 'ScriptRunRobot', + priceFactor: 10, + queueSlotCount: 5, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotScriptRunInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/script/run').describe(` +This Robot allows you to run arbitrary \`JavaScript\` as part of the Assembly +execution process. The Robot is invoked automatically when there are Assembly +Instructions containing \`\${...}\`: + +\`\`\`json +{ + "robot": "/image/resize", + "width": "\${Math.max(file.meta.width, file.meta.height)}" +} +\`\`\` + +You can also invoke this Robot directly, leaving out the \`\${...}\`: + +\`\`\`json +{ + "robot": "/script/run", + "script": "Math.max(file.meta.width, file.meta.height)" +} +\`\`\` + +When accessing arrays, the syntax is the same as in any JavaScript program: + +\`\`\`json +{ + "robot": "/image/resize", + "width": "\${file.meta.faces[0].width * 2}" +} +\`\`\` + +Compared to only accessing an Assembly Variable: + +\`\`\`json +{ + "robot": "/image/resize", + "width": "\${file.meta.faces[0].width}" +} +\`\`\` + +For more information, see [Dynamic Evaluation](/docs/topics/dynamic-evaluation/). +`), + script: z.string().describe(` +A string of JavaScript to evaluate. It has access to all JavaScript features available in a modern browser environment. + +The script is expected to return a \`JSON.stringify\`-able value in the same tick, so no \`await\` or callbacks are allowed (yet). + +If the script does not finish within 1000ms it times out with an error. The return value or error is exported as \`file.meta.result\`. If there was an error, \`file.meta.isError\` is \`true\`. Note that the Assembly will not crash in this case. If you need it to crash, you can check this value with a [🤖/file/filter](/docs/robots/file-filter/) Step, setting \`error_on_decline\` to \`true\`. + +You can check whether evaluating this script was free by inspecting \`file.meta.isFree\`. It is recommended to do this during development as to not see sudden unexpected costs in production. +`), + }) + .strict() + +export const robotScriptRunInstructionsWithHiddenFieldsSchema = + robotScriptRunInstructionsSchema.extend({ + result: z.union([z.literal('debug'), robotScriptRunInstructionsSchema.shape.result]).optional(), + contextJSON: z + .string() + .optional() + .describe(` +A JSON string that provides additional context data to the script. This will be parsed and made available to the script as a \`context\` variable. For example, if you pass \`'{"foo":{"bar":"baz"}}'\`, the script can access \`context.foo.bar\` to get the value \`"baz"\`. +`), + }) + +export type RobotScriptRunInstructions = z.infer +export type RobotScriptRunInstructionsWithHiddenFields = z.infer< + typeof robotScriptRunInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotScriptRunInstructionsSchema = interpolateRobot( + robotScriptRunInstructionsSchema, +) +export type InterpolatableRobotScriptRunInstructions = InterpolatableRobotScriptRunInstructionsInput + +export type InterpolatableRobotScriptRunInstructionsInput = z.input< + typeof interpolatableRobotScriptRunInstructionsSchema +> + +export const interpolatableRobotScriptRunInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotScriptRunInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotScriptRunInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotScriptRunInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotScriptRunInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotScriptRunInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/sftp-import.ts b/packages/transloadit/src/alphalib/types/robots/sftp-import.ts new file mode 100644 index 00000000..8ea81bbb --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/sftp-import.ts @@ -0,0 +1,92 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotImport, sftpBase } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 10, + discount_factor: 0.1, + discount_pct: 90, + example_code: { + steps: { + imported: { + robot: '/sftp/import', + credentials: 'YOUR_SFTP_CREDENTIALS', + path: 'path/to/files/', + }, + }, + }, + example_code_description: + 'Import files from the `path/to/files` directory and its subdirectories:', + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Importing', + purpose_sentence: + 'imports whole libraries of files from your SFTP servers into Transloadit. This Robot relies on public key authentication', + purpose_verb: 'import', + purpose_word: 'SFTP servers', + purpose_words: 'Import files from SFTP servers', + service_slug: 'file-importing', + slot_count: 20, + title: 'Import files from SFTP servers', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'SftpImportRobot', + priceFactor: 6.6666, + queueSlotCount: 20, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: true, + stage: 'ga', +} + +export const robotSftpImportInstructionsSchema = robotBase + .merge(robotImport) + .merge(sftpBase) + .extend({ + robot: z.literal('/sftp/import'), + path: z.string().describe(` +The path on your SFTP server where to search for files. +`), + }) + .strict() + +export const robotSftpImportInstructionsWithHiddenFieldsSchema = + robotSftpImportInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotSftpImportInstructionsSchema.shape.result]) + .optional(), + allowNetwork: z + .string() + .optional() + .describe(` +Network access permission for the SFTP connection. This is used to control which networks the SFTP robot can access. +`), + }) + +export type RobotSftpImportInstructions = z.infer +export type RobotSftpImportInstructionsWithHiddenFields = z.infer< + typeof robotSftpImportInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotSftpImportInstructionsSchema = interpolateRobot( + robotSftpImportInstructionsSchema, +) +export type InterpolatableRobotSftpImportInstructions = + InterpolatableRobotSftpImportInstructionsInput + +export type InterpolatableRobotSftpImportInstructionsInput = z.input< + typeof interpolatableRobotSftpImportInstructionsSchema +> + +export const interpolatableRobotSftpImportInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotSftpImportInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotSftpImportInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotSftpImportInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotSftpImportInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotSftpImportInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/sftp-store.ts b/packages/transloadit/src/alphalib/types/robots/sftp-store.ts new file mode 100644 index 00000000..11c4f53c --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/sftp-store.ts @@ -0,0 +1,110 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse, sftpBase } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 6, + discount_factor: 0.15000150001500018, + discount_pct: 84.99984999849998, + example_code: { + steps: { + exported: { + robot: '/sftp/store', + use: ':original', + credentials: 'YOUR_SFTP_CREDENTIALS', + path: 'my_target_folder/${unique_prefix}/${file.url_name}', + }, + }, + }, + example_code_description: 'Export uploaded files to `my_target_folder` on an SFTP server:', + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Exporting', + purpose_sentence: 'exports encoding results to your own SFTP server', + purpose_verb: 'export', + purpose_word: 'SFTP servers', + purpose_words: 'Export files to SFTP servers', + service_slug: 'file-exporting', + slot_count: 10, + title: 'Export files to SFTP servers', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'SftpStoreRobot', + priceFactor: 6.6666, + queueSlotCount: 10, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotSftpStoreInstructionsSchema = robotBase + .merge(robotUse) + .merge(sftpBase) + .extend({ + robot: z.literal('/sftp/store'), + path: z + .string() + .default('${unique_prefix}/${file.url_name}') + .describe(` +The path at which the file is to be stored. This may include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). +`), + url_template: z + .string() + .default('http://host/path') + .describe(` +The URL of the file in the result JSON. This may include any of the following supported [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). +`), + ssl_url_template: z + .string() + .default('https://{HOST}/{PATH}') + .describe(` + The SSL URL of the file in the result JSON. The following [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables) are supported. +`), + file_chmod: z + .string() + .regex(/([0-7]{3}|auto)/) + .default('auto') + .describe(` +This optional parameter controls how an uploaded file's permission bits are set. You can use any string format that the \`chmod\` command would accept, such as \`"755"\`. If you don't specify this option, the file's permission bits aren't changed at all, meaning it's up to your server's configuration (e.g. umask). + `), + }) + .strict() + +export const robotSftpStoreInstructionsWithHiddenFieldsSchema = + robotSftpStoreInstructionsSchema.extend({ + result: z.union([z.literal('debug'), robotSftpStoreInstructionsSchema.shape.result]).optional(), + allowNetwork: z + .string() + .optional() + .describe(` +Network access permission for the SFTP connection. This is used to control which networks the SFTP robot can access. +`), + }) + +export type RobotSftpStoreInstructions = z.infer +export type RobotSftpStoreInstructionsWithHiddenFields = z.infer< + typeof robotSftpStoreInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotSftpStoreInstructionsSchema = interpolateRobot( + robotSftpStoreInstructionsSchema, +) +export type InterpolatableRobotSftpStoreInstructions = InterpolatableRobotSftpStoreInstructionsInput + +export type InterpolatableRobotSftpStoreInstructionsInput = z.input< + typeof interpolatableRobotSftpStoreInstructionsSchema +> + +export const interpolatableRobotSftpStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotSftpStoreInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotSftpStoreInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotSftpStoreInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotSftpStoreInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotSftpStoreInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/speech-transcribe.ts b/packages/transloadit/src/alphalib/types/robots/speech-transcribe.ts new file mode 100644 index 00000000..2922cc5d --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/speech-transcribe.ts @@ -0,0 +1,139 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + aiProviderSchema, + granularitySchema, + interpolateRobot, + robotBase, + robotUse, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + example_code: { + steps: { + transcribed: { + robot: '/speech/transcribe', + use: ':original', + provider: 'aws', + source_language: 'fr-FR', + format: 'text', + }, + }, + }, + example_code_description: + 'Transcribe speech in French from uploaded audio or video, and save it to a text file:', + extended_description: ` +> [!Warning] +> Transloadit aims to be deterministic, but this Robot uses third-party AI services. The providers (AWS, GCP) will evolve their models over time, giving different responses for the same input media. Avoid relying on exact responses in your tests and application. +`, + minimum_charge: 1048576, + output_factor: 0.05, + override_lvl1: 'Artificial Intelligence', + purpose_sentence: 'transcribes speech in audio or video files', + purpose_verb: 'transcribe', + purpose_word: 'transcribe speech', + purpose_words: 'Transcribe speech in audio or video files', + service_slug: 'artificial-intelligence', + slot_count: 10, + title: 'Transcribe speech in audio or video files', + typical_file_size_mb: 2.4, + typical_file_type: 'audio or video file', + name: 'SpeechTranscribeRobot', + priceFactor: 1, + queueSlotCount: 10, + minimumChargeUsdPerSpeechTranscribeMinute: { + aws: 0.024, + gcp: 0.016, + }, + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotSpeechTranscribeInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/speech/transcribe').describe(` +You can use the text that we return in your application, or you can pass the text down to other Robots to filter audio or video files that contain (or do not contain) certain content, or burn the text into images or video for example. + +Another common use case is automatically subtitling videos, or making audio searchable. +`), + provider: aiProviderSchema.describe(` +Which AI provider to leverage. + +Transloadit outsources this task and abstracts the interface so you can expect the same data structures, but different latencies and information being returned. Different cloud vendors have different areas they shine in, and we recommend to try out and see what yields the best results for your use case. +`), + granularity: granularitySchema.describe(` +Whether to return a full response (\`"full"\`), or a flat list of descriptions (\`"list"\`). +`), + format: z + .enum(['json', 'meta', 'srt', 'meta', 'text', 'webvtt']) + .default('json') + .describe(` +Output format for the transcription. + +- \`"text"\` outputs a plain text file that you can store and process. +- \`"json"\` outputs a JSON file containing timestamped words. +- \`"srt"\` and \`"webvtt"\` output subtitle files of those respective file types, which can be stored separately or used in other encoding Steps. +- \`"meta"\` does not return a file, but stores the data inside Transloadit's file object (under \`\${file.meta.transcription.text}\`) that's passed around between encoding Steps, so that you can use the values to burn the data into videos, filter on them, etc. +`), + // TODO determine the list of languages + source_language: z + .string() + .default('en-US') + .describe(` +The spoken language of the audio or video. This will also be the language of the transcribed text. + +The language should be specified in the [BCP-47](https://www.rfc-editor.org/rfc/bcp/bcp47.txt) format, such as \`"en-GB"\`, \`"de-DE"\` or \`"fr-FR"\`. Please also consult the list of supported languages for [the \`gcp\` provider](https://cloud.google.com/speech-to-text/docs/languages) and the [the \`aws\` provider](https://docs.aws.amazon.com/transcribe/latest/dg/what-is-transcribe.html). +`), + // TODO determine the list of languages + target_language: z + .string() + .default('en-US') + .describe(` + This will also be the language of the written text. + + The language should be specified in the [BCP-47](https://www.rfc-editor.org/rfc/bcp/bcp47.txt) format, such as \`"en-GB"\`, \`"de-DE"\` or \`"fr-FR"\`. Please consult the list of supported languages and voices. + `), + }) + .strict() + +export const robotSpeechTranscribeInstructionsWithHiddenFieldsSchema = + robotSpeechTranscribeInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotSpeechTranscribeInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotSpeechTranscribeInstructions = z.infer< + typeof robotSpeechTranscribeInstructionsSchema +> +export type RobotSpeechTranscribeInstructionsWithHiddenFields = z.infer< + typeof robotSpeechTranscribeInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotSpeechTranscribeInstructionsSchema = interpolateRobot( + robotSpeechTranscribeInstructionsSchema, +) +export type InterpolatableRobotSpeechTranscribeInstructions = + InterpolatableRobotSpeechTranscribeInstructionsInput + +export type InterpolatableRobotSpeechTranscribeInstructionsInput = z.input< + typeof interpolatableRobotSpeechTranscribeInstructionsSchema +> + +export const interpolatableRobotSpeechTranscribeInstructionsWithHiddenFieldsSchema = + interpolateRobot(robotSpeechTranscribeInstructionsWithHiddenFieldsSchema) +export type InterpolatableRobotSpeechTranscribeInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotSpeechTranscribeInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotSpeechTranscribeInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotSpeechTranscribeInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/supabase-import.ts b/packages/transloadit/src/alphalib/types/robots/supabase-import.ts new file mode 100644 index 00000000..3081d9be --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/supabase-import.ts @@ -0,0 +1,122 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + files_per_page, + interpolateRobot, + page_number, + path, + recursive, + return_file_stubs, + robotBase, + robotImport, + supabaseBase, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 10, + discount_factor: 0.1, + discount_pct: 90, + example_code: { + steps: { + imported: { + robot: '/supabase/import', + credentials: 'YOUR_SUPABASE_CREDENTIALS', + path: 'path/to/files/', + recursive: true, + }, + }, + }, + example_code_description: + 'Import files from the `path/to/files` directory and its subdirectories:', + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Importing', + purpose_sentence: 'imports whole directories of files from your Supabase bucket', + purpose_verb: 'import', + purpose_word: 'Supabase', + purpose_words: 'Import files from Supabase', + requires_credentials: true, + service_slug: 'file-importing', + slot_count: 20, + title: 'Import files from Supabase', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'SupabaseImportRobot', + priceFactor: 6.6666, + queueSlotCount: 20, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: true, + stage: 'ga', +} + +export const robotSupabaseImportInstructionsSchema = robotBase + .merge(robotImport) + .merge(supabaseBase) + .extend({ + robot: z.literal('/supabase/import').describe(` +The URL to the result file will be returned in the Assembly Status JSON. +`), + path: path.describe(` +The path in your bucket to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. + +If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are direct descendants of this directory will be imported. For example: \`images/\`. + +Directories are **not** imported recursively. If you want to import files from subdirectories and sub-subdirectories, enable the \`recursive\` parameter. + +If you want to import all files from the root directory, please use \`/\` as the value here. In this case, make sure all your objects belong to a path. If you have objects in the root of your bucket that aren't prefixed with \`/\`, you'll receive an error: \`A client error (NoSuchKey) occurred when calling the GetObject operation: The specified key does not exist.\` + +You can also use an array of path strings here to import multiple paths in the same Robot's Step. +`), + recursive: recursive.describe(` +Setting this to \`true\` will enable importing files from subfolders and sub-subfolders, etc. of the given path. + +Please use the pagination parameters \`page_number\` and \`files_per_page\` wisely here. +`), + page_number: page_number.describe(` +The pagination page number. For now, in order to not break backwards compatibility in non-recursive imports, this only works when recursive is set to \`true\`. + +When doing big imports, make sure no files are added or removed from other scripts within your path, otherwise you might get weird results with the pagination. +`), + files_per_page: files_per_page.describe(` +The pagination page size. This only works when recursive is \`true\` for now, in order to not break backwards compatibility in non-recursive imports. +`), + return_file_stubs, + }) + .strict() + +export const robotSupabaseImportInstructionsWithHiddenFieldsSchema = + robotSupabaseImportInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotSupabaseImportInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotSupabaseImportInstructions = z.infer +export type RobotSupabaseImportInstructionsWithHiddenFields = z.infer< + typeof robotSupabaseImportInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotSupabaseImportInstructionsSchema = interpolateRobot( + robotSupabaseImportInstructionsSchema, +) +export type InterpolatableRobotSupabaseImportInstructions = + InterpolatableRobotSupabaseImportInstructionsInput + +export type InterpolatableRobotSupabaseImportInstructionsInput = z.input< + typeof interpolatableRobotSupabaseImportInstructionsSchema +> + +export const interpolatableRobotSupabaseImportInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotSupabaseImportInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotSupabaseImportInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotSupabaseImportInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotSupabaseImportInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotSupabaseImportInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/supabase-store.ts b/packages/transloadit/src/alphalib/types/robots/supabase-store.ts new file mode 100644 index 00000000..53a60bac --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/supabase-store.ts @@ -0,0 +1,105 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse, supabaseBase } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 6, + discount_factor: 0.15000150001500018, + discount_pct: 84.99984999849998, + example_code: { + steps: { + exported: { + robot: '/supabase/store', + use: ':original', + credentials: 'YOUR_SUPABASE_CREDENTIALS', + path: 'my_target_folder/${unique_prefix}/${file.url_name}', + }, + }, + }, + example_code_description: 'Export uploaded files to `my_target_folder` on supabase R2:', + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Exporting', + purpose_sentence: 'exports encoding results to supabase buckets', + purpose_verb: 'export', + purpose_word: 'Supabase', + purpose_words: 'Export files to Supabase', + service_slug: 'file-exporting', + slot_count: 10, + title: 'Export files to Supabase', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'SupabaseStoreRobot', + priceFactor: 6.6666, + queueSlotCount: 10, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', + isInternal: false, +} + +export const robotSupabaseStoreInstructionsSchema = robotBase + .merge(robotUse) + .merge(supabaseBase) + .extend({ + robot: z.literal('/supabase/store'), + path: z + .string() + .default('${unique_prefix}/${file.url_name}') + .describe(` +The path at which the file is to be stored. This may include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). The path must not be a directory. +`), + headers: z + .record(z.string()) + .default({ 'Content-Type': '${file.mime}' }) + .describe(` +An object containing a list of headers to be set for this file on supabase Spaces, such as \`{ FileURL: "\${file.url_name}" }\`. This can also include any available [Assembly Variables](/docs/topics/assembly-instructions/#assembly-variables). + +Object Metadata can be specified using \`x-amz-meta-*\` headers. Note that these headers [do not support non-ASCII metadata values](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html#UserMetadata). +`), + sign_urls_for: z + .number() + .int() + .min(0) + .optional() + .describe(` +This parameter provides signed URLs in the result JSON (in the \`signed_ssl_url\` property). The number that you set this parameter to is the URL expiry time in seconds. If this parameter is not used, no URL signing is done. +`), + }) + .strict() + +export const robotSupabaseStoreInstructionsWithHiddenFieldsSchema = + robotSupabaseStoreInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotSupabaseStoreInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotSupabaseStoreInstructions = z.infer +export type RobotSupabaseStoreInstructionsWithHiddenFields = z.infer< + typeof robotSupabaseStoreInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotSupabaseStoreInstructionsSchema = interpolateRobot( + robotSupabaseStoreInstructionsSchema, +) +export type InterpolatableRobotSupabaseStoreInstructions = + InterpolatableRobotSupabaseStoreInstructionsInput + +export type InterpolatableRobotSupabaseStoreInstructionsInput = z.input< + typeof interpolatableRobotSupabaseStoreInstructionsSchema +> + +export const interpolatableRobotSupabaseStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotSupabaseStoreInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotSupabaseStoreInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotSupabaseStoreInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotSupabaseStoreInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotSupabaseStoreInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/swift-import.ts b/packages/transloadit/src/alphalib/types/robots/swift-import.ts new file mode 100644 index 00000000..505f7126 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/swift-import.ts @@ -0,0 +1,120 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + files_per_page, + interpolateRobot, + page_number, + path, + recursive, + return_file_stubs, + robotBase, + robotImport, + swiftBase, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 10, + discount_factor: 0.1, + discount_pct: 90, + example_code: { + steps: { + imported: { + robot: '/swift/import', + credentials: 'YOUR_SWIFT_CREDENTIALS', + path: 'path/to/files/', + recursive: true, + }, + }, + }, + example_code_description: + 'Import files from the `path/to/files` directory and its subdirectories:', + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Importing', + purpose_sentence: 'imports whole directories of files from your Openstack/Swift bucket', + purpose_verb: 'import', + purpose_word: 'Openstack/Swift', + purpose_words: 'Import files from Openstack/Swift', + requires_credentials: true, + service_slug: 'file-importing', + slot_count: 20, + title: 'Import files from Openstack/Swift', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'SwiftImportRobot', + priceFactor: 6.6666, + queueSlotCount: 20, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: true, + stage: 'ga', +} + +export const robotSwiftImportInstructionsSchema = robotBase + .merge(robotImport) + .merge(swiftBase) + .extend({ + robot: z.literal('/swift/import'), + path: path.describe(` +The path in your bucket to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. + +If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are direct descendants of this directory will be imported. For example: \`images/\`. + +Directories are **not** imported recursively. If you want to import files from subdirectories and sub-subdirectories, enable the \`recursive\` parameter. + +If you want to import all files from the root directory, please use \`/\` as the value here. In this case, make sure all your objects belong to a path. If you have objects in the root of your bucket that aren't prefixed with \`/\`, you'll receive an error: \`A client error (NoSuchKey) occurred when calling the GetObject operation: The specified key does not exist.\` + +You can also use an array of path strings here to import multiple paths in the same Robot's Step. +`), + recursive: recursive.describe(` +Setting this to \`true\` will enable importing files from subdirectories and sub-subdirectories (etc.) of the given path. + +Please use the pagination parameters \`page_number\` and \`files_per_page\` wisely here. +`), + page_number: page_number.describe(` +The pagination page number. For now, in order to not break backwards compatibility in non-recursive imports, this only works when recursive is set to \`true\`. + +When doing big imports, make sure no files are added or removed from other scripts within your path, otherwise you might get weird results with the pagination. +`), + files_per_page: files_per_page.describe(` +The pagination page size. This only works when recursive is \`true\` for now, in order to not break backwards compatibility in non-recursive imports. +`), + return_file_stubs, + }) + .strict() + +export const robotSwiftImportInstructionsWithHiddenFieldsSchema = + robotSwiftImportInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotSwiftImportInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotSwiftImportInstructions = z.infer +export type RobotSwiftImportInstructionsWithHiddenFields = z.infer< + typeof robotSwiftImportInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotSwiftImportInstructionsSchema = interpolateRobot( + robotSwiftImportInstructionsSchema, +) +export type InterpolatableRobotSwiftImportInstructions = + InterpolatableRobotSwiftImportInstructionsInput + +export type InterpolatableRobotSwiftImportInstructionsInput = z.input< + typeof interpolatableRobotSwiftImportInstructionsSchema +> + +export const interpolatableRobotSwiftImportInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotSwiftImportInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotSwiftImportInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotSwiftImportInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotSwiftImportInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotSwiftImportInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/swift-store.ts b/packages/transloadit/src/alphalib/types/robots/swift-store.ts new file mode 100644 index 00000000..de032bd3 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/swift-store.ts @@ -0,0 +1,112 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse, swiftBase } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 6, + discount_factor: 0.15000150001500018, + discount_pct: 84.99984999849998, + example_code: { + steps: { + exported: { + robot: '/swift/store', + use: ':original', + credentials: 'YOUR_SWIFT_CREDENTIALS', + path: 'my_target_folder/${unique_prefix}/${file.url_name}', + }, + }, + }, + example_code_description: 'Export uploaded files to `my_target_folder` on Swift:', + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Exporting', + purpose_sentence: 'exports encoding results to OpenStack Swift buckets', + purpose_verb: 'export', + purpose_word: 'OpenStack Swift', + purpose_words: 'Export files to OpenStack/Swift', + service_slug: 'file-exporting', + slot_count: 10, + title: 'Export files to OpenStack Swift Spaces', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'SwiftStoreRobot', + priceFactor: 6.6666, + queueSlotCount: 10, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotSwiftStoreInstructionsSchema = robotBase + .merge(robotUse) + .merge(swiftBase) + .extend({ + robot: z.literal('/swift/store').describe(` +The URL to the result file in your OpenStack bucket will be returned in the Assembly Status JSON.`), + path: z + .string() + .default('${unique_prefix}/${file.url_name}') + .describe(` +The path at which the file is to be stored. This may include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). The path must not be a directory. +`), + acl: z + .enum(['private', 'public-read']) + .default('public-read') + .describe(` +The permissions used for this file. +`), + headers: z + .record(z.string()) + .default({ 'Content-Type': '${file.mime}' }) + .describe(` +An object containing a list of headers to be set for this file on swift Spaces, such as \`{ FileURL: "\${file.url_name}" }\`. This can also include any available [Assembly Variables](/docs/topics/assembly-instructions/#assembly-variables). + +Object Metadata can be specified using \`x-amz-meta-*\` headers. Note that these headers [do not support non-ASCII metadata values](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html#UserMetadata). +`), + sign_urls_for: z + .number() + .int() + .min(0) + .optional() + .describe(` +This parameter provides signed URLs in the result JSON (in the \`signed_ssl_url\` property). The number that you set this parameter to is the URL expiry time in seconds. If this parameter is not used, no URL signing is done. +`), + }) + .strict() + +export const robotSwiftStoreInstructionsWithHiddenFieldsSchema = + robotSwiftStoreInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotSwiftStoreInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotSwiftStoreInstructions = z.infer +export type RobotSwiftStoreInstructionsWithHiddenFields = z.infer< + typeof robotSwiftStoreInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotSwiftStoreInstructionsSchema = interpolateRobot( + robotSwiftStoreInstructionsSchema, +) +export type InterpolatableRobotSwiftStoreInstructions = + InterpolatableRobotSwiftStoreInstructionsInput + +export type InterpolatableRobotSwiftStoreInstructionsInput = z.input< + typeof interpolatableRobotSwiftStoreInstructionsSchema +> + +export const interpolatableRobotSwiftStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotSwiftStoreInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotSwiftStoreInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotSwiftStoreInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotSwiftStoreInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotSwiftStoreInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/text-speak.ts b/packages/transloadit/src/alphalib/types/robots/text-speak.ts new file mode 100644 index 00000000..bf7a7710 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/text-speak.ts @@ -0,0 +1,152 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + aiProviderSchema, + interpolateRobot, + robotBase, + robotUse, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + example_code: { + steps: { + synthesized: { + robot: '/text/speak', + use: ':original', + provider: 'aws', + voice: 'female-1', + target_language: 'en-US', + }, + }, + }, + example_code_description: + 'Synthesize speech from uploaded text documents, using a female voice in American English:', + extended_description: ` +> [!Warning] +> Transloadit aims to be deterministic, but this Robot uses third-party AI services. The providers (AWS, GCP) will evolve their models over time, giving different responses for the same input media. Avoid relying on exact responses in your tests and application. + +## Supported languages and voices + +{% for provider in text_speak_voices %} + +### {{provider[0] | upcase }} + + + + + + + + + + {%- for language in provider[1] %} + + + + + {%- endfor %} + +
LanguageVoices
{{language[0]}}{{ language[1] | join: ", " }}
+{% endfor %} +`, + minimum_charge: 1048576, + output_factor: 1, + override_lvl1: 'Artificial Intelligence', + purpose_sentence: 'synthesizes speech in documents', + purpose_verb: 'speak', + purpose_word: 'synthesize speech', + purpose_words: 'Synthesize speech in documents', + service_slug: 'artificial-intelligence', + slot_count: 10, + title: 'Speak text', + typical_file_size_mb: 1, + typical_file_type: 'document', + name: 'TextSpeakRobot', + priceFactor: 1, + queueSlotCount: 10, + minimumChargeUsd: 0.05, + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotTextSpeakInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/text/speak').describe(` +You can use the audio that we return in your application, or you can pass the audio down to other Robots to add a voice track to a video for example. + +Another common use case is making your product accessible to people with a reading disability. +`), + prompt: z + .string() + .nullish() + .describe(` +Which text to speak. You can also set this to \`null\` and supply an input text file. +`), + provider: aiProviderSchema.describe(` +Which AI provider to leverage. + +Transloadit outsources this task and abstracts the interface so you can expect the same data structures, but different latencies and information being returned. Different cloud vendors have different areas they shine in, and we recommend to try out and see what yields the best results for your use case. +`), + // TODO determine the list of languages + target_language: z + .string() + .default('en-US') + .describe(` +The written language of the document. This will also be the language of the spoken text. + +The language should be specified in the [BCP-47](https://www.rfc-editor.org/rfc/bcp/bcp47.txt) format, such as \`"en-GB"\`, \`"de-DE"\` or \`"fr-FR"\`. Please consult the list of supported languages and voices. +`), + voice: z + .enum(['female-1', 'female-2', 'female-3', 'female-child-1', 'male-1', 'male-child-1']) + .default('female-1') + .describe(` +The gender to be used for voice synthesis. Please consult the list of supported languages and voices. + `), + ssml: z + .boolean() + .default(false) + .describe(` +Supply [Speech Synthesis Markup Language](https://en.wikipedia.org/wiki/Speech_Synthesis_Markup_Language) instead of raw text, in order to gain more control over how your text is voiced, including rests and pronounciations. + +Please see the supported syntaxes for [AWS](https://docs.aws.amazon.com/polly/latest/dg/supportedtags.html) and [GCP](https://cloud.google.com/text-to-speech/docs/ssml). +`), + }) + .strict() + +export const robotTextSpeakInstructionsWithHiddenFieldsSchema = + robotTextSpeakInstructionsSchema.extend({ + result: z.union([z.literal('debug'), robotTextSpeakInstructionsSchema.shape.result]).optional(), + }) + +export type RobotTextSpeakInstructions = z.infer +export type RobotTextSpeakInstructionsWithHiddenFields = z.infer< + typeof robotTextSpeakInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotTextSpeakInstructionsSchema = interpolateRobot( + robotTextSpeakInstructionsSchema, +) +export type InterpolatableRobotTextSpeakInstructions = InterpolatableRobotTextSpeakInstructionsInput + +export type InterpolatableRobotTextSpeakInstructionsInput = z.input< + typeof interpolatableRobotTextSpeakInstructionsSchema +> + +export const interpolatableRobotTextSpeakInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotTextSpeakInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotTextSpeakInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotTextSpeakInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotTextSpeakInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotTextSpeakInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/text-translate.ts b/packages/transloadit/src/alphalib/types/robots/text-translate.ts new file mode 100644 index 00000000..a4ea2b5c --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/text-translate.ts @@ -0,0 +1,245 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + aiProviderSchema, + interpolateRobot, + robotBase, + robotUse, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + example_code: { + steps: { + translated: { + robot: '/text/translate', + use: ':original', + target_language: 'de', + provider: 'aws', + }, + }, + }, + example_code_description: 'Translate uploaded text file contents to German:', + extended_description: ` +> [!Warning] +> This Robot uses third-party AI services. They may tweak their models over time, giving different responses for the same input media. Avoid relying on exact responses in your tests and application. + +## Supported languages + +{%- for provider in text_translate_languages %} + +### {{provider[0] | upcase }} + + +{%- for language in provider[1] %} + {{ language }}{% unless forloop.last %},{% endunless %} +{%- endfor %} + +{%- endfor %} +`, + minimum_charge: 1048576, + output_factor: 1, + override_lvl1: 'Artificial Intelligence', + purpose_sentence: 'translates text in documents', + purpose_verb: 'translate', + purpose_word: 'text', + purpose_words: 'Translate text in documents', + service_slug: 'artificial-intelligence', + slot_count: 10, + title: 'Translate text', + typical_file_size_mb: 1, + typical_file_type: 'document', + name: 'TextTranslateRobot', + priceFactor: 0.00008, + queueSlotCount: 10, + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +const translatableLanguages = z + .enum([ + 'af', + 'am', + 'ar', + 'az', + 'be', + 'bg', + 'bn', + 'bs', + 'ca', + 'ceb', + 'co', + 'cs', + 'cy', + 'da', + 'de', + 'el', + 'en', + 'en-US', + 'eo', + 'es', + 'es-MX', + 'et', + 'eu', + 'fa', + 'fa-AF', + 'fi', + 'fr', + 'fr-CA', + 'fy', + 'ga', + 'gd', + 'gl', + 'gu', + 'ha', + 'haw', + 'he', + 'hi', + 'hmn', + 'hr', + 'ht', + 'hu', + 'hy', + 'id', + 'ig', + 'is', + 'it', + 'iw', + 'ja', + 'jv', + 'ka', + 'kk', + 'km', + 'kn', + 'ko', + 'ku', + 'ky', + 'la', + 'lb', + 'lo', + 'lt', + 'lv', + 'mg', + 'mi', + 'mk', + 'ml', + 'mn', + 'mr', + 'ms', + 'mt', + 'my', + 'ne', + 'nl', + 'no', + 'ny', + 'or', + 'pa', + 'pl', + 'ps', + 'pt', + 'ro', + 'ru', + 'rw', + 'sd', + 'si', + 'sk', + 'sl', + 'sm', + 'sn', + 'so', + 'sq', + 'sr', + 'st', + 'su', + 'sv', + 'sw', + 'ta', + 'te', + 'tg', + 'th', + 'tk', + 'tl', + 'tr', + 'tt', + 'ug', + 'uk', + 'ur', + 'uz', + 'vi', + 'xh', + 'yi', + 'yo', + 'zh', + 'zh-CN', + 'zh-TW', + 'zu', + ]) + .default('en') + +export const robotTextTranslateInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/text/translate').describe(` +You can use the text that we return in your application, or you can pass the text down to other Robots to add a translated subtitle track to a video for example. + +> [!Note] +> **This Robot accepts only files with a \`text/*\` MIME-type,** including plain text and Markdown. For documents in other formats, use [🤖/document/convert](/docs/robots/document-convert/) to first convert them into a compatible text format before proceeding. +`), + provider: aiProviderSchema.describe(` +Which AI provider to leverage. Valid values are \`"aws"\` (Amazon Web Services) and \`"gcp"\` (Google Cloud Platform). + +Transloadit outsources this task and abstracts the interface so you can expect the same data structures, but different latencies and information being returned. Different cloud vendors have different areas they shine in, and we recommend to try out and see what yields the best results for your use case. +`), + target_language: translatableLanguages.describe(` +The desired language to translate to. + +If the exact language can't be found, a generic variant can be fallen back to. For example, if you specify \`"en-US"\`, "en" will be used instead. Please consult the list of supported languages for each provider. +`), + source_language: translatableLanguages.describe(` +The desired language to translate from. + +By default, both providers will detect this automatically, but there are cases where specifying the source language prevents ambiguities. + +If the exact language can't be found, a generic variant can be fallen back to. For example, if you specify \`"en-US"\`, "en" will be used instead. Please consult the list of supported languages for each provider. +`), + }) + .strict() + +export const robotTextTranslateInstructionsWithHiddenFieldsSchema = + robotTextTranslateInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotTextTranslateInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotTextTranslateInstructions = z.infer +export type RobotTextTranslateInstructionsWithHiddenFields = z.infer< + typeof robotTextTranslateInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotTextTranslateInstructionsSchema = interpolateRobot( + robotTextTranslateInstructionsSchema, +) +export type InterpolatableRobotTextTranslateInstructions = + InterpolatableRobotTextTranslateInstructionsInput + +export type InterpolatableRobotTextTranslateInstructionsInput = z.input< + typeof interpolatableRobotTextTranslateInstructionsSchema +> + +export const interpolatableRobotTextTranslateInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotTextTranslateInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotTextTranslateInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotTextTranslateInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotTextTranslateInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotTextTranslateInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/tigris-import.ts b/packages/transloadit/src/alphalib/types/robots/tigris-import.ts new file mode 100644 index 00000000..5004c3c4 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/tigris-import.ts @@ -0,0 +1,124 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + files_per_page, + interpolateRobot, + page_number, + path, + recursive, + return_file_stubs, + robotBase, + robotImport, + tigrisBase, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 10, + discount_factor: 0.1, + discount_pct: 90, + example_code: { + steps: { + imported: { + robot: '/tigris/import', + credentials: 'YOUR_TIGRIS_CREDENTIALS', + path: 'path/to/files/', + recursive: true, + }, + }, + }, + example_code_description: + 'Import files from the `path/to/files` directory and its subdirectories:', + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Importing', + purpose_sentence: 'imports whole directories of files from your Tigris bucket', + purpose_verb: 'import', + purpose_word: 'Tigris', + purpose_words: 'Import files from Tigris', + requires_credentials: true, + service_slug: 'file-importing', + slot_count: 20, + title: 'Import files from Tigris', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'TigrisImportRobot', + priceFactor: 6.6666, + queueSlotCount: 20, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: true, + stage: 'ga', +} + +export const robotTigrisImportInstructionsSchema = robotBase + .merge(robotImport) + .merge(tigrisBase) + .extend({ + robot: z.literal('/tigris/import'), + path: path.describe(` +The path in your bucket to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. + +If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are direct descendants of this directory will be imported. For example: \`images/\`. + +Directories are **not** imported recursively. If you want to import files from subdirectories and sub-subdirectories, enable the \`recursive\` parameter. + +If you want to import all files from the root directory, please use \`/\` as the value here. In this case, make sure all your objects belong to a path. If you have objects in the root of your bucket that aren't prefixed with \`/\`, you'll receive an error: \`A client error (NoSuchKey) occurred when calling the GetObject operation: The specified key does not exist.\` + +You can also use an array of path strings here to import multiple paths in the same Robot's Step. +`), + recursive: recursive.describe(` +Setting this to \`true\` will enable importing files from subdirectories and sub-subdirectories (etc.) of the given path. + +Please use the pagination parameters \`page_number\` and \`files_per_page\` wisely here. +`), + page_number: page_number.describe(` +The pagination page number. For now, in order to not break backwards compatibility in non-recursive imports, this only works when recursive is set to \`true\`. + +When doing big imports, make sure no files are added or removed from other scripts within your path, otherwise you might get weird results with the pagination. +`), + files_per_page: files_per_page.describe(` +The pagination page size. This only works when recursive is \`true\` for now, in order to not break backwards compatibility in non-recursive imports. +`), + return_file_stubs, + bucket_region: z + .string() + .optional() + .describe('The region of your Tigris bucket. This is optional as it can often be derived.'), + }) + .strict() + +export const robotTigrisImportInstructionsWithHiddenFieldsSchema = + robotTigrisImportInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotTigrisImportInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotTigrisImportInstructions = z.infer +export type RobotTigrisImportInstructionsWithHiddenFields = z.infer< + typeof robotTigrisImportInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotTigrisImportInstructionsSchema = interpolateRobot( + robotTigrisImportInstructionsSchema, +) +export type InterpolatableRobotTigrisImportInstructions = + InterpolatableRobotTigrisImportInstructionsInput + +export type InterpolatableRobotTigrisImportInstructionsInput = z.input< + typeof interpolatableRobotTigrisImportInstructionsSchema +> + +export const interpolatableRobotTigrisImportInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotTigrisImportInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotTigrisImportInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotTigrisImportInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotTigrisImportInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotTigrisImportInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/tigris-store.ts b/packages/transloadit/src/alphalib/types/robots/tigris-store.ts new file mode 100644 index 00000000..28f0a0f1 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/tigris-store.ts @@ -0,0 +1,119 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse, tigrisBase } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 6, + discount_factor: 0.15000150001500018, + discount_pct: 84.99984999849998, + example_code: { + steps: { + exported: { + robot: '/tigris/store', + use: ':original', + credentials: 'YOUR_TIGRIS_CREDENTIALS', + path: 'my_target_folder/${unique_prefix}/${file.url_name}', + }, + }, + }, + example_code_description: 'Export uploaded files to `my_target_folder` on Tigris:', + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Exporting', + purpose_sentence: 'exports encoding results to Tigris buckets', + purpose_verb: 'export', + purpose_word: 'Tigris', + purpose_words: 'Export files to Tigris', + service_slug: 'file-exporting', + slot_count: 10, + title: 'Export files to Tigris', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'TigrisStoreRobot', + priceFactor: 6.6666, + queueSlotCount: 10, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotTigrisStoreInstructionsSchema = robotBase + .merge(robotUse) + .merge(tigrisBase) + .extend({ + robot: z.literal('/tigris/store').describe(` +The URL to the result file will be returned in the Assembly Status JSON. +`), + path: z + .string() + .default('${unique_prefix}/${file.url_name}') + .describe(` +The path at which the file is to be stored. This may include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). The path must not be a directory. +`), + acl: z + .enum(['private', 'public-read']) + .default('public-read') + .describe(` +The permissions used for this file. +`), + headers: z + .record(z.string()) + .default({ 'Content-Type': '${file.mime}' }) + .describe(` +An object containing a list of headers to be set for this file on Tigris, such as \`{ FileURL: "\${file.url_name}" }\`. This can also include any available [Assembly Variables](/docs/topics/assembly-instructions/#assembly-variables). + +Object Metadata can be specified using \`x-amz-meta-*\` headers. Note that these headers [do not support non-ASCII metadata values](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html#UserMetadata). +`), + sign_urls_for: z + .number() + .int() + .min(0) + .optional() + .describe(` +This parameter provides signed URLs in the result JSON (in the \`signed_ssl_url\` property). The number that you set this parameter to is the URL expiry time in seconds. + +If this parameter is not used, no URL signing is done. +`), + bucket_region: z + .string() + .optional() + .describe('The region of your Tigris bucket. This is optional as it can often be derived.'), + }) + .strict() + +export const robotTigrisStoreInstructionsWithHiddenFieldsSchema = + robotTigrisStoreInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotTigrisStoreInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotTigrisStoreInstructions = z.infer +export type RobotTigrisStoreInstructionsWithHiddenFields = z.infer< + typeof robotTigrisStoreInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotTigrisStoreInstructionsSchema = interpolateRobot( + robotTigrisStoreInstructionsSchema, +) +export type InterpolatableRobotTigrisStoreInstructions = + InterpolatableRobotTigrisStoreInstructionsInput + +export type InterpolatableRobotTigrisStoreInstructionsInput = z.input< + typeof interpolatableRobotTigrisStoreInstructionsSchema +> + +export const interpolatableRobotTigrisStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotTigrisStoreInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotTigrisStoreInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotTigrisStoreInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotTigrisStoreInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotTigrisStoreInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/tlcdn-deliver.ts b/packages/transloadit/src/alphalib/types/robots/tlcdn-deliver.ts new file mode 100644 index 00000000..b327f51e --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/tlcdn-deliver.ts @@ -0,0 +1,73 @@ +import { z } from 'zod' +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: false, + bytescount: 20, + discount_factor: 0.05, + discount_pct: 95, + minimum_charge: 102400, + output_factor: 1, + override_lvl1: 'Content Delivery', + purpose_sentence: 'caches and delivers files globally', + purpose_verb: 'cache & deliver', + purpose_word: 'Cache and deliver files', + purpose_words: 'Cache and deliver files globally', + service_slug: 'content-delivery', + slot_count: 0, + title: 'Cache and deliver files globally', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'TlcdnDeliverRobot', + priceFactor: 20, + queueSlotCount: 0, + minimumCharge: 102400, + downloadInputFiles: false, + preserveInputFileUrls: true, + isAllowedForUrlTransform: false, + trackOutputFileSize: false, + isInternal: true, + stage: 'ga', + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, +} + +export const robotTlcdnDeliverInstructionsSchema = robotBase + .extend({ + robot: z.literal('/tlcdn/deliver').describe(` +When you want Transloadit to tranform files on the fly, this Robot can cache and deliver the results close to your end-user, saving on latency and encoding volume. The use of this Robot is implicit when you use the tlcdn.com domain. +`), + }) + .strict() + +export const robotTlcdnDeliverInstructionsWithHiddenFieldsSchema = + robotTlcdnDeliverInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotTlcdnDeliverInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotTlcdnDeliverInstructions = z.infer +export type RobotTlcdnDeliverInstructionsWithHiddenFields = z.infer< + typeof robotTlcdnDeliverInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotTlcdnDeliverInstructionsSchema = interpolateRobot( + robotTlcdnDeliverInstructionsSchema, +) +export type InterpolatableRobotTlcdnDeliverInstructions = + InterpolatableRobotTlcdnDeliverInstructionsInput + +export type InterpolatableRobotTlcdnDeliverInstructionsInput = z.input< + typeof interpolatableRobotTlcdnDeliverInstructionsSchema +> + +export const interpolatableRobotTlcdnDeliverInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotTlcdnDeliverInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotTlcdnDeliverInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotTlcdnDeliverInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotTlcdnDeliverInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotTlcdnDeliverInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/tus-store.ts b/packages/transloadit/src/alphalib/types/robots/tus-store.ts new file mode 100644 index 00000000..8681b732 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/tus-store.ts @@ -0,0 +1,129 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 10, + discount_factor: 0.1, + discount_pct: 90, + example_code: { + steps: { + exported: { + robot: '/tus/store', + use: ':original', + endpoint: 'https://tusd.tusdemo.net/files/', + }, + }, + }, + example_code_description: 'Export uploaded files to the Tus live demo server:', + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Exporting', + purpose_sentence: 'exports encoding results to any Tus-compatible server', + purpose_verb: 'export', + purpose_word: 'Tus servers', + purpose_words: 'Export files to Tus-compatible servers', + service_slug: 'file-exporting', + slot_count: 10, + title: 'Export files to Tus-compatible servers', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'TusStoreRobot', + priceFactor: 10, + queueSlotCount: 10, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotTusStoreInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/tus/store').describe(` +> [!Note] +> This Robot only accepts videos. + +> [!Warning] +> Vimeo's API limits the number of concurrent uploads per minute based on your Vimeo account plan. To see how many videos can be uploaded at once based on your plan, click the following [link](https://developer.vimeo.com/guidelines/rate-limiting#table-1). + +## Installation + +Since Vimeo works with OAuth, you will need to generate [Template Credentials](https://transloadit.com/c/template-credentials/) to use this Robot. + +To change the \`title\` or \`description\` per video, we recommend to [inject variables into your Template](/docs/topics/templates/). +`), + endpoint: z + .string() + .url() + .describe('The URL of the destination Tus server') + .describe(` +The URL of the Tus-compatible server, which you're uploading files to. +`), + credentials: z + .string() + .optional() + .describe(` +Create Template Credentials for this Robot in your [Transloadit account](/c/template-credentials/) and use the name of the Template Credentials as this parameter's value. For this Robot, use the HTTP template, which allows request headers to be passed along to the destination server. +`), + headers: z + .record(z.string()) + .default({}) + .describe('Headers to pass along to destination') + .describe(` +Optional extra headers outside of the Template Credentials can be passed along within this parameter. + +Although, we recommend to exclusively use Template Credentials, this may be necessary if you're looking to use dynamic credentials, which isn't a feature supported by Template Credentials. +`), + metadata: z + .record(z.string()) + .default({ filename: 'example.png', basename: 'example', extension: 'png' }) + .describe(` +Metadata to pass along to destination. Includes some file info by default. +`), + url_template: z + .string() + .optional() + .describe(` +The URL of the file in the Assembly Status JSON. The following [Assembly Variables](/docs/topics/assembly-instructions/#assembly-variables) are supported. If this is not specified, the upload URL specified by the destination server will be used instead. +`), + ssl_url_template: z + .string() + .optional() + .describe(` +The SSL URL of the file in the Assembly Status JSON. The following [Assembly Variables](/docs/topics/assembly-instructions/#assembly-variables) are supported. If this is not specified, the upload URL specified by the destination server will be used instead, as long as it starts with \`https\`. +`), + }) + .strict() + +export const robotTusStoreInstructionsWithHiddenFieldsSchema = + robotTusStoreInstructionsSchema.extend({ + result: z.union([z.literal('debug'), robotTusStoreInstructionsSchema.shape.result]).optional(), + }) + +export type RobotTusStoreInstructions = z.infer +export type RobotTusStoreInstructionsWithHiddenFields = z.infer< + typeof robotTusStoreInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotTusStoreInstructionsSchema = interpolateRobot( + robotTusStoreInstructionsSchema, +) +export type InterpolatableRobotTusStoreInstructions = InterpolatableRobotTusStoreInstructionsInput + +export type InterpolatableRobotTusStoreInstructionsInput = z.input< + typeof interpolatableRobotTusStoreInstructionsSchema +> + +export const interpolatableRobotTusStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotTusStoreInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotTusStoreInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotTusStoreInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotTusStoreInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotTusStoreInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/upload-handle.ts b/packages/transloadit/src/alphalib/types/robots/upload-handle.ts new file mode 100644 index 00000000..b4167015 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/upload-handle.ts @@ -0,0 +1,95 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: false, + bytescount: 10, + discount_factor: 0.1, + discount_pct: 90, + example_code: { + steps: { + ':original': { + robot: '/upload/handle', + }, + exported: { + robot: '/s3/store', + use: ':original', + credentials: 'YOUR_S3_CREDENTIALS', + }, + }, + }, + example_code_description: 'Handle uploads and export the uploaded files to S3:', + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'Handling Uploads', + purpose_sentence: + 'receives uploads that your users throw at you from browser or apps, or that you throw at us programmatically', + purpose_verb: 'handle', + purpose_word: 'handle uploads', + purpose_words: 'Handle uploads', + service_slug: 'handling-uploads', + slot_count: 0, + title: 'Handle uploads', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'UploadHandleRobot', + priceFactor: 10, + queueSlotCount: 0, + downloadInputFiles: false, + preserveInputFileUrls: true, + isAllowedForUrlTransform: false, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotUploadHandleInstructionsSchema = robotBase + .extend({ + robot: z.literal('/upload/handle').describe(` +Transloadit handles file uploads by default, so specifying this Robot is optional. + +It can still be a good idea to define this Robot, though. It makes your Assembly Instructions explicit, and allows you to configure exactly how uploads should be handled. For example, you can extract specific metadata from the uploaded files. + +There are **3 important constraints** when using this Robot: + +1. Don’t define a \`use\` parameter, unlike with other Robots. +2. Use it only once in a single set of Assembly Instructions. +3. Name the Step as \`:original\`. +`), + }) + .strict() + +export const robotUploadHandleInstructionsWithHiddenFieldsSchema = + robotUploadHandleInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotUploadHandleInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotUploadHandleInstructions = z.infer +export type RobotUploadHandleInstructionsWithHiddenFields = z.infer< + typeof robotUploadHandleInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotUploadHandleInstructionsSchema = interpolateRobot( + robotUploadHandleInstructionsSchema, +) +export type InterpolatableRobotUploadHandleInstructions = + InterpolatableRobotUploadHandleInstructionsInput + +export type InterpolatableRobotUploadHandleInstructionsInput = z.input< + typeof interpolatableRobotUploadHandleInstructionsSchema +> + +export const interpolatableRobotUploadHandleInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotUploadHandleInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotUploadHandleInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotUploadHandleInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotUploadHandleInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotUploadHandleInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/video-adaptive.ts b/packages/transloadit/src/alphalib/types/robots/video-adaptive.ts new file mode 100644 index 00000000..1afdbd66 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/video-adaptive.ts @@ -0,0 +1,179 @@ +import { z } from 'zod' + +import { stackVersions } from '../stackVersions.ts' +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + interpolateRobot, + robotBase, + robotFFmpegVideo, + robotUse, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: false, + bytescount: Number.POSITIVE_INFINITY, + discount_factor: 1, + discount_pct: 0, + example_code: { + steps: { + ':original': { + robot: '/upload/handle', + }, + encoded_480p: { + robot: '/video/encode', + use: ':original', + preset: 'hls/480p', + ffmpeg_stack: stackVersions.ffmpeg.recommendedVersion, + }, + encoded_720p: { + robot: '/video/encode', + use: ':original', + preset: 'hls/720p', + ffmpeg_stack: stackVersions.ffmpeg.recommendedVersion, + }, + encoded_1080p: { + robot: '/video/encode', + use: ':original', + preset: 'hls/1080p', + ffmpeg_stack: stackVersions.ffmpeg.recommendedVersion, + }, + hls_bundled: { + robot: '/video/adaptive', + use: { + steps: ['encoded_480p', 'encoded_720p', 'encoded_1080p'], + bundle_steps: true, + }, + technique: 'hls', + playlist_name: 'my_playlist.m3u8', + }, + }, + }, + example_code_description: + 'Implementing HTTP Live Streaming: encode the uploaded video into three versions, then cut them into several segments and generate playlist files containing all the segments:', + minimum_charge: 0, + output_factor: 1.2, + override_lvl1: 'Video Encoding', + purpose_sentence: + 'encodes videos into HTTP Live Streaming (HLS) and MPEG-Dash supported formats and generates the necessary manifest and playlist files', + purpose_verb: 'convert', + purpose_word: 'make adaptive', + purpose_words: 'Convert videos to HLS and MPEG-Dash', + service_slug: 'video-encoding', + slot_count: 60, + title: 'Convert videos to HLS and MPEG-Dash', + typical_file_size_mb: 80, + typical_file_type: 'video', + name: 'VideoAdaptiveRobot', + priceFactor: 1, + queueSlotCount: 60, + isAllowedForUrlTransform: false, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotVideoAdaptiveInstructionsSchema = robotBase + .merge(robotUse) + .merge(robotFFmpegVideo) + .extend({ + robot: z.literal('/video/adaptive').describe(` +This Robot accepts all types of video files and audio files. Do not forget to use Step bundling in your \`use\` parameter to make the Robot work on several input files at once. + +This Robot is normally used in combination with [🤖/video/encode](/docs/robots/video-encode/). We have implemented video and audio encoding presets specifically for MPEG-Dash and HTTP Live Streaming support. These presets are prefixed with \`"dash/"\` and \`"hls/"\`. [View a HTTP Live Streaming demo here](/demos/video-encoding/implement-http-live-streaming/). + +### Required CORS settings for MPEG-Dash and HTTP Live Streaming + +Playing back MPEG-Dash Manifest or HLS playlist files requires a proper CORS setup on the server-side. The file-serving server should be configured to add the following header fields to responses: + +\`\`\` +Access-Control-Allow-Origin: * +Access-Control-Allow-Methods: GET +Access-Control-Allow-Headers: * +\`\`\` + +If the files are stored in an Amazon S3 Bucket, you can use the following [CORS definition](https://docs.aws.amazon.com/AmazonS3/latest/userguide/ManageCorsUsing.html) to ensure the CORS header fields are set correctly: + +\`\`\`json +[ + { + "AllowedHeaders": ["*"], + "AllowedMethods": ["GET"], + "AllowedOrigins": ["*"], + "ExposeHeaders": [] + } +] +\`\`\` + +To set up CORS for your S3 bucket: + +1. Visit +1. Click on your bucket +1. Click "Permissions" +1. Edit "Cross-origin resource sharing (CORS)" + +### Storing Segments and Playlist files + +The Robot gives its result files (segments, initialization segments, MPD manifest files and M3U8 playlist files) the right metadata property \`relative_path\`, so that you can store them easily using one of our storage Robots. + +In the \`path\` parameter of the storage Robot of your choice, use the Assembly Variable \`\${file.meta.relative_path}\` to store files in the proper paths to make the playlist files work. +`), + technique: z + .enum(['dash', 'hls']) + .default('dash') + .describe(` +Determines which streaming technique should be used. Currently supports \`"dash"\` for MPEG-Dash and \`"hls"\` for HTTP Live Streaming. +`), + playlist_name: z + .string() + .optional() + .describe(` +The filename for the generated manifest/playlist file. The default is \`"playlist.mpd"\` if your \`technique\` is \`"dash"\`, and \`"playlist.m3u8"\` if your \`technique\` is \`"hls"\`. +`), + segment_duration: z + .number() + .int() + .default(10) + .describe(` +The duration for each segment in seconds. +`), + closed_captions: z + .boolean() + .default(true) + .describe(` +Determines whether you want closed caption support when using the \`"hls"\` technique. +`), + }) + .strict() + +export const robotVideoAdaptiveInstructionsWithHiddenFieldsSchema = + robotVideoAdaptiveInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotVideoAdaptiveInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotVideoAdaptiveInstructions = z.infer +export type RobotVideoAdaptiveInstructionsWithHiddenFields = z.infer< + typeof robotVideoAdaptiveInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotVideoAdaptiveInstructionsSchema = interpolateRobot( + robotVideoAdaptiveInstructionsSchema, +) +export type InterpolatableRobotVideoAdaptiveInstructions = + InterpolatableRobotVideoAdaptiveInstructionsInput + +export type InterpolatableRobotVideoAdaptiveInstructionsInput = z.input< + typeof interpolatableRobotVideoAdaptiveInstructionsSchema +> + +export const interpolatableRobotVideoAdaptiveInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotVideoAdaptiveInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotVideoAdaptiveInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotVideoAdaptiveInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotVideoAdaptiveInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotVideoAdaptiveInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/video-concat.ts b/packages/transloadit/src/alphalib/types/robots/video-concat.ts new file mode 100644 index 00000000..492ce2d6 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/video-concat.ts @@ -0,0 +1,130 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + interpolateRobot, + robotBase, + robotFFmpegVideo, + robotUse, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: false, + bytescount: 4, + discount_factor: 0.25, + discount_pct: 75, + example_code: { + steps: { + concatenated: { + robot: '/video/concat', + use: { + steps: [ + { + name: ':original', + fields: 'first_video_file', + as: 'video_1', + }, + { + name: ':original', + fields: 'second_video_file', + as: 'video_2', + }, + { + name: ':original', + fields: 'third_video_file', + as: 'video_3', + }, + ], + }, + }, + }, + }, + example_code_description: + 'If you have a form with 3 file input fields and want to concatenate the uploaded videos in a specific order, instruct Transloadit using the `name` attribute of each input field. Use this attribute as the value for the `fields` key in the JSON, and set `as` to `video_[[index]]`. Transloadit will concatenate the files based on the ascending index order:', + minimum_charge: 0, + output_factor: 0.6, + override_lvl1: 'Video Encoding', + purpose_sentence: 'concatenates several videos together', + purpose_verb: 'concatenate', + purpose_word: 'concatenate', + purpose_words: 'Concatenate videos', + service_slug: 'video-encoding', + slot_count: 60, + title: 'Concatenate videos', + typical_file_size_mb: 80, + typical_file_type: 'video', + uses_tools: ['ffmpeg'], + name: 'VideoConcatRobot', + priceFactor: 4, + queueSlotCount: 60, + isAllowedForUrlTransform: false, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotVideoConcatInstructionsSchema = robotBase + .merge(robotUse) + .merge(robotFFmpegVideo) + .extend({ + robot: z.literal('/video/concat').describe(` +> [!Note] +> Input videos may have differing dimensions and streams - the Robot can handle this fine. It will pre-transcode the input videos if necessary before concatenation at no additional cost. + +Itʼs possible to concatenate a virtually infinite number of video files using [🤖/video/concat](/docs/robots/video-concat/). +`), + video_fade_seconds: z + .number() + .default(1) + .describe(` +When used this adds a video fade in and out effect between each section of your concatenated video. The float value is used so if you want a video delay effect of 500 milliseconds between each video section you would select \`0.5\`, however, integer values can also be represented. + +This parameter does not add a video fade effect at the beginning or end of your video. If you want to do so, create an additional [🤖/video/encode](/docs/robots/video-encode/) Step and use our \`ffmpeg\` parameter as shown in this [demo](/demos/video-encoding/concatenate-fade-effect/). + +Please note this parameter is independent of adding audio fades between sections. +`), + audio_fade_seconds: z + .number() + .default(1) + .describe(` +When used this adds an audio fade in and out effect between each section of your concatenated video. The float value is used so if you want an audio delay effect of 500 milliseconds between each video section you would select \`0.5\`, however, integer values can also be represented. + +This parameter does not add an audio fade effect at the beginning or end of your video. If you want to do so, create an additional [🤖/video/encode](/docs/robots/video-encode/] Step and use our \`ffmpeg\` parameter as shown in this [demo](/demos/audio-encoding/ffmpeg-fade-in-and-out/). + +Please note this parameter is independent of adding video fades between sections. +`), + }) + .strict() + +export const robotVideoConcatInstructionsWithHiddenFieldsSchema = + robotVideoConcatInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotVideoConcatInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotVideoConcatInstructions = z.infer +export type RobotVideoConcatInstructionsWithHiddenFields = z.infer< + typeof robotVideoConcatInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotVideoConcatInstructionsSchema = interpolateRobot( + robotVideoConcatInstructionsSchema, +) +export type InterpolatableRobotVideoConcatInstructions = + InterpolatableRobotVideoConcatInstructionsInput + +export type InterpolatableRobotVideoConcatInstructionsInput = z.input< + typeof interpolatableRobotVideoConcatInstructionsSchema +> + +export const interpolatableRobotVideoConcatInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotVideoConcatInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotVideoConcatInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotVideoConcatInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotVideoConcatInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotVideoConcatInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/video-encode.ts b/packages/transloadit/src/alphalib/types/robots/video-encode.ts new file mode 100644 index 00000000..e34e4f09 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/video-encode.ts @@ -0,0 +1,141 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + interpolateRobot, + robotBase, + robotUse, + videoEncodeSpecificInstructionsSchema, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: false, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + example_code: { + steps: { + hevc_encoded: { + robot: '/video/encode', + use: ':original', + preset: 'hevc', + }, + }, + }, + example_code_description: + 'Transcode uploaded video to [HEVC](https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding) (H.265):', + minimum_charge: 0, + output_factor: 0.6, + override_lvl1: 'Video Encoding', + purpose_sentence: 'encodes, resizes, applies watermarks to videos and animated GIFs', + purpose_verb: 'transcode', + purpose_word: 'transcode/resize/watermark', + purpose_words: 'Transcode, resize, or watermark videos', + service_slug: 'video-encoding', + slot_count: 60, + title: 'Transcode, resize, or watermark videos', + typical_file_size_mb: 80, + typical_file_type: 'video', + uses_tools: ['ffmpeg'], + name: 'VideoEncodeRobot', + priceFactor: 1, + queueSlotCount: 60, + isAllowedForUrlTransform: false, + trackOutputFileSize: true, + isInternal: false, + stage: 'ga', + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, +} + +export const robotVideoEncodeInstructionsSchema = robotBase + .merge(robotUse) + .merge(videoEncodeSpecificInstructionsSchema) + .extend({ + robot: z.literal('/video/encode').describe(` +The /video/encode Robot is a versatile tool for video processing that handles transcoding, resizing, and watermarking. It supports various formats including modern standards like HEVC (H.265), and provides features such as presets for common devices, custom FFmpeg parameters for powerusers, watermark positioning, and more. + +## Adding text overlays with FFmpeg + +You can add text overlays to videos using FFmpeg's \`drawtext\` filter through this Robot's \`ffmpeg\` parameter. Here are two examples — one with the default font and one with a custom font family name: + +\`\`\`json +{ + "steps": { + ":original": { + "robot": "/upload/handle" + }, + "text_overlay_default": { + "use": ":original", + "robot": "/video/encode", + "preset": "empty", + "ffmpeg_stack": "{{stacks.ffmpeg.recommended_version}}", + "ffmpeg": { + "codec:a": "copy", + "vf": "drawtext=text='My text overlay':fontcolor=white:fontsize=24:box=1:boxcolor=black@0.5:boxborderw=5:x=(w-text_w)/2:y=(h-text_h)/2" + }, + "result": true + }, + "text_overlay_custom": { + "use": ":original", + "robot": "/video/encode", + "preset": "empty", + "ffmpeg_stack": "{{stacks.ffmpeg.recommended_version}}", + "ffmpeg": { + "codec:a": "copy", + "vf": "drawtext=font='Times New Roman':text='My text overlay':fontcolor=white:fontsize=24:box=1:boxcolor=black@0.5:boxborderw=5:x=(w-text_w)/2:y=(h-text_h)/2" + }, + "result": true + } + } +} +\`\`\` + +**Notes:** + +- Use the \`font\` attribute to reference a font by family name with FFmpeg's \`drawtext\` +- FFmpeg font family names typically do not contain dashes (e.g. \`Times New Roman\`), while + ImageMagick uses dashed names (e.g. \`Times-New-Roman\`). +- Preserve the source audio by setting \`"codec:a": "copy"\`. +- Position text with the \`x\` and \`y\` expressions. The example above centers the text. + +See the live demo [here](/demos/video-encoding/add-text-overlay/). +`), + font_size: z.number().optional(), + font_color: z.string().optional(), + text_background_color: z.string().optional(), + }) + .strict() + +export const robotVideoEncodeInstructionsWithHiddenFieldsSchema = + robotVideoEncodeInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotVideoEncodeInstructionsSchema.shape.result]) + .optional(), + chunked_transcoding: z.boolean().optional(), + realtime: z.boolean().optional(), + }) + +export type RobotVideoEncodeInstructions = z.infer +export type RobotVideoEncodeInstructionsWithHiddenFields = z.infer< + typeof robotVideoEncodeInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotVideoEncodeInstructionsSchema = interpolateRobot( + robotVideoEncodeInstructionsSchema, +) +export type InterpolatableRobotVideoEncodeInstructions = + InterpolatableRobotVideoEncodeInstructionsInput + +export type InterpolatableRobotVideoEncodeInstructionsInput = z.input< + typeof interpolatableRobotVideoEncodeInstructionsSchema +> + +export const interpolatableRobotVideoEncodeInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotVideoEncodeInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotVideoEncodeInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotVideoEncodeInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotVideoEncodeInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotVideoEncodeInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/video-merge.ts b/packages/transloadit/src/alphalib/types/robots/video-merge.ts new file mode 100644 index 00000000..2858703e --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/video-merge.ts @@ -0,0 +1,138 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + color_with_alpha, + interpolateRobot, + resize_strategy, + robotBase, + robotFFmpegVideo, + robotUse, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: false, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + minimum_charge: 0, + output_factor: 0.6, + override_lvl1: 'Video Encoding', + purpose_sentence: + 'composes a new video by adding an audio track to existing still image(s) or video', + purpose_verb: 'merge', + purpose_word: 'merge', + purpose_words: 'Merge video, audio, images into one video', + service_slug: 'video-encoding', + slot_count: 60, + title: 'Merge video, audio, images into one video', + typical_file_size_mb: 80, + typical_file_type: 'video', + uses_tools: ['ffmpeg'], + name: 'VideoMergeRobot', + priceFactor: 1, + queueSlotCount: 60, + isAllowedForUrlTransform: false, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotVideoMergeInstructionsSchema = robotBase + .merge(robotUse) + .merge(robotFFmpegVideo) + .extend({ + robot: z.literal('/video/merge'), + resize_strategy: resize_strategy.describe(` +If the given width/height parameters are bigger than the input image's dimensions, then the \`resize_strategy\` determines how the image will be resized to match the provided width/height. See the [available resize strategies](/docs/topics/resize-strategies/). +`), + background: color_with_alpha.default('#00000000').describe(` +The background color of the resulting video the \`"rrggbbaa"\` format (red, green, blue, alpha) when used with the \`"pad"\` resize strategy. The default color is black. +`), + framerate: z + .union([z.number().int().min(1), z.string().regex(/^\d+(?:\/\d+)?$/)]) + .default('1/5') + .describe(` +When merging images to generate a video this is the input framerate. A value of "1/5" means each image is given 5 seconds before the next frame appears (the inverse of a framerate of "5"). Likewise for "1/10", "1/20", etc. A value of "5" means there are 5 frames per second. +`), + image_durations: z + .array(z.number()) + .default([]) + .describe(` +When merging images to generate a video this allows you to define how long (in seconds) each image will be shown inside of the video. So if you pass 3 images and define \`[2.4, 5.6, 9]\` the first image will be shown for 2.4s, the second image for 5.6s and the last one for 9s. The \`duration\` parameter will automatically be set to the sum of the image_durations, so \`17\` in our example. It can still be overwritten, though, in which case the last image will be shown until the defined duration is reached. +`), + duration: z + .number() + .default(5) + .describe(` +When merging images to generate a video or when merging audio and video this is the desired target duration in seconds. The float value can take one decimal digit. If you want all images to be displayed exactly once, then you can set the duration according to this formula: \`duration = numberOfImages / framerate\`. This also works for the inverse framerate values like \`1/5\`. + +If you set this value to \`null\` (default), then the duration of the input audio file will be used when merging images with an audio file. + +When merging audio files and video files, the duration of the longest video or audio file is used by default. +`), + audio_delay: z + .number() + .default(0) + .describe(` +When merging a video and an audio file, and when merging images and an audio file to generate a video, this is the desired delay in seconds for the audio file to start playing. Imagine you merge a video file without sound and an audio file, but you wish the audio to start playing after 5 seconds and not immediately, then this is the parameter to use. +`), + loop: z + .boolean() + .default(false) + .describe(` + Determines whether the shorter media file should be looped to match the duration of the longer one. For example, if you merge a 1-minute video with a 3-minute audio file and enable this option, the video will play three times in a row to match the audio length.`), + replace_audio: z + .boolean() + .default(false) + .describe(` +Determines whether the audio of the video should be replaced with a provided audio file. +`), + vstack: z + .boolean() + .default(false) + .describe(` +Stacks the input media vertically. All streams need to have the same pixel format and width - so consider using a [/video/encode](/docs/robots/video-encode/) Step before using this parameter to enforce this. +`), + image_url: z + .string() + .url() + .optional() + .describe(` +The URL of an image to be merged with the audio or video. When this parameter is provided, the robot will download the image from the URL and merge it with the other media. +`), + }) + .strict() + +export const robotVideoMergeInstructionsWithHiddenFieldsSchema = + robotVideoMergeInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotVideoMergeInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotVideoMergeInstructions = z.infer +export type RobotVideoMergeInstructionsWithHiddenFields = z.infer< + typeof robotVideoMergeInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotVideoMergeInstructionsSchema = interpolateRobot( + robotVideoMergeInstructionsSchema, +) +export type InterpolatableRobotVideoMergeInstructions = + InterpolatableRobotVideoMergeInstructionsInput + +export type InterpolatableRobotVideoMergeInstructionsInput = z.input< + typeof interpolatableRobotVideoMergeInstructionsSchema +> + +export const interpolatableRobotVideoMergeInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotVideoMergeInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotVideoMergeInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotVideoMergeInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotVideoMergeInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotVideoMergeInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/video-ondemand.ts b/packages/transloadit/src/alphalib/types/robots/video-ondemand.ts new file mode 100644 index 00000000..8f4356b8 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/video-ondemand.ts @@ -0,0 +1,161 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + interpolateRobot, + robotBase, + robotUse, + videoEncodeSpecificInstructionsSchema, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + discount_factor: 1, + discount_pct: 0, + bytescount: 1, + example_code: { + steps: { + import: { + robot: '/s3/import', + path: '${fields.input}', + credentials: 'YOUR_AWS_CREDENTIALS', + return_file_stubs: true, + }, + vod: { + robot: '/video/ondemand', + use: 'import', + variants: { + '480p': { + preset: 'hls/480p', + ffmpeg_stack: '{{ stacks.ffmpeg.recommended_version }}', + }, + '720p': { + preset: 'hls/720p', + ffmpeg_stack: '{{ stacks.ffmpeg.recommended_version }}', + }, + '1080p': { + preset: 'hls/1080p', + ffmpeg_stack: '{{ stacks.ffmpeg.recommended_version }}', + }, + }, + }, + serve: { + use: 'vod', + robot: '/file/serve', + }, + }, + }, + example_code_description: + 'Enable streaming of a video stored on S3 in three variants (480p, 720p, 1080p) with on-demand encoding:', + minimum_charge: 0, + output_factor: 0.6, + override_lvl1: 'Video Encoding', + purpose_sentence: + 'generates HTTP Live Streaming (HLS) playlists and segments on-demand for adaptive and cost-efficient playback', + purpose_verb: 'stream', + purpose_word: 'stream', + purpose_words: 'Stream videos with on-demand encoding', + service_slug: 'video-encoding', + slot_count: 60, + title: 'Stream videos with on-demand encoding', + typical_file_size_mb: 300, + typical_file_type: 'video', + name: 'VideoOndemandRobot', + priceFactor: 1, + queueSlotCount: 60, + downloadInputFiles: false, + isAllowedForUrlTransform: true, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'beta', +} + +export const robotVideoOndemandInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/video/ondemand'), + variants: z + .record(videoEncodeSpecificInstructionsSchema) + .describe( + 'Defines the variants the video player can choose from. The keys are the names of the variant as they will appear in the generated playlists and URLs.', + ), + enabled_variants: z + .union([z.string(), z.array(z.string())]) + .optional() + .describe( + 'Specifies which variants, defined in the variants parameter, are enabled. Non-enabled variants will not be included in the master playlist.', + ), + segment_duration: z + .number() + .optional() + .default(6) + .describe('The duration of each segment in seconds.'), + sign_urls_for: z + .number() + .optional() + .default(0) + .describe( + 'When signing URLs is enabled, the URLs in the generated playlist files will be signed. This parameter specifies the duration (in seconds) that the signed URLs will remain valid.', + ), + asset: z + .string() + .optional() + .describe( + 'Controls which file is generated. For example, if the parameter is unset, a master playlist referencing the variants is generated.', + ), + asset_param_name: z + .string() + .optional() + .default('asset') + .describe( + 'Specifies from which URL parameter the asset parameter value is taken and which URL parameter to use when generating playlist files.', + ), + }) + .strict() + +export const robotVideoOndemandInstructionsWithHiddenFieldsSchema = + robotVideoOndemandInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotVideoOndemandInstructionsSchema.shape.result]) + .optional(), + cdn_required_bypass: z + .boolean() + .optional() + .default(false) + .describe( + 'Internal parameter that indicates whether `cdn=required` should be added to the URLs in playlists. Useful for testing with URL Transform directly and not through Smart CDN.', + ), + url_transform_format: z + .boolean() + .optional() + .default(false) + .describe( + 'Internal parameter that indicates whether the URLs in playlists should use the Smart CDN or the URL Transform format.', + ), + }) + +export type RobotVideoOndemandInstructions = z.infer +export type RobotVideoOndemandInstructionsWithHiddenFields = z.infer< + typeof robotVideoOndemandInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotVideoOndemandInstructionsSchema = interpolateRobot( + robotVideoOndemandInstructionsSchema, +) +export type InterpolatableRobotVideoOndemandInstructions = + InterpolatableRobotVideoOndemandInstructionsInput + +export type InterpolatableRobotVideoOndemandInstructionsInput = z.input< + typeof interpolatableRobotVideoOndemandInstructionsSchema +> + +export const interpolatableRobotVideoOndemandInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotVideoOndemandInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotVideoOndemandInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotVideoOndemandInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotVideoOndemandInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotVideoOndemandInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/video-subtitle.ts b/packages/transloadit/src/alphalib/types/robots/video-subtitle.ts new file mode 100644 index 00000000..760c5ee7 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/video-subtitle.ts @@ -0,0 +1,159 @@ +import { z } from 'zod' + +import { stackVersions } from '../stackVersions.ts' +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + color_with_alpha, + color_without_alpha, + interpolateRobot, + positionSchema, + robotBase, + robotFFmpegVideo, + robotUse, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: false, + bytescount: 1, + discount_factor: 1, + discount_pct: 0, + example_code: { + steps: { + subtitled: { + robot: '/video/subtitle', + use: { + steps: [ + { + name: ':original', + fields: 'input_video', + as: 'video', + }, + { + name: ':original', + fields: 'input_srt', + as: 'subtitles', + }, + ], + }, + ffmpeg_stack: stackVersions.ffmpeg.recommendedVersion, + }, + }, + }, + example_code_description: + 'If you have two file input fields in a form — one for a video and another for an SRT or VTT subtitle, named `input_video` and `input_srt` respectively (with the HTML `name` attribute), hereʼs how to embed the subtitles into the video with Transloadit:', + minimum_charge: 0, + output_factor: 0.6, + override_lvl1: 'Video Encoding', + purpose_sentence: 'adds subtitles and closed captions to videos', + purpose_verb: 'subtitle', + purpose_word: 'subtitle', + purpose_words: 'Add subtitles to videos', + service_slug: 'video-encoding', + slot_count: 60, + title: 'Add subtitles to videos', + typical_file_size_mb: 80, + typical_file_type: 'video', + uses_tools: ['ffmpeg'], + name: 'VideoSubtitleRobot', + priceFactor: 1, + queueSlotCount: 60, + isAllowedForUrlTransform: false, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotVideoSubtitleInstructionsSchema = robotBase + .merge(robotUse) + .merge(robotFFmpegVideo) + .extend({ + robot: z.literal('/video/subtitle').describe(` +This Robot supports both SRT and VTT subtitle files. +`), + subtitles_type: z + .enum(['burned', 'external', 'burn']) + .transform((val) => (val === 'burn' ? 'burned' : val)) + .default('external') + .describe(` +Determines if subtitles are added as a separate stream to the video (value \`"external"\`) that then can be switched on and off in your video player, or if they should be burned directly into the video (value \`"burned"\` or \`"burn"\`) so that they become part of the video stream. +`), + border_style: z + .enum(['box', 'outline', 'shadow']) + .default('outline') + .describe(` +Specifies the style of the subtitle. Use the \`border_color\` parameter to specify the color of the border. +`), + border_color: color_with_alpha.default('40000000').describe(` +The color for the subtitle border. The first two hex digits specify the alpha value of the color. +`), + // TODO: Make font an enum + font: z + .string() + .default('Arial') + .describe(` +The font family to use. Also includes boldness and style of the font. + +[Here](/docs/supported-formats/fonts/) is a list of all supported fonts. +`), + font_color: color_without_alpha.default('FFFFFF').describe(` +The color of the subtitle text. The first two hex digits specify the alpha value of the color. +`), + font_size: z + .number() + .int() + .min(1) + .default(16) + .describe(` +Specifies the size of the text. +`), + position: positionSchema.default('bottom').describe(` +Specifies the position of the subtitles. +`), + language: z + .string() + .optional() + .nullable() + .describe(` +Specifies the language of the subtitles. Only used if the subtitles are external. +`), + keep_subtitles: z + .boolean() + .default(false) + .describe(` +Specifies if existing subtitles in the input file should be kept or be replaced by the new subtitle. Only used if the subtitles are external. +`), + }) + .strict() + +export const robotVideoSubtitleInstructionsWithHiddenFieldsSchema = + robotVideoSubtitleInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotVideoSubtitleInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotVideoSubtitleInstructions = z.infer +export type RobotVideoSubtitleInstructionsWithHiddenFields = z.infer< + typeof robotVideoSubtitleInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotVideoSubtitleInstructionsSchema = interpolateRobot( + robotVideoSubtitleInstructionsSchema, +) +export type InterpolatableRobotVideoSubtitleInstructions = + InterpolatableRobotVideoSubtitleInstructionsInput + +export type InterpolatableRobotVideoSubtitleInstructionsInput = z.input< + typeof interpolatableRobotVideoSubtitleInstructionsSchema +> + +export const interpolatableRobotVideoSubtitleInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotVideoSubtitleInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotVideoSubtitleInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotVideoSubtitleInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotVideoSubtitleInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotVideoSubtitleInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/video-thumbs.ts b/packages/transloadit/src/alphalib/types/robots/video-thumbs.ts new file mode 100644 index 00000000..77bc03b7 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/video-thumbs.ts @@ -0,0 +1,158 @@ +import { z } from 'zod' + +import { stackVersions } from '../stackVersions.ts' +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + color_with_alpha, + interpolateRobot, + percentageSchema, + resize_strategy, + robotBase, + robotFFmpeg, + robotUse, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: false, + bytescount: 10, + discount_factor: 0.1, + discount_pct: 90, + example_code: { + steps: { + thumbnailed: { + robot: '/video/thumbs', + use: ':original', + count: 10, + ffmpeg_stack: stackVersions.ffmpeg.recommendedVersion, + }, + }, + }, + example_code_description: 'Extract 10 thumbnails from each uploaded video:', + minimum_charge: 0, + output_factor: 0.05, + override_lvl1: 'Video Encoding', + purpose_sentence: 'extracts any number of images from videos for use as previews', + purpose_verb: 'extract', + purpose_word: 'thumbnail', + purpose_words: 'Extract thumbnails from videos', + service_slug: 'video-encoding', + slot_count: 15, + title: 'Extract thumbnails from videos', + typical_file_size_mb: 80, + typical_file_type: 'video', + uses_tools: ['ffmpeg'], + name: 'VideoThumbsRobot', + priceFactor: 10, + queueSlotCount: 15, + isAllowedForUrlTransform: false, + trackOutputFileSize: true, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotVideoThumbsInstructionsSchema = robotBase + .merge(robotUse) + .merge(robotFFmpeg) + .extend({ + robot: z.literal('/video/thumbs').describe(` +> [!Note] +> Even though thumbnails are extracted from videos in parallel, we sort the thumbnails before adding them to the Assembly results. So the order in which they appear there reflects the order in which they appear in the video. You can also make sure by checking the thumb_index meta key. +`), + count: z + .number() + .int() + .min(1) + .max(999) + .default(8) + .describe(` +The number of thumbnails to be extracted. As some videos have incorrect durations, the actual number of thumbnails generated may be less in rare cases. The maximum number of thumbnails we currently allow is 999. + +The thumbnails are taken at regular intervals, determined by dividing the video duration by the count. For example, a count of 3 will produce thumbnails at 25%, 50% and 75% through the video. + +To extract thumbnails for specific timestamps, use the \`offsets\` parameter. +`), + offsets: z + .union([z.array(z.number()), z.array(percentageSchema)]) + .default([]) + .describe(` +An array of offsets representing seconds of the file duration, such as \`[ 2, 45, 120 ]\`. Millisecond durations of a file can also be used by using decimal place values. For example, an offset from 1250 milliseconds would be represented with \`1.25\`. Offsets can also be percentage values such as \`[ "2%", "50%", "75%" ]\`. + +This option cannot be used with the \`count\` parameter, and takes precedence if both are specified. Out-of-range offsets are silently ignored. +`), + format: z + .enum(['jpeg', 'jpg', 'png']) + .default('jpeg') + .describe(` +The format of the extracted thumbnail. Supported values are \`"jpg"\`, \`"jpeg"\` and \`"png"\`. Even if you specify the format to be \`"jpeg"\` the resulting thumbnails will have a \`"jpg"\` file extension. +`), + width: z + .number() + .int() + .min(1) + .max(1920) + .optional() + .describe(` +The width of the thumbnail, in pixels. Defaults to the original width of the video. +`), + height: z + .number() + .int() + .min(1) + .max(1080) + .optional() + .describe(` +The height of the thumbnail, in pixels. Defaults to the original height of the video. +`), + resize_strategy: resize_strategy.describe(` +One of the [available resize strategies](/docs/topics/resize-strategies/). +`), + background: color_with_alpha.default('#00000000').describe(` +The background color of the resulting thumbnails in the \`"rrggbbaa"\` format (red, green, blue, alpha) when used with the \`"pad"\` resize strategy. The default color is black. +`), + rotate: z + .union([z.literal(0), z.literal(90), z.literal(180), z.literal(270), z.literal(360)]) + .default(0) + .describe(` +Forces the video to be rotated by the specified degree integer. Currently, only multiples of 90 are supported. We automatically correct the orientation of many videos when the orientation is provided by the camera. This option is only useful for videos requiring rotation because it was not detected by the camera. +`), + input_codec: z + .string() + .optional() + .describe(` +Specifies the input codec to use when decoding the video. This is useful for videos with special codecs that require specific decoders. +`), + }) + .strict() + +export const robotVideoThumbsInstructionsWithHiddenFieldsSchema = + robotVideoThumbsInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotVideoThumbsInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotVideoThumbsInstructions = z.infer +export type RobotVideoThumbsInstructionsWithHiddenFields = z.infer< + typeof robotVideoThumbsInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotVideoThumbsInstructionsSchema = interpolateRobot( + robotVideoThumbsInstructionsSchema, +) +export type InterpolatableRobotVideoThumbsInstructions = + InterpolatableRobotVideoThumbsInstructionsInput + +export type InterpolatableRobotVideoThumbsInstructionsInput = z.input< + typeof interpolatableRobotVideoThumbsInstructionsSchema +> + +export const interpolatableRobotVideoThumbsInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotVideoThumbsInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotVideoThumbsInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotVideoThumbsInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotVideoThumbsInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotVideoThumbsInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/vimeo-import.ts b/packages/transloadit/src/alphalib/types/robots/vimeo-import.ts new file mode 100644 index 00000000..9a160ae7 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/vimeo-import.ts @@ -0,0 +1,126 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + interpolateRobot, + path, + robotBase, + robotImport, + vimeoBase, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 10, + discount_factor: 0.1, + discount_pct: 90, + example_code: { + steps: { + imported: { + robot: '/vimeo/import', + credentials: 'YOUR_VIMEO_CREDENTIALS', + path: 'me/videos', + rendition: '720p', + page_number: 1, + files_per_page: 20, + }, + }, + }, + example_code_description: 'Import videos from your Vimeo account:', + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Importing', + purpose_sentence: 'imports videos from your Vimeo account', + purpose_verb: 'import', + purpose_word: 'Vimeo', + purpose_words: 'Import videos from Vimeo', + requires_credentials: true, + service_slug: 'file-importing', + slot_count: 20, + title: 'Import videos from Vimeo', + typical_file_size_mb: 50, + typical_file_type: 'video', + name: 'VimeoImportRobot', + priceFactor: 6.6666, + queueSlotCount: 20, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: true, + stage: 'ga', +} + +export const robotVimeoImportInstructionsSchema = robotBase + .merge(robotImport) + .merge(vimeoBase) + .extend({ + robot: z.literal('/vimeo/import'), + path: path.default('me/videos').describe(` +The Vimeo API path to import from. The most common paths are: +- \`me/videos\`: Your own videos +- \`me/likes\`: Videos you've liked +- \`me/albums/:album_id/videos\`: Videos from a specific album +- \`me/channels/:channel_id/videos\`: Videos from a specific channel +- \`me/groups/:group_id/videos\`: Videos from a specific group +- \`me/portfolios/:portfolio_id/videos\`: Videos from a specific portfolio +- \`me/watchlater\`: Videos in your watch later queue + +You can also use an array of path strings here to import multiple paths in the same Robot's Step. +`), + page_number: z + .number() + .int() + .positive() + .default(1) + .describe('The page number to import from. Vimeo API uses pagination for large result sets.'), + files_per_page: z + .number() + .int() + .positive() + .max(100) + .default(20) + .describe('The number of files to import per page. Maximum is 100 as per Vimeo API limits.'), + rendition: z + .enum(['240p', '360p', '540p', '720p', '1080p', 'source']) + .default('720p') + .describe('The quality of the video to import.'), + }) + .strict() + +export type RobotVimeoImportInstructions = z.infer +export type RobotVimeoImportInstructionsInput = z.input + +export const interpolatableRobotVimeoImportInstructionsSchema = interpolateRobot( + robotVimeoImportInstructionsSchema, +) +export type InterpolatableRobotVimeoImportInstructions = + InterpolatableRobotVimeoImportInstructionsInput + +export type InterpolatableRobotVimeoImportInstructionsInput = z.input< + typeof interpolatableRobotVimeoImportInstructionsSchema +> + +export const robotVimeoImportInstructionsWithHiddenFieldsSchema = + robotVimeoImportInstructionsSchema.extend({ + access_token: z + .string() + .optional() + .describe('Legacy authentication field. Use credentials instead.'), + return_file_stubs: z + .boolean() + .optional() + .describe( + 'When true, returns file stubs instead of downloading the actual files. Used for testing.', + ), + }) + +export const interpolatableRobotVimeoImportInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotVimeoImportInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotVimeoImportInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotVimeoImportInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotVimeoImportInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotVimeoImportInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/vimeo-store.ts b/packages/transloadit/src/alphalib/types/robots/vimeo-store.ts new file mode 100644 index 00000000..9d3c89c5 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/vimeo-store.ts @@ -0,0 +1,143 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse, vimeoBase } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 6, + discount_factor: 0.15000150001500018, + discount_pct: 84.99984999849998, + example_code: { + steps: { + exported: { + robot: '/vimeo/store', + use: ':original', + credentials: 'YOUR_VIMEO_CREDENTIALS', + title: 'Transloadit: Video Example', + description: 'Some nice description', + }, + }, + }, + example_code_description: 'Export an uploaded video to Vimeo and set its title and description:', + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Exporting', + purpose_sentence: 'exports encoding results to vimeo', + purpose_verb: 'export', + purpose_word: 'Vimeo', + purpose_words: 'Export files to Vimeo', + service_slug: 'file-exporting', + slot_count: 10, + title: 'Export files to Vimeo', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'VimeoStoreRobot', + priceFactor: 6.6666, + queueSlotCount: 10, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotVimeoStoreInstructionsSchema = robotBase + .merge(robotUse) + .merge(vimeoBase) + .extend({ + robot: z.literal('/vimeo/store'), + title: z.string().describe(` +The title of the video to be displayed on Vimeo. +`), + description: z.string().describe(` +The description of the video to be displayed on Vimeo. +`), + acl: z + .enum(['anybody', 'contacts', 'disable', 'nobody', 'password', 'unlisted', 'users']) + .default('anybody') + .describe(` +Controls access permissions for the video. Here are the valid values: + +- \`"anybody"\` — anyone can access the video. +- \`"contacts"\` — only those who follow the owner on Vimeo can access the video. +- \`"disable"\` — the video is embeddable, but it's hidden on Vimeo and can't be played. +- \`"nobody"\` — no one except the owner can access the video. +- \`"password"\` — only those with the password can access the video. +- \`"unlisted"\` — only those with the private link can access the video. +- \`"users"\` — only Vimeo members can access the video. +`), + password: z + .string() + .optional() + .describe(` +The password to access the video if \`acl\` is \`"password"\`. +`), + showcases: z + .array(z.string()) + .default([]) + .describe(` +An array of string IDs of showcases that you want to add the video to. The IDs can be found when browsing Vimeo. For example \`https://vimeo.com/manage/showcases/[SHOWCASE_ID]/info\`. +`), + downloadable: z + .boolean() + .default(false) + .describe(` +Whether or not the video can be downloaded from the Vimeo website. + +Only set this to \`true\` if you have unlocked this feature in your Vimeo accounting by upgrading to their "Pro" plan. If you use it while on their Freemium plan, the Vimeo API will return an \`"Invalid parameter supplied"\` error. +`), + folder_id: z + .string() + .nullable() + .default(null) + .describe(` +The ID of the folder to which the video is uploaded. + +When visiting one of your folders, the URL is similar to \`https://vimeo.com/manage/folders/xxxxxxxx\`. The folder_id would be \`"xxxxxxxx"\`. +`), + folder_uri: z + .string() + .optional() + .describe(` +Deprecated. Please use \`folder_id\` instead. The URI of the folder to which the video is uploaded. +`), + }) + .strict() + +export const robotVimeoStoreInstructionsWithHiddenFieldsSchema = + robotVimeoStoreInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotVimeoStoreInstructionsSchema.shape.result]) + .optional(), + access_token: z + .string() + .optional() + .describe('Legacy authentication field. Use credentials instead.'), + }) + +export type RobotVimeoStoreInstructions = z.infer +export type RobotVimeoStoreInstructionsWithHiddenFields = z.infer< + typeof robotVimeoStoreInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotVimeoStoreInstructionsSchema = interpolateRobot( + robotVimeoStoreInstructionsSchema, +) +export type InterpolatableRobotVimeoStoreInstructions = + InterpolatableRobotVimeoStoreInstructionsInput + +export type InterpolatableRobotVimeoStoreInstructionsInput = z.input< + typeof interpolatableRobotVimeoStoreInstructionsSchema +> + +export const interpolatableRobotVimeoStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotVimeoStoreInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotVimeoStoreInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotVimeoStoreInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotVimeoStoreInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotVimeoStoreInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/wasabi-import.ts b/packages/transloadit/src/alphalib/types/robots/wasabi-import.ts new file mode 100644 index 00000000..5cf2a8a3 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/wasabi-import.ts @@ -0,0 +1,124 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { + files_per_page, + interpolateRobot, + page_number, + path, + recursive, + return_file_stubs, + robotBase, + robotImport, + wasabiBase, +} from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 10, + discount_factor: 0.1, + discount_pct: 90, + example_code: { + steps: { + imported: { + robot: '/wasabi/import', + credentials: 'YOUR_WASABI_CREDENTIALS', + path: 'path/to/files/', + recursive: true, + }, + }, + }, + example_code_description: + 'Import files from the `path/to/files` directory and its subdirectories:', + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Importing', + purpose_sentence: 'imports whole directories of files from your wasabi bucket', + purpose_verb: 'import', + purpose_word: 'Wasabi', + purpose_words: 'Import files from Wasabi', + requires_credentials: true, + service_slug: 'file-importing', + slot_count: 20, + title: 'Import files from Wasabi', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'WasabiImportRobot', + priceFactor: 6.6666, + queueSlotCount: 20, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: true, + stage: 'ga', +} + +export const robotWasabiImportInstructionsSchema = robotBase + .merge(robotImport) + .merge(wasabiBase) + .extend({ + result: z + .boolean() + .optional() + .describe('Whether the results of this Step should be present in the Assembly Status JSON'), + robot: z.literal('/wasabi/import'), + path: path.describe(` +The path in your bucket to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. + +If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are direct descendants of this directory will be imported. For example: \`images/\`. + +Directories are **not** imported recursively. If you want to import files from subdirectories and sub-subdirectories, enable the \`recursive\` parameter. + +If you want to import all files from the root directory, please use \`/\` as the value here. In this case, make sure all your objects belong to a path. If you have objects in the root of your bucket that aren't prefixed with \`/\`, you'll receive an error: \`A client error (NoSuchKey) occurred when calling the GetObject operation: The specified key does not exist.\` + +You can also use an array of path strings here to import multiple paths in the same Robot's Step. +`), + recursive: recursive.describe(` +Setting this to \`true\` will enable importing files from subfolders and sub-subfolders, etc. of the given path. + +Please use the pagination parameters \`page_number\` and \`files_per_page\` wisely here. +`), + page_number: page_number.describe(` +The pagination page number. For now, in order to not break backwards compatibility in non-recursive imports, this only works when recursive is set to \`true\`. + +When doing big imports, make sure no files are added or removed from other scripts within your path, otherwise you might get weird results with the pagination. +`), + files_per_page: files_per_page.describe(` +The pagination page size. This only works when recursive is \`true\` for now, in order to not break backwards compatibility in non-recursive imports. +`), + return_file_stubs, + }) + .strict() + +export const robotWasabiImportInstructionsWithHiddenFieldsSchema = + robotWasabiImportInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotWasabiImportInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotWasabiImportInstructions = z.infer +export type RobotWasabiImportInstructionsWithHiddenFields = z.infer< + typeof robotWasabiImportInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotWasabiImportInstructionsSchema = interpolateRobot( + robotWasabiImportInstructionsSchema, +) +export type InterpolatableRobotWasabiImportInstructions = + InterpolatableRobotWasabiImportInstructionsInput + +export type InterpolatableRobotWasabiImportInstructionsInput = z.input< + typeof interpolatableRobotWasabiImportInstructionsSchema +> + +export const interpolatableRobotWasabiImportInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotWasabiImportInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotWasabiImportInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotWasabiImportInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotWasabiImportInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotWasabiImportInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/wasabi-store.ts b/packages/transloadit/src/alphalib/types/robots/wasabi-store.ts new file mode 100644 index 00000000..e714cee3 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/wasabi-store.ts @@ -0,0 +1,113 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse, wasabiBase } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 6, + discount_factor: 0.15000150001500018, + discount_pct: 84.99984999849998, + example_code: { + steps: { + exported: { + robot: '/wasabi/store', + use: ':original', + credentials: 'YOUR_WASABI_CREDENTIALS', + path: 'my_target_folder/${unique_prefix}/${file.url_name}', + }, + }, + }, + example_code_description: 'Export uploaded files to `my_target_folder` on Wasabi:', + has_small_icon: true, + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Exporting', + purpose_sentence: 'exports encoding results to Wasabi buckets', + purpose_verb: 'export', + purpose_word: 'Wasabi', + purpose_words: 'Export files to Wasabi', + service_slug: 'file-exporting', + slot_count: 10, + title: 'Export files to Wasabi', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'WasabiStoreRobot', + priceFactor: 6.6666, + queueSlotCount: 10, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotWasabiStoreInstructionsSchema = robotBase + .merge(robotUse) + .merge(wasabiBase) + .extend({ + robot: z.literal('/wasabi/store').describe(` +The URL to the result file will be returned in the Assembly Status JSON. +`), + path: z + .string() + .default('${unique_prefix}/${file.url_name}') + .describe(` +The path at which the file is to be stored. This may include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). The path must not be a directory. +`), + acl: z + .enum(['private', 'public-read']) + .default('public-read') + .describe(` +The permissions used for this file. +`), + headers: z + .record(z.string()) + .default({ 'Content-Type': '${file.mime}' }) + .describe(` +An object containing a list of headers to be set for this file on Wasabi Spaces, such as \`{ FileURL: "\${file.url_name}" }\`. This can also include any available [Assembly Variables](/docs/topics/assembly-instructions/#assembly-variables). + +Object Metadata can be specified using \`x-amz-meta-*\` headers. Note that these headers [do not support non-ASCII metadata values](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html#UserMetadata). +`), + sign_urls_for: z + .number() + .int() + .min(0) + .optional() + .describe(` +This parameter provides signed URLs in the result JSON (in the \`signed_ssl_url\` property). The number that you set this parameter to is the URL expiry time in seconds. If this parameter is not used, no URL signing is done. +`), + }) + .strict() + +export const robotWasabiStoreInstructionsWithHiddenFieldsSchema = + robotWasabiStoreInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotWasabiStoreInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotWasabiStoreInstructions = z.infer +export type RobotWasabiStoreInstructionsWithHiddenFields = z.infer< + typeof robotWasabiStoreInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotWasabiStoreInstructionsSchema = interpolateRobot( + robotWasabiStoreInstructionsSchema, +) +export type InterpolatableRobotWasabiStoreInstructions = + InterpolatableRobotWasabiStoreInstructionsInput + +export type InterpolatableRobotWasabiStoreInstructionsInput = z.input< + typeof interpolatableRobotWasabiStoreInstructionsSchema +> + +export const interpolatableRobotWasabiStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotWasabiStoreInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotWasabiStoreInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotWasabiStoreInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotWasabiStoreInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotWasabiStoreInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/robots/youtube-store.ts b/packages/transloadit/src/alphalib/types/robots/youtube-store.ts new file mode 100644 index 00000000..83f0e040 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/robots/youtube-store.ts @@ -0,0 +1,153 @@ +import { z } from 'zod' + +import type { RobotMetaInput } from './_instructions-primitives.ts' +import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' + +export const meta: RobotMetaInput = { + allowed_for_url_transform: true, + bytescount: 6, + discount_factor: 0.15000150001500018, + discount_pct: 84.99984999849998, + example_code: { + steps: { + exported: { + robot: '/youtube/store', + use: ':original', + credentials: 'YOUR_YOUTUBE_CREDENTIALS', + title: 'Transloadit: Video Example', + description: 'Some nice description', + category: 'science & technology', + keywords: 'transloadit, robots, botty', + visibility: 'private', + }, + }, + }, + example_code_description: 'Export an uploaded video to YouTube and set some basic parameters:', + minimum_charge: 0, + output_factor: 1, + override_lvl1: 'File Exporting', + purpose_sentence: 'exports encoding results to YouTube', + purpose_verb: 'export', + purpose_word: 'YouTube', + purpose_words: 'Export files to YouTube', + service_slug: 'file-exporting', + slot_count: 10, + title: 'Export files to YouTube', + typical_file_size_mb: 1.2, + typical_file_type: 'file', + name: 'YoutubeStoreRobot', + priceFactor: 6.6666, + queueSlotCount: 10, + isAllowedForUrlTransform: true, + trackOutputFileSize: false, + isInternal: false, + removeJobResultFilesFromDiskRightAfterStoringOnS3: false, + stage: 'ga', +} + +export const robotYoutubeStoreInstructionsSchema = robotBase + .merge(robotUse) + .extend({ + robot: z.literal('/youtube/store').describe(` +> [!Note] +> This Robot only accepts videos. + +## Installation + +Since YouTube works with OAuth, you will need to generate [Template Credentials](/c/template-credentials/) to use this Robot. + +To change the \`title\`, \`description\`, \`category\`, or \`keywords\` per video, we recommend to [inject variables into your Template](/docs/topics/templates/). + +## Adding a thumbnail image to your video + +You can add a custom thumbnail to your video on YouTube by using our \`"as"\` syntax for the \`"use"\` parameter to supply both a video and an image to the step: + +\`\`\`json +"exported": { + "use": [ + { "name": "video_encode_step", "as": "video" }, + { "name": "image_resize_step", "as": "image" }, + ], + ... +}, +\`\`\` + +If you encounter an error such as "The authenticated user doesnʼt have permissions to upload and set custom video thumbnails", you should go to your YouTube account and try adding a custom thumbnail to one of your existing videos. Youʼll be prompted to add your phone number. Once youʼve added it, the error should go away. +`), + credentials: z.string().describe(` +The authentication Template credentials used for your YouTube account. You can generate them on the [Template Credentials page](/c/template-credentials/). Simply add the name of your YouTube channel, and you will be redirected to a Google verification page. Accept the presented permissions and you will be good to go. +`), + title: z + .string() + .max(80) + .describe(` +The title of the video to be displayed on YouTube. + +Note that since the YouTube API requires titles to be within 80 characters, longer titles may be truncated. +`), + description: z.string().describe(` +The description of the video to be displayed on YouTube. This can be up to 5000 characters, including \`\\n\` for new-lines. +`), + category: z + .preprocess( + (val) => (typeof val === 'string' ? val.toLowerCase() : val), + z.enum([ + 'autos & vehicles', + 'comedy', + 'education', + 'entertainment', + 'film & animation', + 'gaming', + 'howto & style', + 'music', + 'news & politics', + 'people & blogs', + 'pets & animals', + 'science & technology', + 'sports', + 'travel & events', + ]), + ) + .describe(` +The category to which this video will be assigned. +`), + keywords: z.string().describe(` +Tags used to describe the video, separated by commas. These tags will also be displayed on YouTube. +`), + visibility: z.enum(['public', 'private', 'unlisted']).describe(` +Defines the visibility of the uploaded video. +`), + }) + .strict() + +export const robotYoutubeStoreInstructionsWithHiddenFieldsSchema = + robotYoutubeStoreInstructionsSchema.extend({ + result: z + .union([z.literal('debug'), robotYoutubeStoreInstructionsSchema.shape.result]) + .optional(), + }) + +export type RobotYoutubeStoreInstructions = z.infer +export type RobotYoutubeStoreInstructionsWithHiddenFields = z.infer< + typeof robotYoutubeStoreInstructionsWithHiddenFieldsSchema +> + +export const interpolatableRobotYoutubeStoreInstructionsSchema = interpolateRobot( + robotYoutubeStoreInstructionsSchema, +) +export type InterpolatableRobotYoutubeStoreInstructions = + InterpolatableRobotYoutubeStoreInstructionsInput + +export type InterpolatableRobotYoutubeStoreInstructionsInput = z.input< + typeof interpolatableRobotYoutubeStoreInstructionsSchema +> + +export const interpolatableRobotYoutubeStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( + robotYoutubeStoreInstructionsWithHiddenFieldsSchema, +) +export type InterpolatableRobotYoutubeStoreInstructionsWithHiddenFields = z.infer< + typeof interpolatableRobotYoutubeStoreInstructionsWithHiddenFieldsSchema +> +export type InterpolatableRobotYoutubeStoreInstructionsWithHiddenFieldsInput = z.input< + typeof interpolatableRobotYoutubeStoreInstructionsWithHiddenFieldsSchema +> diff --git a/packages/transloadit/src/alphalib/types/stackVersions.ts b/packages/transloadit/src/alphalib/types/stackVersions.ts new file mode 100644 index 00000000..79f88c3e --- /dev/null +++ b/packages/transloadit/src/alphalib/types/stackVersions.ts @@ -0,0 +1,12 @@ +export const stackVersions = { + ffmpeg: { + recommendedVersion: 'v6' as const, + test: /^v?[567](\.\d+)?(\.\d+)?$/, + suggestedValues: ['v5', 'v6', 'v7'] as const, + }, + imagemagick: { + recommendedVersion: 'v3' as const, + test: /^v?[23](\.\d+)?(\.\d+)?$/, + suggestedValues: ['v2', 'v3'] as const, + }, +} diff --git a/packages/transloadit/src/alphalib/types/template.ts b/packages/transloadit/src/alphalib/types/template.ts new file mode 100644 index 00000000..d237b809 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/template.ts @@ -0,0 +1,277 @@ +import { z } from 'zod' + +import { robotsSchema, robotsWithHiddenBotsAndFieldsSchema } from './robots/_index.ts' +import type { RobotUse } from './robots/_instructions-primitives.ts' + +export const stepSchema = z + .object({ + // This is a hack to get nicer robot hover messages in editors. + robot: z + .string() + .describe('Identifier of the [robot](https://transloadit.com/docs/robots/) to execute'), + }) + .and(robotsSchema) +export const stepsSchema = z.record(stepSchema).describe('Contains Assembly Instructions.') +export type Step = z.infer +export type StepInput = z.input +export type StepInputWithUse = StepInput & RobotUse +export type Steps = z.infer +export type StepsInput = z.input +export const optionalStepsSchema = stepsSchema.optional() + +export const stepSchemaWithHiddenFields = z + .object({ + // This is a hack to get nicer robot hover messages in editors. + robot: z + .string() + .describe('Identifier of the [robot](https://transloadit.com/docs/robots/) to execute'), + }) + .and(robotsWithHiddenBotsAndFieldsSchema) +export const stepsSchemaWithHiddenFields = z + .record(stepSchemaWithHiddenFields) + .describe('Contains Assembly Instructions.') +export type StepWithHiddenFields = z.infer +export type StepWithHiddenFieldsInput = z.input +export type StepsWithHiddenFields = z.infer +export type StepsWithHiddenFieldsInput = z.input +const optionalStepsWithHiddenFieldsSchema = stepsSchemaWithHiddenFields.optional() + +export const fieldsSchema = z + .record(z.any()) + .optional() + .describe( + 'An object of string keyed values (name -> value) that can be used as Assembly Variables, just like additional form fields can. You can use anything that is JSON stringifyable as a value', + ) + +export const notifyUrlSchema = z + .string() + .optional() + .nullable() + .describe( + 'Transloadit can send a Pingback to your server when the Assembly is completed. We’ll send the Assembly status in a form url-encoded JSON string inside of a transloadit field in a multipart POST request to the URL supplied here.', + ) + +export const templateIdSchema = z + .string() + .optional() + .describe( + 'The ID of the Template that contains your Assembly Instructions. If you set `allow_steps_override` to `false` in your Template, then `steps` and `template_id` will be mutually exclusive — you may supply only one of these parameters.', + ) + +export const assemblyAuthInstructionsSchema = z + .object({ + key: z.string().describe('Transloadit API key used to authenticate requests'), + secret: z.string().optional().describe('Transloadit API secret used to sign requests'), + expires: z + .string() + .optional() + .describe('ISO 8601 expiration timestamp for signature authentication'), + max_size: z.number().optional().describe('Maximum allowed upload size in bytes'), + nonce: z.string().optional().describe('Unique, random nonce for this request'), + referer: z + .string() + .optional() + .describe('Regular expression matched against the HTTP Referer to restrict upload origin'), + }) + .describe(`Contains at least your Transloadit Auth Key in the \`key\` property. + +If you enable Signature Authentication, you must also set an expiry date for the request in the expires property: + +\`\`\`jsonc +{ + "key": "23c96d084c744219a2ce156772ec3211", + "expires": "2009-08-28T01:02:03.000Z" +} +\`\`\` + +We strongly recommend including the \`nonce\` property — a randomly generated, unique value per request that prevents duplicate processing upon retries, can aid in debugging, and avoids attack vectors such as signature key reuse: + +\`\`\`jsonc +{ + // … + "nonce": "04ac6cb6-df43-41fb-a7fd-e5dd711a64e1" +} +\`\`\` + +The \`referer\` property is a regular expression to match against the HTTP referer of this upload, such as \`"example\\.org"\`. Specify this key to make sure that uploads only come from your domain. + +Uploads without a referer will always pass (as they are turned off for some browsers) making this useful in just a handful of use cases. For details about regular expressions, see [Mozilla's RegExp documentation](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RegExp). + +The \`max_size\` property can be used to set a maximum size that an upload can have in bytes, such as \`1048576\` (1 MB). Specify this to prevent users from uploading excessively large files. + +This can be set as part of the Assembly request or as part of the Template. + +The file size is checked as soon as the upload is started and if it exceeds the maximum size, the entire upload process is canceled and the Assembly will error out, even if it contains files that do not exceed the \`max_size\` limitation. + +If you want to just ignore the files that exceed a certain size, but process all others, then please use [🤖/file/filter](https://transloadit.com/docs/robots/file-filter/). +`) + +const assemblyInstructionsSharedShape = { + allow_steps_override: z + .boolean() + .optional() + .describe( + 'Set this to false to disallow Overruling Templates at Runtime. If you set this to false then template_id and steps will be mutually exclusive and you may only supply one of those parameters. Recommended when deploying Transloadit in untrusted environments. This makes sense to set as part of a Template, rather than on the Assembly itself when creating it.', + ), + notify_url: notifyUrlSchema, + fields: fieldsSchema, + quiet: z + .boolean() + .optional() + .describe( + 'Set this to true to reduce the response from an Assembly POST request to only the necessary fields. This prevents any potentially confidential information being leaked to the end user who is making the Assembly request. A successful Assembly will only include the ok and assembly_id fields. An erroneous Assembly will only include the error, http_code, message and assembly_id fields. The full Assembly Status will then still be sent to the notify_url if one was specified.', + ), + // This is done to avoid heavy inference cost + steps: optionalStepsSchema as typeof optionalStepsSchema, + template_id: templateIdSchema, +} as const + +export const assemblyInstructionsSchema = z.object({ + auth: assemblyAuthInstructionsSchema.optional(), + ...assemblyInstructionsSharedShape, +}) + +export const assemblyInstructionsSchemaWithRequiredAuth = z.object({ + auth: assemblyAuthInstructionsSchema, + ...assemblyInstructionsSharedShape, +}) + +export type AssemblyInstructions = z.infer +export type AssemblyInstructionsInput = z.input +export type AssemblyAuthInstructionsInput = z.input +export type AssemblyAuthInstructionsPartialInput = Partial< + z.input +> + +export const templateParamsSchema = z + .object({ + auth: assemblyAuthInstructionsSchema, + name: z + .string() + .min(5) + .max(40) + .regex(/^[a-z-]+$/) + .describe( + 'Name of this Template. Must be between 5-40 symbols (inclusive), lowercase, can only contain dashes and latin letters.', + ), + template: z + .string() + .describe(`All the [Assembly Instructions](/docs/topics/assembly-instructions/) and [Template options](/docs/topics/templates/#template-options) as a JSON encoded string. + +Example value: + +\`\`\`json +"{\\"allow_steps_override\\": false, \\"steps\\": { ... }}" +\`\`\` +`), + require_signature_auth: z + .union([z.literal(0), z.literal(1)]) + .default(0) + .describe( + 'Use `1` to deny requests that do not include a signature. With [Signature Authentication](/docs/api/authentication/) you can ensure no one else is sending requests on your behalf.', + ), + }) + .strict() + +export const templateGetParamsSchema = z + .object({ + auth: assemblyAuthInstructionsSchema, + }) + .strict() + +export const templateListParamsSchema = z + .object({ + auth: assemblyAuthInstructionsSchema, + page: z + .number() + .int() + .default(1) + .describe('Specifies the current page, within the current pagination'), + pagesize: z + .number() + .int() + .min(1) + .max(5000) + .default(50) + .describe( + 'Specifies how many Templates to be received per API request, which is useful for pagination.', + ), + sort: z + .enum(['id', 'name', 'created', 'modified']) + .default('created') + .describe('The field to sort by.'), + order: z + .enum(['asc', 'desc']) + .default('desc') + .describe( + 'The sort direction. Can be `"desc"` for descending (default) or `"asc"` for ascending.', + ), + fromdate: z + .string() + .describe( + 'Specifies the minimum Assembly UTC creation date/time. Only Templates after this time will be retrieved. Use the format `Y-m-d H:i:s`.', + ), + todate: z + .string() + .default('NOW()') + .describe( + 'Specifies the maximum Assembly UTC creation date/time. Only Templates before this time will be retrieved. Use the format `Y-m-d H:i:s`.', + ), + keywords: z + .array(z.string()) + .default([]) + .describe( + 'Specifies keywords to be matched in the Assembly Status. The Assembly fields checked include the `id`, `redirect_url`, `fields`, and `notify_url`, as well as error messages and files used.', + ), + }) + .strict() + +// These are used in system tests, but not exposed to the public right now +// Meaning we do not want to document them, do not want to offer auto complete on them +// if customers use them, they will have to surpress a typescript error +// however when they do, our runtime schema validation will not blow up on it +// because we are using this version of the schema in our actual API +// so that tests can also use them: +export const assemblyInstructionsWithHiddenSchema = assemblyInstructionsSchema.extend({ + steps: optionalStepsWithHiddenFieldsSchema as typeof optionalStepsWithHiddenFieldsSchema, + imagemagick_stack: z.string().optional(), + exiftool_stack: z.string().optional(), + mplayer_stack: z.string().optional(), + mediainfo_stack: z.string().optional(), + ffmpeg_stack: z.string().optional(), + usage_tags: z.string().optional(), + response_headers: z + .object({ + cors: z + .object({ + 'Access-Control-Allow-Methods': z.string(), + 'Access-Control-Allow-Origin': z.string(), + 'Access-Control-Allow-Headers': z.string(), + 'Access-Control-Expose-Headers': z.string(), + 'Access-Control-Allow-Credentials': z.boolean(), + 'Access-Control-Max-Age': z.number(), + 'Access-Control-Allow-Private-Network': z.boolean(), + 'Access-Control-Allow-Public-Network': z.boolean(), + }) + .optional(), + }) + .optional(), + randomize_watermarks: z.boolean().optional(), + await: z + .union([ + z.boolean(), + z.literal('notification'), + z.literal('persisting'), + z.literal('transcoding'), + ]) + .optional(), + blocking: z.boolean().optional(), + reparse_template: z.union([z.literal(1), z.boolean()]).optional(), + ignore_upload_meta_data_errors: z.boolean().optional(), + emit_execution_progress: z.boolean().optional(), +}) + +export type AssemblyInstructionsWithHidden = z.infer +export type AssemblyInstructionsWithHiddenInput = z.input< + typeof assemblyInstructionsWithHiddenSchema +> diff --git a/packages/transloadit/src/alphalib/types/templateCredential.ts b/packages/transloadit/src/alphalib/types/templateCredential.ts new file mode 100644 index 00000000..b43a7f87 --- /dev/null +++ b/packages/transloadit/src/alphalib/types/templateCredential.ts @@ -0,0 +1,61 @@ +import { z } from 'zod' + +import { assemblyAuthInstructionsSchema } from './template.ts' + +export const retrieveTemplateCredentialsParamsSchema = z + .object({ + auth: assemblyAuthInstructionsSchema, + }) + .strict() + +export const templateCredentialsSchema = z + .object({ + auth: assemblyAuthInstructionsSchema, + name: z + .string() + .min(4) + .regex(/^[a-zA-Z-]+$/) + .describe( + 'Name of the Template Credentials. Must be longer than 3 characters, can only contain dashes and latin letters.', + ), + type: z + .enum([ + 'ai', + 'azure', + 'backblaze', + 'cloudflare', + 'companion', + 'digitalocean', + 'dropbox', + 'ftp', + 'google', + 'http', + 'minio', + 'rackspace', + 's3', + 'sftp', + 'supabase', + 'swift', + 'tigris', + 'vimeo', + 'wasabi', + 'youtube', + ]) + .describe('The service to create credentials for.'), + content: z + .object({}) + .describe(`Key and value pairs which fill in the details of the Template Credentials. For example, for an S3 bucket, this would be a valid content object to send: + +\`\`\`jsonc +{ + "content": { + "key": "xyxy", + "secret": "xyxyxyxy", + "bucket" : "mybucket.example.com", + "bucket_region": "us-east-1" + } +} +\`\`\` +`), + }) + .strict() diff --git a/packages/transloadit/src/alphalib/zodParseWithContext.ts b/packages/transloadit/src/alphalib/zodParseWithContext.ts new file mode 100644 index 00000000..3bd83c86 --- /dev/null +++ b/packages/transloadit/src/alphalib/zodParseWithContext.ts @@ -0,0 +1,306 @@ +import type { z } from 'zod' + +export type ZodIssueWithContext = z.ZodIssue & { + parentObj: unknown + humanReadable: string +} + +function getByPath(obj: unknown, path: string): unknown { + if (!path) return obj + const parts = path.split('.') + let current = obj + for (const part of parts) { + if (current == null || typeof current !== 'object') return undefined + current = (current as Record)[part] + } + return current +} + +export type ZodParseWithContextResult = { + errors: ZodIssueWithContext[] + humanReadable: string +} & ( + | { + success: true + safe: z.infer + } + | { + success: false + safe?: never + } +) + +export function zodParseWithContext( + schema: T, + obj: unknown, +): ZodParseWithContextResult { + const zodRes = schema.safeParse(obj) + if (!zodRes.success) { + // Check for empty object input causing a general union failure + if ( + typeof obj === 'object' && + obj !== null && + Object.keys(obj).length === 0 && + zodRes.error.errors.length > 0 + ) { + // console.log('[zodParseWithContext] Empty object detected, Zod errors:', JSON.stringify(zodRes.error.errors, null, 2)); + + const firstError = zodRes.error.errors[0] + if ( + zodRes.error.errors.length === 1 && + firstError && + firstError.code === 'invalid_union' && + firstError.path.length === 0 && + Array.isArray((firstError as z.ZodInvalidUnionIssue).unionErrors) && + (firstError as z.ZodInvalidUnionIssue).unionErrors.length > 0 + ) { + const humanReadable = + 'Validation failed: Input object is empty or missing key fields required to determine its type, ' + + 'and does not match any variant of the expected schema. Please provide a valid object.' + return { + success: false, + // For this specific summarized error, we might not need to map all detailed ZodIssueWithContext + // or we can provide a simplified single error entry reflecting this summary. + // For now, let's return the original errors but with the new top-level humanReadable. + errors: zodRes.error.errors.map((e) => ({ + ...e, + parentObj: obj, + humanReadable: e.message, + })), + humanReadable, + } + } + } + + const zodIssuesWithContext: ZodIssueWithContext[] = [] + const badPaths = new Map() + + for (const zodIssue of zodRes.error.errors) { + const lastPath = zodIssue.path + let parentObj: unknown = {} + + if (lastPath) { + const strPath = lastPath.slice(0, -1).join('.') + parentObj = getByPath(obj, strPath) ?? {} + } + + const path = zodIssue.path + .map((p) => (typeof p === 'string' ? p.replaceAll('.', '\\.') : p)) + .join('.') + if (!badPaths.has(path)) { + badPaths.set(path, []) + } + + const messages: string[] = [] + + // --- Handle specific high-priority codes BEFORE union/switch --- + if (zodIssue.code === 'unrecognized_keys') { + const maxKeysToShow = 3 + const { keys } = zodIssue + const truncatedKeys = keys.slice(0, maxKeysToShow) + const ellipsis = keys.length > maxKeysToShow ? '...' : '' + let message = `has unrecognized keys: ${truncatedKeys.map((k) => `\`${k}\``).join(', ')}${ellipsis}` + // Add hint for root-level unrecognized keys, likely from union .strict() failures + if (zodIssue.path.length === 0) { + message += + ' (Hint: No union variant matched. Check for extra keys or type mismatches in variants.)' + } + messages.push(message) + } + // --- End high-priority handling --- + + // Handle union type validation errors (only if not handled above) + else if ('unionErrors' in zodIssue && zodIssue.unionErrors) { + // --- Moved initialization out of the loop --- + const collectedLiterals: Record = {} + const collectedMessages: Record> = {} + + // Process nested issues within the union + for (const unionError of zodIssue.unionErrors) { + for (const issue of unionError.issues) { + // console.log('---- Zod Union Issue ----\\n', JSON.stringify(issue, null, 2)) + const nestedPath = issue.path.join('.') + + // Ensure paths exist in collection maps + if (!collectedLiterals[nestedPath]) collectedLiterals[nestedPath] = [] + if (!collectedMessages[nestedPath]) collectedMessages[nestedPath] = new Set() + + if (issue.code === 'custom' && issue.message.includes('interpolation string')) { + continue + } + if (issue.code === 'invalid_literal') { + const { expected } = issue + if ( + expected !== undefined && + expected !== null && + (typeof expected === 'string' || + typeof expected === 'number' || + typeof expected === 'boolean') + ) { + collectedLiterals[nestedPath].push(expected) + } + // Still add the raw message for fallback + collectedMessages[nestedPath].add(issue.message) + } + // Keep existing enum handling if needed, but literal should cover most cases + else if (issue.code === 'invalid_enum_value') { + const { options } = issue + if (options && options.length > 0) { + collectedLiterals[nestedPath].push(...options.map(String)) // Assuming options are compatible + } + collectedMessages[nestedPath].add(issue.message) + } + // Keep existing unrecognized keys handling + else if (issue.code === 'unrecognized_keys') { + const maxKeysToShow = 3 + const { keys } = issue + const truncatedKeys = keys.slice(0, maxKeysToShow) + const ellipsis = keys.length > maxKeysToShow ? '...' : '' + collectedMessages[nestedPath].add( + `has unrecognized keys: ${truncatedKeys.map((k) => `\`${k}\``).join(', ')}${ellipsis}`, + ) + } + // <-- Add handling for invalid_type here --> + else if (issue.code === 'invalid_type') { + const received = issue.received === 'undefined' ? 'missing' : issue.received + const actualValue = getByPath(parentObj, nestedPath) + const actualValueStr = + typeof actualValue === 'object' && actualValue !== null + ? JSON.stringify(actualValue) + : String(actualValue) + + let expectedOutput = String(issue.expected) + const MAX_EXPECTED_TO_SHOW = 3 + if (typeof issue.expected === 'string' && issue.expected.includes(' | ')) { + const expectedValues = issue.expected.split(' | ') + if (expectedValues.length > MAX_EXPECTED_TO_SHOW) { + const shownValues = expectedValues.slice(0, MAX_EXPECTED_TO_SHOW).join(' | ') + const remainingCount = expectedValues.length - MAX_EXPECTED_TO_SHOW + expectedOutput = `${shownValues} | .. or ${remainingCount} others ..` + } + } + + collectedMessages[nestedPath].add( + `got invalid type: ${received} (value: \`${actualValueStr}\`, expected: ${expectedOutput})`, + ) + } + // <-- End added handling --> + else { + collectedMessages[nestedPath].add(issue.message) // Handle other nested codes + } + } + } + + // --- Moved processing logic here --- + // Now, add messages to badPaths based on collected info AFTER processing ALL union errors + for (const nestedPath in collectedMessages) { + if (!badPaths.has(nestedPath)) { + badPaths.set(nestedPath, []) + } + + const targetMessages = badPaths.get(nestedPath) + if (!targetMessages) { + continue + } + + // Prioritize more specific messages (like invalid type with details) + const invalidTypeMessages = Array.from(collectedMessages[nestedPath]).filter((m) => + m.startsWith('got invalid type:'), + ) + const unrecognizedKeyMessages = Array.from(collectedMessages[nestedPath]).filter((m) => + m.startsWith('has unrecognized keys:'), + ) + const literalMessages = collectedLiterals[nestedPath] ?? [] + + if (invalidTypeMessages.length > 0) { + targetMessages.push(...invalidTypeMessages) + } else if (unrecognizedKeyMessages.length > 0) { + targetMessages.push(...unrecognizedKeyMessages) + } else if (literalMessages.length > 0) { + const uniqueLiterals = [...new Set(literalMessages)] + targetMessages.push(`should be one of: \`${uniqueLiterals.join('`, `')}\``) + } else { + // Fallback to joining the collected raw messages for this path + targetMessages.push(...collectedMessages[nestedPath]) + } + } + } + // Handle other specific error codes (only if not handled above) + else { + // Handle specific error codes for better messages + let received: string + let type: string + let bigType: string + + switch (zodIssue.code) { + case 'invalid_type': { + received = zodIssue.received === 'undefined' ? 'missing' : zodIssue.received + const actualValue = getByPath(obj, path) + const actualValueStr = + typeof actualValue === 'object' && actualValue !== null + ? JSON.stringify(actualValue) + : String(actualValue) // Use String() for null/primitives + + // Simple message not relying on zodIssue.expected + messages.push(`got invalid type: ${received} (value: \`${actualValueStr}\`)`) + break + } + case 'invalid_string': + if (zodIssue.validation === 'email') { + messages.push('should be a valid email address') + } else if (zodIssue.validation === 'url') { + messages.push('should be a valid URL') + } else { + messages.push(zodIssue.message) + } + break + case 'too_small': + type = zodIssue.type === 'string' ? 'characters' : 'items' + messages.push(`should have at least ${zodIssue.minimum} ${type}`) + break + case 'too_big': + bigType = zodIssue.type === 'string' ? 'characters' : 'items' + messages.push(`should have at most ${zodIssue.maximum} ${bigType}`) + break + case 'custom': + messages.push(zodIssue.message) + break + default: + messages.push(zodIssue.message) + } + } + + // Ensure messages collected directly in the `messages` array (e.g., from the switch) + // are added to the correct path in badPaths. + if (messages.length > 0) { + badPaths.get(path)?.push(...messages) + } + + const field = path || 'Input' + // Ensure humanReadable for the individual ZodIssueWithContext is still generated correctly + // even if messages array is empty because handled via badPaths/nested issues. + const issueSpecificMessages = badPaths.get(path) ?? messages + const humanReadable = `Path \`${field}\` ${issueSpecificMessages.join(', ')}` + + zodIssuesWithContext.push({ + ...zodIssue, + parentObj, + humanReadable, + }) + } + + // Improved formatting for the top-level humanReadable string + const errorList = Array.from(badPaths.entries()) + .map(([path, messages]) => { + const field = path || 'Input' + return ` - \`${field}\`: ${messages.join(', ')}` // Format as list item + }) + .join('\n') + + const humanReadable = `Validation failed for the following fields:\n${errorList}` // Add header + + return { success: false, errors: zodIssuesWithContext, humanReadable } + } + + return { success: true, safe: zodRes.data, errors: [], humanReadable: '' } +} diff --git a/packages/transloadit/src/apiTypes.ts b/packages/transloadit/src/apiTypes.ts new file mode 100644 index 00000000..28d474c8 --- /dev/null +++ b/packages/transloadit/src/apiTypes.ts @@ -0,0 +1,154 @@ +import type { AssemblyInstructions, AssemblyInstructionsInput } from './alphalib/types/template.ts' + +export { + type AssemblyIndexItem, + assemblyIndexItemSchema, + assemblyStatusSchema, +} from './alphalib/types/assemblyStatus.ts' +export type { AssemblyInstructions, AssemblyInstructionsInput } from './alphalib/types/template.ts' +export { assemblyInstructionsSchema } from './alphalib/types/template.ts' + +export interface OptionalAuthParams { + auth?: { key?: string; expires?: string } +} + +// todo make zod schemas for these types in the backend for these too (in alphalib?) +// currently the types are not entirely correct, and probably lacking some props + +export interface BaseResponse { + // todo are these always there? maybe sometimes missing or null + ok: string // todo should we type the different possible `ok` responses? + message: string +} + +export interface PaginationList { + items: T[] +} + +export interface PaginationListWithCount extends PaginationList { + count: number +} + +// `auth` is not required in the JS API because it can be specified in the constructor, +// and it will then be auto-added before the request +export type CreateAssemblyParams = Omit & OptionalAuthParams + +export type ListAssembliesParams = OptionalAuthParams & { + page?: number + pagesize?: number + type?: 'all' | 'uploading' | 'executing' | 'canceled' | 'completed' | 'failed' | 'request_aborted' + fromdate?: string + todate?: string + keywords?: string[] +} + +export type ReplayAssemblyParams = Pick< + CreateAssemblyParams, + 'auth' | 'template_id' | 'notify_url' | 'fields' | 'steps' +> & { + reparse_template?: number +} + +export interface ReplayAssemblyResponse extends BaseResponse { + success: boolean + assembly_id: string + assembly_url: string + assembly_ssl_url: string + notify_url?: string +} + +export type ReplayAssemblyNotificationParams = OptionalAuthParams & { + notify_url?: string + wait?: boolean +} + +export interface ReplayAssemblyNotificationResponse { + ok: string + success: boolean + notification_id: string +} + +export type TemplateContent = Pick< + CreateAssemblyParams, + 'allow_steps_override' | 'steps' | 'auth' | 'notify_url' +> + +export type ResponseTemplateContent = Pick< + AssemblyInstructions, + 'allow_steps_override' | 'steps' | 'auth' | 'notify_url' +> + +export type CreateTemplateParams = OptionalAuthParams & { + name: string + template: TemplateContent + require_signature_auth?: number +} + +export type EditTemplateParams = OptionalAuthParams & { + name?: string + template?: TemplateContent + require_signature_auth?: number +} + +export type ListTemplatesParams = OptionalAuthParams & { + page?: number + pagesize?: number + sort?: 'id' | 'name' | 'created' | 'modified' + order?: 'desc' | 'asc' + fromdate?: string + todate?: string + keywords?: string[] +} + +interface TemplateResponseBase { + id: string + name: string + content: ResponseTemplateContent + require_signature_auth: number +} + +export interface ListedTemplate extends TemplateResponseBase { + encryption_version: number + last_used?: string + created: string + modified: string +} + +export interface TemplateResponse extends TemplateResponseBase, BaseResponse {} + +// todo type this according to api2 valid values for better dx? +export type TemplateCredentialContent = Record + +export type CreateTemplateCredentialParams = OptionalAuthParams & { + name: string + type: string + content: TemplateCredentialContent +} + +export type ListTemplateCredentialsParams = OptionalAuthParams & { + page?: number + sort?: string + order: 'asc' | 'desc' +} + +// todo +export interface TemplateCredential { + id: string + name: string + type: string + content: TemplateCredentialContent + account_id?: string + created?: string + modified?: string + stringified?: string +} + +export interface TemplateCredentialResponse extends BaseResponse { + credential: TemplateCredential +} + +export interface TemplateCredentialsResponse extends BaseResponse { + credentials: TemplateCredential[] +} + +export type BillResponse = unknown // todo diff --git a/packages/transloadit/src/cli.ts b/packages/transloadit/src/cli.ts new file mode 100644 index 00000000..bdcd0b93 --- /dev/null +++ b/packages/transloadit/src/cli.ts @@ -0,0 +1,44 @@ +#!/usr/bin/env node + +import { realpathSync } from 'node:fs' +import path from 'node:path' +import process from 'node:process' +import { fileURLToPath } from 'node:url' +import 'dotenv/config' +import { createCli } from './cli/commands/index.ts' + +const currentFile = realpathSync(fileURLToPath(import.meta.url)) + +function resolveInvokedPath(invoked?: string): string | null { + if (invoked == null) return null + try { + return realpathSync(invoked) + } catch { + return path.resolve(invoked) + } +} + +export function shouldRunCli(invoked?: string): boolean { + const resolved = resolveInvokedPath(invoked) + if (resolved == null) return false + return resolved === currentFile +} + +export async function main(args = process.argv.slice(2)): Promise { + const cli = createCli() + const exitCode = await cli.run(args) + if (exitCode !== 0) { + process.exitCode = exitCode + } +} + +export function runCliWhenExecuted(): void { + if (!shouldRunCli(process.argv[1])) return + + void main().catch((error) => { + console.error((error as Error).message) + process.exitCode = 1 + }) +} + +runCliWhenExecuted() diff --git a/packages/transloadit/src/cli/OutputCtl.ts b/packages/transloadit/src/cli/OutputCtl.ts new file mode 100644 index 00000000..be6fc9a2 --- /dev/null +++ b/packages/transloadit/src/cli/OutputCtl.ts @@ -0,0 +1,115 @@ +/** + * Log levels following syslog severity (https://en.wikipedia.org/wiki/Syslog#Severity_level) + * Lower numbers = more severe, higher numbers = more verbose + */ +const LOG_LEVEL = { + ERR: 3, // Error conditions + WARN: 4, // Warning conditions + NOTICE: 5, // Normal but significant (default) + INFO: 6, // Informational + DEBUG: 7, // Debug-level messages + TRACE: 8, // Most verbose/detailed +} as const + +export type LogLevelName = keyof typeof LOG_LEVEL +export type LogLevelValue = (typeof LOG_LEVEL)[LogLevelName] + +export const LOG_LEVEL_DEFAULT: LogLevelValue = LOG_LEVEL.NOTICE + +/** Valid log level names for CLI parsing */ +export const LOG_LEVEL_NAMES = Object.keys(LOG_LEVEL).map((k) => + k.toLowerCase(), +) as Lowercase[] + +/** Valid numeric log level values */ +const LOG_LEVEL_VALUES = new Set(Object.values(LOG_LEVEL)) + +/** Parse a log level string (name or number) to its numeric value */ +export function parseLogLevel(level: string): LogLevelValue { + // Try parsing as number first + const num = Number(level) + if (!Number.isNaN(num)) { + if (LOG_LEVEL_VALUES.has(num as LogLevelValue)) { + return num as LogLevelValue + } + throw new Error( + `Invalid log level: ${level}. Valid values: ${[...LOG_LEVEL_VALUES].join(', ')} or ${LOG_LEVEL_NAMES.join(', ')}`, + ) + } + + // Try as level name + const upper = level.toUpperCase() as LogLevelName + if (upper in LOG_LEVEL) { + return LOG_LEVEL[upper] + } + throw new Error( + `Invalid log level: ${level}. Valid levels: ${LOG_LEVEL_NAMES.join(', ')} or ${[...LOG_LEVEL_VALUES].join(', ')}`, + ) +} + +export interface OutputCtlOptions { + logLevel?: LogLevelValue + jsonMode?: boolean +} + +/** Interface for output controllers (used to allow test mocks) */ +export interface IOutputCtl { + error(msg: unknown): void + warn(msg: unknown): void + notice(msg: unknown): void + info(msg: unknown): void + debug(msg: unknown): void + trace(msg: unknown): void + print(simple: unknown, json: unknown): void +} + +export default class OutputCtl implements IOutputCtl { + private json: boolean + private logLevel: LogLevelValue + + constructor({ logLevel = LOG_LEVEL_DEFAULT, jsonMode = false }: OutputCtlOptions = {}) { + this.json = jsonMode + this.logLevel = logLevel + + process.stdout.on('error', (err: NodeJS.ErrnoException) => { + if (err.code === 'EPIPE') { + process.exitCode = 0 + } + }) + process.stderr.on('error', (err: NodeJS.ErrnoException) => { + if (err.code === 'EPIPE') { + process.exitCode = 0 + } + }) + } + + error(msg: unknown): void { + if (this.logLevel >= LOG_LEVEL.ERR) console.error('err ', msg) + } + + warn(msg: unknown): void { + if (this.logLevel >= LOG_LEVEL.WARN) console.error('warn ', msg) + } + + notice(msg: unknown): void { + if (this.logLevel >= LOG_LEVEL.NOTICE) console.error('notice ', msg) + } + + info(msg: unknown): void { + if (this.logLevel >= LOG_LEVEL.INFO) console.error('info ', msg) + } + + debug(msg: unknown): void { + if (this.logLevel >= LOG_LEVEL.DEBUG) console.error('debug ', msg) + } + + trace(msg: unknown): void { + if (this.logLevel >= LOG_LEVEL.TRACE) console.error('trace ', msg) + } + + print(simple: unknown, json: unknown): void { + if (this.json) console.log(JSON.stringify(json)) + else if (typeof simple === 'string') console.log(simple) + else console.dir(simple, { depth: null }) + } +} diff --git a/packages/transloadit/src/cli/commands/BaseCommand.ts b/packages/transloadit/src/cli/commands/BaseCommand.ts new file mode 100644 index 00000000..e652ec25 --- /dev/null +++ b/packages/transloadit/src/cli/commands/BaseCommand.ts @@ -0,0 +1,71 @@ +import 'dotenv/config' +import process from 'node:process' +import { Command, Option } from 'clipanion' +import { Transloadit as TransloaditClient } from '../../Transloadit.ts' +import { getEnvCredentials } from '../helpers.ts' +import type { IOutputCtl } from '../OutputCtl.ts' +import OutputCtl, { LOG_LEVEL_DEFAULT, LOG_LEVEL_NAMES, parseLogLevel } from '../OutputCtl.ts' + +abstract class BaseCommand extends Command { + logLevelOption = Option.String('-l,--log-level', { + description: `Log level: ${LOG_LEVEL_NAMES.join(', ')} or 3-8 (default: notice)`, + }) + + json = Option.Boolean('-j,--json', false, { + description: 'Output in JSON format', + }) + + endpoint = Option.String('--endpoint', { + description: + 'API endpoint URL (default: https://api2.transloadit.com, or TRANSLOADIT_ENDPOINT env var)', + }) + + protected output!: IOutputCtl + protected client!: TransloaditClient + + protected setupOutput(): void { + const logLevel = this.logLevelOption ? parseLogLevel(this.logLevelOption) : LOG_LEVEL_DEFAULT + this.output = new OutputCtl({ + logLevel, + jsonMode: this.json, + }) + } + + protected setupClient(): boolean { + const creds = getEnvCredentials() + if (!creds) { + this.output.error( + 'Please provide API authentication in the environment variables TRANSLOADIT_KEY and TRANSLOADIT_SECRET', + ) + return false + } + + const endpoint = this.endpoint || process.env.TRANSLOADIT_ENDPOINT + + this.client = new TransloaditClient({ ...creds, ...(endpoint && { endpoint }) }) + return true + } + + abstract override execute(): Promise +} + +export abstract class AuthenticatedCommand extends BaseCommand { + override async execute(): Promise { + this.setupOutput() + if (!this.setupClient()) { + return 1 + } + return await this.run() + } + + protected abstract run(): Promise +} + +export abstract class UnauthenticatedCommand extends BaseCommand { + override async execute(): Promise { + this.setupOutput() + return await this.run() + } + + protected abstract run(): Promise +} diff --git a/packages/transloadit/src/cli/commands/assemblies.ts b/packages/transloadit/src/cli/commands/assemblies.ts new file mode 100644 index 00000000..d29ff660 --- /dev/null +++ b/packages/transloadit/src/cli/commands/assemblies.ts @@ -0,0 +1,1373 @@ +import EventEmitter from 'node:events' +import fs from 'node:fs' +import fsp from 'node:fs/promises' +import path from 'node:path' +import process from 'node:process' +import type { Readable, Writable } from 'node:stream' +import { pipeline } from 'node:stream/promises' +import { setTimeout as delay } from 'node:timers/promises' +import tty from 'node:tty' +import { promisify } from 'node:util' +import { Command, Option } from 'clipanion' +import got from 'got' +import PQueue from 'p-queue' +import * as t from 'typanion' +import { z } from 'zod' +import { tryCatch } from '../../alphalib/tryCatch.ts' +import type { Steps, StepsInput } from '../../alphalib/types/template.ts' +import { stepsSchema } from '../../alphalib/types/template.ts' +import type { CreateAssemblyParams, ReplayAssemblyParams } from '../../apiTypes.ts' +import type { CreateAssemblyOptions, Transloadit } from '../../Transloadit.ts' +import { createReadStream, formatAPIError, streamToBuffer } from '../helpers.ts' +import type { IOutputCtl } from '../OutputCtl.ts' +import { ensureError, isErrnoException } from '../types.ts' +import { AuthenticatedCommand } from './BaseCommand.ts' + +// --- From assemblies.ts: Schemas and interfaces --- +export interface AssemblyListOptions { + before?: string + after?: string + fields?: string[] + keywords?: string[] + pagesize?: number +} + +export interface AssemblyGetOptions { + assemblies: string[] +} + +interface AssemblyDeleteOptions { + assemblies: string[] +} + +export interface AssemblyReplayOptions { + fields?: Record + reparse?: boolean + steps?: string + notify_url?: string + assemblies: string[] +} + +const AssemblySchema = z.object({ + id: z.string(), +}) + +// --- Business logic functions (from assemblies.ts) --- + +export function list( + output: IOutputCtl, + client: Transloadit, + { before, after, fields, keywords }: AssemblyListOptions, +): Promise { + const assemblies = client.streamAssemblies({ + fromdate: after, + todate: before, + keywords, + }) + + assemblies.on('readable', () => { + const assembly: unknown = assemblies.read() + if (assembly == null) return + + const parsed = AssemblySchema.safeParse(assembly) + if (!parsed.success) return + + if (fields == null) { + output.print(parsed.data.id, assembly) + } else { + const assemblyRecord = assembly as Record + output.print(fields.map((field) => assemblyRecord[field]).join(' '), assembly) + } + }) + + return new Promise((resolve) => { + assemblies.on('end', resolve) + assemblies.on('error', (err: unknown) => { + output.error(formatAPIError(err)) + resolve() + }) + }) +} + +export async function get( + output: IOutputCtl, + client: Transloadit, + { assemblies }: AssemblyGetOptions, +): Promise { + for (const assembly of assemblies) { + await delay(1000) + const [err, result] = await tryCatch(client.getAssembly(assembly)) + if (err) { + output.error(formatAPIError(err)) + throw ensureError(err) + } + output.print(result, result) + } +} + +async function deleteAssemblies( + output: IOutputCtl, + client: Transloadit, + { assemblies }: AssemblyDeleteOptions, +): Promise { + const promises = assemblies.map(async (assembly) => { + const [err] = await tryCatch(client.cancelAssembly(assembly)) + if (err) { + output.error(formatAPIError(err)) + } + }) + await Promise.all(promises) +} + +// Export with `delete` alias for tests (can't use `delete` as function name) +export { deleteAssemblies as delete } + +export async function replay( + output: IOutputCtl, + client: Transloadit, + { fields, reparse, steps, notify_url, assemblies }: AssemblyReplayOptions, +): Promise { + if (steps) { + try { + const buf = await streamToBuffer(createReadStream(steps)) + const parsed: unknown = JSON.parse(buf.toString()) + const validated = stepsSchema.safeParse(parsed) + if (!validated.success) { + throw new Error(`Invalid steps format: ${validated.error.message}`) + } + await apiCall(validated.data) + } catch (err) { + const error = ensureError(err) + output.error(error.message) + } + } else { + await apiCall() + } + + async function apiCall(stepsOverride?: Steps): Promise { + const promises = assemblies.map(async (assembly) => { + const [err] = await tryCatch( + client.replayAssembly(assembly, { + reparse_template: reparse ? 1 : 0, + fields, + notify_url, + // Steps (validated) is assignable to StepsInput at runtime; cast for TS + steps: stepsOverride as ReplayAssemblyParams['steps'], + }), + ) + if (err) { + output.error(formatAPIError(err)) + } + }) + await Promise.all(promises) + } +} + +// --- From assemblies-create.ts: Helper classes and functions --- +interface NodeWatcher { + on(event: 'error', listener: (err: Error) => void): void + on(event: 'close', listener: () => void): void + on(event: 'change', listener: (evt: string, filename: string) => void): void + on(event: string, listener: (...args: unknown[]) => void): void + close(): void +} + +type NodeWatchFn = (path: string, options?: { recursive?: boolean }) => NodeWatcher + +let nodeWatch: NodeWatchFn | undefined + +async function getNodeWatch(): Promise { + if (!nodeWatch) { + const mod = (await import('node-watch')) as unknown as { default: NodeWatchFn } + nodeWatch = mod.default + } + return nodeWatch +} + +// workaround for determining mime-type of stdin +const stdinWithPath = process.stdin as unknown as { path: string } +stdinWithPath.path = '/dev/stdin' + +interface OutStream extends Writable { + path?: string + mtime?: Date +} + +interface Job { + in: Readable | null + out: OutStream | null +} + +type OutstreamProvider = (inpath: string | null, indir?: string) => Promise + +interface StreamRegistry { + [key: string]: OutStream | undefined +} + +interface JobEmitterOptions { + recursive?: boolean + outstreamProvider: OutstreamProvider + streamRegistry: StreamRegistry + watch?: boolean + reprocessStale?: boolean +} + +interface ReaddirJobEmitterOptions { + dir: string + streamRegistry: StreamRegistry + recursive?: boolean + outstreamProvider: OutstreamProvider + topdir?: string +} + +interface SingleJobEmitterOptions { + file: string + streamRegistry: StreamRegistry + outstreamProvider: OutstreamProvider +} + +interface WatchJobEmitterOptions { + file: string + streamRegistry: StreamRegistry + recursive?: boolean + outstreamProvider: OutstreamProvider +} + +interface StatLike { + isDirectory(): boolean +} + +const fstatAsync = promisify(fs.fstat) + +async function myStat( + stdioStream: NodeJS.ReadStream | NodeJS.WriteStream, + filepath: string, +): Promise { + if (filepath === '-') { + const stream = stdioStream as NodeJS.ReadStream & { fd: number } + return await fstatAsync(stream.fd) + } + return await fsp.stat(filepath) +} + +function dirProvider(output: string): OutstreamProvider { + return async (inpath, indir = process.cwd()) => { + if (inpath == null || inpath === '-') { + throw new Error('You must provide an input to output to a directory') + } + + let relpath = path.relative(indir, inpath) + relpath = relpath.replace(/^(\.\.\/)+/, '') + const outpath = path.join(output, relpath) + const outdir = path.dirname(outpath) + + await fsp.mkdir(outdir, { recursive: true }) + const [, stats] = await tryCatch(fsp.stat(outpath)) + const mtime = stats?.mtime ?? new Date(0) + const outstream = fs.createWriteStream(outpath) as OutStream + // Attach a no-op error handler to prevent unhandled errors if stream is destroyed + // before being consumed (e.g., due to output collision detection) + outstream.on('error', () => {}) + outstream.mtime = mtime + return outstream + } +} + +function fileProvider(output: string): OutstreamProvider { + const dirExistsP = fsp.mkdir(path.dirname(output), { recursive: true }) + return async (_inpath) => { + await dirExistsP + if (output === '-') return process.stdout as OutStream + + const [, stats] = await tryCatch(fsp.stat(output)) + const mtime = stats?.mtime ?? new Date(0) + const outstream = fs.createWriteStream(output) as OutStream + // Attach a no-op error handler to prevent unhandled errors if stream is destroyed + // before being consumed (e.g., due to output collision detection) + outstream.on('error', () => {}) + outstream.mtime = mtime + return outstream + } +} + +function nullProvider(): OutstreamProvider { + return async (_inpath) => null +} + +class MyEventEmitter extends EventEmitter { + protected hasEnded: boolean + + constructor() { + super() + this.hasEnded = false + } + + override emit(event: string | symbol, ...args: unknown[]): boolean { + if (this.hasEnded) return false + if (event === 'end' || event === 'error') { + this.hasEnded = true + return super.emit(event, ...args) + } + return super.emit(event, ...args) + } +} + +class ReaddirJobEmitter extends MyEventEmitter { + constructor({ + dir, + streamRegistry, + recursive, + outstreamProvider, + topdir = dir, + }: ReaddirJobEmitterOptions) { + super() + + process.nextTick(() => { + this.processDirectory({ dir, streamRegistry, recursive, outstreamProvider, topdir }).catch( + (err) => { + this.emit('error', err) + }, + ) + }) + } + + private async processDirectory({ + dir, + streamRegistry, + recursive, + outstreamProvider, + topdir, + }: ReaddirJobEmitterOptions & { topdir: string }): Promise { + const files = await fsp.readdir(dir) + + const pendingOperations: Promise[] = [] + + for (const filename of files) { + const file = path.normalize(path.join(dir, filename)) + pendingOperations.push( + this.processFile({ file, streamRegistry, recursive, outstreamProvider, topdir }), + ) + } + + await Promise.all(pendingOperations) + this.emit('end') + } + + private async processFile({ + file, + streamRegistry, + recursive = false, + outstreamProvider, + topdir, + }: { + file: string + streamRegistry: StreamRegistry + recursive?: boolean + outstreamProvider: OutstreamProvider + topdir: string + }): Promise { + const stats = await fsp.stat(file) + + if (stats.isDirectory()) { + if (recursive) { + await new Promise((resolve, reject) => { + const subdirEmitter = new ReaddirJobEmitter({ + dir: file, + streamRegistry, + recursive, + outstreamProvider, + topdir, + }) + subdirEmitter.on('job', (job: Job) => this.emit('job', job)) + subdirEmitter.on('error', (error: Error) => reject(error)) + subdirEmitter.on('end', () => resolve()) + }) + } + } else { + const existing = streamRegistry[file] + if (existing) existing.end() + const outstream = await outstreamProvider(file, topdir) + streamRegistry[file] = outstream ?? undefined + const instream = fs.createReadStream(file) + // Attach a no-op error handler to prevent unhandled errors if stream is destroyed + // before being consumed (e.g., due to output collision detection) + instream.on('error', () => {}) + this.emit('job', { in: instream, out: outstream }) + } + } +} + +class SingleJobEmitter extends MyEventEmitter { + constructor({ file, streamRegistry, outstreamProvider }: SingleJobEmitterOptions) { + super() + + const normalizedFile = path.normalize(file) + const existing = streamRegistry[normalizedFile] + if (existing) existing.end() + outstreamProvider(normalizedFile).then((outstream) => { + streamRegistry[normalizedFile] = outstream ?? undefined + + let instream: Readable | null + if (normalizedFile === '-') { + if (tty.isatty(process.stdin.fd)) { + instream = null + } else { + instream = process.stdin + } + } else { + instream = fs.createReadStream(normalizedFile) + // Attach a no-op error handler to prevent unhandled errors if stream is destroyed + // before being consumed (e.g., due to output collision detection) + instream.on('error', () => {}) + } + + process.nextTick(() => { + this.emit('job', { in: instream, out: outstream }) + this.emit('end') + }) + }) + } +} + +class InputlessJobEmitter extends MyEventEmitter { + constructor({ + outstreamProvider, + }: { streamRegistry: StreamRegistry; outstreamProvider: OutstreamProvider }) { + super() + + process.nextTick(() => { + outstreamProvider(null).then((outstream) => { + try { + this.emit('job', { in: null, out: outstream }) + } catch (err) { + this.emit('error', err) + } + + this.emit('end') + }) + }) + } +} + +class NullJobEmitter extends MyEventEmitter { + constructor() { + super() + process.nextTick(() => this.emit('end')) + } +} + +class WatchJobEmitter extends MyEventEmitter { + private watcher: NodeWatcher | null = null + + constructor({ file, streamRegistry, recursive, outstreamProvider }: WatchJobEmitterOptions) { + super() + + this.init({ file, streamRegistry, recursive, outstreamProvider }).catch((err) => { + this.emit('error', err) + }) + + // Clean up watcher on process exit signals + const cleanup = () => this.close() + process.once('SIGINT', cleanup) + process.once('SIGTERM', cleanup) + } + + /** Close the file watcher and release resources */ + close(): void { + if (this.watcher) { + this.watcher.close() + this.watcher = null + } + } + + private async init({ + file, + streamRegistry, + recursive, + outstreamProvider, + }: WatchJobEmitterOptions): Promise { + const stats = await fsp.stat(file) + const topdir = stats.isDirectory() ? file : undefined + + const watchFn = await getNodeWatch() + this.watcher = watchFn(file, { recursive }) + + this.watcher.on('error', (err: Error) => { + this.close() + this.emit('error', err) + }) + this.watcher.on('close', () => this.emit('end')) + this.watcher.on('change', (_evt: string, filename: string) => { + const normalizedFile = path.normalize(filename) + this.handleChange(normalizedFile, topdir, streamRegistry, outstreamProvider).catch((err) => { + this.emit('error', err) + }) + }) + } + + private async handleChange( + normalizedFile: string, + topdir: string | undefined, + streamRegistry: StreamRegistry, + outstreamProvider: OutstreamProvider, + ): Promise { + const stats = await fsp.stat(normalizedFile) + if (stats.isDirectory()) return + + const existing = streamRegistry[normalizedFile] + if (existing) existing.end() + + const outstream = await outstreamProvider(normalizedFile, topdir) + streamRegistry[normalizedFile] = outstream ?? undefined + + const instream = fs.createReadStream(normalizedFile) + // Attach a no-op error handler to prevent unhandled errors if stream is destroyed + // before being consumed (e.g., due to output collision detection) + instream.on('error', () => {}) + this.emit('job', { in: instream, out: outstream }) + } +} + +class MergedJobEmitter extends MyEventEmitter { + constructor(...jobEmitters: MyEventEmitter[]) { + super() + + let ncomplete = 0 + + for (const jobEmitter of jobEmitters) { + jobEmitter.on('error', (err: Error) => this.emit('error', err)) + jobEmitter.on('job', (job: Job) => this.emit('job', job)) + jobEmitter.on('end', () => { + if (++ncomplete === jobEmitters.length) this.emit('end') + }) + } + + if (jobEmitters.length === 0) { + this.emit('end') + } + } +} + +class ConcattedJobEmitter extends MyEventEmitter { + constructor(emitterFn: () => MyEventEmitter, ...emitterFns: (() => MyEventEmitter)[]) { + super() + + const emitter = emitterFn() + + emitter.on('error', (err: Error) => this.emit('error', err)) + emitter.on('job', (job: Job) => this.emit('job', job)) + + if (emitterFns.length === 0) { + emitter.on('end', () => this.emit('end')) + } else { + emitter.on('end', () => { + const firstFn = emitterFns[0] + if (!firstFn) { + this.emit('end') + return + } + const restEmitter = new ConcattedJobEmitter(firstFn, ...emitterFns.slice(1)) + restEmitter.on('error', (err: Error) => this.emit('error', err)) + restEmitter.on('job', (job: Job) => this.emit('job', job)) + restEmitter.on('end', () => this.emit('end')) + }) + } + } +} + +function detectConflicts(jobEmitter: EventEmitter): MyEventEmitter { + const emitter = new MyEventEmitter() + const outfileAssociations: Record = {} + + jobEmitter.on('end', () => emitter.emit('end')) + jobEmitter.on('error', (err: Error) => emitter.emit('error', err)) + jobEmitter.on('job', (job: Job) => { + if (job.in == null || job.out == null) { + emitter.emit('job', job) + return + } + const inPath = (job.in as fs.ReadStream).path as string + const outPath = job.out.path as string + if (Object.hasOwn(outfileAssociations, outPath) && outfileAssociations[outPath] !== inPath) { + emitter.emit( + 'error', + new Error(`Output collision between '${inPath}' and '${outfileAssociations[outPath]}'`), + ) + } else { + outfileAssociations[outPath] = inPath + emitter.emit('job', job) + } + }) + + return emitter +} + +function dismissStaleJobs(jobEmitter: EventEmitter): MyEventEmitter { + const emitter = new MyEventEmitter() + const pendingChecks: Promise[] = [] + + jobEmitter.on('end', () => Promise.all(pendingChecks).then(() => emitter.emit('end'))) + jobEmitter.on('error', (err: Error) => emitter.emit('error', err)) + jobEmitter.on('job', (job: Job) => { + if (job.in == null || job.out == null) { + emitter.emit('job', job) + return + } + + const inPath = (job.in as fs.ReadStream).path as string + const checkPromise = fsp + .stat(inPath) + .then((stats) => { + const inM = stats.mtime + const outM = job.out?.mtime ?? new Date(0) + + if (outM <= inM) emitter.emit('job', job) + }) + .catch(() => { + emitter.emit('job', job) + }) + pendingChecks.push(checkPromise) + }) + + return emitter +} + +function makeJobEmitter( + inputs: string[], + { + recursive, + outstreamProvider, + streamRegistry, + watch: watchOption, + reprocessStale, + }: JobEmitterOptions, +): MyEventEmitter { + const emitter = new EventEmitter() + + const emitterFns: (() => MyEventEmitter)[] = [] + const watcherFns: (() => MyEventEmitter)[] = [] + + async function processInputs(): Promise { + for (const input of inputs) { + if (input === '-') { + emitterFns.push( + () => new SingleJobEmitter({ file: input, outstreamProvider, streamRegistry }), + ) + watcherFns.push(() => new NullJobEmitter()) + } else { + const stats = await fsp.stat(input) + if (stats.isDirectory()) { + emitterFns.push( + () => + new ReaddirJobEmitter({ dir: input, recursive, outstreamProvider, streamRegistry }), + ) + watcherFns.push( + () => + new WatchJobEmitter({ file: input, recursive, outstreamProvider, streamRegistry }), + ) + } else { + emitterFns.push( + () => new SingleJobEmitter({ file: input, outstreamProvider, streamRegistry }), + ) + watcherFns.push( + () => + new WatchJobEmitter({ file: input, recursive, outstreamProvider, streamRegistry }), + ) + } + } + } + + if (inputs.length === 0) { + emitterFns.push(() => new InputlessJobEmitter({ outstreamProvider, streamRegistry })) + } + + startEmitting() + } + + function startEmitting(): void { + let source: MyEventEmitter = new MergedJobEmitter(...emitterFns.map((f) => f())) + + if (watchOption) { + source = new ConcattedJobEmitter( + () => source, + () => new MergedJobEmitter(...watcherFns.map((f) => f())), + ) + } + + source.on('job', (job: Job) => emitter.emit('job', job)) + source.on('error', (err: Error) => emitter.emit('error', err)) + source.on('end', () => emitter.emit('end')) + } + + processInputs().catch((err) => { + emitter.emit('error', err) + }) + + const stalefilter = reprocessStale ? (x: EventEmitter) => x as MyEventEmitter : dismissStaleJobs + return stalefilter(detectConflicts(emitter)) +} + +export interface AssembliesCreateOptions { + steps?: string + template?: string + fields?: Record + watch?: boolean + recursive?: boolean + inputs: string[] + output?: string | null + del?: boolean + reprocessStale?: boolean + singleAssembly?: boolean + concurrency?: number +} + +const DEFAULT_CONCURRENCY = 5 + +// --- Main assembly create function --- +export async function create( + outputctl: IOutputCtl, + client: Transloadit, + { + steps, + template, + fields, + watch: watchOption, + recursive, + inputs, + output, + del, + reprocessStale, + singleAssembly, + concurrency = DEFAULT_CONCURRENCY, + }: AssembliesCreateOptions, +): Promise<{ results: unknown[]; hasFailures: boolean }> { + // Quick fix for https://github.com/transloadit/transloadify/issues/13 + // Only default to stdout when output is undefined (not provided), not when explicitly null + let resolvedOutput = output + if (resolvedOutput === undefined && !process.stdout.isTTY) resolvedOutput = '-' + + // Read steps file async before entering the Promise constructor + // We use StepsInput (the input type) rather than Steps (the transformed output type) + // to avoid zod adding default values that the API may reject + let stepsData: StepsInput | undefined + if (steps) { + const stepsContent = await fsp.readFile(steps, 'utf8') + const parsed: unknown = JSON.parse(stepsContent) + // Basic structural validation: must be an object with step names as keys + if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed)) { + throw new Error('Invalid steps format: expected an object with step names as keys') + } + // Validate each step has a robot field + for (const [stepName, step] of Object.entries(parsed)) { + if (step == null || typeof step !== 'object' || Array.isArray(step)) { + throw new Error(`Invalid steps format: step '${stepName}' must be an object`) + } + if (!('robot' in step) || typeof (step as Record).robot !== 'string') { + throw new Error( + `Invalid steps format: step '${stepName}' must have a 'robot' string property`, + ) + } + } + stepsData = parsed as StepsInput + } + + // Determine output stat async before entering the Promise constructor + let outstat: StatLike | undefined + if (resolvedOutput != null) { + const [err, stat] = await tryCatch(myStat(process.stdout, resolvedOutput)) + if (err && (!isErrnoException(err) || err.code !== 'ENOENT')) throw err + outstat = stat ?? { isDirectory: () => false } + + if (!outstat.isDirectory() && inputs.length !== 0) { + const firstInput = inputs[0] + if (firstInput) { + const firstInputStat = await myStat(process.stdin, firstInput) + if (inputs.length > 1 || firstInputStat.isDirectory()) { + const msg = 'Output must be a directory when specifying multiple inputs' + outputctl.error(msg) + throw new Error(msg) + } + } + } + } + + return new Promise((resolve, reject) => { + const params: CreateAssemblyParams = ( + stepsData ? { steps: stepsData as CreateAssemblyParams['steps'] } : { template_id: template } + ) as CreateAssemblyParams + if (fields) { + params.fields = fields + } + + const outstreamProvider: OutstreamProvider = + resolvedOutput == null + ? nullProvider() + : outstat?.isDirectory() + ? dirProvider(resolvedOutput) + : fileProvider(resolvedOutput) + const streamRegistry: StreamRegistry = {} + + const emitter = makeJobEmitter(inputs, { + recursive, + watch: watchOption, + outstreamProvider, + streamRegistry, + reprocessStale, + }) + + // Use p-queue for concurrency management + const queue = new PQueue({ concurrency }) + const results: unknown[] = [] + let hasFailures = false + // AbortController to cancel all in-flight createAssembly calls when an error occurs + const abortController = new AbortController() + + // Helper to process a single assembly job + async function processAssemblyJob( + inPath: string | null, + outPath: string | null, + outMtime: Date | undefined, + ): Promise { + outputctl.debug(`PROCESSING JOB ${inPath ?? 'null'} ${outPath ?? 'null'}`) + + // Create fresh streams for this job + const inStream = inPath ? fs.createReadStream(inPath) : null + inStream?.on('error', () => {}) + const outStream = outPath ? (fs.createWriteStream(outPath) as OutStream) : null + outStream?.on('error', () => {}) + if (outStream) outStream.mtime = outMtime + + let superceded = false + if (outStream != null) { + outStream.on('finish', () => { + superceded = true + }) + } + + const createOptions: CreateAssemblyOptions = { + params, + signal: abortController.signal, + } + if (inStream != null) { + createOptions.uploads = { in: inStream } + } + + const result = await client.createAssembly(createOptions) + if (superceded) return undefined + + const assemblyId = result.assembly_id + if (!assemblyId) throw new Error('No assembly_id in result') + + const assembly = await client.awaitAssemblyCompletion(assemblyId, { + signal: abortController.signal, + onPoll: () => { + if (superceded) return false + return true + }, + onAssemblyProgress: (status) => { + outputctl.debug(`Assembly status: ${status.ok}`) + }, + }) + + if (superceded) return undefined + + if (assembly.error || (assembly.ok && assembly.ok !== 'ASSEMBLY_COMPLETED')) { + const msg = `Assembly failed: ${assembly.error || assembly.message} (Status: ${assembly.ok})` + outputctl.error(msg) + throw new Error(msg) + } + + if (!assembly.results) throw new Error('No results in assembly') + const resultsKeys = Object.keys(assembly.results) + const firstKey = resultsKeys[0] + if (!firstKey) throw new Error('No results in assembly') + const firstResult = assembly.results[firstKey] + if (!firstResult || !firstResult[0]) throw new Error('No results in assembly') + const resulturl = firstResult[0].url + + if (outStream != null && resulturl && !superceded) { + outputctl.debug('DOWNLOADING') + const [dlErr] = await tryCatch( + pipeline(got.stream(resulturl, { signal: abortController.signal }), outStream), + ) + if (dlErr) { + if (dlErr.name !== 'AbortError') { + outputctl.error(dlErr.message) + throw dlErr + } + } + } + + outputctl.debug(`COMPLETED ${inPath ?? 'null'} ${outPath ?? 'null'}`) + + if (del && inPath) { + await fsp.unlink(inPath) + } + return assembly + } + + if (singleAssembly) { + // Single-assembly mode: collect file paths, then create one assembly with all inputs + // We close streams immediately to avoid exhausting file descriptors with many files + const collectedPaths: string[] = [] + + emitter.on('job', (job: Job) => { + if (job.in != null) { + const inPath = (job.in as fs.ReadStream).path as string + outputctl.debug(`COLLECTING JOB ${inPath}`) + collectedPaths.push(inPath) + // Close the stream immediately to avoid file descriptor exhaustion + ;(job.in as fs.ReadStream).destroy() + outputctl.debug(`STREAM CLOSED ${inPath}`) + } + }) + + emitter.on('error', (err: Error) => { + abortController.abort() + queue.clear() + outputctl.error(err) + reject(err) + }) + + emitter.on('end', async () => { + if (collectedPaths.length === 0) { + resolve({ results: [], hasFailures: false }) + return + } + + // Build uploads object, creating fresh streams for each file + const uploads: Record = {} + const inputPaths: string[] = [] + for (const inPath of collectedPaths) { + const basename = path.basename(inPath) + let key = basename + let counter = 1 + while (key in uploads) { + key = `${path.parse(basename).name}_${counter}${path.parse(basename).ext}` + counter++ + } + uploads[key] = fs.createReadStream(inPath) + inputPaths.push(inPath) + } + + outputctl.debug(`Creating single assembly with ${Object.keys(uploads).length} files`) + + try { + const assembly = await queue.add(async () => { + const createOptions: CreateAssemblyOptions = { + params, + signal: abortController.signal, + } + if (Object.keys(uploads).length > 0) { + createOptions.uploads = uploads + } + + const result = await client.createAssembly(createOptions) + const assemblyId = result.assembly_id + if (!assemblyId) throw new Error('No assembly_id in result') + + const asm = await client.awaitAssemblyCompletion(assemblyId, { + signal: abortController.signal, + onAssemblyProgress: (status) => { + outputctl.debug(`Assembly status: ${status.ok}`) + }, + }) + + if (asm.error || (asm.ok && asm.ok !== 'ASSEMBLY_COMPLETED')) { + const msg = `Assembly failed: ${asm.error || asm.message} (Status: ${asm.ok})` + outputctl.error(msg) + throw new Error(msg) + } + + // Download all results + if (asm.results && resolvedOutput != null) { + for (const [stepName, stepResults] of Object.entries(asm.results)) { + for (const stepResult of stepResults) { + const resultUrl = stepResult.url + if (!resultUrl) continue + + let outPath: string + if (outstat?.isDirectory()) { + outPath = path.join(resolvedOutput, stepResult.name || `${stepName}_result`) + } else { + outPath = resolvedOutput + } + + outputctl.debug(`DOWNLOADING ${stepResult.name} to ${outPath}`) + const [dlErr] = await tryCatch( + pipeline( + got.stream(resultUrl, { signal: abortController.signal }), + fs.createWriteStream(outPath), + ), + ) + if (dlErr) { + if (dlErr.name === 'AbortError') continue + outputctl.error(dlErr.message) + throw dlErr + } + } + } + } + + // Delete input files if requested + if (del) { + for (const inPath of inputPaths) { + await fsp.unlink(inPath) + } + } + return asm + }) + results.push(assembly) + } catch (err) { + hasFailures = true + outputctl.error(err as Error) + } + + resolve({ results, hasFailures }) + }) + } else { + // Default mode: one assembly per file with p-queue concurrency limiting + emitter.on('job', (job: Job) => { + const inPath = job.in + ? (((job.in as fs.ReadStream).path as string | undefined) ?? null) + : null + const outPath = job.out?.path ?? null + const outMtime = job.out?.mtime + outputctl.debug(`GOT JOB ${inPath ?? 'null'} ${outPath ?? 'null'}`) + + // Close the original streams immediately - we'll create fresh ones when processing + if (job.in != null) { + ;(job.in as fs.ReadStream).destroy() + } + if (job.out != null) { + job.out.destroy() + } + + // Add job to queue - p-queue handles concurrency automatically + queue + .add(async () => { + const result = await processAssemblyJob(inPath, outPath, outMtime) + if (result !== undefined) { + results.push(result) + } + }) + .catch((err: unknown) => { + hasFailures = true + outputctl.error(err as Error) + }) + }) + + emitter.on('error', (err: Error) => { + abortController.abort() + queue.clear() + outputctl.error(err) + reject(err) + }) + + emitter.on('end', async () => { + // Wait for all queued jobs to complete + await queue.onIdle() + resolve({ results, hasFailures }) + }) + } + }) +} + +// --- Command classes --- +export class AssembliesCreateCommand extends AuthenticatedCommand { + static override paths = [ + ['assemblies', 'create'], + ['assembly', 'create'], + ['a', 'create'], + ['a', 'c'], + ] + + static override usage = Command.Usage({ + category: 'Assemblies', + description: 'Create assemblies to process media', + details: ` + Create assemblies to process media files using Transloadit. + You must specify either --steps or --template. + `, + examples: [ + [ + 'Process a file with steps', + 'transloadit assemblies create --steps steps.json -i input.jpg -o output.jpg', + ], + [ + 'Process with a template', + 'transloadit assemblies create --template TEMPLATE_ID -i input.jpg -o output/', + ], + [ + 'Watch for changes', + 'transloadit assemblies create --steps steps.json -i input/ -o output/ --watch', + ], + ], + }) + + steps = Option.String('--steps,-s', { + description: 'Specify assembly instructions with a JSON file', + }) + + template = Option.String('--template,-t', { + description: 'Specify a template to use for these assemblies', + }) + + inputs = Option.Array('--input,-i', { + description: 'Provide an input file or a directory', + }) + + outputPath = Option.String('--output,-o', { + description: 'Specify an output file or directory', + }) + + fields = Option.Array('--field,-f', { + description: 'Set a template field (KEY=VAL)', + }) + + watch = Option.Boolean('--watch,-w', false, { + description: 'Watch inputs for changes', + }) + + recursive = Option.Boolean('--recursive,-r', false, { + description: 'Enumerate input directories recursively', + }) + + deleteAfterProcessing = Option.Boolean('--delete-after-processing,-d', false, { + description: 'Delete input files after they are processed', + }) + + reprocessStale = Option.Boolean('--reprocess-stale', false, { + description: 'Process inputs even if output is newer', + }) + + singleAssembly = Option.Boolean('--single-assembly', false, { + description: 'Pass all input files to a single assembly instead of one assembly per file', + }) + + concurrency = Option.String('--concurrency,-c', { + description: 'Maximum number of concurrent assemblies (default: 5)', + validator: t.isNumber(), + }) + + protected async run(): Promise { + if (!this.steps && !this.template) { + this.output.error('assemblies create requires exactly one of either --steps or --template') + return 1 + } + if (this.steps && this.template) { + this.output.error('assemblies create requires exactly one of either --steps or --template') + return 1 + } + + const inputList = this.inputs ?? [] + if (inputList.length === 0 && this.watch) { + this.output.error('assemblies create --watch requires at least one input') + return 1 + } + + // Default to stdin if no inputs and not a TTY + if (inputList.length === 0 && !process.stdin.isTTY) { + inputList.push('-') + } + + const fieldsMap: Record = {} + for (const field of this.fields ?? []) { + const eqIndex = field.indexOf('=') + if (eqIndex === -1) { + this.output.error(`invalid argument for --field: '${field}'`) + return 1 + } + const key = field.slice(0, eqIndex) + const value = field.slice(eqIndex + 1) + fieldsMap[key] = value + } + + if (this.singleAssembly && this.watch) { + this.output.error('--single-assembly cannot be used with --watch') + return 1 + } + + const { hasFailures } = await create(this.output, this.client, { + steps: this.steps, + template: this.template, + fields: fieldsMap, + watch: this.watch, + recursive: this.recursive, + inputs: inputList, + output: this.outputPath ?? null, + del: this.deleteAfterProcessing, + reprocessStale: this.reprocessStale, + singleAssembly: this.singleAssembly, + concurrency: this.concurrency, + }) + return hasFailures ? 1 : undefined + } +} + +export class AssembliesListCommand extends AuthenticatedCommand { + static override paths = [ + ['assemblies', 'list'], + ['assembly', 'list'], + ['a', 'list'], + ['a', 'l'], + ] + + static override usage = Command.Usage({ + category: 'Assemblies', + description: 'List assemblies matching given criteria', + examples: [ + ['List recent assemblies', 'transloadit assemblies list'], + ['List assemblies after a date', 'transloadit assemblies list --after 2024-01-01'], + ], + }) + + before = Option.String('--before,-b', { + description: 'Return only assemblies created before specified date', + }) + + after = Option.String('--after,-a', { + description: 'Return only assemblies created after specified date', + }) + + keywords = Option.String('--keywords', { + description: 'Comma-separated list of keywords to match assemblies', + }) + + fields = Option.String('--fields', { + description: 'Comma-separated list of fields to return for each assembly', + }) + + protected async run(): Promise { + const keywordList = this.keywords ? this.keywords.split(',') : undefined + const fieldList = this.fields ? this.fields.split(',') : undefined + + await list(this.output, this.client, { + before: this.before, + after: this.after, + keywords: keywordList, + fields: fieldList, + }) + return undefined + } +} + +export class AssembliesGetCommand extends AuthenticatedCommand { + static override paths = [ + ['assemblies', 'get'], + ['assembly', 'get'], + ['a', 'get'], + ['a', 'g'], + ] + + static override usage = Command.Usage({ + category: 'Assemblies', + description: 'Fetch assembly statuses', + examples: [['Get assembly status', 'transloadit assemblies get ASSEMBLY_ID']], + }) + + assemblyIds = Option.Rest({ required: 1 }) + + protected async run(): Promise { + await get(this.output, this.client, { + assemblies: this.assemblyIds, + }) + return undefined + } +} + +export class AssembliesDeleteCommand extends AuthenticatedCommand { + static override paths = [ + ['assemblies', 'delete'], + ['assembly', 'delete'], + ['a', 'delete'], + ['a', 'd'], + ['assemblies', 'cancel'], + ['assembly', 'cancel'], + ] + + static override usage = Command.Usage({ + category: 'Assemblies', + description: 'Cancel assemblies', + examples: [['Cancel an assembly', 'transloadit assemblies delete ASSEMBLY_ID']], + }) + + assemblyIds = Option.Rest({ required: 1 }) + + protected async run(): Promise { + await deleteAssemblies(this.output, this.client, { + assemblies: this.assemblyIds, + }) + return undefined + } +} + +export class AssembliesReplayCommand extends AuthenticatedCommand { + static override paths = [ + ['assemblies', 'replay'], + ['assembly', 'replay'], + ['a', 'replay'], + ['a', 'r'], + ] + + static override usage = Command.Usage({ + category: 'Assemblies', + description: 'Replay assemblies', + details: ` + Replay one or more assemblies. By default, replays use the original assembly instructions. + Use --steps to override the instructions, or --reparse-template to use the latest template version. + `, + examples: [ + ['Replay an assembly with original steps', 'transloadit assemblies replay ASSEMBLY_ID'], + [ + 'Replay with different steps', + 'transloadit assemblies replay --steps new-steps.json ASSEMBLY_ID', + ], + [ + 'Replay with updated template', + 'transloadit assemblies replay --reparse-template ASSEMBLY_ID', + ], + ], + }) + + fields = Option.Array('--field,-f', { + description: 'Set a template field (KEY=VAL)', + }) + + steps = Option.String('--steps,-s', { + description: 'Optional JSON file to override assembly instructions', + }) + + notifyUrl = Option.String('--notify-url', { + description: 'Specify a new URL for assembly notifications', + }) + + reparseTemplate = Option.Boolean('--reparse-template', false, { + description: 'Use the most up-to-date version of the template', + }) + + assemblyIds = Option.Rest({ required: 1 }) + + protected async run(): Promise { + const fieldsMap: Record = {} + for (const field of this.fields ?? []) { + const eqIndex = field.indexOf('=') + if (eqIndex === -1) { + this.output.error(`invalid argument for --field: '${field}'`) + return 1 + } + const key = field.slice(0, eqIndex) + const value = field.slice(eqIndex + 1) + fieldsMap[key] = value + } + + await replay(this.output, this.client, { + fields: fieldsMap, + reparse: this.reparseTemplate, + steps: this.steps, + notify_url: this.notifyUrl, + assemblies: this.assemblyIds, + }) + return undefined + } +} diff --git a/packages/transloadit/src/cli/commands/auth.ts b/packages/transloadit/src/cli/commands/auth.ts new file mode 100644 index 00000000..9394367e --- /dev/null +++ b/packages/transloadit/src/cli/commands/auth.ts @@ -0,0 +1,354 @@ +import process from 'node:process' +import { Command, Option } from 'clipanion' +import type { ZodIssue } from 'zod' +import { z } from 'zod' +import { + assemblyAuthInstructionsSchema, + assemblyInstructionsSchema, +} from '../../alphalib/types/template.ts' +import type { OptionalAuthParams } from '../../apiTypes.ts' +import { Transloadit } from '../../Transloadit.ts' +import { getEnvCredentials } from '../helpers.ts' +import { UnauthenticatedCommand } from './BaseCommand.ts' + +type UrlParamPrimitive = string | number | boolean +type UrlParamArray = UrlParamPrimitive[] +type NormalizedUrlParams = Record + +const smartCdnParamsSchema = z + .object({ + workspace: z.string().min(1, 'workspace is required'), + template: z.string().min(1, 'template is required'), + input: z.union([z.string(), z.number(), z.boolean()]), + url_params: z.record(z.unknown()).optional(), + expire_at_ms: z.union([z.number(), z.string()]).optional(), + }) + .passthrough() + +const cliSignatureParamsSchema = assemblyInstructionsSchema + .extend({ auth: assemblyAuthInstructionsSchema.partial().optional() }) + .partial() + .passthrough() + +type CliSignatureParams = z.infer + +function formatIssues(issues: ZodIssue[]): string { + return issues + .map((issue) => { + const path = issue.path.join('.') || '(root)' + return `${path}: ${issue.message}` + }) + .join('; ') +} + +function normalizeUrlParam(value: unknown): UrlParamPrimitive | UrlParamArray | undefined { + if (value == null) return undefined + if (Array.isArray(value)) { + const normalized = value.filter( + (item): item is UrlParamPrimitive => + typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean', + ) + return normalized.length > 0 ? normalized : undefined + } + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + return value + } + return undefined +} + +function normalizeUrlParams(params?: Record): NormalizedUrlParams | undefined { + if (params == null) return undefined + let normalized: NormalizedUrlParams | undefined + for (const [key, value] of Object.entries(params)) { + const normalizedValue = normalizeUrlParam(value) + if (normalizedValue === undefined) continue + if (normalized == null) normalized = {} + normalized[key] = normalizedValue + } + return normalized +} + +async function readStdin(): Promise { + if (process.stdin.isTTY) return '' + + process.stdin.setEncoding('utf8') + let data = '' + + for await (const chunk of process.stdin) { + data += chunk + } + + return data +} + +const getCredentials = getEnvCredentials + +// Result type for signature operations +type SigResult = { ok: true; output: string } | { ok: false; error: string } + +// Core logic for signature generation +function generateSignature( + input: string, + credentials: { authKey: string; authSecret: string }, + algorithm?: string, +): SigResult { + const { authKey, authSecret } = credentials + let params: CliSignatureParams + + if (input === '') { + params = { auth: { key: authKey } } + } else { + let parsed: unknown + try { + parsed = JSON.parse(input) + } catch (error) { + return { ok: false, error: `Failed to parse JSON from stdin: ${(error as Error).message}` } + } + + if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed)) { + return { ok: false, error: 'Invalid params provided via stdin. Expected a JSON object.' } + } + + const parsedResult = cliSignatureParamsSchema.safeParse(parsed) + if (!parsedResult.success) { + return { ok: false, error: `Invalid params: ${formatIssues(parsedResult.error.issues)}` } + } + + const parsedParams = parsedResult.data + const existingAuth = parsedParams.auth ?? {} + + params = { + ...parsedParams, + auth: { + ...existingAuth, + key: authKey, + }, + } + } + + const client = new Transloadit({ authKey, authSecret }) + try { + const signature = client.calcSignature(params as OptionalAuthParams, algorithm) + return { ok: true, output: JSON.stringify(signature) } + } catch (error) { + return { ok: false, error: `Failed to generate signature: ${(error as Error).message}` } + } +} + +// Core logic for Smart CDN URL generation +function generateSmartCdnUrl( + input: string, + credentials: { authKey: string; authSecret: string }, +): SigResult { + const { authKey, authSecret } = credentials + + if (input === '') { + return { + ok: false, + error: + 'Missing params provided via stdin. Expected a JSON object with workspace, template, input, and optional Smart CDN parameters.', + } + } + + let parsed: unknown + try { + parsed = JSON.parse(input) + } catch (error) { + return { ok: false, error: `Failed to parse JSON from stdin: ${(error as Error).message}` } + } + + if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed)) { + return { ok: false, error: 'Invalid params provided via stdin. Expected a JSON object.' } + } + + const parsedResult = smartCdnParamsSchema.safeParse(parsed) + if (!parsedResult.success) { + return { ok: false, error: `Invalid params: ${formatIssues(parsedResult.error.issues)}` } + } + + const { workspace, template, input: inputFieldRaw, url_params, expire_at_ms } = parsedResult.data + const urlParams = normalizeUrlParams(url_params) + + let expiresAt: number | undefined + if (typeof expire_at_ms === 'string') { + const parsedNumber = Number.parseInt(expire_at_ms, 10) + if (Number.isNaN(parsedNumber)) { + return { ok: false, error: 'Invalid params: expire_at_ms must be a number.' } + } + expiresAt = parsedNumber + } else { + expiresAt = expire_at_ms + } + + const inputField = typeof inputFieldRaw === 'string' ? inputFieldRaw : String(inputFieldRaw) + + const client = new Transloadit({ authKey, authSecret }) + try { + const signedUrl = client.getSignedSmartCDNUrl({ + workspace, + template, + input: inputField, + urlParams, + expiresAt, + }) + return { ok: true, output: signedUrl } + } catch (error) { + return { ok: false, error: `Failed to generate Smart CDN URL: ${(error as Error).message}` } + } +} + +// Testable helper functions exported for unit tests +export interface RunSigOptions { + providedInput?: string + algorithm?: string +} + +export interface RunSmartSigOptions { + providedInput?: string +} + +export async function runSig(options: RunSigOptions = {}): Promise { + const credentials = getCredentials() + if (credentials == null) { + console.error( + 'Missing credentials. Please set TRANSLOADIT_KEY and TRANSLOADIT_SECRET environment variables.', + ) + process.exitCode = 1 + return + } + + const rawInput = options.providedInput ?? (await readStdin()) + const result = generateSignature(rawInput.trim(), credentials, options.algorithm) + + if (result.ok) { + process.stdout.write(`${result.output}\n`) + } else { + console.error(result.error) + process.exitCode = 1 + } +} + +export async function runSmartSig(options: RunSmartSigOptions = {}): Promise { + const credentials = getCredentials() + if (credentials == null) { + console.error( + 'Missing credentials. Please set TRANSLOADIT_KEY and TRANSLOADIT_SECRET environment variables.', + ) + process.exitCode = 1 + return + } + + const rawInput = options.providedInput ?? (await readStdin()) + const result = generateSmartCdnUrl(rawInput.trim(), credentials) + + if (result.ok) { + process.stdout.write(`${result.output}\n`) + } else { + console.error(result.error) + process.exitCode = 1 + } +} + +/** + * Generate a signature for assembly params + */ +export class SignatureCommand extends UnauthenticatedCommand { + static override paths = [ + ['auth', 'signature'], + ['auth', 'sig'], + ['signature'], + ['sig'], // BC alias + ] + + static override usage = Command.Usage({ + category: 'Auth', + description: 'Generate a signature for assembly params', + details: ` + Read params JSON from stdin and output signed payload JSON. + If no input is provided, generates a signature with default params. + `, + examples: [ + ['Generate signature', 'echo \'{"steps":{}}\' | transloadit signature'], + ['With algorithm', 'echo \'{"steps":{}}\' | transloadit signature --algorithm sha384'], + ['Using alias', 'echo \'{"steps":{}}\' | transloadit sig'], + ], + }) + + algorithm = Option.String('--algorithm,-a', { + description: 'Signature algorithm to use (sha1, sha256, sha384, sha512)', + }) + + protected async run(): Promise { + const credentials = getCredentials() + if (credentials == null) { + this.output.error( + 'Missing credentials. Please set TRANSLOADIT_KEY and TRANSLOADIT_SECRET environment variables.', + ) + return 1 + } + + const rawInput = await readStdin() + const result = generateSignature(rawInput.trim(), credentials, this.algorithm) + + if (result.ok) { + process.stdout.write(`${result.output}\n`) + return undefined + } + + this.output.error(result.error) + return 1 + } +} + +/** + * Generate a signed Smart CDN URL + */ +export class SmartCdnSignatureCommand extends UnauthenticatedCommand { + static override paths = [ + ['auth', 'smart-cdn'], + ['auth', 'smart_cdn'], + ['smart-cdn'], + ['smart_sig'], // BC alias + ] + + static override usage = Command.Usage({ + category: 'Auth', + description: 'Generate a signed Smart CDN URL', + details: ` + Read Smart CDN params JSON from stdin and output a signed URL. + Required fields: workspace, template, input + Optional fields: expire_at_ms, url_params + `, + examples: [ + [ + 'Generate Smart CDN URL', + 'echo \'{"workspace":"w","template":"t","input":"i"}\' | transloadit smart-cdn', + ], + [ + 'Using alias', + 'echo \'{"workspace":"w","template":"t","input":"i"}\' | transloadit smart_sig', + ], + ], + }) + + protected async run(): Promise { + const credentials = getCredentials() + if (credentials == null) { + this.output.error( + 'Missing credentials. Please set TRANSLOADIT_KEY and TRANSLOADIT_SECRET environment variables.', + ) + return 1 + } + + const rawInput = await readStdin() + const result = generateSmartCdnUrl(rawInput.trim(), credentials) + + if (result.ok) { + process.stdout.write(`${result.output}\n`) + return undefined + } + + this.output.error(result.error) + return 1 + } +} diff --git a/packages/transloadit/src/cli/commands/bills.ts b/packages/transloadit/src/cli/commands/bills.ts new file mode 100644 index 00000000..03d0a998 --- /dev/null +++ b/packages/transloadit/src/cli/commands/bills.ts @@ -0,0 +1,91 @@ +import { Command, Option } from 'clipanion' +import { z } from 'zod' +import { tryCatch } from '../../alphalib/tryCatch.ts' +import type { Transloadit } from '../../Transloadit.ts' +import { formatAPIError } from '../helpers.ts' +import type { IOutputCtl } from '../OutputCtl.ts' +import { AuthenticatedCommand } from './BaseCommand.ts' + +// --- Types and business logic --- + +export interface BillsGetOptions { + months: string[] +} + +const BillResponseSchema = z.object({ + total: z.number(), +}) + +export async function get( + output: IOutputCtl, + client: Transloadit, + { months }: BillsGetOptions, +): Promise { + const requests = months.map((month) => client.getBill(month)) + + const [err, results] = await tryCatch(Promise.all(requests)) + if (err) { + output.error(formatAPIError(err)) + return + } + + for (const result of results) { + const parsed = BillResponseSchema.safeParse(result) + if (parsed.success) { + output.print(`$${parsed.data.total}`, result) + } else { + output.print('Unable to parse bill response', result) + } + } +} + +// --- Command class --- + +export class BillsGetCommand extends AuthenticatedCommand { + static override paths = [ + ['bills', 'get'], + ['bill', 'get'], + ['b', 'get'], + ['b', 'g'], + ] + + static override usage = Command.Usage({ + category: 'Bills', + description: 'Fetch billing information', + details: ` + Fetch billing information for the specified months. + Months should be specified in YYYY-MM format. + If no month is specified, returns the current month. + `, + examples: [ + ['Get current month billing', 'transloadit bills get'], + ['Get specific month', 'transloadit bills get 2024-01'], + ['Get multiple months', 'transloadit bills get 2024-01 2024-02'], + ], + }) + + months = Option.Rest() + + protected async run(): Promise { + const monthList: string[] = [] + + for (const month of this.months) { + if (!/^\d{4}-\d{1,2}$/.test(month)) { + this.output.error(`invalid date format '${month}' (YYYY-MM)`) + return 1 + } + monthList.push(month) + } + + // Default to current month if none specified + if (monthList.length === 0) { + const d = new Date() + monthList.push(`${d.getUTCFullYear()}-${d.getUTCMonth() + 1}`) + } + + await get(this.output, this.client, { + months: monthList, + }) + return undefined + } +} diff --git a/packages/transloadit/src/cli/commands/index.ts b/packages/transloadit/src/cli/commands/index.ts new file mode 100644 index 00000000..5837d5a9 --- /dev/null +++ b/packages/transloadit/src/cli/commands/index.ts @@ -0,0 +1,65 @@ +import { Builtins, Cli } from 'clipanion' + +import packageJson from '../../../package.json' with { type: 'json' } + +import { + AssembliesCreateCommand, + AssembliesDeleteCommand, + AssembliesGetCommand, + AssembliesListCommand, + AssembliesReplayCommand, +} from './assemblies.ts' + +import { SignatureCommand, SmartCdnSignatureCommand } from './auth.ts' + +import { BillsGetCommand } from './bills.ts' + +import { NotificationsReplayCommand } from './notifications.ts' + +import { + TemplatesCreateCommand, + TemplatesDeleteCommand, + TemplatesGetCommand, + TemplatesListCommand, + TemplatesModifyCommand, + TemplatesSyncCommand, +} from './templates.ts' + +export function createCli(): Cli { + const cli = new Cli({ + binaryLabel: 'Transloadit CLI', + binaryName: 'transloadit', + binaryVersion: packageJson.version, + }) + + // Built-in commands + cli.register(Builtins.HelpCommand) + cli.register(Builtins.VersionCommand) + + // Auth commands (signature generation) + cli.register(SignatureCommand) + cli.register(SmartCdnSignatureCommand) + + // Assemblies commands + cli.register(AssembliesCreateCommand) + cli.register(AssembliesListCommand) + cli.register(AssembliesGetCommand) + cli.register(AssembliesDeleteCommand) + cli.register(AssembliesReplayCommand) + + // Templates commands + cli.register(TemplatesCreateCommand) + cli.register(TemplatesGetCommand) + cli.register(TemplatesModifyCommand) + cli.register(TemplatesDeleteCommand) + cli.register(TemplatesListCommand) + cli.register(TemplatesSyncCommand) + + // Bills commands + cli.register(BillsGetCommand) + + // Notifications commands + cli.register(NotificationsReplayCommand) + + return cli +} diff --git a/packages/transloadit/src/cli/commands/notifications.ts b/packages/transloadit/src/cli/commands/notifications.ts new file mode 100644 index 00000000..e65ba452 --- /dev/null +++ b/packages/transloadit/src/cli/commands/notifications.ts @@ -0,0 +1,63 @@ +import { Command, Option } from 'clipanion' +import { tryCatch } from '../../alphalib/tryCatch.ts' +import type { Transloadit } from '../../Transloadit.ts' +import type { IOutputCtl } from '../OutputCtl.ts' +import { ensureError } from '../types.ts' +import { AuthenticatedCommand } from './BaseCommand.ts' + +// --- Types and business logic --- + +interface NotificationsReplayOptions { + notify_url?: string + assemblies: string[] +} + +async function replay( + output: IOutputCtl, + client: Transloadit, + { notify_url, assemblies }: NotificationsReplayOptions, +): Promise { + const promises = assemblies.map((id) => client.replayAssemblyNotification(id, { notify_url })) + const [err] = await tryCatch(Promise.all(promises)) + if (err) { + output.error(ensureError(err).message) + } +} + +// --- Command class --- + +export class NotificationsReplayCommand extends AuthenticatedCommand { + static override paths = [ + ['assembly-notifications', 'replay'], + ['notifications', 'replay'], + ['notification', 'replay'], + ['n', 'replay'], + ['n', 'r'], + ] + + static override usage = Command.Usage({ + category: 'Notifications', + description: 'Replay notifications for assemblies', + examples: [ + ['Replay notifications', 'transloadit assembly-notifications replay ASSEMBLY_ID'], + [ + 'Replay to a new URL', + 'transloadit assembly-notifications replay --notify-url https://example.com/notify ASSEMBLY_ID', + ], + ], + }) + + notifyUrl = Option.String('--notify-url', { + description: 'Specify a new URL to send the notifications to', + }) + + assemblyIds = Option.Rest({ required: 1 }) + + protected async run(): Promise { + await replay(this.output, this.client, { + notify_url: this.notifyUrl, + assemblies: this.assemblyIds, + }) + return undefined + } +} diff --git a/packages/transloadit/src/cli/commands/templates.ts b/packages/transloadit/src/cli/commands/templates.ts new file mode 100644 index 00000000..03405d54 --- /dev/null +++ b/packages/transloadit/src/cli/commands/templates.ts @@ -0,0 +1,556 @@ +import fsp from 'node:fs/promises' +import path from 'node:path' +import { promisify } from 'node:util' +import { Command, Option } from 'clipanion' +import rreaddir from 'recursive-readdir' +import { z } from 'zod' +import { tryCatch } from '../../alphalib/tryCatch.ts' +import type { Steps } from '../../alphalib/types/template.ts' +import { stepsSchema } from '../../alphalib/types/template.ts' +import type { TemplateContent } from '../../apiTypes.ts' +import type { Transloadit } from '../../Transloadit.ts' +import { createReadStream, formatAPIError, streamToBuffer } from '../helpers.ts' +import type { IOutputCtl } from '../OutputCtl.ts' +import ModifiedLookup from '../template-last-modified.ts' +import type { TemplateFile } from '../types.ts' +import { ensureError, isTransloaditAPIError, TemplateFileDataSchema } from '../types.ts' +import { AuthenticatedCommand } from './BaseCommand.ts' + +const rreaddirAsync = promisify(rreaddir) + +export interface TemplateCreateOptions { + name: string + file: string +} + +export interface TemplateGetOptions { + templates: string[] +} + +export interface TemplateModifyOptions { + template: string + name?: string + file: string +} + +interface TemplateDeleteOptions { + templates: string[] +} + +interface TemplateListOptions { + before?: string + after?: string + order?: 'asc' | 'desc' + sort?: string + fields?: string[] +} + +export interface TemplateSyncOptions { + files: string[] + recursive?: boolean +} + +export async function create( + output: IOutputCtl, + client: Transloadit, + { name, file }: TemplateCreateOptions, +): Promise { + try { + const buf = await streamToBuffer(createReadStream(file)) + + const parsed: unknown = JSON.parse(buf.toString()) + const validated = stepsSchema.safeParse(parsed) + if (!validated.success) { + throw new Error(`Invalid template steps format: ${validated.error.message}`) + } + + const result = await client.createTemplate({ + name, + // Steps (validated) is assignable to StepsInput at runtime; cast for TS + template: { steps: validated.data } as TemplateContent, + }) + output.print(result.id, result) + return result + } catch (err) { + const error = ensureError(err) + output.error(error.message) + throw err + } +} + +export async function get( + output: IOutputCtl, + client: Transloadit, + { templates }: TemplateGetOptions, +): Promise { + const requests = templates.map((template) => client.getTemplate(template)) + + const [err, results] = await tryCatch(Promise.all(requests)) + if (err) { + output.error(formatAPIError(err)) + throw err + } + + for (const result of results) { + output.print(result, result) + } +} + +export async function modify( + output: IOutputCtl, + client: Transloadit, + { template, name, file }: TemplateModifyOptions, +): Promise { + try { + const buf = await streamToBuffer(createReadStream(file)) + + let steps: Steps | null = null + let newName = name + + if (buf.length > 0) { + const parsed: unknown = JSON.parse(buf.toString()) + const validated = stepsSchema.safeParse(parsed) + if (!validated.success) { + throw new Error(`Invalid template steps format: ${validated.error.message}`) + } + steps = validated.data + } + + if (!name || buf.length === 0) { + const tpl = await client.getTemplate(template) + if (!name) newName = tpl.name + if (buf.length === 0 && tpl.content.steps) { + steps = tpl.content.steps + } + } + + if (steps === null) { + throw new Error('No steps to update template with') + } + + await client.editTemplate(template, { + name: newName, + // Steps (validated) is assignable to StepsInput at runtime; cast for TS + template: { steps } as TemplateContent, + }) + } catch (err) { + output.error(formatAPIError(err)) + throw err + } +} + +async function deleteTemplates( + output: IOutputCtl, + client: Transloadit, + { templates }: TemplateDeleteOptions, +): Promise { + await Promise.all( + templates.map(async (template) => { + const [err] = await tryCatch(client.deleteTemplate(template)) + if (err) { + output.error(formatAPIError(err)) + throw err + } + }), + ) +} + +// Export with `delete` alias for external consumers +export { deleteTemplates as delete } + +const TemplateIdSchema = z.object({ + id: z.string(), +}) + +function list( + output: IOutputCtl, + client: Transloadit, + { before, after, order, sort, fields }: TemplateListOptions, +): void { + const stream = client.streamTemplates({ + todate: before, + fromdate: after, + order, + sort: sort as 'id' | 'name' | 'created' | 'modified' | undefined, + }) + + stream.on('readable', () => { + const template: unknown = stream.read() + if (template == null) return + + const parsed = TemplateIdSchema.safeParse(template) + if (!parsed.success) return + + if (fields == null) { + output.print(parsed.data.id, template) + } else { + const templateRecord = template as Record + output.print(fields.map((field) => templateRecord[field]).join(' '), template) + } + }) + + stream.on('error', (err: unknown) => { + output.error(formatAPIError(err)) + }) +} + +export async function sync( + output: IOutputCtl, + client: Transloadit, + { files, recursive }: TemplateSyncOptions, +): Promise { + // Promise [String] -- all files in the directory tree + const relevantFilesNested = await Promise.all( + files.map(async (file) => { + const stats = await fsp.stat(file) + if (!stats.isDirectory()) return [file] + + let children: string[] + if (recursive) { + children = (await rreaddirAsync(file)) as string[] + } else { + const list = await fsp.readdir(file) + children = list.map((child) => path.join(file, child)) + } + + if (recursive) return children + + // Filter directories if not recursive + const filtered = await Promise.all( + children.map(async (child) => { + const childStats = await fsp.stat(child) + return childStats.isDirectory() ? null : child + }), + ) + return filtered.filter((f): f is string => f !== null) + }), + ) + const relevantFiles = relevantFilesNested.flat() + + // Promise [{ file: String, data: JSON }] -- all templates + const maybeFiles = await Promise.all(relevantFiles.map(templateFileOrNull)) + const templates = maybeFiles.filter((maybeFile): maybeFile is TemplateFile => maybeFile !== null) + + async function templateFileOrNull(file: string): Promise { + if (path.extname(file) !== '.json') return null + + try { + const data = await fsp.readFile(file, 'utf8') + const parsed: unknown = JSON.parse(data) + const validated = TemplateFileDataSchema.safeParse(parsed) + if (!validated.success) return null + return 'transloadit_template_id' in validated.data ? { file, data: validated.data } : null + } catch (e) { + if (e instanceof SyntaxError) return null + throw e + } + } + + const modified = new ModifiedLookup(client) + + const [err] = await tryCatch( + Promise.all( + templates.map(async (template) => { + if (!('steps' in template.data)) { + if (!template.data.transloadit_template_id) { + throw new Error(`Template file has no id and no steps: ${template.file}`) + } + return download(template) + } + + if (!template.data.transloadit_template_id) return upload(template) + + const stats = await fsp.stat(template.file) + const fileModified = stats.mtime + + let templateModified: Date + const templateId = template.data.transloadit_template_id + try { + await client.getTemplate(templateId) + templateModified = await new Promise((resolve, reject) => + modified.byId(templateId, (err, res) => { + if (err) { + reject(err) + } else if (res) { + resolve(res) + } else { + reject(new Error('No date returned')) + } + }), + ) + } catch (err) { + if (isTransloaditAPIError(err)) { + if (err.code === 'SERVER_404' || (err.response && err.response.statusCode === 404)) { + throw new Error(`Template file references nonexistent template: ${template.file}`) + } + } + throw err + } + + if (fileModified > templateModified) return upload(template) + return download(template) + }), + ), + ) + if (err) { + output.error(err) + throw err + } + + async function upload(template: TemplateFile): Promise { + const params = { + name: path.basename(template.file, '.json'), + template: { steps: template.data.steps } as TemplateContent, + } + + if (!template.data.transloadit_template_id) { + const result = await client.createTemplate(params) + template.data.transloadit_template_id = result.id + await fsp.writeFile(template.file, JSON.stringify(template.data)) + return + } + + await client.editTemplate(template.data.transloadit_template_id, params) + } + + async function download(template: TemplateFile): Promise { + const templateId = template.data.transloadit_template_id + if (!templateId) { + throw new Error('Cannot download template without id') + } + + const result = await client.getTemplate(templateId) + + // Use empty object if template has no steps (undefined would be stripped by JSON.stringify) + template.data.steps = result.content.steps ?? {} + const file = path.join(path.dirname(template.file), `${result.name}.json`) + + await fsp.writeFile(template.file, JSON.stringify(template.data)) + + if (file !== template.file) { + await fsp.rename(template.file, file) + } + } +} +export class TemplatesCreateCommand extends AuthenticatedCommand { + static override paths = [ + ['templates', 'create'], + ['template', 'create'], + ['t', 'create'], + ['t', 'c'], + ] + + static override usage = Command.Usage({ + category: 'Templates', + description: 'Create a new template', + details: ` + Create a new template with the given name. + If FILE is not specified, reads from STDIN. + `, + examples: [ + ['Create template from file', 'transloadit templates create my-template steps.json'], + ['Create template from stdin', 'cat steps.json | transloadit templates create my-template'], + ], + }) + + name = Option.String({ required: true }) + file = Option.String({ required: false }) + + protected async run(): Promise { + await create(this.output, this.client, { + name: this.name, + file: this.file ?? '-', + }) + return undefined + } +} + +export class TemplatesGetCommand extends AuthenticatedCommand { + static override paths = [ + ['templates', 'get'], + ['template', 'get'], + ['t', 'get'], + ['t', 'g'], + ] + + static override usage = Command.Usage({ + category: 'Templates', + description: 'Retrieve the template content as JSON', + examples: [['Get a template', 'transloadit templates get TEMPLATE_ID']], + }) + + templateIds = Option.Rest({ required: 1 }) + + protected async run(): Promise { + await get(this.output, this.client, { + templates: this.templateIds, + }) + return undefined + } +} + +export class TemplatesModifyCommand extends AuthenticatedCommand { + static override paths = [ + ['templates', 'modify'], + ['template', 'modify'], + ['t', 'modify'], + ['t', 'm'], + ['templates', 'edit'], + ['template', 'edit'], + ] + + static override usage = Command.Usage({ + category: 'Templates', + description: 'Change the JSON content of a template', + details: ` + Modify an existing template. + If FILE is not specified, reads from STDIN. + `, + examples: [ + ['Modify template from file', 'transloadit templates modify TEMPLATE_ID steps.json'], + ['Rename a template', 'transloadit templates modify --name new-name TEMPLATE_ID'], + ], + }) + + newName = Option.String('--name,-n', { + description: 'A new name for the template', + }) + + templateId = Option.String({ required: true }) + file = Option.String({ required: false }) + + protected async run(): Promise { + await modify(this.output, this.client, { + template: this.templateId, + name: this.newName, + file: this.file ?? '-', + }) + return undefined + } +} + +export class TemplatesDeleteCommand extends AuthenticatedCommand { + static override paths = [ + ['templates', 'delete'], + ['template', 'delete'], + ['t', 'delete'], + ['t', 'd'], + ] + + static override usage = Command.Usage({ + category: 'Templates', + description: 'Delete templates', + examples: [['Delete a template', 'transloadit templates delete TEMPLATE_ID']], + }) + + templateIds = Option.Rest({ required: 1 }) + + protected async run(): Promise { + await deleteTemplates(this.output, this.client, { + templates: this.templateIds, + }) + return undefined + } +} + +export class TemplatesListCommand extends AuthenticatedCommand { + static override paths = [ + ['templates', 'list'], + ['template', 'list'], + ['t', 'list'], + ['t', 'l'], + ] + + static override usage = Command.Usage({ + category: 'Templates', + description: 'List templates matching given criteria', + examples: [ + ['List all templates', 'transloadit templates list'], + ['List templates sorted by name', 'transloadit templates list --sort name'], + ], + }) + + after = Option.String('--after,-a', { + description: 'Return only templates created after specified date', + }) + + before = Option.String('--before,-b', { + description: 'Return only templates created before specified date', + }) + + sort = Option.String('--sort', { + description: 'Field to sort by (id, name, created, or modified)', + }) + + order = Option.String('--order', { + description: 'Sort ascending or descending (asc or desc)', + }) + + fields = Option.String('--fields', { + description: 'Comma-separated list of fields to return for each template', + }) + + protected async run(): Promise { + if (this.sort && !['id', 'name', 'created', 'modified'].includes(this.sort)) { + this.output.error('invalid argument for --sort') + return 1 + } + + if (this.order && !['asc', 'desc'].includes(this.order)) { + this.output.error('invalid argument for --order') + return 1 + } + + const fieldList = this.fields ? this.fields.split(',') : undefined + + await list(this.output, this.client, { + after: this.after, + before: this.before, + sort: this.sort, + order: this.order as 'asc' | 'desc' | undefined, + fields: fieldList, + }) + return undefined + } +} + +export class TemplatesSyncCommand extends AuthenticatedCommand { + static override paths = [ + ['templates', 'sync'], + ['template', 'sync'], + ['t', 'sync'], + ['t', 's'], + ] + + static override usage = Command.Usage({ + category: 'Templates', + description: 'Synchronize local template files with the Transloadit API', + details: ` + Template files must be named *.json and have the key "transloadit_template_id" + and optionally "steps". If "transloadit_template_id" is an empty string, then + a new template will be created using the instructions in "steps". If "steps" is + missing then it will be filled in by the instructions of the template specified + by "transloadit_template_id". If both keys are present then the local template + file and the remote template will be synchronized to whichever was more recently + modified. + `, + examples: [ + ['Sync templates in a directory', 'transloadit templates sync templates/'], + ['Sync recursively', 'transloadit templates sync --recursive templates/'], + ], + }) + + recursive = Option.Boolean('--recursive,-r', false, { + description: 'Look for template files in directories recursively', + }) + + files = Option.Rest() + + protected async run(): Promise { + await sync(this.output, this.client, { + recursive: this.recursive, + files: this.files, + }) + return undefined + } +} diff --git a/packages/transloadit/src/cli/helpers.ts b/packages/transloadit/src/cli/helpers.ts new file mode 100644 index 00000000..a2029faa --- /dev/null +++ b/packages/transloadit/src/cli/helpers.ts @@ -0,0 +1,50 @@ +import fs from 'node:fs' +import type { Readable } from 'node:stream' +import { isAPIError } from './types.ts' + +export function getEnvCredentials(): { authKey: string; authSecret: string } | null { + const authKey = process.env.TRANSLOADIT_KEY ?? process.env.TRANSLOADIT_AUTH_KEY + const authSecret = process.env.TRANSLOADIT_SECRET ?? process.env.TRANSLOADIT_AUTH_SECRET + + if (!authKey || !authSecret) return null + + return { authKey, authSecret } +} + +export function createReadStream(file: string): Readable { + if (file === '-') return process.stdin + return fs.createReadStream(file) +} + +export async function streamToBuffer(stream: Readable): Promise { + const chunks: Buffer[] = [] + for await (const chunk of stream) { + chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)) + } + return Buffer.concat(chunks) +} + +export function formatAPIError(err: unknown): string { + if (isAPIError(err)) { + return `${err.error}: ${err.message}` + } + if (err instanceof Error) { + return err.message + } + return String(err) +} + +// Re-export APIError type for CLI consumers relying on deep imports. +/** @public */ +export type { APIError } from './types.ts' + +export function zip(listA: A[], listB: B[]): [A, B][] +export function zip(...lists: T[][]): T[][] +export function zip(...lists: T[][]): T[][] { + const length = Math.max(...lists.map((list) => list.length)) + const result: T[][] = new Array(length) + for (let i = 0; i < result.length; i++) { + result[i] = lists.map((list) => list[i] as T) + } + return result +} diff --git a/packages/transloadit/src/cli/template-last-modified.ts b/packages/transloadit/src/cli/template-last-modified.ts new file mode 100644 index 00000000..eae0718a --- /dev/null +++ b/packages/transloadit/src/cli/template-last-modified.ts @@ -0,0 +1,156 @@ +import type { Transloadit } from '../Transloadit.ts' +import { ensureError } from './types.ts' + +interface TemplateItem { + id: string + modified: string +} + +type FetchCallback = (err: Error | null, result?: T) => void +type PageFetcher = (page: number, pagesize: number, cb: FetchCallback) => void + +class MemoizedPagination { + private pagesize: number + private fetch: PageFetcher + private cache: (T | undefined)[] + + constructor(pagesize: number, fetch: PageFetcher) { + this.pagesize = pagesize + this.fetch = fetch + this.cache = [] + } + + get(i: number, cb: FetchCallback): void { + const cached = this.cache[i] + if (cached !== undefined) { + process.nextTick(() => cb(null, cached)) + return + } + + const page = Math.floor(i / this.pagesize) + 1 + const start = (page - 1) * this.pagesize + + this.fetch(page, this.pagesize, (err, result) => { + if (err) { + cb(err) + return + } + if (!result) { + cb(new Error('No result returned from fetch')) + return + } + for (let j = 0; j < this.pagesize; j++) { + this.cache[start + j] = result[j] + } + cb(null, this.cache[i]) + }) + } +} + +export default class ModifiedLookup { + private byOrdinal: MemoizedPagination + + constructor(client: Transloadit, pagesize = 50) { + this.byOrdinal = new MemoizedPagination(pagesize, (page, pagesize, cb) => { + const params = { + sort: 'id' as const, + order: 'asc' as const, + fields: ['id', 'modified'] as ('id' | 'modified')[], + page, + pagesize, + } + + client + .listTemplates(params) + .then((result) => { + const items: TemplateItem[] = new Array(pagesize) + // Fill with sentinel value larger than any hex ID + items.fill({ id: 'gggggggggggggggggggggggggggggggg', modified: '' }) + for (let i = 0; i < result.items.length; i++) { + const item = result.items[i] + if (item) { + items[i] = { id: item.id, modified: item.modified } + } + } + cb(null, items) + }) + .catch((err: unknown) => { + cb(ensureError(err)) + }) + }) + } + + private idByOrd(ord: number, cb: FetchCallback): void { + this.byOrdinal.get(ord, (err, result) => { + if (err) { + cb(err) + return + } + if (!result) { + cb(new Error('No result found')) + return + } + cb(null, result.id) + }) + } + + byId(id: string, cb: FetchCallback): void { + const findUpperBound = (bound: number): void => { + this.idByOrd(bound, (err, idAtBound) => { + if (err) { + cb(err) + return + } + if (idAtBound === id) { + complete(bound) + return + } + if (idAtBound && idAtBound > id) { + refine(Math.floor(bound / 2), bound) + return + } + findUpperBound(bound * 2) + }) + } + + const refine = (lower: number, upper: number): void => { + if (lower >= upper - 1) { + cb(new Error(`Template ID ${id} not found in ModifiedLookup`)) + return + } + + const middle = Math.floor((lower + upper) / 2) + this.idByOrd(middle, (err, idAtMiddle) => { + if (err) { + cb(err) + return + } + if (idAtMiddle === id) { + complete(middle) + return + } + if (idAtMiddle && idAtMiddle < id) { + refine(middle, upper) + return + } + refine(lower, middle) + }) + } + + const complete = (ord: number): void => { + this.byOrdinal.get(ord, (err, result) => { + if (err) { + cb(err) + return + } + if (!result) { + cb(new Error('No result found')) + return + } + cb(null, new Date(result.modified)) + }) + } + + findUpperBound(1) + } +} diff --git a/packages/transloadit/src/cli/types.ts b/packages/transloadit/src/cli/types.ts new file mode 100644 index 00000000..b0a59177 --- /dev/null +++ b/packages/transloadit/src/cli/types.ts @@ -0,0 +1,70 @@ +import { z } from 'zod' +import type { Steps } from '../alphalib/types/template.ts' +import { optionalStepsSchema } from '../alphalib/types/template.ts' + +// Zod schemas for runtime validation +const APIErrorSchema = z.object({ + error: z.string(), + message: z.string(), +}) +export type APIError = z.infer + +const TransloaditAPIErrorSchema = z.object({ + error: z.string().optional(), + message: z.string(), + code: z.string().optional(), + transloaditErrorCode: z.string().optional(), + response: z + .object({ + body: z + .object({ + error: z.string().optional(), + }) + .optional(), + statusCode: z.number().optional(), + }) + .optional(), +}) +export type TransloaditAPIError = z.infer + +// Template file data - explicit type to avoid TS inference limits +export interface TemplateFileData { + transloadit_template_id?: string + steps?: Steps + [key: string]: unknown // passthrough +} + +export const TemplateFileDataSchema: z.ZodType = z + .object({ + transloadit_template_id: z.string().optional(), + steps: optionalStepsSchema, + }) + .passthrough() as z.ZodType + +export interface TemplateFile { + file: string + data: TemplateFileData +} + +// Helper to ensure error is Error type +export function ensureError(value: unknown): Error { + if (value instanceof Error) { + return value + } + return new Error(`Non-error was thrown: ${String(value)}`) +} + +// Type guard for APIError +export function isAPIError(value: unknown): value is APIError { + return APIErrorSchema.safeParse(value).success +} + +// Type guard for TransloaditAPIError +export function isTransloaditAPIError(value: unknown): value is TransloaditAPIError { + return TransloaditAPIErrorSchema.safeParse(value).success +} + +// Type guard for NodeJS.ErrnoException +export function isErrnoException(value: unknown): value is NodeJS.ErrnoException { + return value instanceof Error && 'code' in value +} diff --git a/packages/transloadit/src/tus.ts b/packages/transloadit/src/tus.ts new file mode 100644 index 00000000..5f4a1b3c --- /dev/null +++ b/packages/transloadit/src/tus.ts @@ -0,0 +1,168 @@ +import { stat } from 'node:fs/promises' +import { basename } from 'node:path' +import type { Readable } from 'node:stream' +import debug from 'debug' +import pMap from 'p-map' +import type { OnSuccessPayload, UploadOptions } from 'tus-js-client' +import { Upload } from 'tus-js-client' +import type { AssemblyStatus } from './alphalib/types/assemblyStatus.ts' +import type { UploadProgress } from './Transloadit.ts' + +const log = debug('transloadit') + +export interface Stream { + path?: string + stream: Readable +} + +interface SendTusRequestOptions { + streamsMap: Record + assembly: AssemblyStatus + requestedChunkSize: number + uploadConcurrency: number + onProgress: (options: UploadProgress) => void + signal?: AbortSignal +} + +export async function sendTusRequest({ + streamsMap, + assembly, + requestedChunkSize, + uploadConcurrency, + onProgress, + signal, +}: SendTusRequestOptions) { + const streamLabels = Object.keys(streamsMap) + + let totalBytes = 0 + let lastEmittedProgress = 0 + + const sizes: Record = {} + + const haveUnknownLengthStreams = streamLabels.some((label) => !streamsMap[label]?.path) + + // Initialize size data + await pMap( + streamLabels, + async (label) => { + // Check if aborted before each operation + if (signal?.aborted) throw new Error('Upload aborted') + + const streamInfo = streamsMap[label] + if (!streamInfo) { + throw new Error(`Stream info not found for label: ${label}`) + } + const { path } = streamInfo + + if (path) { + const { size } = await stat(path) + sizes[label] = size + totalBytes += size + } + }, + { concurrency: 5, signal }, + ) + + const uploadProgresses: Record = {} + + async function uploadSingleStream(label: string) { + uploadProgresses[label] = 0 + + const streamInfo = streamsMap[label] + if (!streamInfo) { + throw new Error(`Stream info not found for label: ${label}`) + } + const { stream, path } = streamInfo + const size = sizes[label] + + let chunkSize = requestedChunkSize + let uploadLengthDeferred: boolean + const isStreamLengthKnown = !!path + if (!isStreamLengthKnown) { + // tus-js-client requires these options to be set for unknown size streams + // https://github.com/tus/tus-js-client/blob/master/docs/api.md#uploadlengthdeferred + uploadLengthDeferred = true + if (chunkSize === Number.POSITIVE_INFINITY) chunkSize = 50e6 + } + + const onTusProgress = (bytesUploaded: number): void => { + uploadProgresses[label] = bytesUploaded + + // get all uploaded bytes for all files + let uploadedBytes = 0 + for (const l of streamLabels) { + uploadedBytes += uploadProgresses[l] ?? 0 + } + + // don't send redundant progress + if (lastEmittedProgress < uploadedBytes) { + lastEmittedProgress = uploadedBytes + // If we have any unknown length streams, we cannot trust totalBytes + // totalBytes should then be undefined to mimic behavior of form uploads. + onProgress({ + uploadedBytes, + totalBytes: haveUnknownLengthStreams ? undefined : totalBytes, + }) + } + } + + const filename = path ? basename(path) : label + + await new Promise((resolvePromise, rejectPromise) => { + if (!assembly.assembly_ssl_url) { + rejectPromise(new Error('assembly_ssl_url is not present in the assembly status')) + return + } + + // Check if already aborted before starting + if (signal?.aborted) { + rejectPromise(new Error('Upload aborted')) + return + } + + // Wrap resolve/reject to clean up abort listener + let abortHandler: (() => void) | undefined + const resolve = (payload: OnSuccessPayload) => { + if (abortHandler) signal?.removeEventListener('abort', abortHandler) + resolvePromise(payload) + } + const reject = (err: unknown) => { + if (abortHandler) signal?.removeEventListener('abort', abortHandler) + rejectPromise(err) + } + + const tusOptions: UploadOptions = { + endpoint: assembly.tus_url, + metadata: { + assembly_url: assembly.assembly_ssl_url, + fieldname: label, + filename, + }, + onError: reject, + onProgress: onTusProgress, + onSuccess: resolve, + } + // tus-js-client doesn't like undefined/null + if (size != null) tusOptions.uploadSize = size + if (chunkSize) tusOptions.chunkSize = chunkSize + if (uploadLengthDeferred) tusOptions.uploadLengthDeferred = uploadLengthDeferred + + const tusUpload = new Upload(stream, tusOptions) + + // Handle abort signal + if (signal) { + abortHandler = () => { + tusUpload.abort() + reject(new Error('Upload aborted')) + } + signal.addEventListener('abort', abortHandler, { once: true }) + } + + tusUpload.start() + }) + + log(label, 'upload done') + } + + await pMap(streamLabels, uploadSingleStream, { concurrency: uploadConcurrency, signal }) +} diff --git a/packages/types/package.json b/packages/types/package.json new file mode 100644 index 00000000..d14120a5 --- /dev/null +++ b/packages/types/package.json @@ -0,0 +1,34 @@ +{ + "name": "@transloadit/types", + "version": "4.1.2", + "description": "Transloadit type definitions", + "type": "module", + "license": "MIT", + "files": [ + "dist" + ], + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts" + }, + "./robots": { + "types": "./dist/robots/_index.d.ts" + }, + "./robots/*": { + "types": "./dist/robots/*.d.ts" + }, + "./*": { + "types": "./dist/*.d.ts" + } + }, + "scripts": { + "generate": "node --experimental-strip-types scripts/emit-types.ts", + "build": "yarn generate && tsc --build tsconfig.build.json", + "check": "yarn generate && tsc --build tsconfig.build.json" + }, + "devDependencies": { + "typescript": "5.9.3", + "zod": "3.25.76" + } +} diff --git a/packages/types/scripts/emit-types.ts b/packages/types/scripts/emit-types.ts index 56a88326..4320742c 100644 --- a/packages/types/scripts/emit-types.ts +++ b/packages/types/scripts/emit-types.ts @@ -1,15 +1,429 @@ +import { mkdir, readdir, rm, stat, writeFile } from 'node:fs/promises' +import { dirname, join, relative, resolve, sep } from 'node:path' import { fileURLToPath } from 'node:url' +import ts from 'typescript' -const steps = [ - 'emit-types pipeline (outline):', - '1) import schemas from @transloadit/zod/v3', - '2) generate a temp .ts file exporting z.infer aliases', - '3) run tsc --emitDeclarationOnly against that temp file', - '4) write .d.ts output into packages/types/src/generated', - '5) keep generated .d.ts free of zod imports (structural types only)', - '6) leave CI type-equality tests in @transloadit/zod to ensure parity', -] +const filePath = fileURLToPath(import.meta.url) +const typesRoot = resolve(dirname(filePath), '..') +const schemaRoot = resolve(typesRoot, '../node/src/alphalib/types') +const outputRoot = resolve(typesRoot, 'src/generated') -if (process.argv[1] && fileURLToPath(import.meta.url) === fileURLToPath(process.argv[1])) { - console.log(steps.join('\n')) +const compilerOptions: ts.CompilerOptions = { + module: ts.ModuleKind.NodeNext, + target: ts.ScriptTarget.ES2022, + moduleResolution: ts.ModuleResolutionKind.NodeNext, + allowImportingTsExtensions: true, + strict: true, + noEmit: true, } + +const typeFormatFlags = + ts.TypeFormatFlags.NoTruncation | + ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope | + ts.TypeFormatFlags.UseSingleQuotesForStringLiteralType + +const zodTypeOperators = new Set(['infer', 'input', 'output', 'TypeOf']) +const zodPathToken = `${sep}node_modules${sep}zod${sep}` +const libPathToken = `${sep}node_modules${sep}typescript${sep}lib${sep}` + +const isExported = (node: ts.Node): boolean => { + const flags = ts.getCombinedModifierFlags(node) + return (flags & ts.ModifierFlags.Export) !== 0 +} + +const collectFiles = async (dir: string, acc: string[] = []): Promise => { + const entries = await readdir(dir, { withFileTypes: true }) + for (const entry of entries) { + const full = join(dir, entry.name) + if (entry.isDirectory()) { + await collectFiles(full, acc) + continue + } + if (entry.isFile() && entry.name.endsWith('.ts')) { + acc.push(full) + } + } + return acc +} + +const ensureDir = async (dir: string) => { + await mkdir(dir, { recursive: true }) +} + +const isZodTypeNode = (node: ts.TypeNode): boolean => { + let found = false + const visit = (current: ts.Node) => { + if (found) return + if (ts.isTypeReferenceNode(current)) { + const typeName = current.typeName + if ( + ts.isQualifiedName(typeName) && + ts.isIdentifier(typeName.left) && + typeName.left.text === 'z' && + zodTypeOperators.has(typeName.right.text) + ) { + found = true + return + } + } + current.forEachChild(visit) + } + visit(node) + return found +} + +const isSymbolFromPath = (symbol: ts.Symbol | undefined, token: string): boolean => { + if (!symbol?.declarations?.length) return false + return symbol.declarations.some((decl) => decl.getSourceFile().fileName.includes(token)) +} + +const isZodSymbol = (symbol: ts.Symbol | undefined): boolean => isSymbolFromPath(symbol, zodPathToken) +const isLibSymbol = (symbol: ts.Symbol | undefined): boolean => isSymbolFromPath(symbol, libPathToken) + +const isZodText = (value: string): boolean => + value.includes('Zod') || value.includes('objectOutputType') || value.includes('objectInputType') + +const shouldUseTypeToString = (type: ts.Type, checker: ts.TypeChecker): boolean => { + if (hasZodType(type, checker, new Set())) { + return false + } + const text = checker.typeToString(type, undefined, typeFormatFlags) + return !isZodText(text) +} + +const hasZodType = ( + type: ts.Type, + checker: ts.TypeChecker, + seen: Set, +): boolean => { + if (seen.has(type)) return false + seen.add(type) + + const symbol = type.aliasSymbol ?? type.symbol + if (isZodSymbol(symbol)) return true + + if (type.isUnion()) { + return type.types.some((entry) => hasZodType(entry, checker, seen)) + } + + if (type.isIntersection()) { + return type.types.some((entry) => hasZodType(entry, checker, seen)) + } + + if (checker.isTupleType(type)) { + const tupleType = type as ts.TupleTypeReference + return checker.getTypeArguments(tupleType).some((entry) => hasZodType(entry, checker, seen)) + } + + if (checker.isArrayType(type)) { + const elementType = checker.getElementTypeOfArrayType(type) + return elementType ? hasZodType(elementType, checker, seen) : false + } + + if (type.aliasTypeArguments?.length) { + return type.aliasTypeArguments.some((entry) => hasZodType(entry, checker, seen)) + } + + const apparent = checker.getApparentType(type) + const properties = checker.getPropertiesOfType(apparent) + for (const prop of properties) { + const propDecl = prop.valueDeclaration ?? prop.declarations?.[0] + if (!propDecl) { + continue + } + const propType = checker.getTypeOfSymbolAtLocation(prop, propDecl) + if (propType && hasZodType(propType, checker, seen)) { + return true + } + } + + const stringIndex = checker.getIndexTypeOfType(apparent, ts.IndexKind.String) + if (stringIndex && hasZodType(stringIndex, checker, seen)) { + return true + } + const numberIndex = checker.getIndexTypeOfType(apparent, ts.IndexKind.Number) + if (numberIndex && hasZodType(numberIndex, checker, seen)) { + return true + } + + return false +} + +const escapeStringLiteral = (value: string): string => + value + .replace(/\\/g, '\\\\') + .replace(/'/g, "\\'") + .replace(/\\r/g, '\\\\r') + .replace(/\\n/g, '\\\\n') + .replace(/\\t/g, '\\\\t') + +const formatPropertyName = (name: string): string => { + if (ts.isIdentifierText(name, ts.ScriptTarget.ES2022)) { + return name + } + return `'${escapeStringLiteral(name)}'` +} + +const compareByPropertyOrder = (a: ts.Symbol, b: ts.Symbol): number => { + const aDecl = a.valueDeclaration ?? a.declarations?.[0] + const bDecl = b.valueDeclaration ?? b.declarations?.[0] + const aFile = aDecl?.getSourceFile().fileName ?? '' + const bFile = bDecl?.getSourceFile().fileName ?? '' + if (aFile !== bFile) { + return aFile < bFile ? -1 : 1 + } + const aPos = aDecl?.pos ?? Number.MAX_SAFE_INTEGER + const bPos = bDecl?.pos ?? Number.MAX_SAFE_INTEGER + if (aPos !== bPos) { + return aPos - bPos + } + const aName = a.getName() + const bName = b.getName() + if (aName === bName) return 0 + return aName < bName ? -1 : 1 +} + +const TypePrecedence = { + Union: 1, + Intersection: 2, + Primary: 3, +} as const + +type TypePrecedence = (typeof TypePrecedence)[keyof typeof TypePrecedence] + +const wrap = (value: string, precedence: TypePrecedence, parent: TypePrecedence): string => + precedence < parent ? `(${value})` : value + +const renderType = ( + type: ts.Type, + checker: ts.TypeChecker, + fallbackNode: ts.Node, + inProgress: Set, +): { text: string; precedence: TypePrecedence } => { + if (inProgress.has(type)) { + return { + text: checker.typeToString(type, undefined, typeFormatFlags), + precedence: TypePrecedence.Primary, + } + } + + if (type.isUnion()) { + const parts = type.types.map((subType) => { + const rendered = renderType(subType, checker, fallbackNode, inProgress) + return wrap(rendered.text, rendered.precedence, TypePrecedence.Union) + }) + return { text: parts.join(' | '), precedence: TypePrecedence.Union } + } + + if (type.isIntersection()) { + const parts = type.types.map((subType) => { + const rendered = renderType(subType, checker, fallbackNode, inProgress) + return wrap(rendered.text, rendered.precedence, TypePrecedence.Intersection) + }) + return { text: parts.join(' & '), precedence: TypePrecedence.Intersection } + } + + if (type.isLiteral()) { + if (typeof type.value === 'string') { + return { text: `'${escapeStringLiteral(type.value)}'`, precedence: TypePrecedence.Primary } + } + return { text: String(type.value), precedence: TypePrecedence.Primary } + } + if (type.flags & ts.TypeFlags.BooleanLiteral) { + return { text: type.intrinsicName, precedence: TypePrecedence.Primary } + } + + if (type.flags & ts.TypeFlags.String) { + return { text: 'string', precedence: TypePrecedence.Primary } + } + if (type.flags & ts.TypeFlags.Number) { + return { text: 'number', precedence: TypePrecedence.Primary } + } + if (type.flags & ts.TypeFlags.Boolean) { + return { text: 'boolean', precedence: TypePrecedence.Primary } + } + if (type.flags & ts.TypeFlags.BigInt) { + return { text: 'bigint', precedence: TypePrecedence.Primary } + } + if (type.flags & ts.TypeFlags.Null) { + return { text: 'null', precedence: TypePrecedence.Primary } + } + if (type.flags & ts.TypeFlags.Undefined) { + return { text: 'undefined', precedence: TypePrecedence.Primary } + } + if (type.flags & ts.TypeFlags.Void) { + return { text: 'void', precedence: TypePrecedence.Primary } + } + if (type.flags & ts.TypeFlags.Any) { + return { text: 'any', precedence: TypePrecedence.Primary } + } + if (type.flags & ts.TypeFlags.Unknown) { + return { text: 'unknown', precedence: TypePrecedence.Primary } + } + if (type.flags & ts.TypeFlags.Never) { + return { text: 'never', precedence: TypePrecedence.Primary } + } + + if (checker.isTupleType(type)) { + const tupleType = type as ts.TupleTypeReference + const elements = checker.getTypeArguments(tupleType) + const rendered = elements.map((entry) => renderType(entry, checker, fallbackNode, inProgress)) + const text = `[${rendered.map((entry) => entry.text).join(', ')}]` + return { text, precedence: TypePrecedence.Primary } + } + + if (checker.isArrayType(type)) { + const elementType = checker.getElementTypeOfArrayType(type) + if (elementType) { + const rendered = renderType(elementType, checker, fallbackNode, inProgress) + return { text: `Array<${rendered.text}>`, precedence: TypePrecedence.Primary } + } + } + + const callSignatures = type.getCallSignatures() + if (callSignatures.length > 0) { + const signatureText = checker.signatureToString( + callSignatures[0], + undefined, + typeFormatFlags, + ts.SignatureKind.Call, + ) + return { text: signatureText, precedence: TypePrecedence.Primary } + } + + const aliasSymbol = type.aliasSymbol + const symbol = type.symbol ?? type.aliasSymbol + if (aliasSymbol && !isZodSymbol(aliasSymbol) && shouldUseTypeToString(type, checker)) { + return { + text: checker.typeToString(type, undefined, typeFormatFlags), + precedence: TypePrecedence.Primary, + } + } + + if (symbol && isLibSymbol(symbol) && shouldUseTypeToString(type, checker)) { + return { + text: checker.typeToString(type, undefined, typeFormatFlags), + precedence: TypePrecedence.Primary, + } + } + + const apparent = checker.getApparentType(type) + const properties = checker.getPropertiesOfType(apparent).sort(compareByPropertyOrder) + const stringIndex = checker.getIndexTypeOfType(apparent, ts.IndexKind.String) + const numberIndex = checker.getIndexTypeOfType(apparent, ts.IndexKind.Number) + + if (properties.length > 0 || stringIndex || numberIndex) { + const entries: string[] = [] + if (stringIndex) { + const rendered = renderType(stringIndex, checker, fallbackNode, inProgress) + entries.push(`[key: string]: ${rendered.text}`) + } + if (numberIndex) { + const rendered = renderType(numberIndex, checker, fallbackNode, inProgress) + entries.push(`[key: number]: ${rendered.text}`) + } + inProgress.add(type) + try { + for (const prop of properties) { + const propDecl = prop.valueDeclaration ?? prop.declarations?.[0] + const propType = checker.getTypeOfSymbolAtLocation(prop, propDecl ?? fallbackNode) + const rendered = renderType(propType, checker, fallbackNode, inProgress) + const optional = prop.flags & ts.SymbolFlags.Optional ? '?' : '' + entries.push(`${formatPropertyName(prop.getName())}${optional}: ${rendered.text}`) + } + const text = `{ ${entries.join('; ')} }` + return { text, precedence: TypePrecedence.Primary } + } finally { + inProgress.delete(type) + } + } + + return { + text: checker.typeToString(type, undefined, typeFormatFlags), + precedence: TypePrecedence.Primary, + } +} + +const generateFile = (sourceFile: ts.SourceFile, checker: ts.TypeChecker): string => { + const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }) + const lines: string[] = ['// This file is generated. Do not edit.'] + + for (const statement of sourceFile.statements) { + if (!ts.isImportDeclaration(statement)) continue + if (!statement.importClause?.isTypeOnly) continue + lines.push(printer.printNode(ts.EmitHint.Unspecified, statement, sourceFile)) + } + + if (lines.length > 1) { + lines.push('') + } + + for (const statement of sourceFile.statements) { + if (ts.isTypeAliasDeclaration(statement) && isExported(statement)) { + if (isZodTypeNode(statement.type)) { + const type = checker.getTypeFromTypeNode(statement.type) + const rendered = renderType(type, checker, sourceFile, new Set()) + lines.push(`export type ${statement.name.text} = ${rendered.text}`) + } else { + lines.push(printer.printNode(ts.EmitHint.Unspecified, statement, sourceFile)) + } + continue + } + + if (ts.isInterfaceDeclaration(statement) && isExported(statement)) { + lines.push(printer.printNode(ts.EmitHint.Unspecified, statement, sourceFile)) + continue + } + + if (ts.isEnumDeclaration(statement) && isExported(statement)) { + lines.push(printer.printNode(ts.EmitHint.Unspecified, statement, sourceFile)) + continue + } + + if (ts.isExportDeclaration(statement) && statement.isTypeOnly) { + lines.push(printer.printNode(ts.EmitHint.Unspecified, statement, sourceFile)) + } + } + + return `${lines.join('\n')}\n` +} + +const main = async () => { + const schemaStats = await stat(schemaRoot).catch(() => null) + if (!schemaStats?.isDirectory()) { + throw new Error(`Missing Zod schemas at ${schemaRoot}.`) + } + + const schemaFiles = await collectFiles(schemaRoot) + if (schemaFiles.length === 0) { + throw new Error(`No schema files found under ${schemaRoot}`) + } + + await rm(outputRoot, { recursive: true, force: true }) + await ensureDir(outputRoot) + + const program = ts.createProgram(schemaFiles, compilerOptions) + const checker = program.getTypeChecker() + + const indexExports: string[] = ['// This file is generated. Do not edit.'] + + for (const file of schemaFiles) { + const sourceFile = program.getSourceFile(file) + if (!sourceFile) continue + const rel = relative(schemaRoot, file) + const outFile = join(outputRoot, rel) + await ensureDir(dirname(outFile)) + const content = generateFile(sourceFile, checker) + await writeFile(outFile, content, 'utf8') + + const exportPath = rel.endsWith('.ts') ? rel.slice(0, -3) : rel + if (exportPath.endsWith('/index')) { + continue + } + indexExports.push(`export * from './${exportPath}.js'`) + } + + await writeFile(join(outputRoot, 'index.ts'), `${indexExports.join('\n')}\n`, 'utf8') +} + +await main() diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts new file mode 100644 index 00000000..cf8dc18a --- /dev/null +++ b/packages/types/src/index.ts @@ -0,0 +1 @@ +export * from './generated/index.js' diff --git a/packages/types/tsconfig.build.json b/packages/types/tsconfig.build.json new file mode 100644 index 00000000..80e62408 --- /dev/null +++ b/packages/types/tsconfig.build.json @@ -0,0 +1,21 @@ +{ + "include": ["src"], + "exclude": ["dist"], + "compilerOptions": { + "composite": true, + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "erasableSyntaxOnly": true, + "isolatedModules": true, + "module": "NodeNext", + "allowImportingTsExtensions": true, + "target": "ES2022", + "noImplicitOverride": true, + "rewriteRelativeImportExtensions": true, + "outDir": "dist", + "rootDir": "src", + "resolveJsonModule": true, + "strict": true + } +} diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json new file mode 100644 index 00000000..455c333e --- /dev/null +++ b/packages/types/tsconfig.json @@ -0,0 +1,15 @@ +{ + "files": [], + "references": [{ "path": "./tsconfig.build.json" }], + "compilerOptions": { + "checkJs": true, + "erasableSyntaxOnly": true, + "isolatedModules": true, + "module": "NodeNext", + "allowImportingTsExtensions": true, + "noImplicitOverride": true, + "noEmit": true, + "resolveJsonModule": true, + "strict": true + } +} diff --git a/packages/zod/package.json b/packages/zod/package.json new file mode 100644 index 00000000..e3f03591 --- /dev/null +++ b/packages/zod/package.json @@ -0,0 +1,35 @@ +{ + "name": "@transloadit/zod", + "version": "4.1.2", + "description": "Transloadit Zod schemas", + "type": "module", + "license": "MIT", + "files": [ + "dist" + ], + "exports": { + ".": { + "types": "./dist/v3/index.d.ts", + "default": "./dist/v3/index.js" + }, + "./v3": { + "types": "./dist/v3/index.d.ts", + "default": "./dist/v3/index.js" + }, + "./v3/*": { + "types": "./dist/v3/*.d.ts", + "default": "./dist/v3/*.js" + } + }, + "scripts": { + "sync": "node scripts/sync-v3.js", + "build": "yarn sync && tsc --build tsconfig.build.json", + "check": "yarn sync && tsc --build tsconfig.build.json && tsc --noEmit --project tsconfig.test.json" + }, + "dependencies": { + "zod": "3.25.76" + }, + "devDependencies": { + "typescript": "5.9.3" + } +} diff --git a/packages/zod/scripts/sync-v3.js b/packages/zod/scripts/sync-v3.js new file mode 100644 index 00000000..522bc779 --- /dev/null +++ b/packages/zod/scripts/sync-v3.js @@ -0,0 +1,30 @@ +import { cp, mkdir, rm, writeFile } from 'node:fs/promises' +import { dirname, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const filePath = fileURLToPath(import.meta.url) +const zodRoot = resolve(dirname(filePath), '..') +const sourceRoot = resolve(zodRoot, '../node/src/alphalib/types') +const destRoot = resolve(zodRoot, 'src/v3') + +const indexContents = [ + "export * from './assembliesGet.js'", + "export * from './assemblyReplay.js'", + "export * from './assemblyReplayNotification.js'", + "export * from './assemblyStatus.js'", + "export * from './bill.js'", + "export * from './stackVersions.js'", + "export * from './template.js'", + "export * from './templateCredential.js'", + "export * from './robots/_index.js'", + '', +].join('\n') + +const main = async () => { + await rm(destRoot, { recursive: true, force: true }) + await mkdir(destRoot, { recursive: true }) + await cp(sourceRoot, destRoot, { recursive: true }) + await writeFile(resolve(destRoot, 'index.ts'), indexContents, 'utf8') +} + +await main() diff --git a/packages/zod/test/type-equality.ts b/packages/zod/test/type-equality.ts index 361d66ba..c91c74d1 100644 --- a/packages/zod/test/type-equality.ts +++ b/packages/zod/test/type-equality.ts @@ -1,8 +1,8 @@ -import { z } from 'zod' - -const exampleSchema = z.object({ - // TODO: replace with a real schema export once @transloadit/zod/v3 exists. -}) +import type { z } from 'zod' +import type { AssemblyStatus } from '@transloadit/types/assemblyStatus' +import type { AssemblyInstructions } from '@transloadit/types/template' +import { assemblyStatusSchema } from '../src/v3/assemblyStatus.js' +import { assemblyInstructionsSchema } from '../src/v3/template.js' type Equal = (() => T extends A ? 1 : 2) extends () => T extends B ? 1 : 2 ? true @@ -10,11 +10,9 @@ type Equal = (() => T extends A ? 1 : 2) extends () => T extends B ? type Assert = T -// TODO: replace GeneratedExample with a real type from @transloadit/types. -// import type * as Types from '@transloadit/types' -// type GeneratedExample = Types.Example -type GeneratedExample = z.infer - -export type _ExampleCheck = Assert>> - -export { exampleSchema } +export type _AssemblyStatusCheck = Assert< + Equal> +> +export type _AssemblyInstructionsCheck = Assert< + Equal> +> diff --git a/packages/zod/tsconfig.build.json b/packages/zod/tsconfig.build.json new file mode 100644 index 00000000..4d8b7c4b --- /dev/null +++ b/packages/zod/tsconfig.build.json @@ -0,0 +1,20 @@ +{ + "include": ["src"], + "exclude": ["dist", "test"], + "compilerOptions": { + "composite": true, + "declaration": true, + "declarationMap": true, + "erasableSyntaxOnly": true, + "isolatedModules": true, + "module": "NodeNext", + "allowImportingTsExtensions": true, + "target": "ES2022", + "noImplicitOverride": true, + "rewriteRelativeImportExtensions": true, + "outDir": "dist", + "rootDir": "src", + "resolveJsonModule": true, + "strict": true + } +} diff --git a/packages/zod/tsconfig.json b/packages/zod/tsconfig.json new file mode 100644 index 00000000..455c333e --- /dev/null +++ b/packages/zod/tsconfig.json @@ -0,0 +1,15 @@ +{ + "files": [], + "references": [{ "path": "./tsconfig.build.json" }], + "compilerOptions": { + "checkJs": true, + "erasableSyntaxOnly": true, + "isolatedModules": true, + "module": "NodeNext", + "allowImportingTsExtensions": true, + "noImplicitOverride": true, + "noEmit": true, + "resolveJsonModule": true, + "strict": true + } +} diff --git a/packages/zod/tsconfig.test.json b/packages/zod/tsconfig.test.json new file mode 100644 index 00000000..b7084a65 --- /dev/null +++ b/packages/zod/tsconfig.test.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "moduleResolution": "NodeNext", + "paths": { + "@transloadit/types": ["../types/src/index.ts"], + "@transloadit/types/*": ["../types/src/generated/*.ts"] + } + }, + "include": ["test/type-equality.ts"] +} diff --git a/scripts/fingerprint-pack.js b/scripts/fingerprint-pack.js index 24997807..ee82bd3a 100644 --- a/scripts/fingerprint-pack.js +++ b/scripts/fingerprint-pack.js @@ -1,89 +1,163 @@ -import { execFile } from 'node:child_process' import { createHash } from 'node:crypto' -import { readFile } from 'node:fs/promises' +import { execFile, spawn } from 'node:child_process' +import { createReadStream } from 'node:fs' +import { mkdir, rm, stat, writeFile } from 'node:fs/promises' import { resolve } from 'node:path' import { promisify } from 'node:util' const execFileAsync = promisify(execFile) -const cwd = process.argv[2] ? resolve(process.argv[2]) : process.cwd() +const usage = () => { + console.log(`Usage: node scripts/fingerprint-pack.js [path] [options] -/** @param {Uint8Array} buffer */ -const sha256 = (buffer) => createHash('sha256').update(buffer).digest('hex') - -const pack = async () => { - const { stdout } = await execFileAsync('npm', ['pack', '--json'], { - cwd, - encoding: 'utf8', - maxBuffer: 10 * 1024 * 1024, - }) +Options: + -o, --out Write JSON output to a file + --ignore-scripts Pass --ignore-scripts to npm pack + --keep Keep the generated tarball + -h, --help Show help +`) +} - const parsed = JSON.parse(stdout) - if (!Array.isArray(parsed) || !parsed[0] || !parsed[0].filename) { - throw new Error('Unexpected npm pack output') +const parseArgs = () => { + const args = process.argv.slice(2) + let target = '.' + let out = null + let keep = false + let ignoreScripts = false + + for (let i = 0; i < args.length; i += 1) { + const arg = args[i] + if (arg === '-h' || arg === '--help') { + usage() + process.exit(0) + } + if (arg === '-o' || arg === '--out') { + out = args[i + 1] + i += 1 + continue + } + if (arg === '--keep') { + keep = true + continue + } + if (arg === '--ignore-scripts') { + ignoreScripts = true + continue + } + if (arg.startsWith('-')) { + throw new Error(`Unknown option: ${arg}`) + } + target = arg } - return parsed[0] + return { target, out, keep, ignoreScripts } } -/** @param {string} tarballPath */ -const listTarFiles = async (tarballPath) => { - const { stdout } = await execFileAsync('tar', ['-tf', tarballPath], { - cwd, - encoding: 'utf8', - maxBuffer: 50 * 1024 * 1024, +const hashFile = async (filePath) => + new Promise((resolvePromise, reject) => { + const hash = createHash('sha256') + const stream = createReadStream(filePath) + stream.on('error', reject) + stream.on('data', (chunk) => hash.update(chunk)) + stream.on('end', () => resolvePromise(hash.digest('hex'))) }) - return stdout - .split('\n') - .map((entry) => entry.trim()) - .filter(Boolean) - .filter((entry) => !entry.endsWith('/')) -} - -/** - * @param {string} tarballPath - * @param {string} entry - * @returns {Promise} - */ -const readTarFile = async (tarballPath, entry) => { - const { stdout } = await execFileAsync('tar', ['-xOf', tarballPath, entry], { - cwd, - encoding: 'buffer', - maxBuffer: 200 * 1024 * 1024, +const hashTarEntry = async (tarballPath, entry) => + new Promise((resolvePromise, reject) => { + const hash = createHash('sha256') + let sizeBytes = 0 + const child = spawn('tar', ['-xOf', tarballPath, entry], { + stdio: ['ignore', 'pipe', 'pipe'], + }) + child.stdout.on('data', (chunk) => { + sizeBytes += chunk.length + hash.update(chunk) + }) + let stderr = '' + child.stderr.on('data', (chunk) => { + stderr += chunk.toString() + }) + child.on('error', reject) + child.on('close', (code) => { + if (code !== 0) { + reject(new Error(`tar failed for ${entry}: ${stderr.trim()}`)) + return + } + resolvePromise({ sha256: hash.digest('hex'), sizeBytes }) + }) }) +const readTarEntry = async (tarballPath, entry) => { + const { stdout } = await execFileAsync('tar', ['-xOf', tarballPath, entry]) return stdout } const main = async () => { - const packInfo = await pack() - const tarballPath = resolve(cwd, packInfo.filename) - const tarballBuffer = await readFile(tarballPath) - - const files = await listTarFiles(tarballPath) - /** @type {Record} */ - const fileFingerprints = {} - - for (const entry of files) { - const contents = await readTarFile(tarballPath, entry) - const buffer = Buffer.isBuffer(contents) ? contents : Buffer.from(contents) - fileFingerprints[entry] = { - sha256: sha256(buffer), - size: buffer.length, - } + const { target, out, keep, ignoreScripts } = parseArgs() + const cwd = resolve(process.cwd(), target) + + const packArgs = ['pack', '--json'] + if (ignoreScripts) { + packArgs.push('--ignore-scripts') + } + const { stdout } = await execFileAsync('npm', packArgs, { cwd }) + const packed = JSON.parse(stdout.trim()) + const info = Array.isArray(packed) ? packed[0] : packed + if (!info?.filename) { + throw new Error('npm pack did not return a tarball filename') } - const fingerprint = { + const tarballPath = resolve(cwd, info.filename) + const tarballStat = await stat(tarballPath) + const tarballSha = await hashFile(tarballPath) + + const { stdout: listStdout } = await execFileAsync('tar', ['-tf', tarballPath]) + const entries = listStdout + .split(/\r?\n/) + .map((line) => line.trim()) + .filter((line) => line.length > 0) + .filter((line) => !line.endsWith('/')) + + const files = [] + for (const entry of entries) { + const { sha256, sizeBytes } = await hashTarEntry(tarballPath, entry) + const normalized = entry.startsWith('package/') ? entry.slice('package/'.length) : entry + files.push({ path: normalized, sizeBytes, sha256 }) + } + + const packageJsonRaw = await readTarEntry(tarballPath, 'package/package.json') + const packageJson = JSON.parse(packageJsonRaw) + + const summary = { + packageDir: cwd, tarball: { - file: packInfo.filename, - sha256: sha256(tarballBuffer), - size: tarballBuffer.length, + filename: info.filename, + sizeBytes: tarballStat.size, + sha256: tarballSha, }, - files: fileFingerprints, + packageJson: { + name: packageJson.name, + version: packageJson.version, + main: packageJson.main, + types: packageJson.types, + exports: packageJson.exports, + files: packageJson.files, + }, + files, + } + + const json = `${JSON.stringify(summary, null, 2)}\n` + if (out) { + const outPath = resolve(process.cwd(), out) + await mkdir(resolve(outPath, '..'), { recursive: true }) + await writeFile(outPath, json) } - process.stdout.write(`${JSON.stringify(fingerprint, null, 2)}\n`) + process.stdout.write(json) + + if (!keep) { + await rm(tarballPath, { force: true }) + } } await main() diff --git a/scripts/prepare-transloadit.js b/scripts/prepare-transloadit.js new file mode 100644 index 00000000..53f1ceb0 --- /dev/null +++ b/scripts/prepare-transloadit.js @@ -0,0 +1,31 @@ +import { cp, mkdir, rm } from 'node:fs/promises' +import { dirname, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' +import { execFile } from 'node:child_process' +import { promisify } from 'node:util' + +const execFileAsync = promisify(execFile) + +const filePath = fileURLToPath(import.meta.url) +const repoRoot = resolve(dirname(filePath), '..') +const nodePackage = resolve(repoRoot, 'packages/node') +const legacyPackage = resolve(repoRoot, 'packages/transloadit') + +const copyDir = async (from, to) => { + await rm(to, { recursive: true, force: true }) + await mkdir(to, { recursive: true }) + await cp(from, to, { recursive: true }) +} + +const main = async () => { + await execFileAsync('yarn', ['workspace', '@transloadit/node', 'prepack'], { + cwd: repoRoot, + }) + + await copyDir(resolve(nodePackage, 'dist'), resolve(legacyPackage, 'dist')) + await copyDir(resolve(nodePackage, 'src'), resolve(legacyPackage, 'src')) + await cp(resolve(repoRoot, 'README.md'), resolve(legacyPackage, 'README.md')) + await cp(resolve(repoRoot, 'LICENSE'), resolve(legacyPackage, 'LICENSE')) +} + +await main() diff --git a/tsconfig.json b/tsconfig.json index 59824675..f242767a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,16 +1,8 @@ { - "exclude": ["dist", "src", "coverage"], - "references": [{ "path": "./tsconfig.build.json" }], - "compilerOptions": { - "checkJs": true, - "erasableSyntaxOnly": true, - "isolatedModules": true, - "module": "NodeNext", - "allowImportingTsExtensions": true, - "noImplicitOverride": true, - "noEmit": true, - "resolveJsonModule": true, - "strict": true, - "types": ["vitest/globals"] - } + "files": [], + "references": [ + { "path": "./packages/node" }, + { "path": "./packages/types" }, + { "path": "./packages/zod" } + ] } diff --git a/yarn.lock b/yarn.lock index 1bec426a..9c8b28c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -722,6 +722,13 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.5.5": + version: 7.28.6 + resolution: "@babel/runtime@npm:7.28.6" + checksum: 10c0/358cf2429992ac1c466df1a21c1601d595c46930a13c1d4662fde908d44ee78ec3c183aaff513ecb01ef8c55c3624afe0309eeeb34715672dbfadb7feedb2c0d + languageName: node + linkType: hard + "@babel/types@npm:^7.25.4, @babel/types@npm:^7.27.3": version: 7.27.3 resolution: "@babel/types@npm:7.27.3" @@ -830,6 +837,240 @@ __metadata: languageName: node linkType: hard +"@changesets/apply-release-plan@npm:^7.0.14": + version: 7.0.14 + resolution: "@changesets/apply-release-plan@npm:7.0.14" + dependencies: + "@changesets/config": "npm:^3.1.2" + "@changesets/get-version-range-type": "npm:^0.4.0" + "@changesets/git": "npm:^3.0.4" + "@changesets/should-skip-package": "npm:^0.1.2" + "@changesets/types": "npm:^6.1.0" + "@manypkg/get-packages": "npm:^1.1.3" + detect-indent: "npm:^6.0.0" + fs-extra: "npm:^7.0.1" + lodash.startcase: "npm:^4.4.0" + outdent: "npm:^0.5.0" + prettier: "npm:^2.7.1" + resolve-from: "npm:^5.0.0" + semver: "npm:^7.5.3" + checksum: 10c0/097c7ebcec758966b6728696498d59cfac23271aba2a56824ee307be1eefb2d0c6974aef1be4841e20b3458546ffacfd108c1afbf3acc512d6c3a4e30fa28622 + languageName: node + linkType: hard + +"@changesets/assemble-release-plan@npm:^6.0.9": + version: 6.0.9 + resolution: "@changesets/assemble-release-plan@npm:6.0.9" + dependencies: + "@changesets/errors": "npm:^0.2.0" + "@changesets/get-dependents-graph": "npm:^2.1.3" + "@changesets/should-skip-package": "npm:^0.1.2" + "@changesets/types": "npm:^6.1.0" + "@manypkg/get-packages": "npm:^1.1.3" + semver: "npm:^7.5.3" + checksum: 10c0/128f87975f65d9ceb2c997df186a5deae8637fd3868098bb4fb9772f35fdd3b47883ccbdc2761d0468e60a83ef4e2c1561a8e58f8052bfe2daf1ea046803fe1a + languageName: node + linkType: hard + +"@changesets/changelog-git@npm:^0.2.1": + version: 0.2.1 + resolution: "@changesets/changelog-git@npm:0.2.1" + dependencies: + "@changesets/types": "npm:^6.1.0" + checksum: 10c0/6a6fb315ffb2266fcb8f32ae9a60ccdb5436e52350a2f53beacf9822d3355f9052aba5001a718e12af472b4a8fabd69b408d0b11c02ac909ba7a183d27a9f7fd + languageName: node + linkType: hard + +"@changesets/cli@npm:^2.29.7": + version: 2.29.8 + resolution: "@changesets/cli@npm:2.29.8" + dependencies: + "@changesets/apply-release-plan": "npm:^7.0.14" + "@changesets/assemble-release-plan": "npm:^6.0.9" + "@changesets/changelog-git": "npm:^0.2.1" + "@changesets/config": "npm:^3.1.2" + "@changesets/errors": "npm:^0.2.0" + "@changesets/get-dependents-graph": "npm:^2.1.3" + "@changesets/get-release-plan": "npm:^4.0.14" + "@changesets/git": "npm:^3.0.4" + "@changesets/logger": "npm:^0.1.1" + "@changesets/pre": "npm:^2.0.2" + "@changesets/read": "npm:^0.6.6" + "@changesets/should-skip-package": "npm:^0.1.2" + "@changesets/types": "npm:^6.1.0" + "@changesets/write": "npm:^0.4.0" + "@inquirer/external-editor": "npm:^1.0.2" + "@manypkg/get-packages": "npm:^1.1.3" + ansi-colors: "npm:^4.1.3" + ci-info: "npm:^3.7.0" + enquirer: "npm:^2.4.1" + fs-extra: "npm:^7.0.1" + mri: "npm:^1.2.0" + p-limit: "npm:^2.2.0" + package-manager-detector: "npm:^0.2.0" + picocolors: "npm:^1.1.0" + resolve-from: "npm:^5.0.0" + semver: "npm:^7.5.3" + spawndamnit: "npm:^3.0.1" + term-size: "npm:^2.1.0" + bin: + changeset: bin.js + checksum: 10c0/85c32814698403f1634b649d96b8b32f04fa7f2065e455df672c0b39e9a2dc3a05538b82496536ac00aabf7810dfa68ff8049fa4f618e50ed00a29ceb302a7b5 + languageName: node + linkType: hard + +"@changesets/config@npm:^3.1.2": + version: 3.1.2 + resolution: "@changesets/config@npm:3.1.2" + dependencies: + "@changesets/errors": "npm:^0.2.0" + "@changesets/get-dependents-graph": "npm:^2.1.3" + "@changesets/logger": "npm:^0.1.1" + "@changesets/types": "npm:^6.1.0" + "@manypkg/get-packages": "npm:^1.1.3" + fs-extra: "npm:^7.0.1" + micromatch: "npm:^4.0.8" + checksum: 10c0/76065383cd5b7595f95ad7dc4aacfa74dd4ebb2ef956c30ea97e6f09b87b2e73b870676e7b294290b6cf9b1777983526bc8d3bb58dedd37dfa8a5ddbb02ebe1a + languageName: node + linkType: hard + +"@changesets/errors@npm:^0.2.0": + version: 0.2.0 + resolution: "@changesets/errors@npm:0.2.0" + dependencies: + extendable-error: "npm:^0.1.5" + checksum: 10c0/f2757c752ab04e9733b0dfd7903f1caf873f9e603794c4d9ea2294af4f937c73d07273c24be864ad0c30b6a98424360d5b96a6eab14f97f3cf2cbfd3763b95c1 + languageName: node + linkType: hard + +"@changesets/get-dependents-graph@npm:^2.1.3": + version: 2.1.3 + resolution: "@changesets/get-dependents-graph@npm:2.1.3" + dependencies: + "@changesets/types": "npm:^6.1.0" + "@manypkg/get-packages": "npm:^1.1.3" + picocolors: "npm:^1.1.0" + semver: "npm:^7.5.3" + checksum: 10c0/b9d9992440b7e09dcaf22f57d28f1d8e0e31996e1bc44dbbfa1801e44f93fa49ebba6f9356c60f6ff0bd85cd0f0d0b8602f7e0f2addc5be647b686e6f8985f70 + languageName: node + linkType: hard + +"@changesets/get-release-plan@npm:^4.0.14": + version: 4.0.14 + resolution: "@changesets/get-release-plan@npm:4.0.14" + dependencies: + "@changesets/assemble-release-plan": "npm:^6.0.9" + "@changesets/config": "npm:^3.1.2" + "@changesets/pre": "npm:^2.0.2" + "@changesets/read": "npm:^0.6.6" + "@changesets/types": "npm:^6.1.0" + "@manypkg/get-packages": "npm:^1.1.3" + checksum: 10c0/24a15056955fc3967e023f058fa6c1e7550f3aad5c299264307a09b6d312868715684595bdb45a79c3f25fc809a70582be39861f3ae958d392b89a234f65b670 + languageName: node + linkType: hard + +"@changesets/get-version-range-type@npm:^0.4.0": + version: 0.4.0 + resolution: "@changesets/get-version-range-type@npm:0.4.0" + checksum: 10c0/e466208c8383489a383f37958d8b5b9aed38539f9287b47fe155a2e8855973f6960fb1724a1ee33b11580d65e1011059045ee654e8ef51e4783017d8989c9d3f + languageName: node + linkType: hard + +"@changesets/git@npm:^3.0.4": + version: 3.0.4 + resolution: "@changesets/git@npm:3.0.4" + dependencies: + "@changesets/errors": "npm:^0.2.0" + "@manypkg/get-packages": "npm:^1.1.3" + is-subdir: "npm:^1.1.1" + micromatch: "npm:^4.0.8" + spawndamnit: "npm:^3.0.1" + checksum: 10c0/4abbdc1dec6ddc50b6ad927d9eba4f23acd775fdff615415813099befb0cecd1b0f56ceea5e18a5a3cbbb919d68179366074b02a954fbf4016501e5fd125d2b5 + languageName: node + linkType: hard + +"@changesets/logger@npm:^0.1.1": + version: 0.1.1 + resolution: "@changesets/logger@npm:0.1.1" + dependencies: + picocolors: "npm:^1.1.0" + checksum: 10c0/a0933b5bd4d99e10730b22612dc1bdfd25b8804c5b48f8cada050bf5c7a89b2ae9a61687f846a5e9e5d379a95b59fef795c8d5d91e49a251f8da2be76133f83f + languageName: node + linkType: hard + +"@changesets/parse@npm:^0.4.2": + version: 0.4.2 + resolution: "@changesets/parse@npm:0.4.2" + dependencies: + "@changesets/types": "npm:^6.1.0" + js-yaml: "npm:^4.1.1" + checksum: 10c0/fdc1c99e01257e194a5ff59213993158deae9f84a66f5444a636645ff2655f67b6031589bab796a8c3ed653220d3c55fd62a6af2504a7c54bb541ac573119c5d + languageName: node + linkType: hard + +"@changesets/pre@npm:^2.0.2": + version: 2.0.2 + resolution: "@changesets/pre@npm:2.0.2" + dependencies: + "@changesets/errors": "npm:^0.2.0" + "@changesets/types": "npm:^6.1.0" + "@manypkg/get-packages": "npm:^1.1.3" + fs-extra: "npm:^7.0.1" + checksum: 10c0/0af9396d84c47a88d79b757e9db4e3579b6620260f92c243b8349e7fcefca3c2652583f6d215c13115bed5d5cdc30c975f307fd6acbb89d205b1ba2ae403b918 + languageName: node + linkType: hard + +"@changesets/read@npm:^0.6.6": + version: 0.6.6 + resolution: "@changesets/read@npm:0.6.6" + dependencies: + "@changesets/git": "npm:^3.0.4" + "@changesets/logger": "npm:^0.1.1" + "@changesets/parse": "npm:^0.4.2" + "@changesets/types": "npm:^6.1.0" + fs-extra: "npm:^7.0.1" + p-filter: "npm:^2.1.0" + picocolors: "npm:^1.1.0" + checksum: 10c0/a0a503061764bb391e00a37df1251c90356cf46519663dd517e58bc170c290f591abc1cff44569c88c87083360a36e2d756afcf7537b8725f4decfd915f838d3 + languageName: node + linkType: hard + +"@changesets/should-skip-package@npm:^0.1.2": + version: 0.1.2 + resolution: "@changesets/should-skip-package@npm:0.1.2" + dependencies: + "@changesets/types": "npm:^6.1.0" + "@manypkg/get-packages": "npm:^1.1.3" + checksum: 10c0/484e339e7d6e6950e12bff4eda6e8eccb077c0fbb1f09dd95d2ae948b715226a838c71eaf50cd2d7e0e631ce3bfb1ca93ac752436e6feae5b87aece2e917b440 + languageName: node + linkType: hard + +"@changesets/types@npm:^4.0.1": + version: 4.1.0 + resolution: "@changesets/types@npm:4.1.0" + checksum: 10c0/a372ad21f6a1e0d4ce6c19573c1ca269eef1ad53c26751ad9515a24f003e7c49dcd859dbb1fedb6badaf7be956c1559e8798304039e0ec0da2d9a68583f13464 + languageName: node + linkType: hard + +"@changesets/types@npm:^6.1.0": + version: 6.1.0 + resolution: "@changesets/types@npm:6.1.0" + checksum: 10c0/b4cea3a4465d1eaf0bbd7be1e404aca5a055a61d4cc72aadcb73bbbda1670b4022736b8d3052616cbf1f451afa0637545d077697f4b923236539af9cd5abce6c + languageName: node + linkType: hard + +"@changesets/write@npm:^0.4.0": + version: 0.4.0 + resolution: "@changesets/write@npm:0.4.0" + dependencies: + "@changesets/types": "npm:^6.1.0" + fs-extra: "npm:^7.0.1" + human-id: "npm:^4.1.1" + prettier: "npm:^2.7.1" + checksum: 10c0/311f4d0e536d1b5f2d3f9053537d62b2d4cdbd51e1d2767807ac9d1e0f380367f915d2ad370e5c73902d5a54bffd282d53fff5418c8ad31df51751d652bea826 + languageName: node + linkType: hard + "@emnapi/core@npm:^1.7.1": version: 1.7.1 resolution: "@emnapi/core@npm:1.7.1" @@ -1222,6 +1463,21 @@ __metadata: languageName: node linkType: hard +"@inquirer/external-editor@npm:^1.0.2": + version: 1.0.3 + resolution: "@inquirer/external-editor@npm:1.0.3" + dependencies: + chardet: "npm:^2.1.1" + iconv-lite: "npm:^0.7.0" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/82951cb7f3762dd78cca2ea291396841e3f4adfe26004b5badfed1cec4b6a04bb567dff94d0e41b35c61bdd7957317c64c22f58074d14b238d44e44d9e420019 + languageName: node + linkType: hard + "@isaacs/balanced-match@npm:^4.0.1": version: 4.0.1 resolution: "@isaacs/balanced-match@npm:4.0.1" @@ -1310,6 +1566,32 @@ __metadata: languageName: node linkType: hard +"@manypkg/find-root@npm:^1.1.0": + version: 1.1.0 + resolution: "@manypkg/find-root@npm:1.1.0" + dependencies: + "@babel/runtime": "npm:^7.5.5" + "@types/node": "npm:^12.7.1" + find-up: "npm:^4.1.0" + fs-extra: "npm:^8.1.0" + checksum: 10c0/0ee907698e6c73d6f1821ff630f3fec6dcf38260817c8752fec8991ac38b95ba431ab11c2773ddf9beb33d0e057f1122b00e8ffc9b8411b3fd24151413626fa6 + languageName: node + linkType: hard + +"@manypkg/get-packages@npm:^1.1.3": + version: 1.1.3 + resolution: "@manypkg/get-packages@npm:1.1.3" + dependencies: + "@babel/runtime": "npm:^7.5.5" + "@changesets/types": "npm:^4.0.1" + "@manypkg/find-root": "npm:^1.1.0" + fs-extra: "npm:^8.1.0" + globby: "npm:^11.0.0" + read-yaml-file: "npm:^1.1.0" + checksum: 10c0/f05907d1174ae28861eaa06d0efdc144f773d9a4b8b65e1e7cdc01eb93361d335351b4a336e05c6aac02661be39e8809a3f7ad28bc67b6b338071434ab442130 + languageName: node + linkType: hard + "@mswjs/interceptors@npm:^0.39.5": version: 0.39.6 resolution: "@mswjs/interceptors@npm:0.39.6" @@ -2335,6 +2617,53 @@ __metadata: languageName: node linkType: hard +"@transloadit/node@workspace:packages/node": + version: 0.0.0-use.local + resolution: "@transloadit/node@workspace:packages/node" + dependencies: + "@aws-sdk/client-s3": "npm:^3.891.0" + "@aws-sdk/s3-request-presigner": "npm:^3.891.0" + "@biomejs/biome": "npm:^2.2.4" + "@transloadit/sev-logger": "npm:^0.0.15" + "@types/debug": "npm:^4.1.12" + "@types/minimist": "npm:^1.2.5" + "@types/node": "npm:^24.10.3" + "@types/recursive-readdir": "npm:^2.2.4" + "@types/temp": "npm:^0.9.4" + "@vitest/coverage-v8": "npm:^3.2.4" + badge-maker: "npm:^5.0.2" + clipanion: "npm:^4.0.0-rc.4" + debug: "npm:^4.4.3" + dotenv: "npm:^17.2.3" + execa: "npm:9.6.0" + form-data: "npm:^4.0.4" + got: "npm:14.4.9" + image-size: "npm:^2.0.2" + into-stream: "npm:^9.0.0" + is-stream: "npm:^4.0.1" + knip: "npm:^5.73.3" + minimatch: "npm:^10.1.1" + nock: "npm:^14.0.10" + node-watch: "npm:^0.7.4" + npm-run-all: "npm:^4.1.5" + p-map: "npm:^7.0.3" + p-queue: "npm:^9.0.1" + p-retry: "npm:^7.0.0" + recursive-readdir: "npm:^2.2.3" + rimraf: "npm:^6.1.2" + temp: "npm:^0.9.4" + tsx: "npm:4.21.0" + tus-js-client: "npm:^4.3.1" + typanion: "npm:^3.14.0" + type-fest: "npm:^4.41.0" + typescript: "npm:5.9.3" + vitest: "npm:^3.2.4" + zod: "npm:3.25.76" + bin: + node: ./dist/cli.js + languageName: unknown + linkType: soft + "@transloadit/sev-logger@npm:^0.0.15": version: 0.0.15 resolution: "@transloadit/sev-logger@npm:0.0.15" @@ -2342,6 +2671,24 @@ __metadata: languageName: node linkType: hard +"@transloadit/types@workspace:packages/types": + version: 0.0.0-use.local + resolution: "@transloadit/types@workspace:packages/types" + dependencies: + typescript: "npm:5.9.3" + zod: "npm:3.25.76" + languageName: unknown + linkType: soft + +"@transloadit/zod@workspace:packages/zod": + version: 0.0.0-use.local + resolution: "@transloadit/zod@workspace:packages/zod" + dependencies: + typescript: "npm:5.9.3" + zod: "npm:3.25.76" + languageName: unknown + linkType: soft + "@tybys/wasm-util@npm:^0.10.1": version: 0.10.1 resolution: "@tybys/wasm-util@npm:0.10.1" @@ -2413,6 +2760,13 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^12.7.1": + version: 12.20.55 + resolution: "@types/node@npm:12.20.55" + checksum: 10c0/3b190bb0410047d489c49bbaab592d2e6630de6a50f00ba3d7d513d59401d279972a8f5a598b5bb8ddc1702f8a2f4ec57a65d93852f9c329639738e7053637d1 + languageName: node + linkType: hard + "@types/node@npm:^24.10.3": version: 24.10.4 resolution: "@types/node@npm:24.10.4" @@ -2580,6 +2934,13 @@ __metadata: languageName: node linkType: hard +"ansi-colors@npm:^4.1.1, ansi-colors@npm:^4.1.3": + version: 4.1.3 + resolution: "ansi-colors@npm:4.1.3" + checksum: 10c0/ec87a2f59902f74e61eada7f6e6fe20094a628dab765cfdbd03c3477599368768cffccdb5d3bb19a1b6c99126783a143b1fee31aab729b31ffe5836c7e5e28b9 + languageName: node + linkType: hard + "ansi-regex@npm:^5.0.1": version: 5.0.1 resolution: "ansi-regex@npm:5.0.1" @@ -2619,6 +2980,15 @@ __metadata: languageName: node linkType: hard +"argparse@npm:^1.0.7": + version: 1.0.10 + resolution: "argparse@npm:1.0.10" + dependencies: + sprintf-js: "npm:~1.0.2" + checksum: 10c0/b2972c5c23c63df66bca144dbc65d180efa74f25f8fd9b7d9a0a6c88ae839db32df3d54770dcb6460cf840d232b60695d1a6b1053f599d84e73f7437087712de + languageName: node + linkType: hard + "argparse@npm:^2.0.1": version: 2.0.1 resolution: "argparse@npm:2.0.1" @@ -2636,6 +3006,13 @@ __metadata: languageName: node linkType: hard +"array-union@npm:^2.1.0": + version: 2.1.0 + resolution: "array-union@npm:2.1.0" + checksum: 10c0/429897e68110374f39b771ec47a7161fc6a8fc33e196857c0a396dc75df0b5f65e4d046674db764330b6bb66b39ef48dd7c53b6a2ee75cfb0681e0c1a7033962 + languageName: node + linkType: hard + "arraybuffer.prototype.slice@npm:^1.0.4": version: 1.0.4 resolution: "arraybuffer.prototype.slice@npm:1.0.4" @@ -2711,6 +3088,15 @@ __metadata: languageName: node linkType: hard +"better-path-resolve@npm:1.0.0": + version: 1.0.0 + resolution: "better-path-resolve@npm:1.0.0" + dependencies: + is-windows: "npm:^1.0.0" + checksum: 10c0/7335130729d59a14b8e4753fea180ca84e287cccc20cb5f2438a95667abc5810327c414eee7b3c79ed1b5a348a40284ea872958f50caba69432c40405eb0acce + languageName: node + linkType: hard + "binary-search@npm:^1.3.5": version: 1.3.6 resolution: "binary-search@npm:1.3.6" @@ -2874,6 +3260,13 @@ __metadata: languageName: node linkType: hard +"chardet@npm:^2.1.1": + version: 2.1.1 + resolution: "chardet@npm:2.1.1" + checksum: 10c0/d8391dd412338442b3de0d3a488aa9327f8bcf74b62b8723d6bd0b85c4084d50b731320e0a7c710edb1d44de75969995d2784b80e4c13b004a6c7a0db4c6e793 + languageName: node + linkType: hard + "check-error@npm:^2.1.1": version: 2.1.1 resolution: "check-error@npm:2.1.1" @@ -2888,6 +3281,13 @@ __metadata: languageName: node linkType: hard +"ci-info@npm:^3.7.0": + version: 3.9.0 + resolution: "ci-info@npm:3.9.0" + checksum: 10c0/6f0109e36e111684291d46123d491bc4e7b7a1934c3a20dea28cba89f1d4a03acd892f5f6a81ed3855c38647e285a150e3c9ba062e38943bef57fee6c1554c3a + languageName: node + linkType: hard + "clipanion@npm:^4.0.0-rc.4": version: 4.0.0-rc.4 resolution: "clipanion@npm:4.0.0-rc.4" @@ -2977,7 +3377,7 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^7.0.6": +"cross-spawn@npm:^7.0.5, cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" dependencies: @@ -3110,6 +3510,22 @@ __metadata: languageName: node linkType: hard +"detect-indent@npm:^6.0.0": + version: 6.1.0 + resolution: "detect-indent@npm:6.1.0" + checksum: 10c0/dd83cdeda9af219cf77f5e9a0dc31d828c045337386cfb55ce04fad94ba872ee7957336834154f7647b89b899c3c7acc977c57a79b7c776b506240993f97acc7 + languageName: node + linkType: hard + +"dir-glob@npm:^3.0.1": + version: 3.0.1 + resolution: "dir-glob@npm:3.0.1" + dependencies: + path-type: "npm:^4.0.0" + checksum: 10c0/dcac00920a4d503e38bb64001acb19df4efc14536ada475725e12f52c16777afdee4db827f55f13a908ee7efc0cb282e2e3dbaeeb98c0993dd93d1802d3bf00c + languageName: node + linkType: hard + "dotenv@npm:^17.2.3": version: 17.2.3 resolution: "dotenv@npm:17.2.3" @@ -3158,6 +3574,16 @@ __metadata: languageName: node linkType: hard +"enquirer@npm:^2.4.1": + version: 2.4.1 + resolution: "enquirer@npm:2.4.1" + dependencies: + ansi-colors: "npm:^4.1.1" + strip-ansi: "npm:^6.0.1" + checksum: 10c0/43850479d7a51d36a9c924b518dcdc6373b5a8ae3401097d336b7b7e258324749d0ad37a1fcaa5706f04799baa05585cd7af19ebdf7667673e7694435fcea918 + languageName: node + linkType: hard + "env-paths@npm:^2.2.0": version: 2.2.1 resolution: "env-paths@npm:2.2.1" @@ -3478,6 +3904,16 @@ __metadata: languageName: node linkType: hard +"esprima@npm:^4.0.0": + version: 4.0.1 + resolution: "esprima@npm:4.0.1" + bin: + esparse: ./bin/esparse.js + esvalidate: ./bin/esvalidate.js + checksum: 10c0/ad4bab9ead0808cf56501750fd9d3fb276f6b105f987707d059005d57e182d18a7c9ec7f3a01794ebddcca676773e42ca48a32d67a250c9d35e009ca613caba3 + languageName: node + linkType: hard + "estree-walker@npm:^3.0.3": version: 3.0.3 resolution: "estree-walker@npm:3.0.3" @@ -3528,7 +3964,14 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.3.3": +"extendable-error@npm:^0.1.5": + version: 0.1.7 + resolution: "extendable-error@npm:0.1.7" + checksum: 10c0/c46648b7682448428f81b157cbfe480170fd96359c55db477a839ddeaa34905a18cba0b989bafe5e83f93c2491a3fcc7cc536063ea326ba9d72e9c6e2fe736a7 + languageName: node + linkType: hard + +"fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.3": version: 3.3.3 resolution: "fast-glob@npm:3.3.3" dependencies: @@ -3600,6 +4043,16 @@ __metadata: languageName: node linkType: hard +"find-up@npm:^4.1.0": + version: 4.1.0 + resolution: "find-up@npm:4.1.0" + dependencies: + locate-path: "npm:^5.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/0406ee89ebeefa2d507feb07ec366bebd8a6167ae74aa4e34fb4c4abd06cf782a3ce26ae4194d70706f72182841733f00551c209fe575cb00bd92104056e78c1 + languageName: node + linkType: hard + "for-each@npm:^0.3.3, for-each@npm:^0.3.5": version: 0.3.5 resolution: "for-each@npm:0.3.5" @@ -3650,6 +4103,28 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:^7.0.1": + version: 7.0.1 + resolution: "fs-extra@npm:7.0.1" + dependencies: + graceful-fs: "npm:^4.1.2" + jsonfile: "npm:^4.0.0" + universalify: "npm:^0.1.0" + checksum: 10c0/1943bb2150007e3739921b8d13d4109abdc3cc481e53b97b7ea7f77eda1c3c642e27ae49eac3af074e3496ea02fde30f411ef410c760c70a38b92e656e5da784 + languageName: node + linkType: hard + +"fs-extra@npm:^8.1.0": + version: 8.1.0 + resolution: "fs-extra@npm:8.1.0" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^4.0.0" + universalify: "npm:^0.1.0" + checksum: 10c0/259f7b814d9e50d686899550c4f9ded85c46c643f7fe19be69504888e007fcbc08f306fae8ec495b8b998635e997c9e3e175ff2eeed230524ef1c1684cc96423 + languageName: node + linkType: hard + "fs-minipass@npm:^3.0.0": version: 3.0.3 resolution: "fs-minipass@npm:3.0.3" @@ -3831,6 +4306,20 @@ __metadata: languageName: node linkType: hard +"globby@npm:^11.0.0": + version: 11.1.0 + resolution: "globby@npm:11.1.0" + dependencies: + array-union: "npm:^2.1.0" + dir-glob: "npm:^3.0.1" + fast-glob: "npm:^3.2.9" + ignore: "npm:^5.2.0" + merge2: "npm:^1.4.1" + slash: "npm:^3.0.0" + checksum: 10c0/b39511b4afe4bd8a7aead3a27c4ade2b9968649abab0a6c28b1a90141b96ca68ca5db1302f7c7bd29eab66bf51e13916b8e0a3d0ac08f75e1e84a39b35691189 + languageName: node + linkType: hard + "gopd@npm:^1.0.1, gopd@npm:^1.2.0": version: 1.2.0 resolution: "gopd@npm:1.2.0" @@ -3857,7 +4346,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6": +"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.5, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 @@ -3979,6 +4468,15 @@ __metadata: languageName: node linkType: hard +"human-id@npm:^4.1.1": + version: 4.1.3 + resolution: "human-id@npm:4.1.3" + bin: + human-id: dist/cli.js + checksum: 10c0/c0e6aacfa71adff6e9783fc209493a7f8de92da041bea32deb3a9cd1244a4d2b89f32d5e90130e8e22da4e6fe15b61cf4e533f114927384de1418460c92b5a7a + languageName: node + linkType: hard + "human-signals@npm:^8.0.1": version: 8.0.1 resolution: "human-signals@npm:8.0.1" @@ -3995,6 +4493,22 @@ __metadata: languageName: node linkType: hard +"iconv-lite@npm:^0.7.0": + version: 0.7.2 + resolution: "iconv-lite@npm:0.7.2" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/3c228920f3bd307f56bf8363706a776f4a060eb042f131cd23855ceca962951b264d0997ab38a1ad340e1c5df8499ed26e1f4f0db6b2a2ad9befaff22f14b722 + languageName: node + linkType: hard + +"ignore@npm:^5.2.0": + version: 5.3.2 + resolution: "ignore@npm:5.3.2" + checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 + languageName: node + linkType: hard + "image-size@npm:^2.0.2": version: 2.0.2 resolution: "image-size@npm:2.0.2" @@ -4284,6 +4798,15 @@ __metadata: languageName: node linkType: hard +"is-subdir@npm:^1.1.1": + version: 1.2.0 + resolution: "is-subdir@npm:1.2.0" + dependencies: + better-path-resolve: "npm:1.0.0" + checksum: 10c0/03a03ee2ee6578ce589b1cfaf00e65c86b20fd1b82c1660625557c535439a7477cda77e20c62cda6d4c99e7fd908b4619355ae2d989f4a524a35350a44353032 + languageName: node + linkType: hard + "is-symbol@npm:^1.0.4, is-symbol@npm:^1.1.1": version: 1.1.1 resolution: "is-symbol@npm:1.1.1" @@ -4337,6 +4860,13 @@ __metadata: languageName: node linkType: hard +"is-windows@npm:^1.0.0": + version: 1.0.2 + resolution: "is-windows@npm:1.0.2" + checksum: 10c0/b32f418ab3385604a66f1b7a3ce39d25e8881dee0bd30816dc8344ef6ff9df473a732bcc1ec4e84fe99b2f229ae474f7133e8e93f9241686cfcf7eebe53ba7a5 + languageName: node + linkType: hard + "isarray@npm:^2.0.5": version: 2.0.5 resolution: "isarray@npm:2.0.5" @@ -4433,6 +4963,18 @@ __metadata: languageName: node linkType: hard +"js-yaml@npm:^3.6.1": + version: 3.14.2 + resolution: "js-yaml@npm:3.14.2" + dependencies: + argparse: "npm:^1.0.7" + esprima: "npm:^4.0.0" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/3261f25912f5dd76605e5993d0a126c2b6c346311885d3c483706cd722efe34f697ea0331f654ce27c00a42b426e524518ec89d65ed02ea47df8ad26dcc8ce69 + languageName: node + linkType: hard + "js-yaml@npm:^4.1.1": version: 4.1.1 resolution: "js-yaml@npm:4.1.1" @@ -4472,6 +5014,18 @@ __metadata: languageName: node linkType: hard +"jsonfile@npm:^4.0.0": + version: 4.0.0 + resolution: "jsonfile@npm:4.0.0" + dependencies: + graceful-fs: "npm:^4.1.6" + dependenciesMeta: + graceful-fs: + optional: true + checksum: 10c0/7dc94b628d57a66b71fb1b79510d460d662eb975b5f876d723f81549c2e9cd316d58a2ddf742b2b93a4fa6b17b2accaf1a738a0e2ea114bdfb13a32e5377e480 + languageName: node + linkType: hard + "keyv@npm:^4.5.4": version: 4.5.4 resolution: "keyv@npm:4.5.4" @@ -4519,6 +5073,15 @@ __metadata: languageName: node linkType: hard +"locate-path@npm:^5.0.0": + version: 5.0.0 + resolution: "locate-path@npm:5.0.0" + dependencies: + p-locate: "npm:^4.1.0" + checksum: 10c0/33a1c5247e87e022f9713e6213a744557a3e9ec32c5d0b5efb10aa3a38177615bf90221a5592674857039c1a0fd2063b82f285702d37b792d973e9e72ace6c59 + languageName: node + linkType: hard + "lodash._baseiteratee@npm:~4.7.0": version: 4.7.0 resolution: "lodash._baseiteratee@npm:4.7.0" @@ -4568,6 +5131,13 @@ __metadata: languageName: node linkType: hard +"lodash.startcase@npm:^4.4.0": + version: 4.4.0 + resolution: "lodash.startcase@npm:4.4.0" + checksum: 10c0/bd82aa87a45de8080e1c5ee61128c7aee77bf7f1d86f4ff94f4a6d7438fc9e15e5f03374b947be577a93804c8ad6241f0251beaf1452bf716064eeb657b3a9f0 + languageName: node + linkType: hard + "lodash.throttle@npm:^4.1.1": version: 4.1.1 resolution: "lodash.throttle@npm:4.1.1" @@ -4675,7 +5245,7 @@ __metadata: languageName: node linkType: hard -"merge2@npm:^1.3.0": +"merge2@npm:^1.3.0, merge2@npm:^1.4.1": version: 1.4.1 resolution: "merge2@npm:1.4.1" checksum: 10c0/254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb @@ -4852,6 +5422,13 @@ __metadata: languageName: node linkType: hard +"mri@npm:^1.2.0": + version: 1.2.0 + resolution: "mri@npm:1.2.0" + checksum: 10c0/a3d32379c2554cf7351db6237ddc18dc9e54e4214953f3da105b97dc3babe0deb3ffe99cf409b38ea47cc29f9430561ba6b53b24ab8f9ce97a4b50409e4a50e7 + languageName: node + linkType: hard + "ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" @@ -5018,6 +5595,13 @@ __metadata: languageName: node linkType: hard +"outdent@npm:^0.5.0": + version: 0.5.0 + resolution: "outdent@npm:0.5.0" + checksum: 10c0/e216a4498889ba1babae06af84cdc4091f7cac86da49d22d0163b3be202a5f52efcd2bcd3dfca60a361eb3a27b4299f185c5655061b6b402552d7fcd1d040cff + languageName: node + linkType: hard + "outvariant@npm:^1.4.0, outvariant@npm:^1.4.3": version: 1.4.3 resolution: "outvariant@npm:1.4.3" @@ -5112,6 +5696,40 @@ __metadata: languageName: node linkType: hard +"p-filter@npm:^2.1.0": + version: 2.1.0 + resolution: "p-filter@npm:2.1.0" + dependencies: + p-map: "npm:^2.0.0" + checksum: 10c0/5ac34b74b3b691c04212d5dd2319ed484f591c557a850a3ffc93a08cb38c4f5540be059c6b10a185773c479ca583a91ea00c7d6c9958c815e6b74d052f356645 + languageName: node + linkType: hard + +"p-limit@npm:^2.2.0": + version: 2.3.0 + resolution: "p-limit@npm:2.3.0" + dependencies: + p-try: "npm:^2.0.0" + checksum: 10c0/8da01ac53efe6a627080fafc127c873da40c18d87b3f5d5492d465bb85ec7207e153948df6b9cbaeb130be70152f874229b8242ee2be84c0794082510af97f12 + languageName: node + linkType: hard + +"p-locate@npm:^4.1.0": + version: 4.1.0 + resolution: "p-locate@npm:4.1.0" + dependencies: + p-limit: "npm:^2.2.0" + checksum: 10c0/1b476ad69ad7f6059744f343b26d51ce091508935c1dbb80c4e0a2f397ffce0ca3a1f9f5cd3c7ce19d7929a09719d5c65fe70d8ee289c3f267cd36f2881813e9 + languageName: node + linkType: hard + +"p-map@npm:^2.0.0": + version: 2.1.0 + resolution: "p-map@npm:2.1.0" + checksum: 10c0/735dae87badd4737a2dd582b6d8f93e49a1b79eabbc9815a4d63a528d5e3523e978e127a21d784cccb637010e32103a40d2aaa3ab23ae60250b1a820ca752043 + languageName: node + linkType: hard + "p-map@npm:^7.0.2, p-map@npm:^7.0.3": version: 7.0.3 resolution: "p-map@npm:7.0.3" @@ -5145,6 +5763,13 @@ __metadata: languageName: node linkType: hard +"p-try@npm:^2.0.0": + version: 2.2.0 + resolution: "p-try@npm:2.2.0" + checksum: 10c0/c36c19907734c904b16994e6535b02c36c2224d433e01a2f1ab777237f4d86e6289fd5fd464850491e940379d4606ed850c03e0f9ab600b0ebddb511312e177f + languageName: node + linkType: hard + "package-json-from-dist@npm:^1.0.0, package-json-from-dist@npm:^1.0.1": version: 1.0.1 resolution: "package-json-from-dist@npm:1.0.1" @@ -5152,6 +5777,15 @@ __metadata: languageName: node linkType: hard +"package-manager-detector@npm:^0.2.0": + version: 0.2.11 + resolution: "package-manager-detector@npm:0.2.11" + dependencies: + quansync: "npm:^0.2.7" + checksum: 10c0/247991de461b9e731f3463b7dae9ce187e53095b7b94d7d96eec039abf418b61ccf74464bec1d0c11d97311f33472e77baccd4c5898f77358da4b5b33395e0b1 + languageName: node + linkType: hard + "parse-json@npm:^4.0.0": version: 4.0.0 resolution: "parse-json@npm:4.0.0" @@ -5169,6 +5803,13 @@ __metadata: languageName: node linkType: hard +"path-exists@npm:^4.0.0": + version: 4.0.0 + resolution: "path-exists@npm:4.0.0" + checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b + languageName: node + linkType: hard + "path-is-absolute@npm:^1.0.0": version: 1.0.1 resolution: "path-is-absolute@npm:1.0.1" @@ -5233,6 +5874,13 @@ __metadata: languageName: node linkType: hard +"path-type@npm:^4.0.0": + version: 4.0.0 + resolution: "path-type@npm:4.0.0" + checksum: 10c0/666f6973f332f27581371efaf303fd6c272cc43c2057b37aa99e3643158c7e4b2626549555d88626e99ea9e046f82f32e41bbde5f1508547e9a11b149b52387c + languageName: node + linkType: hard + "pathe@npm:^2.0.3": version: 2.0.3 resolution: "pathe@npm:2.0.3" @@ -5247,7 +5895,7 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:^1.1.1": +"picocolors@npm:^1.1.0, picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 @@ -5284,6 +5932,13 @@ __metadata: languageName: node linkType: hard +"pify@npm:^4.0.1": + version: 4.0.1 + resolution: "pify@npm:4.0.1" + checksum: 10c0/6f9d404b0d47a965437403c9b90eca8bb2536407f03de165940e62e72c8c8b75adda5516c6b9b23675a5877cc0bcac6bdfb0ef0e39414cd2476d5495da40e7cf + languageName: node + linkType: hard + "possible-typed-array-names@npm:^1.0.0": version: 1.1.0 resolution: "possible-typed-array-names@npm:1.1.0" @@ -5302,6 +5957,15 @@ __metadata: languageName: node linkType: hard +"prettier@npm:^2.7.1": + version: 2.8.8 + resolution: "prettier@npm:2.8.8" + bin: + prettier: bin-prettier.js + checksum: 10c0/463ea8f9a0946cd5b828d8cf27bd8b567345cf02f56562d5ecde198b91f47a76b7ac9eae0facd247ace70e927143af6135e8cf411986b8cb8478784a4d6d724a + languageName: node + linkType: hard + "pretty-ms@npm:^9.2.0": version: 9.3.0 resolution: "pretty-ms@npm:9.3.0" @@ -5346,6 +6010,13 @@ __metadata: languageName: node linkType: hard +"quansync@npm:^0.2.7": + version: 0.2.11 + resolution: "quansync@npm:0.2.11" + checksum: 10c0/cb9a1f8ebce074069f2f6a78578873ffedd9de9f6aa212039b44c0870955c04a71c3b1311b5d97f8ac2f2ec476de202d0a5c01160cb12bc0a11b7ef36d22ef56 + languageName: node + linkType: hard + "querystringify@npm:^2.1.1": version: 2.2.0 resolution: "querystringify@npm:2.2.0" @@ -5378,6 +6049,18 @@ __metadata: languageName: node linkType: hard +"read-yaml-file@npm:^1.1.0": + version: 1.1.0 + resolution: "read-yaml-file@npm:1.1.0" + dependencies: + graceful-fs: "npm:^4.1.5" + js-yaml: "npm:^3.6.1" + pify: "npm:^4.0.1" + strip-bom: "npm:^3.0.0" + checksum: 10c0/85a9ba08bb93f3c91089bab4f1603995ec7156ee595f8ce40ae9f49d841cbb586511508bd47b7cf78c97f678c679b2c6e2c0092e63f124214af41b6f8a25ca31 + languageName: node + linkType: hard + "recursive-readdir@npm:^2.2.3": version: 2.2.3 resolution: "recursive-readdir@npm:2.2.3" @@ -5431,6 +6114,13 @@ __metadata: languageName: node linkType: hard +"resolve-from@npm:^5.0.0": + version: 5.0.0 + resolution: "resolve-from@npm:5.0.0" + checksum: 10c0/b21cb7f1fb746de8107b9febab60095187781137fd803e6a59a76d421444b1531b641bba5857f5dc011974d8a5c635d61cec49e6bd3b7fc20e01f0fafc4efbf2 + languageName: node + linkType: hard + "resolve-pkg-maps@npm:^1.0.0": version: 1.0.0 resolution: "resolve-pkg-maps@npm:1.0.0" @@ -5801,6 +6491,13 @@ __metadata: languageName: node linkType: hard +"slash@npm:^3.0.0": + version: 3.0.0 + resolution: "slash@npm:3.0.0" + checksum: 10c0/e18488c6a42bdfd4ac5be85b2ced3ccd0224773baae6ad42cfbb9ec74fc07f9fa8396bd35ee638084ead7a2a0818eb5e7151111544d4731ce843019dab4be47b + languageName: node + linkType: hard + "smart-buffer@npm:^4.2.0": version: 4.2.0 resolution: "smart-buffer@npm:4.2.0" @@ -5843,6 +6540,16 @@ __metadata: languageName: node linkType: hard +"spawndamnit@npm:^3.0.1": + version: 3.0.1 + resolution: "spawndamnit@npm:3.0.1" + dependencies: + cross-spawn: "npm:^7.0.5" + signal-exit: "npm:^4.0.1" + checksum: 10c0/a9821a59bc78a665bd44718dea8f4f4010bb1a374972b0a6a1633b9186cda6d6fd93f22d1e49d9944d6bb175ba23ce29036a4bd624884fb157d981842c3682f3 + languageName: node + linkType: hard + "spdx-correct@npm:^3.0.0": version: 3.2.0 resolution: "spdx-correct@npm:3.2.0" @@ -5884,6 +6591,13 @@ __metadata: languageName: node linkType: hard +"sprintf-js@npm:~1.0.2": + version: 1.0.3 + resolution: "sprintf-js@npm:1.0.3" + checksum: 10c0/ecadcfe4c771890140da5023d43e190b7566d9cf8b2d238600f31bec0fc653f328da4450eb04bd59a431771a8e9cc0e118f0aa3974b683a4981b4e07abc2a5bb + languageName: node + linkType: hard + "ssri@npm:^12.0.0": version: 12.0.0 resolution: "ssri@npm:12.0.0" @@ -6090,6 +6804,13 @@ __metadata: languageName: node linkType: hard +"term-size@npm:^2.1.0": + version: 2.2.1 + resolution: "term-size@npm:2.2.1" + checksum: 10c0/89f6bba1d05d425156c0910982f9344d9e4aebf12d64bfa1f460d93c24baa7bc4c4a21d355fbd7153c316433df0538f64d0ae6e336cc4a69fdda4f85d62bc79d + languageName: node + linkType: hard + "test-exclude@npm:^7.0.1": version: 7.0.1 resolution: "test-exclude@npm:7.0.1" @@ -6155,9 +6876,17 @@ __metadata: languageName: node linkType: hard -"transloadit@workspace:.": +"transloadit-node-sdk@workspace:.": version: 0.0.0-use.local - resolution: "transloadit@workspace:." + resolution: "transloadit-node-sdk@workspace:." + dependencies: + "@changesets/cli": "npm:^2.29.7" + languageName: unknown + linkType: soft + +"transloadit@workspace:packages/transloadit": + version: 0.0.0-use.local + resolution: "transloadit@workspace:packages/transloadit" dependencies: "@aws-sdk/client-s3": "npm:^3.891.0" "@aws-sdk/s3-request-presigner": "npm:^3.891.0" @@ -6378,6 +7107,13 @@ __metadata: languageName: node linkType: hard +"universalify@npm:^0.1.0": + version: 0.1.2 + resolution: "universalify@npm:0.1.2" + checksum: 10c0/e70e0339f6b36f34c9816f6bf9662372bd241714dc77508d231d08386d94f2c4aa1ba1318614f92015f40d45aae1b9075cd30bd490efbe39387b60a76ca3f045 + languageName: node + linkType: hard + "url-parse@npm:^1.5.7": version: 1.5.10 resolution: "url-parse@npm:1.5.10" From 36114fb42c1952257830eea9c27585726a7974d3 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Thu, 15 Jan 2026 22:35:03 +0100 Subject: [PATCH 06/43] chore: align types version with node and transloadit --- .changeset/config.json | 3 ++- docs/todo.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.changeset/config.json b/.changeset/config.json index 9658be22..b4ac3b60 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -10,7 +10,8 @@ "fixed": [ [ "@transloadit/node", - "transloadit" + "transloadit", + "@transloadit/types" ] ], "linked": [], diff --git a/docs/todo.md b/docs/todo.md index aced4e18..4474ac29 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -24,7 +24,7 @@ - [ ] Use `yarn changeset version` to bump versions - [ ] Use `yarn changeset publish` to publish packages - [x] Keep `transloadit` and `@transloadit/node` versions synchronized -- [ ] Decide whether `@transloadit/types` tracks same version or diverges +- [x] Decide whether `@transloadit/types` tracks same version or diverges ## Implementation phases ### Phase 1: monorepo scaffolding From f989dc1f4a745c6a45c0a89e45a481a37b90eb3e Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Thu, 15 Jan 2026 23:14:07 +0100 Subject: [PATCH 07/43] chore: add changeset for monorepo alignment --- .changeset/monorepo-alignment.md | 8 ++++++++ docs/todo.md | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 .changeset/monorepo-alignment.md diff --git a/.changeset/monorepo-alignment.md b/.changeset/monorepo-alignment.md new file mode 100644 index 00000000..4d62568b --- /dev/null +++ b/.changeset/monorepo-alignment.md @@ -0,0 +1,8 @@ +--- +"@transloadit/node": patch +"transloadit": patch +"@transloadit/types": patch +"@transloadit/zod": patch +--- + +chore: align workspace packages for upcoming monorepo releases diff --git a/docs/todo.md b/docs/todo.md index 4474ac29..860e8185 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -20,7 +20,7 @@ ## Versioning and releases - [x] Add Changesets at workspace root -- [ ] Use `yarn changeset` for changes +- [x] Use `yarn changeset` for changes - [ ] Use `yarn changeset version` to bump versions - [ ] Use `yarn changeset publish` to publish packages - [x] Keep `transloadit` and `@transloadit/node` versions synchronized From 54cad4a99e4ed39ad49f4e1e003651280bc234df Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Thu, 15 Jan 2026 23:15:46 +0100 Subject: [PATCH 08/43] chore: version packages with changesets --- .changeset/monorepo-alignment.md | 8 -------- CONTRIBUTING.md | 22 +++++++++++----------- docs/todo.md | 2 +- packages/node/CHANGELOG.md | 7 +++++++ packages/node/package.json | 2 +- packages/transloadit/CHANGELOG.md | 7 +++++++ packages/transloadit/package.json | 2 +- packages/types/CHANGELOG.md | 7 +++++++ packages/types/package.json | 2 +- packages/zod/CHANGELOG.md | 7 +++++++ packages/zod/package.json | 2 +- 11 files changed, 44 insertions(+), 24 deletions(-) delete mode 100644 .changeset/monorepo-alignment.md create mode 100644 packages/node/CHANGELOG.md create mode 100644 packages/transloadit/CHANGELOG.md create mode 100644 packages/types/CHANGELOG.md create mode 100644 packages/zod/CHANGELOG.md diff --git a/.changeset/monorepo-alignment.md b/.changeset/monorepo-alignment.md deleted file mode 100644 index 4d62568b..00000000 --- a/.changeset/monorepo-alignment.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@transloadit/node": patch -"transloadit": patch -"@transloadit/types": patch -"@transloadit/zod": patch ---- - -chore: align workspace packages for upcoming monorepo releases diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d0e048f5..48e28d71 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -83,15 +83,15 @@ View the coverage report locally by opening `coverage/index.html` in your browse Only maintainers can make releases. Releases to [npm](https://www.npmjs.com) are automated using GitHub actions. To make a release, perform the following steps: -1. Update `CHANGELOG.md` with a new entry describing the release. And `git add CHANGELOG.md && git commit -m "Update CHANGELOG.md"`. -2. Update the version using npm. This will update the version in the `package.json` file and create a git tag. E.g.: - -- `npm version patch` -- OR, for pre-releases: `npm version prerelease` - -3. Push the tag to GitHub: `git push origin main --tags` -4. If the tests pass, GitHub actions will now publish the new version to npm. +1. Create a changeset: + - `yarn changeset` +2. Version packages (updates `CHANGELOG.md` + workspace `package.json` files): + - `yarn changeset version` + - `git add -A && git commit -m "chore: version packages"` +3. Push the version commit and tags: + - `git push origin main` +4. Publish (maintainers only; GitHub Actions handles the release): + - `yarn changeset publish` 5. When successful add [release notes](https://github.com/transloadit/node-sdk/releases). -6. If this was a pre-release, remember to run this to reset the [npm `latest` tag](https://www.npmjs.com/package/transloadit?activeTab=versions) to the previous version (replace `x.y.z` with previous version): - -- `npm dist-tag add transloadit@X.Y.Z latest` +6. If this was a pre-release, remember to reset the [npm `latest` tag](https://www.npmjs.com/package/transloadit?activeTab=versions) to the previous version (replace `x.y.z` with previous version): + - `npm dist-tag add transloadit@X.Y.Z latest` diff --git a/docs/todo.md b/docs/todo.md index 860e8185..f50b2851 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -21,7 +21,7 @@ ## Versioning and releases - [x] Add Changesets at workspace root - [x] Use `yarn changeset` for changes -- [ ] Use `yarn changeset version` to bump versions +- [x] Use `yarn changeset version` to bump versions - [ ] Use `yarn changeset publish` to publish packages - [x] Keep `transloadit` and `@transloadit/node` versions synchronized - [x] Decide whether `@transloadit/types` tracks same version or diverges diff --git a/packages/node/CHANGELOG.md b/packages/node/CHANGELOG.md new file mode 100644 index 00000000..39cd42df --- /dev/null +++ b/packages/node/CHANGELOG.md @@ -0,0 +1,7 @@ +# @transloadit/node + +## 4.1.3 + +### Patch Changes + +- f989dc1: chore: align workspace packages for upcoming monorepo releases diff --git a/packages/node/package.json b/packages/node/package.json index d1f7afef..62a23a75 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -1,6 +1,6 @@ { "name": "@transloadit/node", - "version": "4.1.2", + "version": "4.1.3", "description": "Node.js SDK for Transloadit", "type": "module", "keywords": ["transloadit", "encoding", "transcoding", "video", "audio", "mp3"], diff --git a/packages/transloadit/CHANGELOG.md b/packages/transloadit/CHANGELOG.md new file mode 100644 index 00000000..1d3b80fa --- /dev/null +++ b/packages/transloadit/CHANGELOG.md @@ -0,0 +1,7 @@ +# transloadit + +## 4.1.3 + +### Patch Changes + +- f989dc1: chore: align workspace packages for upcoming monorepo releases diff --git a/packages/transloadit/package.json b/packages/transloadit/package.json index 263c7949..d0f9e62e 100644 --- a/packages/transloadit/package.json +++ b/packages/transloadit/package.json @@ -1,6 +1,6 @@ { "name": "transloadit", - "version": "4.1.2", + "version": "4.1.3", "description": "Node.js SDK for Transloadit", "type": "module", "keywords": [ diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md new file mode 100644 index 00000000..b4308fde --- /dev/null +++ b/packages/types/CHANGELOG.md @@ -0,0 +1,7 @@ +# @transloadit/types + +## 4.1.3 + +### Patch Changes + +- f989dc1: chore: align workspace packages for upcoming monorepo releases diff --git a/packages/types/package.json b/packages/types/package.json index d14120a5..5c5bf5d1 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@transloadit/types", - "version": "4.1.2", + "version": "4.1.3", "description": "Transloadit type definitions", "type": "module", "license": "MIT", diff --git a/packages/zod/CHANGELOG.md b/packages/zod/CHANGELOG.md new file mode 100644 index 00000000..bfcdfe2a --- /dev/null +++ b/packages/zod/CHANGELOG.md @@ -0,0 +1,7 @@ +# @transloadit/zod + +## 4.1.3 + +### Patch Changes + +- f989dc1: chore: align workspace packages for upcoming monorepo releases diff --git a/packages/zod/package.json b/packages/zod/package.json index e3f03591..24fc5c23 100644 --- a/packages/zod/package.json +++ b/packages/zod/package.json @@ -1,6 +1,6 @@ { "name": "@transloadit/zod", - "version": "4.1.2", + "version": "4.1.3", "description": "Transloadit Zod schemas", "type": "module", "license": "MIT", From c8089f6a420dc3f5536d1ae00e8da8bb414da267 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Fri, 16 Jan 2026 07:06:36 +0100 Subject: [PATCH 09/43] docs: add cross-repo schema validation todo --- docs/todo.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/todo.md b/docs/todo.md index f50b2851..bfe4602f 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -51,3 +51,6 @@ ### Phase 5: compatibility validation - [ ] Record baseline fingerprint from current `transloadit` - [ ] Validate refactor output matches baseline (except package name if applicable) + +## Cross-repo validation +- [ ] Trial the updated robot schemas in alphalib within `~/code/content` and `~/code/api2`, then run `yarn check` in both repos From 2e8049a066b32cdabadc153171ab8a2e59c14351 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Fri, 16 Jan 2026 07:33:56 +0100 Subject: [PATCH 10/43] docs: mark cross-repo schema validation complete --- docs/todo.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/todo.md b/docs/todo.md index bfe4602f..ed3b4653 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -49,8 +49,8 @@ - [ ] Confirm publish via Changesets ### Phase 5: compatibility validation -- [ ] Record baseline fingerprint from current `transloadit` -- [ ] Validate refactor output matches baseline (except package name if applicable) +- [x] Record baseline fingerprint from current `transloadit` +- [x] Validate refactor output matches baseline (except package name if applicable) ## Cross-repo validation -- [ ] Trial the updated robot schemas in alphalib within `~/code/content` and `~/code/api2`, then run `yarn check` in both repos +- [x] Trial the updated robot schemas in alphalib within `~/code/content` and `~/code/api2`, then run `yarn check` in both repos From b7665a42bd8e14adea68f5644fcb6775a1406cba Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Fri, 16 Jan 2026 07:48:22 +0100 Subject: [PATCH 11/43] chore: sync transloadit wrapper packaging --- docs/todo.md | 4 ++-- packages/transloadit/package.json | 2 +- scripts/prepare-transloadit.js | 33 ++++++++++++++++++++++++++++++- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/docs/todo.md b/docs/todo.md index ed3b4653..bfe6cfe0 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -44,8 +44,8 @@ - [x] Depend on `zod` ### Phase 4: transloadit wrapper -- [ ] Publish identical output to `@transloadit/node` -- [ ] Copy dist + transform `package.json` +- [x] Publish identical output to `@transloadit/node` +- [x] Copy dist + transform `package.json` - [ ] Confirm publish via Changesets ### Phase 5: compatibility validation diff --git a/packages/transloadit/package.json b/packages/transloadit/package.json index d0f9e62e..f54b16ae 100644 --- a/packages/transloadit/package.json +++ b/packages/transloadit/package.json @@ -76,7 +76,7 @@ "lint:deps": "knip --dependencies --no-progress", "fix:deps": "knip --dependencies --no-progress --fix", "knip": "knip --no-config-hints --no-progress", - "prepack": "rm -f tsconfig.tsbuildinfo tsconfig.build.tsbuildinfo && tsc --build tsconfig.build.json", + "prepack": "node ../../scripts/prepare-transloadit.js", "test:unit": "vitest run --coverage ./test/unit", "test:e2e": "vitest run ./test/e2e", "test": "vitest run --coverage" diff --git a/scripts/prepare-transloadit.js b/scripts/prepare-transloadit.js index 53f1ceb0..309bfe2c 100644 --- a/scripts/prepare-transloadit.js +++ b/scripts/prepare-transloadit.js @@ -1,4 +1,4 @@ -import { cp, mkdir, rm } from 'node:fs/promises' +import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises' import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { execFile } from 'node:child_process' @@ -17,6 +17,35 @@ const copyDir = async (from, to) => { await cp(from, to, { recursive: true }) } +const readJson = async (filePath) => { + const raw = await readFile(filePath, 'utf8') + return JSON.parse(raw) +} + +const writeJson = async (filePath, data) => { + const json = `${JSON.stringify(data, null, 2)}\n` + await writeFile(filePath, json) +} + +const writeLegacyPackageJson = async () => { + const nodePackageJson = await readJson(resolve(nodePackage, 'package.json')) + const scripts = { ...(nodePackageJson.scripts ?? {}) } + scripts.prepack = 'node ../../scripts/prepare-transloadit.js' + const legacyPackageJson = { + ...nodePackageJson, + name: 'transloadit', + scripts, + } + + await writeJson(resolve(legacyPackage, 'package.json'), legacyPackageJson) +} + +const writeLegacyChangelog = async () => { + const changelog = await readFile(resolve(nodePackage, 'CHANGELOG.md'), 'utf8') + const updated = changelog.replace(/^# .+$/m, '# transloadit') + await writeFile(resolve(legacyPackage, 'CHANGELOG.md'), updated) +} + const main = async () => { await execFileAsync('yarn', ['workspace', '@transloadit/node', 'prepack'], { cwd: repoRoot, @@ -26,6 +55,8 @@ const main = async () => { await copyDir(resolve(nodePackage, 'src'), resolve(legacyPackage, 'src')) await cp(resolve(repoRoot, 'README.md'), resolve(legacyPackage, 'README.md')) await cp(resolve(repoRoot, 'LICENSE'), resolve(legacyPackage, 'LICENSE')) + await writeLegacyPackageJson() + await writeLegacyChangelog() } await main() From 6e4ad08f56e38b767486539f6df4efdb2544d44b Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Fri, 16 Jan 2026 11:51:28 +0100 Subject: [PATCH 12/43] fix: align codecov path and schema exports --- .github/workflows/ci.yml | 2 +- packages/node/src/alphalib/types/template.ts | 2 +- packages/zod/scripts/sync-v3.js | 18 +++++++++--------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8542016..8448bc19 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -158,7 +158,7 @@ jobs: uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} - files: ./packages/node/coverage/lcov.info + files: ./coverage/lcov.info flags: unittests name: node-sdk fail_ci_if_error: true diff --git a/packages/node/src/alphalib/types/template.ts b/packages/node/src/alphalib/types/template.ts index d237b809..180db100 100644 --- a/packages/node/src/alphalib/types/template.ts +++ b/packages/node/src/alphalib/types/template.ts @@ -37,7 +37,7 @@ export type StepsWithHiddenFieldsInput = z.input value) that can be used as Assembly Variables, just like additional form fields can. You can use anything that is JSON stringifyable as a value', diff --git a/packages/zod/scripts/sync-v3.js b/packages/zod/scripts/sync-v3.js index 522bc779..bcaf299c 100644 --- a/packages/zod/scripts/sync-v3.js +++ b/packages/zod/scripts/sync-v3.js @@ -8,15 +8,15 @@ const sourceRoot = resolve(zodRoot, '../node/src/alphalib/types') const destRoot = resolve(zodRoot, 'src/v3') const indexContents = [ - "export * from './assembliesGet.js'", - "export * from './assemblyReplay.js'", - "export * from './assemblyReplayNotification.js'", - "export * from './assemblyStatus.js'", - "export * from './bill.js'", - "export * from './stackVersions.js'", - "export * from './template.js'", - "export * from './templateCredential.js'", - "export * from './robots/_index.js'", + "export * from './assembliesGet.ts'", + "export * from './assemblyReplay.ts'", + "export * from './assemblyReplayNotification.ts'", + "export * from './assemblyStatus.ts'", + "export * from './bill.ts'", + "export * from './stackVersions.ts'", + "export * from './template.ts'", + "export * from './templateCredential.ts'", + "export * from './robots/_index.ts'", '', ].join('\n') From 963bb5aad7aa2a16d44f22f08f5f2d1f4684b413 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Fri, 16 Jan 2026 12:28:07 +0100 Subject: [PATCH 13/43] fix: pack job and types exports --- .github/workflows/ci.yml | 2 +- packages/types/package.json | 9 ++++--- packages/types/scripts/emit-types.test.ts | 32 +++++++++++++++++++++++ packages/types/scripts/emit-types.ts | 8 +++--- 4 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 packages/types/scripts/emit-types.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8448bc19..edf7b8e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: with: node-version: 22 - run: corepack yarn - - run: corepack yarn pack + - run: corepack yarn run pack - uses: actions/upload-artifact@v4 with: name: package diff --git a/packages/types/package.json b/packages/types/package.json index 5c5bf5d1..b2d68e47 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -13,19 +13,20 @@ "types": "./dist/index.d.ts" }, "./robots": { - "types": "./dist/robots/_index.d.ts" + "types": "./dist/generated/robots/_index.d.ts" }, "./robots/*": { - "types": "./dist/robots/*.d.ts" + "types": "./dist/generated/robots/*.d.ts" }, "./*": { - "types": "./dist/*.d.ts" + "types": "./dist/generated/*.d.ts" } }, "scripts": { "generate": "node --experimental-strip-types scripts/emit-types.ts", + "test:unit": "node --experimental-strip-types scripts/emit-types.test.ts", "build": "yarn generate && tsc --build tsconfig.build.json", - "check": "yarn generate && tsc --build tsconfig.build.json" + "check": "yarn generate && yarn test:unit && tsc --build tsconfig.build.json" }, "devDependencies": { "typescript": "5.9.3", diff --git a/packages/types/scripts/emit-types.test.ts b/packages/types/scripts/emit-types.test.ts new file mode 100644 index 00000000..cc0990cd --- /dev/null +++ b/packages/types/scripts/emit-types.test.ts @@ -0,0 +1,32 @@ +import assert from 'node:assert/strict' + +import { escapeStringLiteral } from './emit-types.ts' + +const cases = [ + { + input: 'line1\nline2', + expected: 'line1\\nline2', + }, + { + input: 'line1\rline2', + expected: 'line1\\rline2', + }, + { + input: 'col1\tcol2', + expected: 'col1\\tcol2', + }, + { + input: 'path\\name', + expected: 'path\\\\name', + }, + { + input: 'it\'s fine', + expected: 'it\\\'s fine', + }, +] + +for (const { input, expected } of cases) { + assert.equal(escapeStringLiteral(input), expected) +} + +console.log('emit-types escapeStringLiteral: ok') diff --git a/packages/types/scripts/emit-types.ts b/packages/types/scripts/emit-types.ts index 4320742c..27a0c97c 100644 --- a/packages/types/scripts/emit-types.ts +++ b/packages/types/scripts/emit-types.ts @@ -149,13 +149,13 @@ const hasZodType = ( return false } -const escapeStringLiteral = (value: string): string => +export const escapeStringLiteral = (value: string): string => value .replace(/\\/g, '\\\\') .replace(/'/g, "\\'") - .replace(/\\r/g, '\\\\r') - .replace(/\\n/g, '\\\\n') - .replace(/\\t/g, '\\\\t') + .replace(/\r/g, '\\r') + .replace(/\n/g, '\\n') + .replace(/\t/g, '\\t') const formatPropertyName = (name: string): string => { if (ts.isIdentifierText(name, ts.ScriptTarget.ES2022)) { From cbb660d1ffe79ab2920fb867a88a66e1ce58d76f Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Fri, 16 Jan 2026 12:44:04 +0100 Subject: [PATCH 14/43] refactor: unify pack flow and exports --- docs/todo.md | 7 ++++ package.json | 2 +- packages/node/src/alphalib/types/template.ts | 2 +- packages/types/package.json | 3 ++ packages/zod/scripts/sync-v3.js | 22 ++++++----- scripts/pack-transloadit.ts | 39 ++++++++++++++++++++ 6 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 scripts/pack-transloadit.ts diff --git a/docs/todo.md b/docs/todo.md index bfe6cfe0..5deb3cda 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -54,3 +54,10 @@ ## Cross-repo validation - [x] Trial the updated robot schemas in alphalib within `~/code/content` and `~/code/api2`, then run `yarn check` in both repos + +## Refactors +- [x] Unify pack/build orchestration into a single script used by CI + local pack +- [x] Align `@transloadit/types` export map with the generated layout (keep subpath imports stable) +- [x] Centralize Zod sync export list/extension to avoid drift +- [x] Document inline heavy-inference casts (helper wrappers trigger TS7056) +- [x] Keep legacy `transloadit` metadata synced from `@transloadit/node` with one source of truth diff --git a/package.json b/package.json index 9120a274..ff81168a 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "fix:js": "yarn workspace @transloadit/node fix:js", "fix:js:unsafe": "yarn workspace @transloadit/node fix:js:unsafe", "knip": "yarn workspace @transloadit/node knip", - "pack": "node scripts/prepare-transloadit.js && npm pack --ignore-scripts --prefix packages/transloadit && mv packages/transloadit/*.tgz .", + "pack": "node --experimental-strip-types scripts/pack-transloadit.ts", "test:unit": "yarn workspace @transloadit/node test:unit", "test:e2e": "yarn workspace @transloadit/node test:e2e", "test": "yarn workspace @transloadit/node test" diff --git a/packages/node/src/alphalib/types/template.ts b/packages/node/src/alphalib/types/template.ts index 180db100..793e22a1 100644 --- a/packages/node/src/alphalib/types/template.ts +++ b/packages/node/src/alphalib/types/template.ts @@ -121,7 +121,7 @@ const assemblyInstructionsSharedShape = { .describe( 'Set this to true to reduce the response from an Assembly POST request to only the necessary fields. This prevents any potentially confidential information being leaked to the end user who is making the Assembly request. A successful Assembly will only include the ok and assembly_id fields. An erroneous Assembly will only include the error, http_code, message and assembly_id fields. The full Assembly Status will then still be sent to the notify_url if one was specified.', ), - // This is done to avoid heavy inference cost + // Keep the inline cast; helper wrappers trigger TS7056 (type serialization limit). steps: optionalStepsSchema as typeof optionalStepsSchema, template_id: templateIdSchema, } as const diff --git a/packages/types/package.json b/packages/types/package.json index b2d68e47..d91cb6c3 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -18,6 +18,9 @@ "./robots/*": { "types": "./dist/generated/robots/*.d.ts" }, + "./generated/*": { + "types": "./dist/generated/*.d.ts" + }, "./*": { "types": "./dist/generated/*.d.ts" } diff --git a/packages/zod/scripts/sync-v3.js b/packages/zod/scripts/sync-v3.js index bcaf299c..815912b7 100644 --- a/packages/zod/scripts/sync-v3.js +++ b/packages/zod/scripts/sync-v3.js @@ -7,16 +7,20 @@ const zodRoot = resolve(dirname(filePath), '..') const sourceRoot = resolve(zodRoot, '../node/src/alphalib/types') const destRoot = resolve(zodRoot, 'src/v3') +const reexportExtension = 'ts' +const indexModules = [ + 'assembliesGet', + 'assemblyReplay', + 'assemblyReplayNotification', + 'assemblyStatus', + 'bill', + 'stackVersions', + 'template', + 'templateCredential', + 'robots/_index', +] const indexContents = [ - "export * from './assembliesGet.ts'", - "export * from './assemblyReplay.ts'", - "export * from './assemblyReplayNotification.ts'", - "export * from './assemblyStatus.ts'", - "export * from './bill.ts'", - "export * from './stackVersions.ts'", - "export * from './template.ts'", - "export * from './templateCredential.ts'", - "export * from './robots/_index.ts'", + ...indexModules.map((module) => `export * from './${module}.${reexportExtension}'`), '', ].join('\n') diff --git a/scripts/pack-transloadit.ts b/scripts/pack-transloadit.ts new file mode 100644 index 00000000..a166f0eb --- /dev/null +++ b/scripts/pack-transloadit.ts @@ -0,0 +1,39 @@ +import { execFile } from 'node:child_process' +import { readdir, rename, rm } from 'node:fs/promises' +import { dirname, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' +import { promisify } from 'node:util' + +const execFileAsync = promisify(execFile) + +const filePath = fileURLToPath(import.meta.url) +const repoRoot = resolve(dirname(filePath), '..') +const legacyPackage = resolve(repoRoot, 'packages/transloadit') + +const runPack = async () => { + await execFileAsync('node', [resolve(repoRoot, 'scripts/prepare-transloadit.js')], { + cwd: repoRoot, + }) + + await execFileAsync('npm', ['pack', '--ignore-scripts', '--prefix', legacyPackage], { + cwd: repoRoot, + }) + + const entries = await readdir(legacyPackage) + const tarballs = entries.filter((entry) => entry.endsWith('.tgz')) + if (tarballs.length === 0) { + throw new Error('No tarball produced by npm pack') + } + + for (const tarball of tarballs) { + const from = resolve(legacyPackage, tarball) + const to = resolve(repoRoot, tarball) + await rm(to, { force: true }) + await rename(from, to) + } +} + +runPack().catch((error) => { + console.error(error) + process.exit(1) +}) From 4b96cde7b66f7553a76401d09a32cc0b4fafc6e4 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Fri, 16 Jan 2026 12:55:13 +0100 Subject: [PATCH 15/43] docs: add dual zod v3/v4 generation plan --- docs/multi-module.md | 53 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/docs/multi-module.md b/docs/multi-module.md index ff20de7e..f4313f45 100644 --- a/docs/multi-module.md +++ b/docs/multi-module.md @@ -152,6 +152,59 @@ When we move to Zod v4 (outside this scope), we can add `@transloadit/zod/v4` an JSON Schema exporter to generate a separate `@transloadit/jsonschema` package (or subpath) without changing the `@transloadit/types` contract. +## Supporting Zod v3 and v4 from one source + +We can support both Zod v3 and v4 while keeping alphalib as the synced source of truth, but we must +constrain the source to a shared subset and enforce equivalence in CI. The safest plan is: + +1) Keep alphalib schemas v3-compatible so all current repos can sync without upgrades. +2) In node-sdk, generate the v3 artifacts directly from alphalib. +3) Generate v4 artifacts via a codemod transform and then validate equivalence. + +This lets us ship `@transloadit/zod/v3` and `@transloadit/zod/v4` from one repo while other repos +continue to sync alphalib in their existing environments. + +### Proposed generation pipeline + +``` +packages/zod/ + scripts/ + sync-v3.js # Copy alphalib into src/v3 and write index exports + sync-v4.ts # Transform src/v3 -> src/v4 (import path + API rewrites) + src/ + v3/ # Zod v3 schemas (direct from alphalib) + v4/ # Zod v4 schemas (generated) + test/ + type-equality-v3.ts # z.infer(v3) === @transloadit/types + type-equality-v4.ts # z.infer(v4) === @transloadit/types + runtime-fixtures.ts # Shared validation fixtures +``` + +**sync-v4.ts responsibilities** +- Rewrite Zod imports to `zod/v4`. +- Apply a small codemod pass for known API differences. +- Preserve descriptions/defaults/refinements (no semantic changes). + +### CI gates (no compatibility loss) + +1) **Type equivalence**: `z.infer` from v3 and v4 must match `@transloadit/types` using the existing + `Equal` assertion pattern. This protects type-level compatibility. +2) **Runtime equivalence**: Run a shared fixture set through both v3 and v4 schemas and compare + ok/error outcomes + error paths. This protects behavioral compatibility. +3) **JSON Schema parity (optional)**: When v4 JSON Schema export is in scope, generate schema from + v4 and compare against a golden snapshot or a normalized hash. + +If any gate fails, we block release. + +### Should we upgrade all alphalib consumers to v4 first? + +Not initially. v4 should be treated as an output target, not the input source, until: +- All alphalib-consuming repos can adopt v4 with minimal friction. +- The generator + equivalence tests have proven stable across several releases. + +Once those conditions hold, we can decide whether to flip alphalib to v4 or keep it v3-safe and +continue generating both versions indefinitely. + ## Publishing strategy ### transloadit and @transloadit/node From 64dbe6bd06e7beaa166e3003f28bcbe793346e67 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Fri, 16 Jan 2026 14:50:47 +0100 Subject: [PATCH 16/43] chore(zod): add v4 sync and parity checks --- .gitignore | 1 + docs/multi-module.md | 2 +- packages/zod/package.json | 16 +- .../zod/scripts/{sync-v3.js => sync-v3.ts} | 29 +- packages/zod/scripts/sync-v4.ts | 364 ++++++++++++++++++ .../test/fixtures/assembly-instructions.ts | 110 ++++++ packages/zod/test/runtime-parity.test.ts | 34 ++ .../{type-equality.ts => type-equality-v3.ts} | 2 +- packages/zod/test/type-equality-v4.ts | 16 + packages/zod/tsconfig.test.json | 6 +- yarn.lock | 9 +- 11 files changed, 581 insertions(+), 8 deletions(-) rename packages/zod/scripts/{sync-v3.js => sync-v3.ts} (53%) create mode 100644 packages/zod/scripts/sync-v4.ts create mode 100644 packages/zod/test/fixtures/assembly-instructions.ts create mode 100644 packages/zod/test/runtime-parity.test.ts rename packages/zod/test/{type-equality.ts => type-equality-v3.ts} (95%) create mode 100644 packages/zod/test/type-equality-v4.ts diff --git a/.gitignore b/.gitignore index d4f73c52..e0da6dec 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ packages/node/coverage .env packages/types/src/generated packages/zod/src/v3 +packages/zod/src/v4 diff --git a/docs/multi-module.md b/docs/multi-module.md index f4313f45..9b5245ac 100644 --- a/docs/multi-module.md +++ b/docs/multi-module.md @@ -169,7 +169,7 @@ continue to sync alphalib in their existing environments. ``` packages/zod/ scripts/ - sync-v3.js # Copy alphalib into src/v3 and write index exports + sync-v3.ts # Copy alphalib into src/v3 and write index exports sync-v4.ts # Transform src/v3 -> src/v4 (import path + API rewrites) src/ v3/ # Zod v3 schemas (direct from alphalib) diff --git a/packages/zod/package.json b/packages/zod/package.json index 24fc5c23..01833fc9 100644 --- a/packages/zod/package.json +++ b/packages/zod/package.json @@ -19,15 +19,25 @@ "./v3/*": { "types": "./dist/v3/*.d.ts", "default": "./dist/v3/*.js" + }, + "./v4": { + "types": "./dist/v4/index.d.ts", + "default": "./dist/v4/index.js" + }, + "./v4/*": { + "types": "./dist/v4/*.d.ts", + "default": "./dist/v4/*.js" } }, "scripts": { - "sync": "node scripts/sync-v3.js", + "sync:v3": "node scripts/sync-v3.ts", + "sync:v4": "node scripts/sync-v4.ts", + "sync": "yarn sync:v3 && yarn sync:v4", "build": "yarn sync && tsc --build tsconfig.build.json", - "check": "yarn sync && tsc --build tsconfig.build.json && tsc --noEmit --project tsconfig.test.json" + "check": "yarn sync && tsc --build tsconfig.build.json && tsc --noEmit --project tsconfig.test.json && node test/runtime-parity.test.ts" }, "dependencies": { - "zod": "3.25.76" + "zod": "^4.0.0" }, "devDependencies": { "typescript": "5.9.3" diff --git a/packages/zod/scripts/sync-v3.js b/packages/zod/scripts/sync-v3.ts similarity index 53% rename from packages/zod/scripts/sync-v3.js rename to packages/zod/scripts/sync-v3.ts index 815912b7..04897ce3 100644 --- a/packages/zod/scripts/sync-v3.js +++ b/packages/zod/scripts/sync-v3.ts @@ -1,4 +1,4 @@ -import { cp, mkdir, rm, writeFile } from 'node:fs/promises' +import { cp, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises' import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' @@ -24,11 +24,38 @@ const indexContents = [ '', ].join('\n') +const collectFiles = async (dir, acc = []) => { + const entries = await readdir(dir, { withFileTypes: true }) + for (const entry of entries) { + const full = resolve(dir, entry.name) + if (entry.isDirectory()) { + await collectFiles(full, acc) + continue + } + if (entry.isFile() && entry.name.endsWith('.ts')) { + acc.push(full) + } + } + return acc +} + +const rewriteZodImports = async () => { + const files = await collectFiles(destRoot) + for (const file of files) { + const contents = await readFile(file, 'utf8') + const next = contents.replace(/from ['"]zod['"]/g, "from 'zod/v3'") + if (next !== contents) { + await writeFile(file, next, 'utf8') + } + } +} + const main = async () => { await rm(destRoot, { recursive: true, force: true }) await mkdir(destRoot, { recursive: true }) await cp(sourceRoot, destRoot, { recursive: true }) await writeFile(resolve(destRoot, 'index.ts'), indexContents, 'utf8') + await rewriteZodImports() } await main() diff --git a/packages/zod/scripts/sync-v4.ts b/packages/zod/scripts/sync-v4.ts new file mode 100644 index 00000000..2430e70a --- /dev/null +++ b/packages/zod/scripts/sync-v4.ts @@ -0,0 +1,364 @@ +import { cp, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises' +import { dirname, resolve, sep } from 'node:path' +import { fileURLToPath } from 'node:url' + +const filePath = fileURLToPath(import.meta.url) +const zodRoot = resolve(dirname(filePath), '..') +const sourceRoot = resolve(zodRoot, 'src/v3') +const destRoot = resolve(zodRoot, 'src/v4') + +const collectFiles = async (dir: string, acc: string[] = []): Promise => { + const entries = await readdir(dir, { withFileTypes: true }) + for (const entry of entries) { + const full = resolve(dir, entry.name) + if (entry.isDirectory()) { + await collectFiles(full, acc) + continue + } + if (entry.isFile() && entry.name.endsWith('.ts')) { + acc.push(full) + } + } + return acc +} + +const rewriteZodImports = (contents: string): string => + contents.replace(/from ['"]zod\/v3['"]/g, "from 'zod/v4'") + +const rewritePassthroughCalls = (contents: string): string => + contents.replace(/\.passthrough\(\)/g, '.catchall(z.unknown())') + +const rewriteRecordCalls = (contents: string): string => { + const marker = 'z.record(' + const normalized = contents.replace(/\bz\s*\.record\(/g, marker) + + const hasTopLevelComma = (source: string): boolean => { + let depth = 0 + let inString: '"' | "'" | null = null + let escaped = false + + for (let i = 0; i < source.length; i += 1) { + const char = source[i] + if (inString) { + if (!escaped && char === inString) { + inString = null + } + escaped = char === '\\' && !escaped + continue + } + if (char === '"' || char === "'") { + inString = char + continue + } + if (char === '(') depth += 1 + if (char === ')') depth -= 1 + if (char === ',' && depth === 0) return true + } + return false + } + + const transform = (source: string): string => { + let output = '' + let index = 0 + + while (index < source.length) { + if (!source.startsWith(marker, index)) { + output += source[index] + index += 1 + continue + } + + const argsStart = index + marker.length + let depth = 1 + let position = argsStart + let inString: '"' | "'" | null = null + let escaped = false + + while (position < source.length) { + const char = source[position] + if (inString) { + if (!escaped && char === inString) { + inString = null + } + escaped = char === '\\' && !escaped + position += 1 + continue + } + if (char === '"' || char === "'") { + inString = char + position += 1 + continue + } + if (char === '(') depth += 1 + if (char === ')') depth -= 1 + if (depth === 0) break + position += 1 + } + + const argsEnd = position + const rawArgs = source.slice(argsStart, argsEnd) + const rewrittenArgs = transform(rawArgs) + const nextArgs = hasTopLevelComma(rewrittenArgs) + ? rewrittenArgs + : `z.string(), ${rewrittenArgs.trim()}` + output += `${marker}${nextArgs})` + index = argsEnd + 1 + } + + return output + } + + return transform(normalized) +} + +const patchInterpolatableHelpers = (contents: string): string => { + const start = contents.indexOf('type InterpolatableTuple') + const marker = '/**\n * The robot keys specified in this array can’t be interpolated.' + const end = contents.indexOf(marker) + if (start === -1 || end === -1) { + return contents + } + + const replacement = `type InterpolatableTuple = Schemas extends readonly [\n` + + ` infer Head extends z.core.SomeType,\n` + + ` ...infer Rest extends z.core.SomeType[],\n` + + `]\n` + + ` ? [InterpolatableSchema, ...InterpolatableTuple]\n` + + ` : Schemas\n\n` + + `type InterpolatableSchema = Schema extends z.ZodString\n` + + ` ? Schema\n` + + ` : Schema extends\n` + + ` | z.ZodBoolean\n` + + ` | z.ZodEnum\n` + + ` | z.ZodLiteral\n` + + ` | z.ZodNumber\n` + + ` | z.ZodPipe\n` + + ` ? z.ZodUnion<[z.ZodString, Schema]>\n` + + ` : Schema extends z.ZodArray\n` + + ` ? z.ZodUnion<[z.ZodString, z.ZodArray>]>\n` + + ` : Schema extends z.ZodDefault\n` + + ` ? z.ZodDefault>\n` + + ` : Schema extends z.ZodNullable\n` + + ` ? z.ZodNullable>\n` + + ` : Schema extends z.ZodOptional\n` + + ` ? z.ZodOptional>\n` + + ` : Schema extends z.ZodRecord\n` + + ` ? z.ZodRecord>\n` + + ` : Schema extends z.ZodTuple\n` + + ` ? z.ZodUnion<[\n` + + ` z.ZodString,\n` + + ` z.ZodTuple<\n` + + ` InterpolatableTuple,\n` + + ` Rest extends z.core.SomeType ? InterpolatableSchema : null\n` + + ` >,\n` + + ` ]>\n` + + ` : Schema extends z.ZodObject\n` + + ` ? z.ZodUnion<[\n` + + ` z.ZodString,\n` + + ` z.ZodObject<{ [Key in keyof T]: InterpolatableSchema }, Config>,\n` + + ` ]>\n` + + ` : Schema extends z.ZodDiscriminatedUnion\n` + + ` ? Schema\n` + + ` : Schema extends z.ZodUnion\n` + + ` ? z.ZodUnion<[z.ZodString, ...InterpolatableTuple]>\n` + + ` : Schema\n\n` + + `const applyArrayChecks = (schema: z.ZodArray, checks: unknown): z.ZodArray => {\n` + + ` if (!Array.isArray(checks)) return schema\n` + + ` let next = schema\n` + + ` for (const check of checks) {\n` + + ` const def = (check as { _zod?: { def?: { check?: string; minimum?: number; maximum?: number; length?: number } } })?._zod?.def\n` + + ` if (!def) continue\n` + + ` if (def.check === 'min_length' && typeof def.minimum === 'number') {\n` + + ` next = next.min(def.minimum)\n` + + ` }\n` + + ` if (def.check === 'max_length' && typeof def.maximum === 'number') {\n` + + ` next = next.max(def.maximum)\n` + + ` }\n` + + ` if (def.check === 'length_equals' && typeof def.length === 'number') {\n` + + ` next = next.length(def.length)\n` + + ` }\n` + + ` }\n` + + ` return next\n` + + `}\n\n` + + `export function interpolateRecursive(\n` + + ` schema: Schema,\n` + + `): InterpolatableSchema {\n` + + ` const def = (schema as z.core.SomeType)._zod.def as unknown\n\n` + + ` switch ((def as { type?: string }).type) {\n` + + ` case 'boolean':\n` + + ` return z.union([\n` + + ` interpolationSchemaFull,\n` + + ` z\n` + + ` .union([schema, booleanStringSchema])\n` + + ` .transform((value) => value === true || value === false),\n` + + ` ]) as unknown as InterpolatableSchema\n` + + ` case 'array': {\n` + + ` const arrayDef = def as { element: z.ZodTypeAny; checks?: unknown }\n` + + ` let replacement = z.array(interpolateRecursive(arrayDef.element))\n` + + ` replacement = applyArrayChecks(replacement, arrayDef.checks)\n` + + ` return z.union([interpolationSchemaFull, replacement]) as unknown as InterpolatableSchema\n` + + ` }\n` + + ` case 'default': {\n` + + ` const defaultDef = def as { innerType: z.ZodTypeAny; defaultValue: unknown }\n` + + ` const replacement = interpolateRecursive(defaultDef.innerType).default(defaultDef.defaultValue as never)\n` + + ` const description = (schema as { description?: string }).description\n` + + ` return (description ? replacement.describe(description) : replacement) as unknown as InterpolatableSchema\n` + + ` }\n` + + ` case 'enum':\n` + + ` case 'literal':\n` + + ` return z.union([interpolationSchemaFull, schema]) as unknown as InterpolatableSchema\n` + + ` case 'number':\n` + + ` return z.union([\n` + + ` z\n` + + ` .string()\n` + + ` .regex(/^\\d+(\\.\\d+)?$/)\n` + + ` .transform((value) => Number(value)),\n` + + ` interpolationSchemaFull,\n` + + ` schema,\n` + + ` ]) as unknown as InterpolatableSchema\n` + + ` case 'nullable': {\n` + + ` const nullableDef = def as { innerType: z.ZodTypeAny }\n` + + ` const replacement = interpolateRecursive(nullableDef.innerType).nullable()\n` + + ` const description = (schema as { description?: string }).description\n` + + ` return (description ? replacement.describe(description) : replacement) as unknown as InterpolatableSchema\n` + + ` }\n` + + ` case 'object': {\n` + + ` const objectDef = def as { shape: Record | (() => Record); catchall?: z.ZodTypeAny }\n` + + ` const shape = typeof objectDef.shape === 'function' ? objectDef.shape() : objectDef.shape\n` + + ` let replacement = z.object(\n` + + ` Object.fromEntries(\n` + + ` Object.entries(shape).map(([key, nested]) => [\n` + + ` key,\n` + + ` interpolateRecursive(nested as z.ZodTypeAny),\n` + + ` ]),\n` + + ` ),\n` + + ` )\n` + + ` if (objectDef.catchall) {\n` + + ` const catchallType = objectDef.catchall._zod.def.type\n` + + ` if (catchallType === 'never') {\n` + + ` replacement = replacement.strict()\n` + + ` } else {\n` + + ` replacement = replacement.catchall(objectDef.catchall)\n` + + ` }\n` + + ` }\n` + + ` return z.union([interpolationSchemaFull, replacement]) as unknown as InterpolatableSchema\n` + + ` }\n` + + ` case 'optional': {\n` + + ` const optionalDef = def as { innerType: z.ZodTypeAny }\n` + + ` return interpolateRecursive(optionalDef.innerType).optional() as unknown as InterpolatableSchema\n` + + ` }\n` + + ` case 'record': {\n` + + ` const recordDef = def as { keyType?: z.ZodTypeAny; valueType: z.ZodTypeAny }\n` + + ` const keyType = (recordDef.keyType ?? z.string()) as z.core.$ZodRecordKey\n` + + ` return z.record(keyType, interpolateRecursive(recordDef.valueType)) as unknown as InterpolatableSchema\n` + + ` }\n` + + ` case 'string':\n` + + ` return z.union([interpolationSchemaPartial, schema]) as unknown as InterpolatableSchema\n` + + ` case 'tuple': {\n` + + ` const tupleDef = def as { items: z.ZodTypeAny[]; rest?: z.ZodTypeAny }\n` + + ` const items = tupleDef.items.map(interpolateRecursive)\n` + + ` const tuple = items.length === 0 ? z.tuple([]) : z.tuple(items as [z.ZodTypeAny, ...z.ZodTypeAny[]])\n` + + ` return z.union([\n` + + ` interpolationSchemaFull,\n` + + ` tupleDef.rest ? tuple.rest(tupleDef.rest) : tuple,\n` + + ` ]) as unknown as InterpolatableSchema\n` + + ` }\n` + + ` case 'union': {\n` + + ` const unionDef = def as { options: z.ZodTypeAny[]; discriminator?: string }\n` + + ` if (unionDef.discriminator) {\n` + + ` return schema as unknown as InterpolatableSchema\n` + + ` }\n` + + ` return z.union([interpolationSchemaFull, ...unionDef.options.map(interpolateRecursive)]) as unknown as InterpolatableSchema\n` + + ` }\n` + + ` case 'pipe':\n` + + ` return z.union([interpolationSchemaFull, schema]) as unknown as InterpolatableSchema\n` + + ` default:\n` + + ` return schema as unknown as InterpolatableSchema\n` + + ` }\n` + + `}\n\n` + + return `${contents.slice(0, start)}${replacement}${contents.slice(end)}` +} + +const patchInterpolatableRobot = (contents: string): string => { + const start = contents.indexOf('type InterpolatableRobot') + const marker = '/**\n * Fields that are shared by all Transloadit robots.' + const end = contents.indexOf(marker) + if (start === -1 || end === -1) { + return contents + } + + const replacement = `type InterpolatableRobot =\n` + + ` Schema extends z.ZodObject\n` + + ` ? z.ZodObject<\n` + + ` {\n` + + ` [Key in keyof T]: Key extends (typeof uninterpolatableKeys)[number]\n` + + ` ? T[Key]\n` + + ` : InterpolatableSchema\n` + + ` },\n` + + ` Config\n` + + ` >\n` + + ` : never\n\n` + + `export function interpolateRobot(\n` + + ` schema: Schema,\n` + + `): InterpolatableRobot {\n` + + ` const def = (schema as z.core.SomeType)._zod.def as unknown\n` + + ` const shape = typeof (def as { shape: Record | (() => Record) }).shape === 'function'\n` + + ` ? (def as { shape: () => Record }).shape()\n` + + ` : (def as { shape: Record }).shape\n` + + ` return z\n` + + ` .object(\n` + + ` Object.fromEntries(\n` + + ` Object.entries(shape).map(([key, nested]) => [\n` + + ` key,\n` + + ` (uninterpolatableKeys as readonly string[]).includes(key)\n` + + ` ? nested\n` + + ` : interpolateRecursive(nested as z.ZodTypeAny),\n` + + ` ]),\n` + + ` ),\n` + + ` )\n` + + ` .strict() as InterpolatableRobot\n` + + `}\n\n` + + return `${contents.slice(0, start)}${replacement}${contents.slice(end)}` +} + +const patchAiChatSchema = (contents: string): string => + contents + .replace( + 'const jsonValueSchema: z.ZodType =', + 'const jsonValueSchema: z.ZodType =', + ) + .replace('result: z.unknown(),', 'result: z.unknown().optional(),') + +const patchFile = (filePath: string, contents: string): string => { + let next = contents + if (filePath.endsWith(`${sep}robots${sep}_instructions-primitives.ts`)) { + next = patchInterpolatableHelpers(next) + next = patchInterpolatableRobot(next) + } + if (filePath.endsWith(`${sep}robots${sep}ai-chat.ts`)) { + next = patchAiChatSchema(next) + } + return next +} + +const main = async () => { + await rm(destRoot, { recursive: true, force: true }) + await mkdir(destRoot, { recursive: true }) + await cp(sourceRoot, destRoot, { recursive: true }) + + const files = await collectFiles(destRoot) + for (const file of files) { + const contents = await readFile(file, 'utf8') + const patched = patchFile( + file, + rewritePassthroughCalls(rewriteRecordCalls(rewriteZodImports(contents))), + ) + if (patched !== contents) { + await writeFile(file, patched, 'utf8') + } + } +} + +await main() diff --git a/packages/zod/test/fixtures/assembly-instructions.ts b/packages/zod/test/fixtures/assembly-instructions.ts new file mode 100644 index 00000000..0937b037 --- /dev/null +++ b/packages/zod/test/fixtures/assembly-instructions.ts @@ -0,0 +1,110 @@ +import type { AssemblyInstructionsInput } from '@transloadit/types/template' + +type AssemblyInstructionFixture = + | { name: string; value: AssemblyInstructionsInput; valid: true } + | { name: string; value: unknown; valid: false } + +const ffmpegTemplate: AssemblyInstructionsInput = { + steps: { + ':original': { + robot: '/upload/handle', + }, + 'webm-normal': { + use: ':original', + robot: '/video/encode', + ffmpeg_stack: 'v6.0.0', + preset: 'webm', + width: 1024, + height: 576, + resize_strategy: 'pad', + ffmpeg: { + b: '2000K', + }, + }, + 'webm-no-b': { + use: ':original', + robot: '/video/encode', + ffmpeg_stack: 'v6.0.0', + preset: 'webm', + width: 1024, + height: 576, + resize_strategy: 'pad', + }, + 'webm-realtime': { + use: ':original', + robot: '/video/encode', + ffmpeg_stack: 'v6.0.0', + preset: 'webm', + width: 1024, + height: 576, + resize_strategy: 'pad', + ffmpeg: { + b: '2000K', + deadline: 'realtime', + }, + }, + 'webm-good': { + use: ':original', + robot: '/video/encode', + ffmpeg_stack: 'v6.0.0', + preset: 'webm', + width: 1024, + height: 576, + resize_strategy: 'pad', + ffmpeg: { + b: '2000K', + deadline: 'good', + 'cpu-used': '2', + }, + }, + }, +} + +const invalidRotateTemplate = { + steps: { + import_video: { + robot: '/http/import', + url: 'https://tmp-eu-west-1.transloadit.net/example.mp4', + }, + encode: { + robot: '/video/encode', + use: 'import_video', + ffmpeg_stack: 'v6.0.0', + preset: 'web/mp4/4k', + rotate: 355, + }, + }, +} satisfies Record + +const basicImageResizeTemplate: AssemblyInstructionsInput = { + steps: { + ':original': { + robot: '/upload/handle', + }, + resized: { + robot: '/image/resize', + use: ':original', + width: 1024, + height: 768, + resize_strategy: 'fit', + }, + }, +} + +export const assemblyInstructionFixtures: AssemblyInstructionFixture[] = [ + { + name: 'ffmpeg-template', + value: ffmpegTemplate, + valid: true, + }, + { + name: 'rotate-invalid', + value: invalidRotateTemplate, + valid: false, + }, + { + name: 'basic-image-resize', + value: basicImageResizeTemplate, + valid: true, + }, +] diff --git a/packages/zod/test/runtime-parity.test.ts b/packages/zod/test/runtime-parity.test.ts new file mode 100644 index 00000000..5df1238c --- /dev/null +++ b/packages/zod/test/runtime-parity.test.ts @@ -0,0 +1,34 @@ +import assert from 'node:assert/strict' + +import { assemblyInstructionsSchema as v3AssemblyInstructions } from '../src/v3/template.ts' +import { assemblyInstructionsSchema as v4AssemblyInstructions } from '../src/v4/template.ts' +import { assemblyInstructionFixtures } from './fixtures/assembly-instructions.ts' + +const schemas = [ + { + name: 'assemblyInstructions', + v3: v3AssemblyInstructions, + v4: v4AssemblyInstructions, + fixtures: assemblyInstructionFixtures, + }, +] + +for (const schema of schemas) { + for (const fixture of schema.fixtures) { + const v3Result = schema.v3.safeParse(fixture.value) + const v4Result = schema.v4.safeParse(fixture.value) + + assert.equal( + v3Result.success, + v4Result.success, + `${schema.name}:${fixture.name} v3/v4 mismatch`, + ) + assert.equal( + v3Result.success, + fixture.valid, + `${schema.name}:${fixture.name} expected valid=${fixture.valid}`, + ) + } +} + +console.log('zod runtime parity: ok') diff --git a/packages/zod/test/type-equality.ts b/packages/zod/test/type-equality-v3.ts similarity index 95% rename from packages/zod/test/type-equality.ts rename to packages/zod/test/type-equality-v3.ts index c91c74d1..10cee065 100644 --- a/packages/zod/test/type-equality.ts +++ b/packages/zod/test/type-equality-v3.ts @@ -1,4 +1,4 @@ -import type { z } from 'zod' +import type { z } from 'zod/v3' import type { AssemblyStatus } from '@transloadit/types/assemblyStatus' import type { AssemblyInstructions } from '@transloadit/types/template' import { assemblyStatusSchema } from '../src/v3/assemblyStatus.js' diff --git a/packages/zod/test/type-equality-v4.ts b/packages/zod/test/type-equality-v4.ts new file mode 100644 index 00000000..cdd12997 --- /dev/null +++ b/packages/zod/test/type-equality-v4.ts @@ -0,0 +1,16 @@ +import type { z } from 'zod/v4' +import type { AssemblyStatus } from '@transloadit/types/assemblyStatus' +import type { AssemblyInstructions } from '@transloadit/types/template' +import { assemblyStatusSchema } from '../src/v4/assemblyStatus.js' +import { assemblyInstructionsSchema } from '../src/v4/template.js' + +type Equal = [A] extends [B] ? ([B] extends [A] ? true : false) : false + +type Assert = T + +export type _AssemblyStatusCheck = Assert< + Equal> +> +export type _AssemblyInstructionsCheck = Assert< + Equal> +> diff --git a/packages/zod/tsconfig.test.json b/packages/zod/tsconfig.test.json index b7084a65..3b4898fb 100644 --- a/packages/zod/tsconfig.test.json +++ b/packages/zod/tsconfig.test.json @@ -8,5 +8,9 @@ "@transloadit/types/*": ["../types/src/generated/*.ts"] } }, - "include": ["test/type-equality.ts"] + "include": [ + "test/type-equality-v3.ts", + "test/type-equality-v4.ts", + "test/runtime-parity.test.ts" + ] } diff --git a/yarn.lock b/yarn.lock index 9c8b28c1..5603f056 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2685,7 +2685,7 @@ __metadata: resolution: "@transloadit/zod@workspace:packages/zod" dependencies: typescript: "npm:5.9.3" - zod: "npm:3.25.76" + zod: "npm:^4.0.0" languageName: unknown linkType: soft @@ -7439,6 +7439,13 @@ __metadata: languageName: node linkType: hard +"zod@npm:^4.0.0": + version: 4.3.5 + resolution: "zod@npm:4.3.5" + checksum: 10c0/5a2db7e59177a3d7e202543f5136cb87b97b047b77c8a3d824098d3fa8b80d3aa40a0a5f296965c3b82dfdccdd05dbbfacce91347f16a39c675680fd7b1ab109 + languageName: node + linkType: hard + "zod@npm:^4.1.11": version: 4.2.1 resolution: "zod@npm:4.2.1" From 06c0a82cbd786f289a58cd47c684f2383398a7d0 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Fri, 16 Jan 2026 16:22:24 +0100 Subject: [PATCH 17/43] docs: rename plan and harden zod fixtures --- docs/monorepo-architecture.md | 98 +++++ docs/multi-module.md | 344 ------------------ docs/todo.md | 6 + .../test/fixtures/assembly-instructions.ts | 43 +++ packages/zod/test/runtime-parity.test.ts | 37 ++ 5 files changed, 184 insertions(+), 344 deletions(-) create mode 100644 docs/monorepo-architecture.md delete mode 100644 docs/multi-module.md diff --git a/docs/monorepo-architecture.md b/docs/monorepo-architecture.md new file mode 100644 index 00000000..444d529d --- /dev/null +++ b/docs/monorepo-architecture.md @@ -0,0 +1,98 @@ +# Monorepo architecture + +## Overview + +This repository is a Yarn 4 workspace that publishes a small set of cohesive packages: + +- `@transloadit/node` — canonical Node SDK runtime. +- `transloadit` — legacy wrapper package that stays byte-for-byte compatible with the current npm artifact. +- `@transloadit/types` — generated `.d.ts` contract (assemblies, robots, templates, responses), no runtime deps. +- `@transloadit/zod` — Zod schemas, with versioned subpaths (`/v3`, `/v4`). + +The goal is stable backwards compatibility, a single source of truth for types, and a clean path to multi‑platform SDKs. + +## Repository layout + +``` +node-sdk/ + packages/ + node/ # @transloadit/node runtime + transloadit/ # legacy wrapper package + types/ # @transloadit/types (d.ts only) + zod/ # @transloadit/zod (v3 + v4 schemas) + docs/ + scripts/ +``` + +## Source of truth: alphalib + +`packages/node/src/alphalib/types` is the synced source of truth for the public contract. We do not hand‑edit schema types in other packages; everything is derived from alphalib. + +## Packages + +### @transloadit/node +- Canonical runtime implementation. +- Used as the source for the legacy `transloadit` wrapper. + +### transloadit +- Publishes the legacy package name. +- Built from the same runtime artifacts as `@transloadit/node`. +- Compatibility is verified via tarball fingerprinting. + +### @transloadit/types +- Pure type package; no Zod runtime dependency. +- Types are generated from Zod v3 schemas into `.d.ts` only. +- Consumers get fast type‑checking without pulling in Node or Zod. + +### @transloadit/zod +- Publishes Zod schemas for runtime validation. +- Versioned exports: + - `@transloadit/zod/v3/*` + - `@transloadit/zod/v4/*` + +## Zod sync & generation pipeline + +### v3 sync (`packages/zod/scripts/sync-v3.ts`) +- Copies alphalib types into `packages/zod/src/v3`. +- Rewrites `zod` imports to `zod/v3`. +- Writes the v3 `index.ts` barrel. + +### v4 sync (`packages/zod/scripts/sync-v4.ts`) +- Transforms `src/v3` into `src/v4`. +- Rewrites imports to `zod/v4` and applies compatibility patches: + - `z.record(...)` gets explicit string key types for v4. + - `.passthrough()` is rewritten to `.catchall(z.unknown())` to preserve output types. + - Interpolation helpers handle v4 `pipe` (used by transforms and preprocess) and avoid discriminated‑union expansion. + - Known ai‑chat schema differences are patched (JSON value output + tool‑result optionality). + +### Generated sources +- `packages/zod/src/v3` and `packages/zod/src/v4` are generated and git‑ignored. +- Build output is emitted to `packages/zod/dist`. + +## Validation gates + +1) **Type equality** + - `packages/zod/test/type-equality-v3.ts` + - `packages/zod/test/type-equality-v4.ts` + - These assert `z.infer` output matches `@transloadit/types` exactly (non‑distributive mutual assignability). + +2) **Runtime parity** + - `packages/zod/test/runtime-parity.test.ts` + - Uses fixtures from `packages/zod/test/fixtures/assembly-instructions.ts` and checks v3/v4 success parity. + +The `@transloadit/zod` `check` script runs `sync`, `tsc`, and runtime parity. + +## Node & TypeScript execution + +All internal scripts are TypeScript and run via Node 24’s erasable syntax support (no `--experimental-strip-types` in the zod package). Other packages may still use the flag where needed. + +## Publishing strategy + +- `@transloadit/node` is canonical. +- `transloadit` is generated from the same artifacts and fingerprinted. +- `@transloadit/types` and `@transloadit/zod` are versioned in lock‑step (changesets). + +## Future extensions + +- Add JSON Schema export for v4 (`@transloadit/jsonschema` or subpath) once v4 tooling is stable. +- Consider promoting alphalib into its own workspace package if/when all consuming repos are v4‑ready. diff --git a/docs/multi-module.md b/docs/multi-module.md deleted file mode 100644 index 9b5245ac..00000000 --- a/docs/multi-module.md +++ /dev/null @@ -1,344 +0,0 @@ -# Proposal: multi-package repo with @transloadit/types, legacy transloadit, and @transloadit/node - -## Summary - -Split the current node-sdk repo into a Yarn 4 workspace monorepo that publishes: - -- `@transloadit/node` (canonical Node runtime, current source lives here) -- `transloadit` (legacy wrapper, byte-for-byte compatible with current npm package) -- `@transloadit/types` (shared types for assemblies, robots, API responses) -- `@transloadit/zod` (Zod schemas with versioned subpaths like `@transloadit/zod/v3`) - -This keeps backwards compatibility, adds a canonical types package for reuse across Convex, browser, and future SDKs, and sets the stage for a future repo rename to `typescript-sdk` with additional packages like `@transloadit/browser` and `@transloadit/deno`. - -## Why a dedicated @transloadit/types - -### 1) One source of truth for the public contract -The repo already has structured types under `src/alphalib/types` (templates, assemblies, robots, bills, etc). These are synced across internal repos and should remain the single source of truth. A types package should re-export from alphalib without copying the files, so there is one canonical contract. - -A `@transloadit/types` package would provide: - -- Strongly typed assembly instruction shapes (robot params, steps, template payloads) -- Shared response types (assembly status, templates, bills) -- Schema output for runtime validation without pulling in Node APIs - -This reduces duplication and drift between SDKs, Convex integrations, and docs examples. - -### 2) Better DX without runtime coupling -Consumers like browser SDKs (uppy) and Convex components often need types but not Node runtime code. A types-only package keeps bundles smaller and avoids Node-only dependencies (fs, streams, crypto). - -### 3) Future-proof for multi-platform SDKs -If we eventually publish `@transloadit/browser`, `@transloadit/deno`, or a shared `@transloadit/core`, they can all depend on `@transloadit/types` for the contract while keeping platform-specific runtime. Zod schemas live in `@transloadit/zod` with versioned subpaths (`/v3`, later `/v4`) so we can add a new major without breaking existing imports. - -## Goals - -- Preserve `transloadit` package identity and output (byte-for-byte where possible). -- Make `@transloadit/node` the canonical source for the existing Node SDK runtime. -- Introduce `@transloadit/types` with stable, documented export surface. -- Publish Zod schemas without forcing a runtime dependency choice for types consumers. -- Enable future modularization without breaking existing users. -- Keep publish and versioning consistent (changesets, Yarn 4). - -## Proposed workspace layout - -``` -node-sdk/ - packages/ - node/ - package.json (name: "@transloadit/node") - src/ (current src moved here) - dist/ - transloadit/ - package.json (name: "transloadit") - src/ (thin wrapper or re-export) - dist/ - types/ - package.json (name: "@transloadit/types") - src/ - index.ts - robots/ - assemblies/ - templates/ - schemas/ - dist/ - zod/ - package.json (name: "@transloadit/zod") - src/v3/ (zod v3 schema export surface) - src/v4/ (future) - dist/ - package.json (workspace root) - tsconfig.base.json - biome.json - .changeset/ -``` - -### Notes -- `packages/node` is the canonical source for the existing Node SDK runtime. -- `packages/transloadit` is a wrapper package that publishes the same artifacts under the legacy name. -- `packages/types` re-exports from `src/alphalib/types` so alphalib remains the single source of truth. -- `packages/zod` exports Zod schemas and depends on `zod` explicitly, with `exports` for `./v3` (and later `./v4`). - -## Alphalib strategy (synced source of truth) - -`src/alphalib` is already synced across internal Transloadit repos. We should keep it as the single -source of truth for types and schemas, and avoid copying files into packages. Two viable approaches: - -1) **Keep alphalib at repo root and re-export it** - - `packages/types` uses TS path mapping or barrel re-exports that point to `src/alphalib/types`. - - `packages/zod` imports from `src/alphalib/types` or from generated schema - artifacts stored alongside alphalib. - - This keeps the sync workflow intact and avoids duplication. - -2) **Promote alphalib to a workspace package** - - Move `src/alphalib` to `packages/alphalib` and keep the sync tool aligned. - - `@transloadit/types` depends on `@transloadit/alphalib` internally. - - This is cleaner structurally but changes the sync story. - -Given the current sync mechanism, option 1 is safer and lower risk. - -## Single source of truth for types vs Zod schemas - -If we want Zod to be the canonical source, we should generate the types at build time and publish -only plain `.d.ts` files in `@transloadit/types` that do **not** reference Zod. - -### Recommended pipeline (guaranteed compatibility) - -1) **Zod schemas live in `@transloadit/zod/v3`.** -2) **Generate structural types** into `@transloadit/types` (no `z.infer`, no `zod` imports). -3) **Enforce type equality in CI** between generated types and `z.infer` from the schemas. - -This gives a compile-time guarantee that the generated types are 100% compatible with Zod inference. - -### Build script sketch - -``` -packages/ - zod/ - src/v3/*.ts # Zod schemas - test/type-equality.ts # Assert z.infer === generated types - types/ - scripts/emit-types.ts # Generates .ts from zod schemas - src/index.ts # Re-exports generated types -``` - -`emit-types.ts` can use the TS compiler API or ts-morph to: -- import `@transloadit/zod/v3` schemas, -- create `type Foo = z.infer` aliases, -- emit a temporary `.ts`, -- run `tsc --emitDeclarationOnly` to produce `.d.ts`, -- then strip the intermediate `.ts` from the published package. - -`type-equality.ts` then asserts: - -```ts -type Equal = - (() => T extends A ? 1 : 2) extends - (() => T extends B ? 1 : 2) ? true : false; -type Assert = T; - -type _Check = Assert>>; -``` - -If anything drifts, the build fails. This is the compatibility guarantee. - -### Why not export `z.infer` directly? - -That would force all `@transloadit/types` consumers to install `zod` just to typecheck. Generating -`.d.ts` avoids runtime and typechecking dependencies downstream. - -### Future: JSON Schema from Zod v4 - -When we move to Zod v4 (outside this scope), we can add `@transloadit/zod/v4` and use the native -JSON Schema exporter to generate a separate `@transloadit/jsonschema` package (or subpath) without -changing the `@transloadit/types` contract. - -## Supporting Zod v3 and v4 from one source - -We can support both Zod v3 and v4 while keeping alphalib as the synced source of truth, but we must -constrain the source to a shared subset and enforce equivalence in CI. The safest plan is: - -1) Keep alphalib schemas v3-compatible so all current repos can sync without upgrades. -2) In node-sdk, generate the v3 artifacts directly from alphalib. -3) Generate v4 artifacts via a codemod transform and then validate equivalence. - -This lets us ship `@transloadit/zod/v3` and `@transloadit/zod/v4` from one repo while other repos -continue to sync alphalib in their existing environments. - -### Proposed generation pipeline - -``` -packages/zod/ - scripts/ - sync-v3.ts # Copy alphalib into src/v3 and write index exports - sync-v4.ts # Transform src/v3 -> src/v4 (import path + API rewrites) - src/ - v3/ # Zod v3 schemas (direct from alphalib) - v4/ # Zod v4 schemas (generated) - test/ - type-equality-v3.ts # z.infer(v3) === @transloadit/types - type-equality-v4.ts # z.infer(v4) === @transloadit/types - runtime-fixtures.ts # Shared validation fixtures -``` - -**sync-v4.ts responsibilities** -- Rewrite Zod imports to `zod/v4`. -- Apply a small codemod pass for known API differences. -- Preserve descriptions/defaults/refinements (no semantic changes). - -### CI gates (no compatibility loss) - -1) **Type equivalence**: `z.infer` from v3 and v4 must match `@transloadit/types` using the existing - `Equal` assertion pattern. This protects type-level compatibility. -2) **Runtime equivalence**: Run a shared fixture set through both v3 and v4 schemas and compare - ok/error outcomes + error paths. This protects behavioral compatibility. -3) **JSON Schema parity (optional)**: When v4 JSON Schema export is in scope, generate schema from - v4 and compare against a golden snapshot or a normalized hash. - -If any gate fails, we block release. - -### Should we upgrade all alphalib consumers to v4 first? - -Not initially. v4 should be treated as an output target, not the input source, until: -- All alphalib-consuming repos can adopt v4 with minimal friction. -- The generator + equivalence tests have proven stable across several releases. - -Once those conditions hold, we can decide whether to flip alphalib to v4 or keep it v3-safe and -continue generating both versions indefinitely. - -## Publishing strategy - -### transloadit and @transloadit/node - -Two options that both preserve compatibility: - -1) **Source duplication with shared build inputs** - - Build `packages/node` as the canonical runtime. - - Build `packages/transloadit` from the same source using a small wrapper that re-exports the runtime, or by pointing its build output to the same compiled artifacts. - -2) **Single build output, dual packaging** - - Build once in `packages/node/dist`. - - For `transloadit`, publish a package that contains the same files as `@transloadit/node`, generated during `prepack` (copy/rsync dist + package.json transforms). - -Option 2 is stricter for byte-for-byte output parity but requires packaging logic. Option 1 is easier but can drift if any build settings differ. I recommend option 2 and automated fingerprinting (see below). - -### @transloadit/types / @transloadit/zod - -- `@transloadit/types`: types only (`.d.ts`), no runtime dependencies. -- `@transloadit/zod`: Zod schemas derived from alphalib types (depends on `zod`). -- Export stable API surfaces: - -``` -@transloadit/types - /assemblies - /templates - /robots - /webhooks -@transloadit/zod/v3 - /assemblies - /templates - /robots - /webhooks -``` - -Zod schemas cannot be shipped without a runtime dependency; separating them avoids forcing Zod on all consumers. A single `@transloadit/zod` package with `./v3` and `./v4` subpath exports keeps upgrade paths explicit. - -## Compatibility verification plan (byte-for-byte) - -We can treat compatibility as a deterministic refactor target using a pre/post fingerprint. - -### Proposed fingerprint process - -1) **Baseline (current repo)** - - `npm pack` or `yarn pack` from current `transloadit` package. - - Capture: - - tarball SHA256 - - file list, sizes, file SHA256 inside tarball - - `package.json` fields that affect install (name, version, main, types, exports, files) - -2) **After refactor** - - `yarn workspace transloadit pack` - - Compute the same fingerprint. - - Assert byte-for-byte identity. - -### Suggested script - -A small Node script can be added under `scripts/fingerprint-pack.js` (uses `npm pack` + `tar`): - -- `npm pack --json` to get the tarball name -- `sha256` the tarball -- `tar -tf` + `tar -xOf` to hash each file -- output JSON artifact for comparison - -Example usage: - -```bash -node scripts/fingerprint-pack.js . -node scripts/fingerprint-pack.js packages/transloadit -``` - -This becomes a refactor gate: only proceed if fingerprints match. - -## Versioning and releases - -Use Changesets in the workspace root (similar to `~/code/monolib`): - -- `yarn changeset` for changes -- `yarn changeset version` to bump versions -- `yarn changeset publish` to publish all packages - -We can keep versions synchronized across `transloadit` and `@transloadit/node`. `@transloadit/types` can either track the same version (simpler) or diverge if needed later. - -## Suggested implementation phases - -### Phase 1: Prepare monorepo scaffolding -- Add root workspace config, changesets, base tsconfig. -- Move current package into `packages/node` with minimal changes. -- Ensure existing build/test scripts still pass. - -### Phase 2: Add @transloadit/types (re-export alphalib) -- Keep alphalib as a synced directory (single source of truth). -- In `packages/types`, re-export from `src/alphalib/types` via TS path mapping or barrel files. -- Build `.d.ts` only, no runtime JS. - -### Phase 3: Add @transloadit/zod -- Generate Zod schemas in `packages/zod` from alphalib types (requires `zod`). - -### Phase 4: Add transloadit wrapper -- Package identical output to `@transloadit/node` (copy dist + package.json transform). -- Confirm both packages are publishable via Changesets. - -### Phase 5: Byte-for-byte compatibility validation -- Record baseline fingerprint from current `transloadit`. -- Validate refactor output is identical (except for `package.json` name where applicable). - -## Risks and mitigations - -- **Risk: Build output drift** - - Mitigation: fingerprint test gate; lock tsconfig, build tools, and emitted files. - -- **Risk: Consumers depend on side-effects or file paths** - - Mitigation: keep `transloadit` package output identical and preserve exports layout. - -- **Risk: Types package diverges from runtime API** - - Mitigation: generate or validate types against runtime tests; add type-level tests. - -## Long-term direction - -A rename from `node-sdk` to `typescript-sdk` makes sense if this becomes the home for: - -- `@transloadit/node` (Node runtime) -- `@transloadit/browser` (browser runtime) -- `@transloadit/deno` (Deno runtime) -- `@transloadit/types` (shared contract) - -All can share `@transloadit/types` for schema and type fidelity, with platform-specific transport in each runtime package. - -## Open questions - -- Should we make `@transloadit/zod` a dedicated package or make Zod a peer dep of `@transloadit/types`? I recommend a dedicated package to keep types-only consumers clean. -- Do we keep versions in lock-step across all packages or allow independent versioning? -- Should the root package publish anything, or remain private-only? - -## Recommendation - -Proceed with a workspace refactor using a fingerprint gate to keep `transloadit` byte-for-byte compatible, and introduce `@transloadit/types` as the contract layer for all SDKs. This unlocks future multi-platform SDKs while preserving existing consumers. diff --git a/docs/todo.md b/docs/todo.md index 5deb3cda..0851839c 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -61,3 +61,9 @@ - [x] Centralize Zod sync export list/extension to avoid drift - [x] Document inline heavy-inference casts (helper wrappers trigger TS7056) - [x] Keep legacy `transloadit` metadata synced from `@transloadit/node` with one source of truth + +## Zod v4 parity hardening +- [x] Add runtime parity fixtures by copying proven Assembly Instructions from `~/code/content` (stepParsing fixtures) +- [x] Add a sync-v4 regression fixture to verify `.passthrough()` → `.catchall(z.unknown())` and `z.record(...)` key injection +- [ ] Add runtime parity coverage for `assemblyStatus` (ok/error/busy) once stable samples are selected +- [ ] Add a CI guard that `@transloadit/zod` exports list stays in sync with `packages/node/src/alphalib/types` diff --git a/packages/zod/test/fixtures/assembly-instructions.ts b/packages/zod/test/fixtures/assembly-instructions.ts index 0937b037..ecc50606 100644 --- a/packages/zod/test/fixtures/assembly-instructions.ts +++ b/packages/zod/test/fixtures/assembly-instructions.ts @@ -91,6 +91,39 @@ const basicImageResizeTemplate: AssemblyInstructionsInput = { }, } +const importResizeStoreTemplate: AssemblyInstructionsInput = { + steps: { + imported: { + robot: '/http/import', + url: 'https://demos.transloadit.com/${fields.input}', + }, + resized: { + use: 'imported', + robot: '/image/resize', + width: 300, + height: 200, + }, + stored: { + robot: '/s3/store', + use: ['resized'], + credentials: 'YOUR_S3_CREDENTIALS', + }, + }, +} + +const importServeTemplate: AssemblyInstructionsInput = { + steps: { + imported: { + robot: '/http/import', + url: 'https://demos.transloadit.com/${fields.input}', + }, + served: { + robot: '/file/serve', + use: 'imported', + }, + }, +} + export const assemblyInstructionFixtures: AssemblyInstructionFixture[] = [ { name: 'ffmpeg-template', @@ -107,4 +140,14 @@ export const assemblyInstructionFixtures: AssemblyInstructionFixture[] = [ value: basicImageResizeTemplate, valid: true, }, + { + name: 'import-resize-store', + value: importResizeStoreTemplate, + valid: true, + }, + { + name: 'import-serve', + value: importServeTemplate, + valid: true, + }, ] diff --git a/packages/zod/test/runtime-parity.test.ts b/packages/zod/test/runtime-parity.test.ts index 5df1238c..16184a8f 100644 --- a/packages/zod/test/runtime-parity.test.ts +++ b/packages/zod/test/runtime-parity.test.ts @@ -2,6 +2,8 @@ import assert from 'node:assert/strict' import { assemblyInstructionsSchema as v3AssemblyInstructions } from '../src/v3/template.ts' import { assemblyInstructionsSchema as v4AssemblyInstructions } from '../src/v4/template.ts' +import { robotBase as v3RobotBase, robotFFmpeg as v3RobotFFmpeg } from '../src/v3/robots/_instructions-primitives.ts' +import { robotBase as v4RobotBase, robotFFmpeg as v4RobotFFmpeg } from '../src/v4/robots/_instructions-primitives.ts' import { assemblyInstructionFixtures } from './fixtures/assembly-instructions.ts' const schemas = [ @@ -31,4 +33,39 @@ for (const schema of schemas) { } } +const passthroughFixture = { + ffmpeg: { + unexpected_option: 'keep-me', + }, +} +const v3Passthrough = v3RobotFFmpeg.safeParse(passthroughFixture) +const v4Passthrough = v4RobotFFmpeg.safeParse(passthroughFixture) +assert.equal(v3Passthrough.success, true, 'robotFFmpeg v3 passthrough should parse') +assert.equal(v4Passthrough.success, true, 'robotFFmpeg v4 passthrough should parse') +if (v3Passthrough.success) { + assert.equal( + (v3Passthrough.data.ffmpeg as Record).unexpected_option, + 'keep-me', + 'robotFFmpeg v3 should preserve passthrough keys', + ) +} +if (v4Passthrough.success) { + assert.equal( + (v4Passthrough.data.ffmpeg as Record).unexpected_option, + 'keep-me', + 'robotFFmpeg v4 should preserve passthrough keys', + ) +} + +const outputMetaValid = { output_meta: { has_transparency: true } } +const outputMetaInvalid = { output_meta: { has_transparency: 1 } } +const v3OutputMetaValid = v3RobotBase.safeParse(outputMetaValid) +const v4OutputMetaValid = v4RobotBase.safeParse(outputMetaValid) +const v3OutputMetaInvalid = v3RobotBase.safeParse(outputMetaInvalid) +const v4OutputMetaInvalid = v4RobotBase.safeParse(outputMetaInvalid) +assert.equal(v3OutputMetaValid.success, true, 'robotBase v3 output_meta valid should parse') +assert.equal(v4OutputMetaValid.success, true, 'robotBase v4 output_meta valid should parse') +assert.equal(v3OutputMetaInvalid.success, false, 'robotBase v3 output_meta invalid should fail') +assert.equal(v4OutputMetaInvalid.success, false, 'robotBase v4 output_meta invalid should fail') + console.log('zod runtime parity: ok') From 32f1c52157a41bfd26cdf72de3ef4af333c024a1 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Fri, 16 Jan 2026 16:23:53 +0100 Subject: [PATCH 18/43] test: guard zod export parity --- docs/todo.md | 2 +- packages/zod/package.json | 2 +- packages/zod/test/exports-sync.test.ts | 46 ++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 packages/zod/test/exports-sync.test.ts diff --git a/docs/todo.md b/docs/todo.md index 0851839c..5d61cb1d 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -66,4 +66,4 @@ - [x] Add runtime parity fixtures by copying proven Assembly Instructions from `~/code/content` (stepParsing fixtures) - [x] Add a sync-v4 regression fixture to verify `.passthrough()` → `.catchall(z.unknown())` and `z.record(...)` key injection - [ ] Add runtime parity coverage for `assemblyStatus` (ok/error/busy) once stable samples are selected -- [ ] Add a CI guard that `@transloadit/zod` exports list stays in sync with `packages/node/src/alphalib/types` +- [x] Add a CI guard that `@transloadit/zod` exports list stays in sync with `packages/node/src/alphalib/types` diff --git a/packages/zod/package.json b/packages/zod/package.json index 01833fc9..5dafb843 100644 --- a/packages/zod/package.json +++ b/packages/zod/package.json @@ -34,7 +34,7 @@ "sync:v4": "node scripts/sync-v4.ts", "sync": "yarn sync:v3 && yarn sync:v4", "build": "yarn sync && tsc --build tsconfig.build.json", - "check": "yarn sync && tsc --build tsconfig.build.json && tsc --noEmit --project tsconfig.test.json && node test/runtime-parity.test.ts" + "check": "yarn sync && tsc --build tsconfig.build.json && tsc --noEmit --project tsconfig.test.json && node test/exports-sync.test.ts && node test/runtime-parity.test.ts" }, "dependencies": { "zod": "^4.0.0" diff --git a/packages/zod/test/exports-sync.test.ts b/packages/zod/test/exports-sync.test.ts new file mode 100644 index 00000000..44b08c5c --- /dev/null +++ b/packages/zod/test/exports-sync.test.ts @@ -0,0 +1,46 @@ +import assert from 'node:assert/strict' +import { readdir, readFile } from 'node:fs/promises' +import { dirname, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const filePath = fileURLToPath(import.meta.url) +const zodRoot = resolve(dirname(filePath), '..') +const typesRoot = resolve(zodRoot, '../node/src/alphalib/types') + +const normalize = (items: string[]): string[] => [...new Set(items)].sort() + +const listTypeModules = async (): Promise => { + const entries = await readdir(typesRoot, { withFileTypes: true }) + const modules = entries + .filter((entry) => entry.isFile() && entry.name.endsWith('.ts')) + .map((entry) => entry.name.replace(/\.ts$/, '')) + modules.push('robots/_index') + return normalize(modules) +} + +const readIndexModules = async (indexPath: string): Promise => { + const contents = await readFile(indexPath, 'utf8') + const modules = contents + .split('\n') + .map((line) => line.match(/export \* from ['"]\.\/(.+?)\.(?:ts|js)['"]/)) + .filter((match): match is RegExpMatchArray => Boolean(match)) + .map((match) => match[1]) + return normalize(modules) +} + +const expected = await listTypeModules() +const v3Index = await readIndexModules(resolve(zodRoot, 'src/v3/index.ts')) +const v4Index = await readIndexModules(resolve(zodRoot, 'src/v4/index.ts')) + +assert.deepEqual( + v3Index, + expected, + 'zod v3 index exports must match packages/node/src/alphalib/types', +) +assert.deepEqual( + v4Index, + expected, + 'zod v4 index exports must match packages/node/src/alphalib/types', +) + +console.log('zod exports: ok') From 4976e0ec1caf078d941cb3b3160cd902058de047 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Fri, 16 Jan 2026 17:43:43 +0100 Subject: [PATCH 19/43] test: add assembly status parity fixtures --- docs/todo.md | 2 +- packages/zod/test/fixtures/assembly-status.ts | 248 ++++++++++++++++++ packages/zod/test/runtime-parity.test.ts | 9 + 3 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 packages/zod/test/fixtures/assembly-status.ts diff --git a/docs/todo.md b/docs/todo.md index 5d61cb1d..dc41f133 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -65,5 +65,5 @@ ## Zod v4 parity hardening - [x] Add runtime parity fixtures by copying proven Assembly Instructions from `~/code/content` (stepParsing fixtures) - [x] Add a sync-v4 regression fixture to verify `.passthrough()` → `.catchall(z.unknown())` and `z.record(...)` key injection -- [ ] Add runtime parity coverage for `assemblyStatus` (ok/error/busy) once stable samples are selected +- [x] Add runtime parity coverage for `assemblyStatus` (ok/error/busy) once stable samples are selected - [x] Add a CI guard that `@transloadit/zod` exports list stays in sync with `packages/node/src/alphalib/types` diff --git a/packages/zod/test/fixtures/assembly-status.ts b/packages/zod/test/fixtures/assembly-status.ts new file mode 100644 index 00000000..19631225 --- /dev/null +++ b/packages/zod/test/fixtures/assembly-status.ts @@ -0,0 +1,248 @@ +type AssemblyStatusFixture = + | { name: string; value: unknown; valid: true } + | { name: string; value: unknown; valid: false } + +const assemblyStatusOk = { + ok: 'ASSEMBLY_COMPLETED', + http_code: 200, + message: 'The Assembly was successfully completed.', + assembly_id: '4723718834074932b22a59b209220018', + parent_id: null, + account_id: '4ce4fb3d9d1842b6ba5d6f3ccee4b066', + account_name: 'examples-section-prod', + account_slug: 'examples-section-prod', + template_id: null, + template_name: null, + instance: 'spenge.transloadit.com', + assembly_url: 'http://api2.spenge.transloadit.com/assemblies/4723718834074932b22a59b209220018', + assembly_ssl_url: + 'https://api2-spenge.transloadit.com/assemblies/4723718834074932b22a59b209220018', + uppyserver_url: 'https://api2-spenge.transloadit.com/companion/', + companion_url: 'https://api2-spenge.transloadit.com/companion/', + websocket_url: 'https://api2-spenge.transloadit.com/ws20008', + update_stream_url: + 'https://api2-spenge.transloadit.com/ws20008?assembly=4723718834074932b22a59b209220018', + tus_url: 'https://api2-spenge.transloadit.com/resumable/files/', + bytes_received: 1113, + bytes_expected: 1113, + upload_duration: 0.325, + client_agent: null, + client_ip: null, + client_referer: null, + transloadit_client: 'node-sdk:3.0.2', + start_date: '2023/12/20 16:36:27 GMT', + upload_meta_data_extracted: true, + warnings: [], + is_infinite: false, + has_dupe_jobs: false, + execution_start: '2023/12/20 16:36:27 GMT', + execution_duration: 1.399, + queue_duration: 0.421, + jobs_queue_duration: 0.04, + notify_start: null, + notify_url: null, + notify_response_code: null, + notify_response_data: null, + notify_duration: null, + last_job_completed: '2023/12/20 16:36:28 GMT', + fields: {}, + running_jobs: [], + bytes_usage: 1048639, + executing_jobs: [], + started_jobs: [':original:::original', 'converted:::original', 'exported:::original', 'exported::converted'], + parent_assembly_status: null, + params: + '{"steps":{":original":{"robot":"/upload/handle"},"converted":{"use":":original","robot":"/document/convert","result":true,"format":"vtt"},"exported":{"use":["converted",":original"],"robot":"/s3/store","credentials":"demo_s3_credentials","url_prefix":"https://demos.transloadit.com/"}}, "auth":{"key":"****","expires":"2023-12-21T16:36:26.972Z"}}', + template: null, + merged_params: + '{"steps":{":original":{"robot":"/upload/handle"},"converted":{"use":":original","robot":"/document/convert","result":true,"format":"vtt"},"exported":{"use":["converted",":original"],"robot":"/s3/store","credentials":"demo_s3_credentials","url_prefix":"https://demos.transloadit.com/"}}, "auth":{"key":"****","expires":"2023-12-21T16:36:26.972Z"}}', + expected_tus_uploads: 1, + started_tus_uploads: 1, + finished_tus_uploads: 1, + tus_uploads: [ + { + filename: 'subtitle.srt', + fieldname: 'file', + size: 302, + offset: 302, + finished: true, + upload_url: 'https://api2-buchen.transloadit.com/resumable/files/ab9753a6f59d0ef51b881a56004d9f14', + }, + ], + uploads: [ + { + id: '7e5ce0bb323044e7a9deeb7d6f6e312f', + name: 'subtitle.srt', + basename: 'subtitle', + ext: 'srt', + size: 302, + mime: 'application/x-subrip', + type: null, + field: 'file', + md5hash: '23664a1e4a8cad08d4ca6294d3d9bee3', + original_id: '7e5ce0bb323044e7a9deeb7d6f6e312f', + original_basename: 'subtitle', + original_name: 'subtitle.srt', + original_path: '/', + original_md5hash: '23664a1e4a8cad08d4ca6294d3d9bee3', + from_batch_import: false, + is_tus_file: true, + tus_upload_url: 'https://api2-buchen.transloadit.com/resumable/files/ab9753a6f59d0ef51b881a56004d9f14', + url: 'https://demos.transloadit.com/7e/5ce0bb323044e7a9deeb7d6f6e312f/subtitle.srt', + ssl_url: 'https://demos.transloadit.com/7e/5ce0bb323044e7a9deeb7d6f6e312f/subtitle.srt', + meta: {}, + is_temp_url: false, + }, + ], + results: { + ':original': [ + { + id: '7e5ce0bb323044e7a9deeb7d6f6e312f', + name: 'subtitle.srt', + basename: 'subtitle', + ext: 'srt', + size: 302, + mime: 'application/x-subrip', + type: null, + field: 'file', + md5hash: '23664a1e4a8cad08d4ca6294d3d9bee3', + original_id: '7e5ce0bb323044e7a9deeb7d6f6e312f', + original_basename: 'subtitle', + original_name: 'subtitle.srt', + original_path: '/', + original_md5hash: '23664a1e4a8cad08d4ca6294d3d9bee3', + from_batch_import: false, + is_tus_file: false, + tus_upload_url: null, + url: 'https://demos.transloadit.com/7e/5ce0bb323044e7a9deeb7d6f6e312f/subtitle.srt', + ssl_url: 'https://demos.transloadit.com/7e/5ce0bb323044e7a9deeb7d6f6e312f/subtitle.srt', + meta: {}, + is_temp_url: false, + queue: 'live', + queue_time: 0.02, + exec_time: 0.43, + cost: 61, + }, + ], + converted: [ + { + id: '5a6d14421bb9494b8b01edb4f674d767', + name: 'subtitle.vtt', + basename: 'subtitle', + ext: 'vtt', + size: 328, + mime: 'text/vtt', + type: null, + field: 'file', + md5hash: '30aabe4875747e49e1bc1be0ed2d7acb', + original_id: '7e5ce0bb323044e7a9deeb7d6f6e312f', + original_basename: 'subtitle', + original_name: 'subtitle.srt', + original_path: '/', + original_md5hash: '23664a1e4a8cad08d4ca6294d3d9bee3', + from_batch_import: false, + is_tus_file: false, + tus_upload_url: null, + url: 'https://demos.transloadit.com/5a/6d14421bb9494b8b01edb4f674d767/subtitle.vtt', + ssl_url: 'https://demos.transloadit.com/5a/6d14421bb9494b8b01edb4f674d767/subtitle.vtt', + meta: {}, + is_temp_url: false, + queue: 'live', + queue_time: 0.02, + exec_time: 0.47, + cost: 1048576, + }, + ], + }, + build_id: '7261900535', +} + +const assemblyStatusUploading = { + ok: 'ASSEMBLY_UPLOADING', + assembly_id: 'b841ea401e1a11e7b37d7bda1b503cdd', + assembly_ssl_url: 'https://api2-freja.transloadit.com/assemblies/b841ea401e1a11e7b37d7bda1b503cdd', + websocket_url: 'https://api2-freja.transloadit.com/ws20277', + tus_url: 'https://api2-freja.transloadit.com/resumable/files/', + expected_tus_uploads: 2, + started_tus_uploads: 0, + finished_tus_uploads: 0, +} + +const assemblyStatusExpired = { + error: 'ASSEMBLY_EXPIRED', + http_code: 200, + message: 'The Assembly expired.', + ok: null, + assembly_id: '8b9dd2803eee11f0810ddf94e3864ef6', + parent_id: null, + account_id: '47c2abfc5ae1437ca330bc628dddd557', + account_name: null, + account_slug: null, + api_auth_key_id: '5db01f1876a84a998b583f82b24eddf6', + template_id: null, + template_name: null, + instance: 'u535up.transloadit.com', + region: 'us-east-1', + assembly_url: 'http://api2-u535up.transloadit.com/assemblies/8b9dd2803eee11f0810ddf94e3864ef6', + assembly_ssl_url: + 'https://api2-u535up.transloadit.com/assemblies/8b9dd2803eee11f0810ddf94e3864ef6', + uppyserver_url: 'https://api2-u535up.transloadit.com/companion/', + companion_url: 'https://api2-u535up.transloadit.com/companion/', + websocket_url: 'https://api2-u535up.transloadit.com/ws20011', + update_stream_url: + 'https://api2-u535up.transloadit.com/ws20011?assembly=8b9dd2803eee11f0810ddf94e3864ef6', + tus_url: 'https://api2-u535up.transloadit.com/resumable/files/', + bytes_received: 0, + bytes_expected: 0, + upload_duration: 0.132, + client_agent: null, + client_ip: null, + client_referer: null, + start_date: '2025/06/01 13:44:31 GMT', + upload_meta_data_extracted: false, + warnings: [], + is_infinite: false, + has_dupe_jobs: false, + execution_start: '2025-06-01T13:44:31.000Z', + execution_duration: 28805.262, + queue_duration: 0, + jobs_queue_duration: 0, + notify_start: null, + notify_url: null, + notify_response_code: null, + notify_response_data: null, + notify_duration: null, + last_job_completed: null, + fields: {}, + running_jobs: [], + bytes_usage: 3024, + usage_tags: '', + executing_jobs: [], + started_jobs: [], + parent_assembly_status: null, + params: null, + template: null, + merged_params: null, + num_input_files: 0, + uploads: [], + results: {}, + build_id: '15302315784', +} + +export const assemblyStatusFixtures: AssemblyStatusFixture[] = [ + { + name: 'demo-ok', + value: assemblyStatusOk, + valid: true, + }, + { + name: 'busy-uploading', + value: assemblyStatusUploading, + valid: true, + }, + { + name: 'expired-error', + value: assemblyStatusExpired, + valid: true, + }, +] diff --git a/packages/zod/test/runtime-parity.test.ts b/packages/zod/test/runtime-parity.test.ts index 16184a8f..dae96c49 100644 --- a/packages/zod/test/runtime-parity.test.ts +++ b/packages/zod/test/runtime-parity.test.ts @@ -1,12 +1,21 @@ import assert from 'node:assert/strict' +import { assemblyStatusSchema as v3AssemblyStatus } from '../src/v3/assemblyStatus.ts' import { assemblyInstructionsSchema as v3AssemblyInstructions } from '../src/v3/template.ts' +import { assemblyStatusSchema as v4AssemblyStatus } from '../src/v4/assemblyStatus.ts' import { assemblyInstructionsSchema as v4AssemblyInstructions } from '../src/v4/template.ts' import { robotBase as v3RobotBase, robotFFmpeg as v3RobotFFmpeg } from '../src/v3/robots/_instructions-primitives.ts' import { robotBase as v4RobotBase, robotFFmpeg as v4RobotFFmpeg } from '../src/v4/robots/_instructions-primitives.ts' +import { assemblyStatusFixtures } from './fixtures/assembly-status.ts' import { assemblyInstructionFixtures } from './fixtures/assembly-instructions.ts' const schemas = [ + { + name: 'assemblyStatus', + v3: v3AssemblyStatus, + v4: v4AssemblyStatus, + fixtures: assemblyStatusFixtures, + }, { name: 'assemblyInstructions', v3: v3AssemblyInstructions, From f9b29fb1a197975964bb7242affe1fc805dca0cc Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Fri, 16 Jan 2026 18:12:33 +0100 Subject: [PATCH 20/43] chore: slim transloadit pack output --- .gitignore | 4 + packages/transloadit/CHANGELOG.md | 7 - packages/transloadit/LICENSE | 21 - packages/transloadit/README.md | 698 ------- packages/transloadit/src/ApiError.ts | 49 - .../src/InconsistentResponseError.ts | 3 - packages/transloadit/src/PaginationStream.ts | 54 - .../transloadit/src/PollingTimeoutError.ts | 5 - packages/transloadit/src/Transloadit.ts | 980 --------- .../src/alphalib/lib/nativeGlobby.ts | 244 --- packages/transloadit/src/alphalib/mcache.ts | 184 -- packages/transloadit/src/alphalib/tryCatch.ts | 30 - .../src/alphalib/types/assembliesGet.ts | 42 - .../src/alphalib/types/assemblyReplay.ts | 24 - .../types/assemblyReplayNotification.ts | 16 - .../src/alphalib/types/assemblyStatus.ts | 794 -------- .../transloadit/src/alphalib/types/bill.ts | 9 - .../src/alphalib/types/robots/_index.ts | 1201 ----------- .../types/robots/_instructions-primitives.ts | 1770 ----------------- .../src/alphalib/types/robots/ai-chat.ts | 278 --- .../types/robots/assembly-savejson.ts | 37 - .../alphalib/types/robots/audio-artwork.ts | 107 - .../src/alphalib/types/robots/audio-concat.ts | 142 -- .../src/alphalib/types/robots/audio-encode.ts | 103 - .../src/alphalib/types/robots/audio-loop.ts | 102 - .../src/alphalib/types/robots/audio-merge.ts | 137 -- .../alphalib/types/robots/audio-waveform.ts | 280 --- .../src/alphalib/types/robots/azure-import.ts | 111 -- .../src/alphalib/types/robots/azure-store.ts | 143 -- .../alphalib/types/robots/backblaze-import.ts | 119 -- .../alphalib/types/robots/backblaze-store.ts | 104 - .../types/robots/cloudfiles-import.ts | 118 -- .../alphalib/types/robots/cloudfiles-store.ts | 104 - .../types/robots/cloudflare-import.ts | 121 -- .../alphalib/types/robots/cloudflare-store.ts | 120 -- .../types/robots/digitalocean-import.ts | 118 -- .../types/robots/digitalocean-store.ts | 125 -- .../types/robots/document-autorotate.ts | 74 - .../alphalib/types/robots/document-convert.ts | 302 --- .../alphalib/types/robots/document-merge.ts | 114 -- .../src/alphalib/types/robots/document-ocr.ts | 120 -- .../alphalib/types/robots/document-split.ts | 78 - .../alphalib/types/robots/document-thumbs.ts | 231 --- .../alphalib/types/robots/dropbox-import.ts | 100 - .../alphalib/types/robots/dropbox-store.ts | 97 - .../alphalib/types/robots/edgly-deliver.ts | 73 - .../alphalib/types/robots/file-compress.ts | 167 -- .../alphalib/types/robots/file-decompress.ts | 125 -- .../src/alphalib/types/robots/file-filter.ts | 173 -- .../src/alphalib/types/robots/file-hash.ts | 86 - .../src/alphalib/types/robots/file-preview.ts | 260 --- .../src/alphalib/types/robots/file-read.ts | 71 - .../src/alphalib/types/robots/file-serve.ts | 128 -- .../src/alphalib/types/robots/file-verify.ts | 102 - .../alphalib/types/robots/file-virusscan.ts | 113 -- .../alphalib/types/robots/file-watermark.ts | 56 - .../src/alphalib/types/robots/ftp-import.ts | 95 - .../src/alphalib/types/robots/ftp-store.ts | 119 -- .../alphalib/types/robots/google-import.ts | 115 -- .../src/alphalib/types/robots/google-store.ts | 139 -- .../src/alphalib/types/robots/html-convert.ts | 165 -- .../src/alphalib/types/robots/http-import.ts | 168 -- .../alphalib/types/robots/image-bgremove.ts | 95 - .../alphalib/types/robots/image-describe.ts | 121 -- .../alphalib/types/robots/image-facedetect.ts | 187 -- .../alphalib/types/robots/image-generate.ts | 92 - .../src/alphalib/types/robots/image-merge.ts | 127 -- .../src/alphalib/types/robots/image-ocr.ts | 112 -- .../alphalib/types/robots/image-optimize.ts | 114 -- .../src/alphalib/types/robots/image-resize.ts | 653 ------ .../src/alphalib/types/robots/meta-read.ts | 44 - .../src/alphalib/types/robots/meta-write.ts | 93 - .../src/alphalib/types/robots/minio-import.ts | 120 -- .../src/alphalib/types/robots/minio-store.ts | 115 -- .../types/robots/progress-simulate.ts | 40 - .../src/alphalib/types/robots/s3-import.ts | 175 -- .../src/alphalib/types/robots/s3-store.ts | 198 -- .../src/alphalib/types/robots/script-run.ts | 122 -- .../src/alphalib/types/robots/sftp-import.ts | 92 - .../src/alphalib/types/robots/sftp-store.ts | 110 - .../types/robots/speech-transcribe.ts | 139 -- .../alphalib/types/robots/supabase-import.ts | 122 -- .../alphalib/types/robots/supabase-store.ts | 105 - .../src/alphalib/types/robots/swift-import.ts | 120 -- .../src/alphalib/types/robots/swift-store.ts | 112 -- .../src/alphalib/types/robots/text-speak.ts | 152 -- .../alphalib/types/robots/text-translate.ts | 245 --- .../alphalib/types/robots/tigris-import.ts | 124 -- .../src/alphalib/types/robots/tigris-store.ts | 119 -- .../alphalib/types/robots/tlcdn-deliver.ts | 73 - .../src/alphalib/types/robots/tus-store.ts | 129 -- .../alphalib/types/robots/upload-handle.ts | 95 - .../alphalib/types/robots/video-adaptive.ts | 179 -- .../src/alphalib/types/robots/video-concat.ts | 130 -- .../src/alphalib/types/robots/video-encode.ts | 141 -- .../src/alphalib/types/robots/video-merge.ts | 138 -- .../alphalib/types/robots/video-ondemand.ts | 161 -- .../alphalib/types/robots/video-subtitle.ts | 159 -- .../src/alphalib/types/robots/video-thumbs.ts | 158 -- .../src/alphalib/types/robots/vimeo-import.ts | 126 -- .../src/alphalib/types/robots/vimeo-store.ts | 143 -- .../alphalib/types/robots/wasabi-import.ts | 124 -- .../src/alphalib/types/robots/wasabi-store.ts | 113 -- .../alphalib/types/robots/youtube-store.ts | 153 -- .../src/alphalib/types/stackVersions.ts | 12 - .../src/alphalib/types/template.ts | 277 --- .../src/alphalib/types/templateCredential.ts | 61 - .../src/alphalib/zodParseWithContext.ts | 306 --- packages/transloadit/src/apiTypes.ts | 154 -- packages/transloadit/src/cli.ts | 44 - packages/transloadit/src/cli/OutputCtl.ts | 115 -- .../src/cli/commands/BaseCommand.ts | 71 - .../src/cli/commands/assemblies.ts | 1373 ------------- packages/transloadit/src/cli/commands/auth.ts | 354 ---- .../transloadit/src/cli/commands/bills.ts | 91 - .../transloadit/src/cli/commands/index.ts | 65 - .../src/cli/commands/notifications.ts | 63 - .../transloadit/src/cli/commands/templates.ts | 556 ------ packages/transloadit/src/cli/helpers.ts | 50 - .../src/cli/template-last-modified.ts | 156 -- packages/transloadit/src/cli/types.ts | 70 - packages/transloadit/src/tus.ts | 168 -- scripts/pack-transloadit.ts | 4 +- 123 files changed, 6 insertions(+), 21629 deletions(-) delete mode 100644 packages/transloadit/CHANGELOG.md delete mode 100644 packages/transloadit/LICENSE delete mode 100644 packages/transloadit/README.md delete mode 100644 packages/transloadit/src/ApiError.ts delete mode 100644 packages/transloadit/src/InconsistentResponseError.ts delete mode 100644 packages/transloadit/src/PaginationStream.ts delete mode 100644 packages/transloadit/src/PollingTimeoutError.ts delete mode 100644 packages/transloadit/src/Transloadit.ts delete mode 100644 packages/transloadit/src/alphalib/lib/nativeGlobby.ts delete mode 100644 packages/transloadit/src/alphalib/mcache.ts delete mode 100644 packages/transloadit/src/alphalib/tryCatch.ts delete mode 100644 packages/transloadit/src/alphalib/types/assembliesGet.ts delete mode 100644 packages/transloadit/src/alphalib/types/assemblyReplay.ts delete mode 100644 packages/transloadit/src/alphalib/types/assemblyReplayNotification.ts delete mode 100644 packages/transloadit/src/alphalib/types/assemblyStatus.ts delete mode 100644 packages/transloadit/src/alphalib/types/bill.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/_index.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/_instructions-primitives.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/ai-chat.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/assembly-savejson.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/audio-artwork.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/audio-concat.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/audio-encode.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/audio-loop.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/audio-merge.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/audio-waveform.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/azure-import.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/azure-store.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/backblaze-import.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/backblaze-store.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/cloudfiles-import.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/cloudfiles-store.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/cloudflare-import.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/cloudflare-store.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/digitalocean-import.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/digitalocean-store.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/document-autorotate.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/document-convert.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/document-merge.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/document-ocr.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/document-split.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/document-thumbs.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/dropbox-import.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/dropbox-store.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/edgly-deliver.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/file-compress.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/file-decompress.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/file-filter.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/file-hash.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/file-preview.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/file-read.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/file-serve.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/file-verify.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/file-virusscan.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/file-watermark.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/ftp-import.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/ftp-store.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/google-import.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/google-store.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/html-convert.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/http-import.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/image-bgremove.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/image-describe.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/image-facedetect.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/image-generate.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/image-merge.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/image-ocr.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/image-optimize.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/image-resize.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/meta-read.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/meta-write.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/minio-import.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/minio-store.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/progress-simulate.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/s3-import.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/s3-store.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/script-run.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/sftp-import.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/sftp-store.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/speech-transcribe.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/supabase-import.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/supabase-store.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/swift-import.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/swift-store.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/text-speak.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/text-translate.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/tigris-import.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/tigris-store.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/tlcdn-deliver.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/tus-store.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/upload-handle.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/video-adaptive.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/video-concat.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/video-encode.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/video-merge.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/video-ondemand.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/video-subtitle.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/video-thumbs.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/vimeo-import.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/vimeo-store.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/wasabi-import.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/wasabi-store.ts delete mode 100644 packages/transloadit/src/alphalib/types/robots/youtube-store.ts delete mode 100644 packages/transloadit/src/alphalib/types/stackVersions.ts delete mode 100644 packages/transloadit/src/alphalib/types/template.ts delete mode 100644 packages/transloadit/src/alphalib/types/templateCredential.ts delete mode 100644 packages/transloadit/src/alphalib/zodParseWithContext.ts delete mode 100644 packages/transloadit/src/apiTypes.ts delete mode 100644 packages/transloadit/src/cli.ts delete mode 100644 packages/transloadit/src/cli/OutputCtl.ts delete mode 100644 packages/transloadit/src/cli/commands/BaseCommand.ts delete mode 100644 packages/transloadit/src/cli/commands/assemblies.ts delete mode 100644 packages/transloadit/src/cli/commands/auth.ts delete mode 100644 packages/transloadit/src/cli/commands/bills.ts delete mode 100644 packages/transloadit/src/cli/commands/index.ts delete mode 100644 packages/transloadit/src/cli/commands/notifications.ts delete mode 100644 packages/transloadit/src/cli/commands/templates.ts delete mode 100644 packages/transloadit/src/cli/helpers.ts delete mode 100644 packages/transloadit/src/cli/template-last-modified.ts delete mode 100644 packages/transloadit/src/cli/types.ts delete mode 100644 packages/transloadit/src/tus.ts diff --git a/.gitignore b/.gitignore index e0da6dec..2e761c24 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,7 @@ packages/node/coverage packages/types/src/generated packages/zod/src/v3 packages/zod/src/v4 +packages/transloadit/src +packages/transloadit/README.md +packages/transloadit/CHANGELOG.md +packages/transloadit/LICENSE diff --git a/packages/transloadit/CHANGELOG.md b/packages/transloadit/CHANGELOG.md deleted file mode 100644 index 1d3b80fa..00000000 --- a/packages/transloadit/CHANGELOG.md +++ /dev/null @@ -1,7 +0,0 @@ -# transloadit - -## 4.1.3 - -### Patch Changes - -- f989dc1: chore: align workspace packages for upcoming monorepo releases diff --git a/packages/transloadit/LICENSE b/packages/transloadit/LICENSE deleted file mode 100644 index 6a259963..00000000 --- a/packages/transloadit/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Tim Koschuetzki - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/packages/transloadit/README.md b/packages/transloadit/README.md deleted file mode 100644 index c041b465..00000000 --- a/packages/transloadit/README.md +++ /dev/null @@ -1,698 +0,0 @@ -[![Build Status](https://github.com/transloadit/node-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/transloadit/node-sdk/actions/workflows/ci.yml) -[![Coverage](https://codecov.io/gh/transloadit/node-sdk/branch/main/graph/badge.svg)](https://codecov.io/gh/transloadit/node-sdk) - - - - - - Transloadit Logo - - - -This is the official **Node.js** SDK for [Transloadit](https://transloadit.com)'s file uploading and encoding service. - -## Intro - -[Transloadit](https://transloadit.com) is a service that helps you handle file -uploads, resize, crop and watermark your images, make GIFs, transcode your -videos, extract thumbnails, generate audio waveforms, [and so much more](https://transloadit.com/demos/). In -short, [Transloadit](https://transloadit.com) is the Swiss Army Knife for your -files. - -This is a **Node.js** SDK to make it easy to talk to the -[Transloadit](https://transloadit.com) REST API. - -## Requirements - -- [Node.js](https://nodejs.org/en/) version 20 or newer -- [A Transloadit account](https://transloadit.com/signup/) ([free signup](https://transloadit.com/pricing/)) -- [Your API credentials](https://transloadit.com/c/template-credentials) (`authKey`, `authSecret`) - -## Install - -Inside your project, type: - -```bash -yarn add transloadit -``` - -or - -```bash -npm install --save transloadit -``` - -## Command Line Interface (CLI) - -This package includes a full-featured CLI for interacting with Transloadit from your terminal. - -### Quick Start - -```bash -# Set your credentials -export TRANSLOADIT_KEY="YOUR_TRANSLOADIT_KEY" -export TRANSLOADIT_SECRET="YOUR_TRANSLOADIT_SECRET" - -# See all available commands -npx transloadit --help -``` - -### Processing Media - -Create Assemblies to process files using Assembly Instructions (steps) or Templates: - -```bash -# Process a file using a steps file -npx transloadit assemblies create --steps steps.json --input image.jpg --output result.jpg - -# Process using a Template -npx transloadit assemblies create --template YOUR_TEMPLATE_ID --input image.jpg --output result.jpg - -# Process with custom fields -npx transloadit assemblies create --template YOUR_TEMPLATE_ID --field size=100 --input image.jpg --output thumb.jpg - -# Process a directory of files -npx transloadit assemblies create --template YOUR_TEMPLATE_ID --input images/ --output thumbs/ - -# Process recursively with file watching -npx transloadit assemblies create --template YOUR_TEMPLATE_ID --input images/ --output thumbs/ --recursive --watch - -# Process multiple files in a single assembly -npx transloadit assemblies create --template YOUR_TEMPLATE_ID --input file1.jpg --input file2.jpg --output results/ --single-assembly - -# Limit concurrent processing (default: 5) -npx transloadit assemblies create --template YOUR_TEMPLATE_ID --input images/ --output thumbs/ --concurrency 2 -``` - -### Managing Assemblies - -```bash -# List recent assemblies -npx transloadit assemblies list - -# List assemblies with filters -npx transloadit assemblies list --after 2024-01-01 --before 2024-12-31 - -# Get assembly status -npx transloadit assemblies get ASSEMBLY_ID - -# Cancel an assembly -npx transloadit assemblies delete ASSEMBLY_ID - -# Replay an assembly (re-run with original instructions) -npx transloadit assemblies replay ASSEMBLY_ID - -# Replay with different steps -npx transloadit assemblies replay --steps new-steps.json ASSEMBLY_ID - -# Replay using latest template version -npx transloadit assemblies replay --reparse-template ASSEMBLY_ID -``` - -### Managing Templates - -```bash -# List all templates -npx transloadit templates list - -# Get template content -npx transloadit templates get TEMPLATE_ID - -# Create a template from a JSON file -npx transloadit templates create my-template template.json - -# Modify a template -npx transloadit templates modify TEMPLATE_ID template.json - -# Rename a template -npx transloadit templates modify TEMPLATE_ID --name new-name - -# Delete a template -npx transloadit templates delete TEMPLATE_ID - -# Sync local template files with Transloadit (bidirectional) -npx transloadit templates sync templates/*.json -npx transloadit templates sync --recursive templates/ -``` - -### Billing - -```bash -# Get bill for a month -npx transloadit bills get 2024-01 - -# Get detailed bill as JSON -npx transloadit bills get 2024-01 --json -``` - -### Assembly Notifications - -```bash -# Replay a notification -npx transloadit assembly-notifications replay ASSEMBLY_ID - -# Replay to a different URL -npx transloadit assembly-notifications replay --notify-url https://example.com/hook ASSEMBLY_ID -``` - -### Signature Generation - -```bash -# Generate a signature for assembly params -echo '{"steps":{}}' | npx transloadit auth signature - -# Generate with specific algorithm -echo '{"steps":{}}' | npx transloadit auth signature --algorithm sha256 - -# Generate a signed Smart CDN URL -echo '{"workspace":"my-workspace","template":"my-template","input":"image.jpg"}' | npx transloadit auth smart-cdn -``` - -### CLI Options - -All commands support these common options: - -- `--json, -j` - Output results as JSON (useful for scripting) -- `--log-level, -l` - Set log verbosity level by name or number (default: notice) -- `--endpoint` - Custom API endpoint URL (or set `TRANSLOADIT_ENDPOINT` env var) -- `--help, -h` - Show help for a command - -The `assemblies create` command additionally supports: - -- `--single-assembly` - Pass all input files to a single assembly instead of one assembly per file - -#### Log Levels - -The CLI uses [syslog severity levels](https://en.wikipedia.org/wiki/Syslog#Severity_level). Lower = more severe, higher = more verbose: - -| Level | Value | Description | -| -------- | ----- | ------------------------------------- | -| `err` | 3 | Error conditions | -| `warn` | 4 | Warning conditions | -| `notice` | 5 | Normal but significant **(default)** | -| `info` | 6 | Informational messages | -| `debug` | 7 | Debug-level messages | -| `trace` | 8 | Most verbose/detailed | - -You can use either the level name or its numeric value: - -```bash -# Show only errors and warnings -npx transloadit assemblies list -l warn -npx transloadit assemblies list -l 4 - -# Show debug output -npx transloadit assemblies list -l debug -npx transloadit assemblies list -l 7 -``` - -## SDK Usage - -The following code will upload an image and resize it to a thumbnail: - -```javascript -import { Transloadit } from 'transloadit' - -const transloadit = new Transloadit({ - authKey: 'YOUR_TRANSLOADIT_KEY', - authSecret: 'YOUR_TRANSLOADIT_SECRET', -}) - -try { - const options = { - files: { - file1: '/PATH/TO/FILE.jpg', - }, - params: { - steps: { - // You can have many Steps. In this case we will just resize any inputs (:original) - resize: { - use: ':original', - robot: '/image/resize', - result: true, - width: 75, - height: 75, - }, - }, - // OR if you already created a template, you can use it instead of "steps": - // template_id: 'YOUR_TEMPLATE_ID', - }, - waitForCompletion: true, // Wait for the Assembly (job) to finish executing before returning - } - - const status = await transloadit.createAssembly(options) - - if (status.results.resize) { - console.log('✅ Success - Your resized image:', status.results.resize[0].ssl_url) - } else { - console.log("❌ The Assembly didn't produce any output. Make sure you used a valid image file") - } -} catch (err) { - console.error('❌ Unable to process Assembly.', err) - if (err instanceof ApiError && err.assemblyId) { - console.error(`💡 More info: https://transloadit.com/assemblies/${err.assemblyId}`) - } -} -``` - -You can find [details about your executed Assemblies here](https://transloadit.com/assemblies). - -## Examples - -- [Upload and resize image](https://github.com/transloadit/node-sdk/blob/main/examples/resize_an_image.ts) -- [Upload image and convert to WebP](https://github.com/transloadit/node-sdk/blob/main/examples/convert_to_webp.ts) -- [Rasterize SVG to PNG](https://github.com/transloadit/node-sdk/blob/main/examples/rasterize_svg_to_png.ts) -- [Crop a face out of an image and download the result](https://github.com/transloadit/node-sdk/blob/main/examples/face_detect_download.ts) -- [Retry example](https://github.com/transloadit/node-sdk/blob/main/examples/retry.ts) -- [Calculate total costs (GB usage)](https://github.com/transloadit/node-sdk/blob/main/examples/fetch_costs_of_all_assemblies_in_timeframe.ts) -- [Templates CRUD](https://github.com/transloadit/node-sdk/blob/main/examples/template_api.ts) -- [Template Credentials CRUD](https://github.com/transloadit/node-sdk/blob/main/examples/credentials.ts) - -For more fully working examples take a look at [`examples/`](https://github.com/transloadit/node-sdk/blob/main/examples/). - -For more example use cases and information about the available robots and their parameters, check out the [Transloadit website](https://transloadit.com/). - -## API - -These are the public methods on the `Transloadit` object and their descriptions. The methods are based on the [Transloadit API](https://transloadit.com/docs/api/). - -Table of contents: - -- [Main](#main) -- [Assemblies](#assemblies) -- [Assembly notifications](#assembly-notifications) -- [Templates](#templates) -- [Template Credentials](#template-credentials) -- [Errors](#errors) -- [Rate limiting & auto retry](#rate-limiting--auto-retry) - -### Main - -#### constructor(options) - -Returns a new instance of the client. - -The `options` object can contain the following keys: - -- `authKey` **(required)** - see [requirements](#requirements) -- `authSecret` **(required)** - see [requirements](#requirements) -- `endpoint` (default `'https://api2.transloadit.com'`) -- `maxRetries` (default `5`) - see [Rate limiting & auto retry](#rate-limiting--auto-retry) -- `gotRetry` (default `0`) - see [Rate limiting & auto retry](#rate-limiting--auto-retry) -- `timeout` (default `60000`: 1 minute) - the timeout (in milliseconds) for all requests (except `createAssembly`) -- `validateResponses` (default `false`) - -### Assemblies - -#### async createAssembly(options) - -Creates a new Assembly on Transloadit and optionally upload the specified `files` and `uploads`. - -You can provide the following keys inside the `options` object: - -- `params` **(required)** - An object containing keys defining the Assembly's behavior with the following keys: (See also [API doc](https://transloadit.com/docs/api/assemblies-post/) and [examples](#examples)) - - `steps` - Assembly instructions - See [Transloadit docs](https://transloadit.com/docs/topics/assembly-instructions/) and [demos](https://transloadit.com/demos/) for inspiration. - - `template_id` - The ID of the Template that contains your Assembly Instructions. **One of either `steps` or `template_id` is required.** If you specify both, then [any Steps will overrule the template](https://transloadit.com/docs/topics/templates/#overruling-templates-at-runtime). - - `fields` - An object of form fields to add to the request, to make use of in the Assembly instructions via [Assembly variables](https://transloadit.com/docs#assembly-variables). - - `notify_url` - Transloadit can send a Pingback to your server when the Assembly is completed. We'll send the Assembly Status in JSON encoded string inside a transloadit field in a multipart POST request to the URL supplied here. -- `files` - An object (key-value pairs) containing one or more file paths to upload and use in your Assembly. The _key_ is the _field name_ and the _value_ is the path to the file to be uploaded. The _field name_ and the file's name may be used in the ([Assembly instructions](https://transloadit.com/docs/topics/assembly-instructions/)) (`params`.`steps`) to refer to the particular file. See example below. - - `'fieldName': '/path/to/file'` - - more files... -- `uploads` - An object (key-value pairs) containing one or more files to upload and use in your Assembly. The _key_ is the _file name_ and the _value_ is the _content_ of the file to be uploaded. _Value_ can be one of many types: - - `'fieldName': (Readable | Buffer | TypedArray | ArrayBuffer | string | Iterable | AsyncIterable | Promise)`. - - more uploads... -- `waitForCompletion` - A boolean (default is `false`) to indicate whether you want to wait for the Assembly to finish with all encoding results present before the promise is fulfilled. If `waitForCompletion` is `true`, this SDK will poll for status updates and fulfill the promise when all encoding work is done. -- `timeout` - Number of milliseconds to wait before aborting (default `86400000`: 24 hours). -- `onUploadProgress` - An optional function that will be periodically called with the file upload progress, which is an with an object containing: - - `uploadedBytes` - Number of bytes uploaded so far. - - `totalBytes` - Total number of bytes to upload or `undefined` if unknown (Streams). -- `onAssemblyProgress` - Once the Assembly has started processing this will be periodically called with the _Assembly Execution Status_ (result of `getAssembly`) **only if `waitForCompletion` is `true`**. -- `chunkSize` - (for uploads) a number indicating the maximum size of a tus `PATCH` request body in bytes. Default to `Infinity` for file uploads and 50MB for streams of unknown length. See [tus-js-client](https://github.com/tus/tus-js-client/blob/master/docs/api.md#chunksize). -- `uploadConcurrency` - Maximum number of concurrent tus file uploads to occur at any given time (default 10.) - -**NOTE**: Make sure the key in `files` and `uploads` is not one of `signature`, `params` or `max_size`. - -Example code showing all options: - -```js -await transloadit.createAssembly({ - files: { - file1: '/path/to/file.jpg' - // ... - }, - uploads: { - 'file2.bin': Buffer.from([0, 0, 7]), // A buffer - 'file3.txt': 'file contents', // A string - 'file4.jpg': process.stdin // A stream - // ... - }, - params: { - steps: { ... }, - template_id: 'MY_TEMPLATE_ID', - fields: { - field1: 'Field value', - // ... - }, - notify_url: 'https://example.com/notify-url', - }, - waitForCompletion: true, - timeout: 60000, - onUploadProgress, - onAssemblyProgress, -}) -``` - -Example `onUploadProgress` and `onAssemblyProgress` handlers: - -```javascript -function onUploadProgress({ uploadedBytes, totalBytes }) { - // NOTE: totalBytes may be undefined - console.log(`♻️ Upload progress polled: ${uploadedBytes} of ${totalBytes} bytes uploaded.`) -} -function onAssemblyProgress(assembly) { - console.log( - `♻️ Assembly progress polled: ${assembly.error ? assembly.error : assembly.ok} ${ - assembly.assembly_id - } ... ` - ) -} -``` - -**Tip:** `createAssembly` returns a `Promise` with an extra property `assemblyId`. This can be used to retrieve the Assembly ID before the Assembly has even been created. Useful for debugging by logging this ID when the request starts, for example: - -```js -const promise = transloadit.createAssembly(options) -console.log('Creating', promise.assemblyId) -const status = await promise -``` - -See also: - -- [API documentation](https://transloadit.com/docs/api/assemblies-post/) -- Error codes and retry logic below - -#### async listAssemblies(params) - -Retrieve Assemblies according to the given `params`. - -Valid params can be `page`, `pagesize`, `type`, `fromdate`, `todate` and `keywords`. Please consult the [API documentation](https://transloadit.com/docs/api/assemblies-get/) for details. - -The method returns an object containing these properties: - -- `items`: An `Array` of up to `pagesize` Assemblies -- `count`: Total number of Assemblies - -#### streamAssemblies(params) - -Creates an `objectMode` `Readable` stream that automates handling of `listAssemblies` pagination. It accepts the same `params` as `listAssemblies`. - -This can be used to iterate through Assemblies: - -```javascript -const assemblyStream = transloadit.streamAssemblies({ fromdate: '2016-08-19 01:15:00 UTC' }) - -assemblyStream.on('readable', function () { - const assembly = assemblyStream.read() - if (assembly == null) console.log('end of stream') - - console.log(assembly.id) -}) -``` - -Results can also be piped. Here's an example using -[through2](https://github.com/rvagg/through2): - -```javascript -const assemblyStream = transloadit.streamAssemblies({ fromdate: '2016-08-19 01:15:00 UTC' }) - -assemblyStream - .pipe( - through.obj(function (chunk, enc, callback) { - this.push(chunk.id + '\n') - callback() - }) - ) - .pipe(fs.createWriteStream('assemblies.txt')) -``` - -#### async getAssembly(assemblyId) - -Retrieves the JSON status of the Assembly identified by the given `assemblyId`. See [API documentation](https://transloadit.com/docs/api/assemblies-assembly-id-get/). - -#### async cancelAssembly(assemblyId) - -Removes the Assembly identified by the given `assemblyId` from the memory of the Transloadit machines, ultimately cancelling it. This does not delete the Assembly from the database - you can still access it on `https://transloadit.com/assemblies/{assembly_id}` in your Transloadit account. This also does not delete any files associated with the Assembly from the Transloadit servers. See [API documentation](https://transloadit.com/docs/api/assemblies-assembly-id-delete/). - -#### async replayAssembly(assemblyId, params) - -Replays the Assembly identified by the given `assemblyId` (required argument). Optionally you can also provide a `notify_url` key inside `params` if you want to change the notification target. See [API documentation](https://transloadit.com/docs/api/assemblies-assembly-id-replay-post/) for more info about `params`. - -The response from the `replayAssembly` is minimal and does not contain much information about the replayed assembly. Please call `getAssembly` or `awaitAssemblyCompletion` after replay to get more information: - -```js -const replayAssemblyResponse = await transloadit.replayAssembly(failedAssemblyId) - -const assembly = await transloadit.getAssembly(replayAssemblyResponse.assembly_id) -// Or -const completedAssembly = await transloadit.awaitAssemblyCompletion( - replayAssemblyResponse.assembly_id -) -``` - -#### async awaitAssemblyCompletion(assemblyId, opts) - -This function will continously poll the specified Assembly `assemblyId` and resolve when it is done uploading and executing (until `result.ok` is no longer `ASSEMBLY_UPLOADING`, `ASSEMBLY_EXECUTING` or `ASSEMBLY_REPLAYING`). It resolves with the same value as `getAssembly`. - -`opts` is an object with the keys: - -- `onAssemblyProgress` - A progress function called on each poll. See `createAssembly` -- `timeout` - How many milliseconds until polling times out (default: no timeout) -- `interval` - Poll interval in milliseconds (default `1000`) -- `signal` - An `AbortSignal` to cancel polling. When aborted, the promise rejects with an `AbortError`. -- `onPoll` - A callback invoked at the start of each poll iteration. Return `false` to stop polling early and resolve with the last known status. Useful for implementing custom cancellation logic (e.g., superseding assemblies in watch mode). - -#### getLastUsedAssemblyUrl() - -Returns the internal url that was used for the last call to `createAssembly`. This is meant to be used for debugging purposes. - -### Assembly notifications - -#### async replayAssemblyNotification(assemblyId, params) - -Replays the notification for the Assembly identified by the given `assemblyId` (required argument). Optionally you can also provide a `notify_url` key inside `params` if you want to change the notification target. See [API documentation](https://transloadit.com/docs/api/assembly-notifications-assembly-id-replay-post/) for more info about `params`. - -### Templates - -Templates are Steps that can be reused. [See example template code](examples/template_api.ts). - -#### async createTemplate(params) - -Creates a template the provided params. The required `params` keys are: - -- `name` - The template name -- `template` - The template JSON object containing its `steps` - -See also [API documentation](https://transloadit.com/docs/api/templates-post/). - -```js -const template = { - steps: { - encode: { - use: ':original', - robot: '/video/encode', - preset: 'ipad-high', - }, - thumbnail: { - use: 'encode', - robot: '/video/thumbnails', - }, - }, -} - -const result = await transloadit.createTemplate({ name: 'my-template-name', template }) -console.log('✅ Template created with template_id', result.id) -``` - -#### async editTemplate(templateId, params) - -Updates the template represented by the given `templateId` with the new value. The `params` works just like the one from the `createTemplate` call. See [API documentation](https://transloadit.com/docs/api/templates-template-id-put/). - -#### async getTemplate(templateId) - -Retrieves the name and the template JSON for the template represented by the given `templateId`. See [API documentation](https://transloadit.com/docs/api/templates-template-id-get/). - -#### async deleteTemplate(templateId) - -Deletes the template represented by the given `templateId`. See [API documentation](https://transloadit.com/docs/api/templates-template-id-delete/). - -#### async listTemplates(params) - -Retrieve all your templates. See [API documentation](https://transloadit.com/docs/api/templates-template-id-get/) for more info about `params`. - -The method returns an object containing these properties: - -- `items`: An `Array` of up to `pagesize` templates -- `count`: Total number of templates - -#### streamTemplates(params) - -Creates an `objectMode` `Readable` stream that automates handling of `listTemplates` pagination. Similar to `streamAssemblies`. - -### Template Credentials - -Template Credentials allow you to store third-party credentials (e.g., AWS S3, Google Cloud Storage, FTP) securely on Transloadit for use in your Assembly Instructions. - -#### async createTemplateCredential(params) - -Creates a new Template Credential. The `params` object should contain the credential configuration. See [API documentation](https://transloadit.com/docs/api/template-credentials-post/). - -#### async editTemplateCredential(credentialId, params) - -Updates an existing Template Credential identified by `credentialId`. See [API documentation](https://transloadit.com/docs/api/template-credentials-credential-id-put/). - -#### async deleteTemplateCredential(credentialId) - -Deletes the Template Credential identified by `credentialId`. See [API documentation](https://transloadit.com/docs/api/template-credentials-credential-id-delete/). - -#### async getTemplateCredential(credentialId) - -Retrieves the Template Credential identified by `credentialId`. See [API documentation](https://transloadit.com/docs/api/template-credentials-credential-id-get/). - -#### async listTemplateCredentials(params) - -Lists all Template Credentials. See [API documentation](https://transloadit.com/docs/api/template-credentials-get/). - -#### streamTemplateCredentials(params) - -Creates an `objectMode` `Readable` stream that automates handling of `listTemplateCredentials` pagination. Similar to `streamAssemblies`. - -### Other - -#### setDefaultTimeout(timeout) - -Same as `constructor` `timeout` option: Set the default timeout (in milliseconds) for all requests (except `createAssembly`) - -#### async getBill(date) - -Retrieves the billing data for a given `date` string with format `YYYY-MM`. See [API documentation](https://transloadit.com/docs/api/bill-date-get/). - -#### calcSignature(params) - -Calculates a signature for the given `params` JSON object. If the `params` object does not include an `authKey` or `expires` keys (and their values) in the `auth` sub-key, then they are set automatically. - -This function returns an object with the key `signature` (containing the calculated signature string) and a key `params`, which contains the stringified version of the passed `params` object (including the set expires and authKey keys). - -See [Signature Generation](#signature-generation) in the CLI section for command-line usage. - -#### getSignedSmartCDNUrl(params) - -Constructs a signed Smart CDN URL, as defined in the [API documentation](https://transloadit.com/docs/topics/signature-authentication/#smart-cdn). `params` must be an object with the following properties: - -- `workspace` - Workspace slug (required) -- `template` - Template slug or template ID (required) -- `input` - Input value that is provided as `${fields.input}` in the template (required) -- `urlParams` - Object with additional parameters for the URL query string (optional) -- `expiresAt` - Expiration timestamp of the signature in milliseconds since UNIX epoch. Defaults to 1 hour from now. (optional) - -Example: - -```js -const client = new Transloadit({ authKey: 'foo_key', authSecret: 'foo_secret' }) -const url = client.getSignedSmartCDNUrl({ - workspace: 'foo_workspace', - template: 'foo_template', - input: 'foo_input', - urlParams: { - foo: 'bar', - }, -}) - -// url is: -// https://foo_workspace.tlcdn.com/foo_template/foo_input?auth_key=foo_key&exp=1714525200000&foo=bar&sig=sha256:9548915ec70a5f0d05de9497289e792201ceec19a526fe315f4f4fd2e7e377ac -``` - -### Errors - -Any errors originating from Node.js will be passed on and we use [GOT](https://github.com/sindresorhus/got) v11 for HTTP requests. [Errors from `got`](https://github.com/sindresorhus/got/tree/v11.8.6?tab=readme-ov-file#errors) will also be passed on, _except_ the `got.HTTPError` which will be replaced with a `transloadit.ApiError`, which will have its `cause` property set to the instance of the original `got.HTTPError`. `transloadit.ApiError` has these properties: - -- `code` (`string`) - [The Transloadit API error code](https://transloadit.com/docs/api/response-codes/#error-codes). -- `rawMessage` (`string`) - A textual representation of the Transloadit API error. -- `reason` (`string`) - Additional information about the Transloadit API error. -- `assemblyId`: (`string`) - If the request is related to an assembly, this will be the ID of the assembly. -- `assemblySslUrl` (`string`) - If the request is related to an assembly, this will be the SSL URL to the assembly . - -To identify errors you can either check its props or use `instanceof`, e.g.: - -```js -try { - await transloadit.createAssembly(options) -} catch (err) { - if (err instanceof got.TimeoutError) { - return console.error('The request timed out', err) - } - if (err.code === 'ENOENT') { - return console.error('Cannot open file', err) - } - if (err instanceof ApiError && err.code === 'ASSEMBLY_INVALID_STEPS') { - return console.error('Invalid Assembly Steps', err) - } -} -``` - -**Note:** Assemblies that have an error status (`assembly.error`) will only result in an error being thrown from `createAssembly` and `replayAssembly`. For other Assembly methods, no errors will be thrown, but any error can be found in the response's `error` property (also `ApiError.code`). - -- [More information on Transloadit errors (`ApiError.code`)](https://transloadit.com/docs/api/response-codes/#error-codes) -- [More information on request errors](https://github.com/sindresorhus/got#errors) - -### Rate limiting & auto retry - -There are three kinds of retries: - -#### Retry on rate limiting (`maxRetries`, default `5`) - -All functions of the client automatically obey all rate limiting imposed by Transloadit (e.g. `RATE_LIMIT_REACHED`), so there is no need to write your own wrapper scripts to handle rate limits. The SDK will by default retry requests **5 times** with auto back-off (See `maxRetries` constructor option). - -#### GOT HTTP retries (`gotRetry`, default `{ limit: 0 }`) - -Because we use [got](https://github.com/sindresorhus/got) under the hood, you can pass a `gotRetry` constructor option which is passed on to `got`. This offers great flexibility for handling retries on network errors and HTTP status codes with auto back-off. See [`got` `retry` object documentation](https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md). - -**Note that the above `maxRetries` option does not affect the `gotRetry` logic.** - -#### Validate API responses (`validateResponses`, default `false`) - -As we have ported the JavaScript SDK to TypeScript in v4, we are now also validating API responses using `zod` schemas. Having schema validation enabled (`true`), guarantees that the data returned by the SDK adheres to the TypeScript types of this SDK. However we are still working on improving the schemas and they are not yet 100% complete. This means that if you hit a bug in the schemas, a `zod` schema validation error will be thrown. If you encounter such an error, please report it and we will fix it as soon as possible. If you set this option to `false`, schema validation will be disabled, and you won't get any such errors, however the TypeScript types will not protect you should such a bug be encountered. - -#### Custom retry logic - -If you want to retry on other errors, please see the [retry example code](examples/retry.ts). - -- https://transloadit.com/docs/api/rate-limiting/ -- https://transloadit.com/blog/2012/04/introducing-rate-limiting/ - -## Debugging - -This project uses [debug](https://github.com/visionmedia/debug) so you can run node with the `DEBUG=transloadit` evironment variable to enable verbose logging. Example: - -```bash -DEBUG=transloadit* npx tsx examples/template_api.ts -``` - -## Maintainers - -- [Mikael Finstad](https://github.com/mifi) - -### Changelog - -See [Releases](https://github.com/transloadit/node-sdk/releases) - -## Attribution - -Thanks to [Ian Hansen](https://github.com/supershabam) for donating the `transloadit` npm name. You can still access his code under [`v0.0.0`](https://www.npmjs.com/package/transloadit/v/0.0.0). - -## License - -[MIT](LICENSE) © [Transloadit](https://transloadit.com) - -## Development - -See [CONTRIBUTING](./CONTRIBUTING.md). diff --git a/packages/transloadit/src/ApiError.ts b/packages/transloadit/src/ApiError.ts deleted file mode 100644 index 9719a678..00000000 --- a/packages/transloadit/src/ApiError.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { RequestError } from 'got' -import { HTTPError } from 'got' - -export interface TransloaditErrorResponseBody { - error?: string - message?: string - reason?: string - assembly_ssl_url?: string - assembly_id?: string -} - -export class ApiError extends Error { - override name = 'ApiError' - - // there might not be an error code (or message) if the server didn't respond with any JSON response at all - // e.g. if there was a 500 in the HTTP reverse proxy - code?: string - - rawMessage?: string - - reason?: string - - assemblySslUrl?: string - - assemblyId?: string - - override cause?: RequestError | undefined - - constructor(params: { cause?: RequestError; body: TransloaditErrorResponseBody | undefined }) { - const { cause, body = {} } = params - - const parts = ['API error'] - if (cause instanceof HTTPError && cause?.response.statusCode) - parts.push(`(HTTP ${cause.response.statusCode})`) - if (body.error) parts.push(`${body.error}:`) - if (body.message) parts.push(body.message) - if (body.assembly_ssl_url) parts.push(body.assembly_ssl_url) - - const message = parts.join(' ') - - super(message) - this.rawMessage = body.message - this.reason = body.reason - this.assemblyId = body.assembly_id - this.assemblySslUrl = body.assembly_ssl_url - this.code = body.error - this.cause = cause - } -} diff --git a/packages/transloadit/src/InconsistentResponseError.ts b/packages/transloadit/src/InconsistentResponseError.ts deleted file mode 100644 index 455fe499..00000000 --- a/packages/transloadit/src/InconsistentResponseError.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default class InconsistentResponseError extends Error { - override name = 'InconsistentResponseError' -} diff --git a/packages/transloadit/src/PaginationStream.ts b/packages/transloadit/src/PaginationStream.ts deleted file mode 100644 index c0d463cc..00000000 --- a/packages/transloadit/src/PaginationStream.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Readable } from 'node:stream' -import type { PaginationList, PaginationListWithCount } from './apiTypes.ts' - -type FetchPage = ( - pageno: number, -) => - | PaginationList - | PromiseLike> - | PaginationListWithCount - | PromiseLike> - -export default class PaginationStream extends Readable { - private _fetchPage: FetchPage - - private _nitems?: number - - private _pageno = 0 - - private _items: T[] = [] - - private _itemsRead = 0 - - constructor(fetchPage: FetchPage) { - super({ objectMode: true }) - this._fetchPage = fetchPage - } - - override async _read() { - if (this._items.length > 0) { - this._itemsRead++ - process.nextTick(() => this.push(this._items.pop())) - return - } - - if (this._nitems != null && this._itemsRead >= this._nitems) { - process.nextTick(() => this.push(null)) - return - } - - try { - const { items, ...rest } = await this._fetchPage(++this._pageno) - if ('count' in rest) { - this._nitems = rest.count - } - - this._items = Array.from(items) - this._items.reverse() - - this._read() - } catch (err) { - this.emit('error', err) - } - } -} diff --git a/packages/transloadit/src/PollingTimeoutError.ts b/packages/transloadit/src/PollingTimeoutError.ts deleted file mode 100644 index e26cd9c9..00000000 --- a/packages/transloadit/src/PollingTimeoutError.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default class PollingTimeoutError extends Error { - override name = 'PollingTimeoutError' - - code = 'POLLING_TIMED_OUT' -} diff --git a/packages/transloadit/src/Transloadit.ts b/packages/transloadit/src/Transloadit.ts deleted file mode 100644 index a093675e..00000000 --- a/packages/transloadit/src/Transloadit.ts +++ /dev/null @@ -1,980 +0,0 @@ -import * as assert from 'node:assert' -import { createHmac, randomUUID } from 'node:crypto' -import { constants, createReadStream } from 'node:fs' -import { access } from 'node:fs/promises' -import type { Readable } from 'node:stream' -import { setTimeout as delay } from 'node:timers/promises' -import debug from 'debug' -import FormData from 'form-data' -import type { Delays, Headers, OptionsOfJSONResponseBody, RetryOptions } from 'got' -import got, { HTTPError, RequestError } from 'got' -import intoStream, { type Input as IntoStreamInput } from 'into-stream' -import { isReadableStream, isStream } from 'is-stream' -import pMap from 'p-map' -import packageJson from '../package.json' with { type: 'json' } -import type { TransloaditErrorResponseBody } from './ApiError.ts' -import { ApiError } from './ApiError.ts' -import type { - AssemblyIndex, - AssemblyIndexItem, - AssemblyStatus, -} from './alphalib/types/assemblyStatus.ts' -import { assemblyIndexSchema, assemblyStatusSchema } from './alphalib/types/assemblyStatus.ts' -import { zodParseWithContext } from './alphalib/zodParseWithContext.ts' -import type { - BaseResponse, - BillResponse, - CreateAssemblyParams, - CreateTemplateCredentialParams, - CreateTemplateParams, - EditTemplateParams, - ListAssembliesParams, - ListedTemplate, - ListTemplateCredentialsParams, - ListTemplatesParams, - OptionalAuthParams, - PaginationListWithCount, - ReplayAssemblyNotificationParams, - ReplayAssemblyNotificationResponse, - ReplayAssemblyParams, - ReplayAssemblyResponse, - TemplateCredentialResponse, - TemplateCredentialsResponse, - TemplateResponse, -} from './apiTypes.ts' -import InconsistentResponseError from './InconsistentResponseError.ts' -import PaginationStream from './PaginationStream.ts' -import PollingTimeoutError from './PollingTimeoutError.ts' -import type { Stream } from './tus.ts' -import { sendTusRequest } from './tus.ts' - -// See https://github.com/sindresorhus/got/tree/v11.8.6?tab=readme-ov-file#errors -// Expose relevant errors -export { - HTTPError, - MaxRedirectsError, - ParseError, - ReadError, - RequestError, - TimeoutError, - UploadError, -} from 'got' - -export type { AssemblyStatus } from './alphalib/types/assemblyStatus.ts' -export * from './apiTypes.ts' -export { InconsistentResponseError, ApiError } - -const log = debug('transloadit') -const logWarn = debug('transloadit:warn') - -export interface UploadProgress { - uploadedBytes?: number | undefined - totalBytes?: number | undefined -} - -const { version } = packageJson - -export type AssemblyProgress = (assembly: AssemblyStatus) => void - -export interface CreateAssemblyOptions { - params?: CreateAssemblyParams - files?: { - [name: string]: string - } - uploads?: { - [name: string]: Readable | IntoStreamInput - } - waitForCompletion?: boolean - chunkSize?: number - uploadConcurrency?: number - timeout?: number - onUploadProgress?: (uploadProgress: UploadProgress) => void - onAssemblyProgress?: AssemblyProgress - assemblyId?: string - /** - * Optional AbortSignal to cancel the assembly creation and upload. - * When aborted, any in-flight HTTP requests and TUS uploads will be cancelled. - */ - signal?: AbortSignal -} - -export interface AwaitAssemblyCompletionOptions { - onAssemblyProgress?: AssemblyProgress - timeout?: number - interval?: number - startTimeMs?: number - /** - * Optional AbortSignal to cancel polling. - * When aborted, the polling loop will stop and throw an AbortError. - */ - signal?: AbortSignal - /** - * Optional callback invoked before each poll iteration. - * Return `false` to stop polling early and return the current assembly status. - * Useful for watch mode where a newer job may supersede the current one. - */ - onPoll?: () => boolean | undefined -} - -export interface SmartCDNUrlOptions { - /** - * Workspace slug - */ - workspace: string - /** - * Template slug or template ID - */ - template: string - /** - * Input value that is provided as `${fields.input}` in the template - */ - input: string - /** - * Additional parameters for the URL query string - */ - urlParams?: Record - /** - * Expiration timestamp of the signature in milliseconds since UNIX epoch. - * Defaults to 1 hour from now. - */ - expiresAt?: number -} - -export type Fields = Record - -// A special promise that lets the user immediately get the assembly ID (synchronously before the request is sent) -interface CreateAssemblyPromise extends Promise { - assemblyId: string -} - -// Not sure if this is still a problem with the API, but throw a special error type so the user can retry if needed -function checkAssemblyUrls(result: AssemblyStatus) { - if (result.assembly_url == null || result.assembly_ssl_url == null) { - throw new InconsistentResponseError('Server returned an incomplete assembly response (no URL)') - } -} - -function getHrTimeMs(): number { - return Number(process.hrtime.bigint() / 1000000n) -} - -function checkResult(result: T | { error: string }): asserts result is T { - // In case server returned a successful HTTP status code, but an `error` in the JSON object - // This happens sometimes, for example when createAssembly with an invalid file (IMPORT_FILE_ERROR) - if ( - typeof result === 'object' && - result !== null && - 'error' in result && - typeof result.error === 'string' - ) { - throw new ApiError({ body: result }) // in this case there is no `cause` because we don't have an HTTPError - } -} - -export interface Options { - authKey: string - authSecret: string - endpoint?: string - maxRetries?: number - timeout?: number - gotRetry?: Partial - validateResponses?: boolean -} - -export class Transloadit { - private _authKey: string - - private _authSecret: string - - private _endpoint: string - - private _maxRetries: number - - private _defaultTimeout: number - - private _gotRetry: Partial - - private _lastUsedAssemblyUrl = '' - - private _validateResponses = false - - constructor(opts: Options) { - if (opts?.authKey == null) { - throw new Error('Please provide an authKey') - } - - if (opts.authSecret == null) { - throw new Error('Please provide an authSecret') - } - - if (opts.endpoint?.endsWith('/')) { - throw new Error('Trailing slash in endpoint is not allowed') - } - - this._authKey = opts.authKey - this._authSecret = opts.authSecret - this._endpoint = opts.endpoint || 'https://api2.transloadit.com' - this._maxRetries = opts.maxRetries != null ? opts.maxRetries : 5 - this._defaultTimeout = opts.timeout != null ? opts.timeout : 60000 - - // Passed on to got https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md - this._gotRetry = opts.gotRetry != null ? opts.gotRetry : { limit: 0 } - - if (opts.validateResponses != null) this._validateResponses = opts.validateResponses - } - - getLastUsedAssemblyUrl(): string { - return this._lastUsedAssemblyUrl - } - - setDefaultTimeout(timeout: number): void { - this._defaultTimeout = timeout - } - - /** - * Create an Assembly - * - * @param opts assembly options - */ - createAssembly(opts: CreateAssemblyOptions = {}): CreateAssemblyPromise { - const { - params = {}, - waitForCompletion = false, - chunkSize: requestedChunkSize = Number.POSITIVE_INFINITY, - uploadConcurrency = 10, - timeout = 24 * 60 * 60 * 1000, // 1 day - onUploadProgress = () => {}, - onAssemblyProgress = () => {}, - files = {}, - uploads = {}, - assemblyId, - signal, - } = opts - - // Keep track of how long the request took - const startTimeMs = getHrTimeMs() - - // Undocumented feature to allow specifying a custom assembly id from the client - // Not recommended for general use due to security. E.g if the user doesn't provide a cryptographically - // secure ID, then anyone could access the assembly. - let effectiveAssemblyId: string - if (assemblyId != null) { - effectiveAssemblyId = assemblyId - } else { - effectiveAssemblyId = randomUUID().replace(/-/g, '') - } - const urlSuffix = `/assemblies/${effectiveAssemblyId}` - - // We want to be able to return the promise immediately with custom data - const promise = (async () => { - this._lastUsedAssemblyUrl = `${this._endpoint}${urlSuffix}` - - await pMap( - Object.entries(files), - async ([, path]) => access(path, constants.F_OK | constants.R_OK), - { concurrency: 5 }, - ) - - // Convert uploads to streams - const streamsMap = Object.fromEntries( - Object.entries(uploads).map(([label, value]) => { - const isReadable = isReadableStream(value) - if (!isReadable && isStream(value)) { - // https://github.com/transloadit/node-sdk/issues/92 - throw new Error(`Upload named "${label}" is not a Readable stream`) - } - - return [label, isReadableStream(value) ? value : intoStream(value)] - }), - ) - - // Wrap in object structure (so we can store whether it's a pathless stream or not) - const allStreamsMap = Object.fromEntries( - Object.entries(streamsMap).map(([label, stream]) => [label, { stream }]), - ) - - // Create streams from files too - for (const [label, path] of Object.entries(files)) { - const stream = createReadStream(path) - allStreamsMap[label] = { stream, path } // File streams have path - } - - const allStreams = Object.values(allStreamsMap) - - // Pause all streams - for (const { stream } of allStreams) { - stream.pause() - } - - // If any stream emits error, we want to handle this and exit with error. - // This promise races against createAssemblyAndUpload() below via Promise.race(). - // When createAssemblyAndUpload wins the race, this promise becomes "orphaned" - - // it's no longer awaited, but stream error handlers remain attached. - // The no-op catch prevents Node's unhandled rejection warning if a stream - // errors after the race is already won. - const streamErrorPromise = new Promise((_resolve, reject) => { - for (const { stream } of allStreams) { - stream.on('error', reject) - } - }) - streamErrorPromise.catch(() => {}) - - const createAssemblyAndUpload = async () => { - const result: AssemblyStatus = await this._remoteJson({ - urlSuffix, - method: 'post', - timeout: { request: timeout }, - params, - fields: { - tus_num_expected_upload_files: allStreams.length, - }, - signal, - }) - checkResult(result) - - if (Object.keys(allStreamsMap).length > 0) { - await sendTusRequest({ - streamsMap: allStreamsMap, - assembly: result, - onProgress: onUploadProgress, - requestedChunkSize, - uploadConcurrency, - signal, - }) - } - - if (!waitForCompletion) return result - - if (result.assembly_id == null) { - throw new InconsistentResponseError( - 'Server returned an assembly response without an assembly_id after creation', - ) - } - const awaitResult = await this.awaitAssemblyCompletion(result.assembly_id, { - timeout, - onAssemblyProgress, - startTimeMs, - signal, - }) - checkResult(awaitResult) - return awaitResult - } - - return Promise.race([createAssemblyAndUpload(), streamErrorPromise]) - })() - - // This allows the user to use or log the assemblyId even before it has been created for easier debugging - return Object.assign(promise, { assemblyId: effectiveAssemblyId }) - } - - async awaitAssemblyCompletion( - assemblyId: string, - { - onAssemblyProgress = () => {}, - timeout, - startTimeMs = getHrTimeMs(), - interval = 1000, - signal, - onPoll, - }: AwaitAssemblyCompletionOptions = {}, - ): Promise { - assert.ok(assemblyId) - - let lastResult: AssemblyStatus | undefined - - while (true) { - // Check if caller wants to stop polling early - if (onPoll?.() === false && lastResult) { - return lastResult - } - - // Check if aborted before making the request - if (signal?.aborted) { - throw signal.reason ?? new DOMException('Aborted', 'AbortError') - } - - const result = await this.getAssembly(assemblyId, { signal }) - lastResult = result - - // If 'ok' is not in result, it implies a terminal state (e.g., error, completed, canceled). - // If 'ok' is present, then we check if it's one of the non-terminal polling states. - if ( - !('ok' in result) || - (result.ok !== 'ASSEMBLY_UPLOADING' && - result.ok !== 'ASSEMBLY_EXECUTING' && - // ASSEMBLY_REPLAYING is not a valid 'ok' status for polling, it means it's done replaying. - // The API does not seem to have an ASSEMBLY_REPLAYING status in the typical polling loop. - // It's usually a final status from the replay endpoint. - // For polling, we only care about UPLOADING and EXECUTING. - // If a replay operation puts it into a pollable state, that state would be EXECUTING. - result.ok !== 'ASSEMBLY_REPLAYING') // This line might need review based on actual API behavior for replayed assembly polling - ) { - return result // Done! - } - - try { - onAssemblyProgress(result) - } catch (err) { - log('Caught onAssemblyProgress error', err) - } - - const nowMs = getHrTimeMs() - if (timeout != null && nowMs - startTimeMs >= timeout) { - throw new PollingTimeoutError('Polling timed out') - } - - // Make the sleep abortable, ensuring listener cleanup to prevent memory leaks - await new Promise((resolve, reject) => { - const timeoutId = setTimeout(() => { - signal?.removeEventListener('abort', onAbort) - resolve() - }, interval) - - function onAbort() { - clearTimeout(timeoutId) - reject(signal?.reason ?? new DOMException('Aborted', 'AbortError')) - } - - signal?.addEventListener('abort', onAbort, { once: true }) - }) - } - } - - maybeThrowInconsistentResponseError(message: string) { - const err = new InconsistentResponseError(message) - - // @TODO, once our schemas have matured, we should remove the option and always throw the error here. - // But as it stands, schemas are new, and we can't easily update all customer's node-sdks, - // so there will be a long tail of throws if we enable this now. - if (this._validateResponses) { - throw err - } - - console.error( - `---\nPlease report this error to Transloadit (support@transloadit.com). We are working on better schemas for our API and this looks like something we do not cover yet: \n\n${err}\nThank you in advance!\n---\n`, - ) - } - - /** - * Cancel the assembly - * - * @param assemblyId assembly ID - * @returns after the assembly is deleted - */ - async cancelAssembly(assemblyId: string): Promise { - const { assembly_ssl_url: url } = await this.getAssembly(assemblyId) - const rawResult = await this._remoteJson, OptionalAuthParams>({ - url, - method: 'delete', - }) - - const parsedResult = zodParseWithContext(assemblyStatusSchema, rawResult) - - if (!parsedResult.success) { - this.maybeThrowInconsistentResponseError( - `The API responded with data that does not match the expected schema while cancelling Assembly: ${assemblyId}.\n${parsedResult.humanReadable}`, - ) - } - - checkAssemblyUrls(rawResult as AssemblyStatus) - return rawResult as AssemblyStatus - } - - /** - * Replay an Assembly - * - * @param assemblyId of the assembly to replay - * @param optional params - * @returns after the replay is started - */ - async replayAssembly( - assemblyId: string, - params: ReplayAssemblyParams = {}, - ): Promise { - const result: ReplayAssemblyResponse = await this._remoteJson({ - urlSuffix: `/assemblies/${assemblyId}/replay`, - method: 'post', - ...(Object.keys(params).length > 0 && { params }), - }) - checkResult(result) - return result - } - - /** - * Replay an Assembly notification - * - * @param assemblyId of the assembly whose notification to replay - * @param optional params - * @returns after the replay is started - */ - async replayAssemblyNotification( - assemblyId: string, - params: ReplayAssemblyNotificationParams = {}, - ): Promise { - return await this._remoteJson({ - urlSuffix: `/assembly_notifications/${assemblyId}/replay`, - method: 'post', - ...(Object.keys(params).length > 0 && { params }), - }) - } - - /** - * List all assemblies - * - * @param params optional request options - * @returns list of Assemblies - */ - async listAssemblies( - params?: ListAssembliesParams, - ): Promise> { - const rawResponse = await this._remoteJson< - PaginationListWithCount>, - ListAssembliesParams - >({ - urlSuffix: '/assemblies', - method: 'get', - params: params || {}, - }) - - if ( - rawResponse == null || - typeof rawResponse !== 'object' || - !Array.isArray(rawResponse.items) - ) { - throw new InconsistentResponseError( - 'API response for listAssemblies is malformed or missing items array', - ) - } - - const parsedResult = zodParseWithContext(assemblyIndexSchema, rawResponse.items) - - if (!parsedResult.success) { - this.maybeThrowInconsistentResponseError( - `API response for listAssemblies contained items that do not match the expected schema.\n${parsedResult.humanReadable}`, - ) - } - - return { - items: rawResponse.items as AssemblyIndex, - count: rawResponse.count, - } - } - - streamAssemblies(params: ListAssembliesParams): Readable { - return new PaginationStream(async (page) => this.listAssemblies({ ...params, page })) - } - - /** - * Get an Assembly - * - * @param assemblyId the Assembly Id - * @param options optional request options - * @returns the retrieved Assembly - */ - async getAssembly( - assemblyId: string, - options?: { signal?: AbortSignal }, - ): Promise { - const rawResult = await this._remoteJson, OptionalAuthParams>({ - urlSuffix: `/assemblies/${assemblyId}`, - signal: options?.signal, - }) - - const parsedResult = zodParseWithContext(assemblyStatusSchema, rawResult) - - if (!parsedResult.success) { - this.maybeThrowInconsistentResponseError( - `The API responded with data that does not match the expected schema while getting Assembly: ${assemblyId}.\n${parsedResult.humanReadable}`, - ) - } - - checkAssemblyUrls(rawResult as AssemblyStatus) - return rawResult as AssemblyStatus - } - - /** - * Create a Credential - * - * @param params optional request options - * @returns when the Credential is created - */ - async createTemplateCredential( - params: CreateTemplateCredentialParams, - ): Promise { - return await this._remoteJson({ - urlSuffix: '/template_credentials', - method: 'post', - params: params || {}, - }) - } - - /** - * Edit a Credential - * - * @param credentialId the Credential ID - * @param params optional request options - * @returns when the Credential is edited - */ - async editTemplateCredential( - credentialId: string, - params: CreateTemplateCredentialParams, - ): Promise { - return await this._remoteJson({ - urlSuffix: `/template_credentials/${credentialId}`, - method: 'put', - params: params || {}, - }) - } - - /** - * Delete a Credential - * - * @param credentialId the Credential ID - * @returns when the Credential is deleted - */ - async deleteTemplateCredential(credentialId: string): Promise { - return await this._remoteJson({ - urlSuffix: `/template_credentials/${credentialId}`, - method: 'delete', - }) - } - - /** - * Get a Credential - * - * @param credentialId the Credential ID - * @returns when the Credential is retrieved - */ - async getTemplateCredential(credentialId: string): Promise { - return await this._remoteJson({ - urlSuffix: `/template_credentials/${credentialId}`, - method: 'get', - }) - } - - /** - * List all TemplateCredentials - * - * @param params optional request options - * @returns the list of templates - */ - async listTemplateCredentials( - params?: ListTemplateCredentialsParams, - ): Promise { - return await this._remoteJson({ - urlSuffix: '/template_credentials', - method: 'get', - params: params || {}, - }) - } - - streamTemplateCredentials(params: ListTemplateCredentialsParams) { - return new PaginationStream(async (page) => ({ - items: (await this.listTemplateCredentials({ ...params, page })).credentials, - })) - } - - /** - * Create an Assembly Template - * - * @param params optional request options - * @returns when the template is created - */ - async createTemplate(params: CreateTemplateParams): Promise { - return await this._remoteJson({ - urlSuffix: '/templates', - method: 'post', - params: params || {}, - }) - } - - /** - * Edit an Assembly Template - * - * @param templateId the template ID - * @param params optional request options - * @returns when the template is edited - */ - async editTemplate(templateId: string, params: EditTemplateParams): Promise { - return await this._remoteJson({ - urlSuffix: `/templates/${templateId}`, - method: 'put', - params: params || {}, - }) - } - - /** - * Delete an Assembly Template - * - * @param templateId the template ID - * @returns when the template is deleted - */ - async deleteTemplate(templateId: string): Promise { - return await this._remoteJson({ - urlSuffix: `/templates/${templateId}`, - method: 'delete', - }) - } - - /** - * Get an Assembly Template - * - * @param templateId the template ID - * @returns when the template is retrieved - */ - async getTemplate(templateId: string): Promise { - return await this._remoteJson({ - urlSuffix: `/templates/${templateId}`, - method: 'get', - }) - } - - /** - * List all Assembly Templates - * - * @param params optional request options - * @returns the list of templates - */ - async listTemplates( - params?: ListTemplatesParams, - ): Promise> { - return await this._remoteJson({ - urlSuffix: '/templates', - method: 'get', - params: params || {}, - }) - } - - streamTemplates(params?: ListTemplatesParams): PaginationStream { - return new PaginationStream(async (page) => this.listTemplates({ ...params, page })) - } - - /** - * Get account Billing details for a specific month - * - * @param month the date for the required billing in the format yyyy-mm - * @returns with billing data - * @see https://transloadit.com/docs/api/bill-date-get/ - */ - async getBill(month: string): Promise { - assert.ok(month, 'month is required') - return await this._remoteJson({ - urlSuffix: `/bill/${month}`, - method: 'get', - }) - } - - calcSignature( - params: OptionalAuthParams, - algorithm?: string, - ): { signature: string; params: string } { - const jsonParams = this._prepareParams(params) - const signature = this._calcSignature(jsonParams, algorithm) - - return { signature, params: jsonParams } - } - - /** - * Construct a signed Smart CDN URL. See https://transloadit.com/docs/topics/signature-authentication/#smart-cdn. - */ - getSignedSmartCDNUrl(opts: SmartCDNUrlOptions): string { - if (opts.workspace == null || opts.workspace === '') - throw new TypeError('workspace is required') - if (opts.template == null || opts.template === '') throw new TypeError('template is required') - if (opts.input == null) throw new TypeError('input is required') // `input` can be an empty string. - - const workspaceSlug = encodeURIComponent(opts.workspace) - const templateSlug = encodeURIComponent(opts.template) - const inputField = encodeURIComponent(opts.input) - const expiresAt = opts.expiresAt || Date.now() + 60 * 60 * 1000 // 1 hour - - const queryParams = new URLSearchParams() - for (const [key, value] of Object.entries(opts.urlParams || {})) { - if (Array.isArray(value)) { - for (const val of value) { - queryParams.append(key, `${val}`) - } - } else { - queryParams.append(key, `${value}`) - } - } - - queryParams.set('auth_key', this._authKey) - queryParams.set('exp', `${expiresAt}`) - // The signature changes depending on the order of the query parameters. We therefore sort them on the client- - // and server-side to ensure that we do not get mismatching signatures if a proxy changes the order of query - // parameters or implementations handle query parameters ordering differently. - queryParams.sort() - - const stringToSign = `${workspaceSlug}/${templateSlug}/${inputField}?${queryParams}` - const algorithm = 'sha256' - const signature = createHmac(algorithm, this._authSecret).update(stringToSign).digest('hex') - - queryParams.set('sig', `sha256:${signature}`) - const signedUrl = `https://${workspaceSlug}.tlcdn.com/${templateSlug}/${inputField}?${queryParams}` - return signedUrl - } - - private _calcSignature(toSign: string, algorithm = 'sha384'): string { - return `${algorithm}:${createHmac(algorithm, this._authSecret) - .update(Buffer.from(toSign, 'utf-8')) - .digest('hex')}` - } - - // Sets the multipart/form-data for POST, PUT and DELETE requests, including - // the streams, the signed params, and any additional fields. - private _appendForm(form: FormData, params: OptionalAuthParams, fields?: Fields): void { - const sigData = this.calcSignature(params) - const jsonParams = sigData.params - const { signature } = sigData - - form.append('params', jsonParams) - - if (fields != null) { - for (const [key, val] of Object.entries(fields)) { - form.append(key, val) - } - } - - form.append('signature', signature) - } - - // Implements HTTP GET query params, handling the case where the url already - // has params. - private _appendParamsToUrl(url: string, params: OptionalAuthParams): string { - const { signature, params: jsonParams } = this.calcSignature(params) - - const prefix = url.indexOf('?') === -1 ? '?' : '&' - - return `${url}${prefix}signature=${signature}¶ms=${encodeURIComponent(jsonParams)}` - } - - // Responsible for including auth parameters in all requests - private _prepareParams(paramsIn?: OptionalAuthParams): string { - let params = paramsIn - if (params == null) { - params = {} - } - if (params.auth == null) { - params.auth = {} - } - if (params.auth.key == null) { - params.auth.key = this._authKey - } - if (params.auth.expires == null) { - params.auth.expires = this._getExpiresDate() - } - - return JSON.stringify(params) - } - - // We want to mock this method - private _getExpiresDate(): string { - const expiresDate = new Date() - expiresDate.setDate(expiresDate.getDate() + 1) - return expiresDate.toISOString() - } - - // Responsible for making API calls. Automatically sends streams with any POST, - // PUT or DELETE requests. Automatically adds signature parameters to all - // requests. Also automatically parses the JSON response. - private async _remoteJson(opts: { - urlSuffix?: string - url?: string - timeout?: Delays - method?: 'delete' | 'get' | 'post' | 'put' - params?: TParams - fields?: Fields - headers?: Headers - signal?: AbortSignal - }): Promise { - const { - urlSuffix, - url: urlInput, - timeout = { request: this._defaultTimeout }, - method = 'get', - params = {}, - fields, - headers, - signal, - } = opts - - // Allow providing either a `urlSuffix` or a full `url` - if (!urlSuffix && !urlInput) throw new Error('No URL provided') - let url = urlInput || `${this._endpoint}${urlSuffix}` - - if (method === 'get') { - url = this._appendParamsToUrl(url, params) - } - - log('Sending request', method, url) - - // todo use got.retry instead because we are no longer using FormData (which is a stream and can only be used once) - // https://github.com/sindresorhus/got/issues/1282 - for (let retryCount = 0; ; retryCount++) { - let form: FormData | undefined - - if (method === 'post' || method === 'put' || method === 'delete') { - form = new FormData() - this._appendForm(form, params, fields) - } - - const requestOpts: OptionsOfJSONResponseBody = { - retry: this._gotRetry, - body: form, - timeout, - headers: { - 'Transloadit-Client': `node-sdk:${version}`, - 'User-Agent': undefined, // Remove got's user-agent - ...headers, - }, - responseType: 'json', - signal, - } - - try { - const request = got[method](url, requestOpts) - const { body } = await request - // console.log(body) - return body - } catch (err) { - if (!(err instanceof RequestError)) throw err - - if (err instanceof HTTPError) { - const { statusCode, body } = err.response - logWarn('HTTP error', statusCode, body) - - // check whether we should retry - // https://transloadit.com/blog/2012/04/introducing-rate-limiting/ - if ( - typeof body === 'object' && - body != null && - 'error' in body && - 'info' in body && - typeof body.info === 'object' && - body.info != null && - 'retryIn' in body.info && - typeof body.info.retryIn === 'number' && - Boolean(body.info.retryIn) && - retryCount < this._maxRetries && // 413 taken from https://transloadit.com/blog/2012/04/introducing-rate-limiting/ - // todo can 413 be removed? - ((statusCode === 413 && body.error === 'RATE_LIMIT_REACHED') || statusCode === 429) - ) { - const { retryIn: retryInSec } = body.info - logWarn(`Rate limit reached, retrying request in approximately ${retryInSec} seconds.`) - const retryInMs = 1000 * (retryInSec * (1 + 0.1 * Math.random())) - await delay(retryInMs) - // Retry - } else { - throw new ApiError({ - cause: err, - body: err.response?.body as TransloaditErrorResponseBody | undefined, - }) // todo don't assert type - } - } else { - throw err - } - } - } - } -} diff --git a/packages/transloadit/src/alphalib/lib/nativeGlobby.ts b/packages/transloadit/src/alphalib/lib/nativeGlobby.ts deleted file mode 100644 index 61d0dcd3..00000000 --- a/packages/transloadit/src/alphalib/lib/nativeGlobby.ts +++ /dev/null @@ -1,244 +0,0 @@ -import type { GlobOptionsWithoutFileTypes } from 'node:fs' -import * as fs from 'node:fs' -import { glob as fsGlob, stat as statAsync } from 'node:fs/promises' -import path from 'node:path' - -type PatternInput = string | readonly string[] -const { globSync: fsGlobSync } = fs - -export interface NativeGlobbyOptions { - cwd?: string - absolute?: boolean - onlyFiles?: boolean - ignore?: readonly string[] -} - -interface NormalizedOptions { - cwd?: string - absolute: boolean - onlyFiles: boolean - ignore?: readonly string[] -} - -interface Candidate { - rawPath: string - absolutePath: string -} - -const normalizeSlashes = (value: string) => value.replace(/\\/g, '/') - -const toAbsolutePath = (rawPath: string, cwd?: string): string => { - if (path.isAbsolute(rawPath)) { - return rawPath - } - if (cwd) { - return path.join(cwd, rawPath) - } - return path.resolve(rawPath) -} - -const normalizeOptions = (options: NativeGlobbyOptions = {}): NormalizedOptions => ({ - cwd: options.cwd ? path.resolve(options.cwd) : undefined, - absolute: options.absolute ?? false, - onlyFiles: options.onlyFiles ?? true, - ignore: options.ignore && options.ignore.length > 0 ? options.ignore : undefined, -}) - -const hasGlobMagic = (pattern: string) => /[*?[\]{}()!]/.test(pattern) - -const expandPatternAsync = async (pattern: string, options: NormalizedOptions) => { - if (hasGlobMagic(pattern)) { - return [pattern] - } - - try { - const absolute = toAbsolutePath(pattern, options.cwd) - const stats = await statAsync(absolute) - if (stats.isDirectory()) { - const expanded = normalizeSlashes(path.join(pattern, '**/*')) - return [expanded] - } - } catch { - // ignore missing paths; fall back to original pattern - } - - return [pattern] -} - -const expandPatternSync = (pattern: string, options: NormalizedOptions) => { - if (hasGlobMagic(pattern)) { - return [pattern] - } - - try { - const absolute = toAbsolutePath(pattern, options.cwd) - const stats = fs.statSync(absolute) - if (stats.isDirectory()) { - const expanded = normalizeSlashes(path.join(pattern, '**/*')) - return [expanded] - } - } catch { - // ignore missing paths; fall back to original pattern - } - - return [pattern] -} - -const splitPatterns = (patterns: PatternInput) => { - const list = Array.isArray(patterns) ? patterns : [patterns] - const positive: string[] = [] - const negative: string[] = [] - - for (const pattern of list) { - if (pattern.startsWith('!')) { - const negated = pattern.slice(1) - if (negated) { - negative.push(negated) - } - } else { - positive.push(pattern) - } - } - - return { positive, negative } -} - -const toGlobOptions = (options: NormalizedOptions): GlobOptionsWithoutFileTypes => { - const globOptions: GlobOptionsWithoutFileTypes = { withFileTypes: false } - if (options.cwd) { - globOptions.cwd = options.cwd - } - if (options.ignore) { - // Node's glob implementation uses `exclude` for ignore patterns - globOptions.exclude = options.ignore - } - return globOptions -} - -const filterFilesAsync = async (candidates: Candidate[], requireFiles: boolean) => { - if (!requireFiles) { - return candidates - } - - const filtered = await Promise.all( - candidates.map(async (candidate) => { - try { - const stats = await statAsync(candidate.absolutePath) - return stats.isFile() ? candidate : null - } catch { - return null - } - }), - ) - - return filtered.filter(Boolean) as Candidate[] -} - -const filterFilesSync = (candidates: Candidate[], requireFiles: boolean) => { - if (!requireFiles) { - return candidates - } - - const filtered: Candidate[] = [] - for (const candidate of candidates) { - try { - const stats = fs.statSync(candidate.absolutePath) - if (stats.isFile()) { - filtered.push(candidate) - } - } catch { - // Ignore files that cannot be stat'ed - } - } - return filtered -} - -const formatResult = (candidate: Candidate, options: NormalizedOptions) => { - const output = options.absolute ? candidate.absolutePath : candidate.rawPath - return normalizeSlashes(output) -} - -const collectMatchesAsync = async (pattern: string, options: NormalizedOptions) => { - const matches: Candidate[] = [] - for await (const match of fsGlob(pattern, toGlobOptions(options))) { - matches.push({ - rawPath: match as string, - absolutePath: toAbsolutePath(match as string, options.cwd), - }) - } - const filtered = await filterFilesAsync(matches, options.onlyFiles) - return filtered.map((candidate) => formatResult(candidate, options)) -} - -const collectMatchesSync = (pattern: string, options: NormalizedOptions) => { - const matches = (fsGlobSync(pattern, toGlobOptions(options)) as string[]).map((match) => ({ - rawPath: match, - absolutePath: toAbsolutePath(match, options.cwd), - })) - const filtered = filterFilesSync(matches, options.onlyFiles) - return filtered.map((candidate) => formatResult(candidate, options)) -} - -type GlobbyLike = { - (patterns: PatternInput, options?: NativeGlobbyOptions): Promise - sync(patterns: PatternInput, options?: NativeGlobbyOptions): string[] -} - -export const nativeGlobby: GlobbyLike = Object.assign( - async (patterns: PatternInput, options?: NativeGlobbyOptions) => { - const normalized = normalizeOptions(options) - const { positive, negative } = splitPatterns(patterns) - const expandedPositives = ( - await Promise.all(positive.map((pattern) => expandPatternAsync(pattern, normalized))) - ).flat() - const expandedNegatives = ( - await Promise.all(negative.map((pattern) => expandPatternAsync(pattern, normalized))) - ).flat() - const results = new Set() - - for (const pattern of expandedPositives) { - const matches = await collectMatchesAsync(pattern, normalized) - for (const match of matches) { - results.add(match) - } - } - - for (const pattern of expandedNegatives) { - const matches = await collectMatchesAsync(pattern, normalized) - for (const match of matches) { - results.delete(match) - } - } - - return Array.from(results) - }, - { - sync(patterns: PatternInput, options?: NativeGlobbyOptions) { - const normalized = normalizeOptions(options) - const { positive, negative } = splitPatterns(patterns) - const expandedPositives = positive.flatMap((pattern) => - expandPatternSync(pattern, normalized), - ) - const expandedNegatives = negative.flatMap((pattern) => - expandPatternSync(pattern, normalized), - ) - const results = new Set() - - for (const pattern of expandedPositives) { - const matches = collectMatchesSync(pattern, normalized) - for (const match of matches) { - results.add(match) - } - } - - for (const pattern of expandedNegatives) { - const matches = collectMatchesSync(pattern, normalized) - for (const match of matches) { - results.delete(match) - } - } - - return Array.from(results) - }, - }, -) diff --git a/packages/transloadit/src/alphalib/mcache.ts b/packages/transloadit/src/alphalib/mcache.ts deleted file mode 100644 index 852a7df9..00000000 --- a/packages/transloadit/src/alphalib/mcache.ts +++ /dev/null @@ -1,184 +0,0 @@ -import type { SevLogger } from '@transloadit/sev-logger' -import type { Schema } from 'zod' - -export interface McacheOpts { - ttlMs?: number - zodSchema?: Schema - logger?: SevLogger - /** - * Maximum number of entries in the cache. When exceeded, oldest entries are removed. - * Defaults to 10,000 entries. - */ - maxSize?: number - /** - * Custom key generator function. If not provided, uses JSON.stringify. - */ - keyFn?: (...args: unknown[]) => string -} - -interface CacheEntry { - value: T - timestamp: number -} - -/** - * Memory cache abstraction to help cache function results in-process. - * - * Example: - * - * const cache = new Mcache({ ttlMs: 1000 * 60 * 10 }) - * - * async function fetchInstances(region: string): Promise { - * return cache.get(region, async () => { - * // Do work, e.g. fetch instances from AWS - * return await this._fetchInstances(region) - * }) - * } - */ -export class Mcache { - #cache: Map> - #opts: Required> & - Pick - - constructor(opts: McacheOpts = {}) { - this.#cache = new Map() - this.#opts = { - ttlMs: opts.ttlMs ?? Number.POSITIVE_INFINITY, - maxSize: opts.maxSize ?? 10_000, - zodSchema: opts.zodSchema, - logger: opts.logger, - keyFn: opts.keyFn, - } - } - - /** - * Get a value from cache, or compute it using the provided function. - * The cache key is generated from the args using JSON.stringify by default, - * or using the custom keyFn if provided. - */ - async get(producer: () => Promise | T, ...args: unknown[]): Promise { - const key = this.#opts.keyFn ? this.#opts.keyFn(...args) : JSON.stringify(args) - const cached = this.#cache.get(key) - - if (cached) { - const age = Date.now() - cached.timestamp - if (age <= this.#opts.ttlMs || this.#opts.ttlMs === Number.POSITIVE_INFINITY) { - this.#opts.logger?.debug(`Cache hit for key ${key} (age: ${age}ms)`) - return cached.value - } - - this.#opts.logger?.debug( - `Cache expired for key ${key} (age: ${age}ms > ${this.#opts.ttlMs}ms)`, - ) - this.#cache.delete(key) - } - - this.#opts.logger?.debug(`Cache miss for key ${key}, computing value`) - const value = await producer() - - // Validate if schema provided - if (this.#opts.zodSchema) { - this.#opts.zodSchema.parse(value) - } - - this.#set(key, value) - return value - } - - /** - * Set a value in the cache directly. - */ - set(value: T, ...args: unknown[]): void { - const key = this.#opts.keyFn ? this.#opts.keyFn(...args) : JSON.stringify(args) - - // Validate if schema provided - if (this.#opts.zodSchema) { - this.#opts.zodSchema.parse(value) - } - - this.#set(key, value) - } - - /** - * Check if a key exists in cache and is not expired. - */ - has(...args: unknown[]): boolean { - const key = this.#opts.keyFn ? this.#opts.keyFn(...args) : JSON.stringify(args) - const cached = this.#cache.get(key) - - if (!cached) { - return false - } - - const age = Date.now() - cached.timestamp - if (age > this.#opts.ttlMs && this.#opts.ttlMs !== Number.POSITIVE_INFINITY) { - this.#cache.delete(key) - return false - } - - return true - } - - /** - * Clear all entries from the cache. - */ - clear(): void { - this.#cache.clear() - } - - /** - * Delete a specific entry from the cache. - */ - delete(...args: unknown[]): boolean { - const key = this.#opts.keyFn ? this.#opts.keyFn(...args) : JSON.stringify(args) - return this.#cache.delete(key) - } - - /** - * Get the current size of the cache. - */ - get size(): number { - return this.#cache.size - } - - /** - * Clean up expired entries and enforce size limit. - */ - cleanup(): void { - const now = Date.now() - - // Remove expired entries - if (this.#opts.ttlMs !== Number.POSITIVE_INFINITY) { - for (const [key, entry] of this.#cache.entries()) { - if (now - entry.timestamp > this.#opts.ttlMs) { - this.#cache.delete(key) - } - } - } - - // Enforce size limit by removing oldest entries - if (this.#cache.size > this.#opts.maxSize) { - const entries = Array.from(this.#cache.entries()) - entries.sort((a, b) => a[1].timestamp - b[1].timestamp) - const toRemove = entries.slice(0, this.#cache.size - this.#opts.maxSize) - for (const [key] of toRemove) { - this.#cache.delete(key) - } - this.#opts.logger?.debug( - `Cache size limit reached, removed ${toRemove.length} oldest entries`, - ) - } - } - - #set(key: string, value: T): void { - this.#cache.set(key, { - value, - timestamp: Date.now(), - }) - - // Trigger cleanup if we're over size limit - if (this.#cache.size > this.#opts.maxSize) { - this.cleanup() - } - } -} diff --git a/packages/transloadit/src/alphalib/tryCatch.ts b/packages/transloadit/src/alphalib/tryCatch.ts deleted file mode 100644 index c3dc8dd5..00000000 --- a/packages/transloadit/src/alphalib/tryCatch.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Represents a successful result where error is null and data is present - */ -export type Success = [null, T] - -/** - * Represents a failure result where error contains an error instance and data is null - */ -export type Failure = [E, null] - -/** - * Represents the result of an operation that can either succeed with T or fail with E - */ -export type Result = Success | Failure - -/** - * Wraps a promise in a try-catch block and returns a tuple of [error, data] - * where exactly one value is non-null - * - * @param promise The promise to execute safely - * @returns A tuple of [error, data] where one is null - */ -export async function tryCatch(promise: Promise): Promise> { - try { - const data = await promise - return [null, data] as Success - } catch (error) { - return [error as E, null] as Failure - } -} diff --git a/packages/transloadit/src/alphalib/types/assembliesGet.ts b/packages/transloadit/src/alphalib/types/assembliesGet.ts deleted file mode 100644 index 17633b1c..00000000 --- a/packages/transloadit/src/alphalib/types/assembliesGet.ts +++ /dev/null @@ -1,42 +0,0 @@ -import z from 'zod' - -import { assemblyAuthInstructionsSchema } from './template.ts' - -export const assembliesGetSchema = z - .object({ - auth: assemblyAuthInstructionsSchema, - page: z - .number() - .int() - .default(1) - .describe('Specifies the current page, within the current pagination'), - pagesize: z - .number() - .int() - .min(1) - .max(5000) - .default(50) - .describe( - 'Specifies how many Assemblies to be received per API request, which is useful for pagination.', - ), - type: z - .enum(['all', 'uploading', 'executing', 'canceled', 'completed', 'failed', 'request_aborted']) - .describe('Specifies the types of Assemblies to be retrieved.'), - fromdate: z - .string() - .describe( - 'Specifies the minimum Assembly UTC creation date/time. Only Assemblies after this time will be retrieved. Use the format `Y-m-d H:i:s`.', - ), - todate: z - .string() - .default('NOW()') - .describe( - 'Specifies the maximum Assembly UTC creation date/time. Only Assemblies before this time will be retrieved. Use the format `Y-m-d H:i:s`.', - ), - keywords: z - .array(z.string()) - .describe( - 'Specifies keywords to be matched in the Assembly Status. The Assembly fields checked include the `id`, `redirect_url`, `fields`, and `notify_url`, as well as error messages and files used.', - ), - }) - .strict() diff --git a/packages/transloadit/src/alphalib/types/assemblyReplay.ts b/packages/transloadit/src/alphalib/types/assemblyReplay.ts deleted file mode 100644 index 0a2d5e44..00000000 --- a/packages/transloadit/src/alphalib/types/assemblyReplay.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { z } from 'zod' - -import { - assemblyAuthInstructionsSchema, - fieldsSchema, - notifyUrlSchema, - optionalStepsSchema, - templateIdSchema, -} from './template.ts' - -export const assemblyReplaySchema = z - .object({ - auth: assemblyAuthInstructionsSchema, - steps: optionalStepsSchema as typeof optionalStepsSchema, - template_id: templateIdSchema, - notify_url: notifyUrlSchema, - fields: fieldsSchema, - reparse_template: z - .union([z.literal(0), z.literal(1)]) - .describe( - 'Specify `1` to reparse the Template used in your Assembly (useful if the Template changed in the meantime). Alternatively, `0` replays the identical Steps used in the Assembly.', - ), - }) - .strict() diff --git a/packages/transloadit/src/alphalib/types/assemblyReplayNotification.ts b/packages/transloadit/src/alphalib/types/assemblyReplayNotification.ts deleted file mode 100644 index ce8ee098..00000000 --- a/packages/transloadit/src/alphalib/types/assemblyReplayNotification.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { z } from 'zod' - -import { assemblyAuthInstructionsSchema, optionalStepsSchema } from './template.ts' - -export const assemblyReplayNotificationSchema = z - .object({ - auth: assemblyAuthInstructionsSchema, - steps: optionalStepsSchema as typeof optionalStepsSchema, - wait: z - .boolean() - .default(true) - .describe( - 'If it is provided with the value `false`, then the API request will return immediately even though the Notification is still in progress. This can be useful if your server takes some time to respond, but you do not want the replay API request to hang.', - ), - }) - .strict() diff --git a/packages/transloadit/src/alphalib/types/assemblyStatus.ts b/packages/transloadit/src/alphalib/types/assemblyStatus.ts deleted file mode 100644 index 6dea5e00..00000000 --- a/packages/transloadit/src/alphalib/types/assemblyStatus.ts +++ /dev/null @@ -1,794 +0,0 @@ -import { z } from 'zod' - -export const assemblyBusyCodeSchema = z.enum(['ASSEMBLY_UPLOADING']) - -export const assemblyStatusOkCodeSchema = z.enum([ - 'ASSEMBLY_CANCELED', - 'ASSEMBLY_COMPLETED', - 'ASSEMBLY_EXECUTING', - 'ASSEMBLY_EXPIRED', - 'ASSEMBLY_REPLAYING', - 'ASSEMBLY_UPLOADING', - 'REQUEST_ABORTED', - // 'ASSEMBLY_EXECUTION_PROGRESS_FETCHED', - // 'ASSEMBLY_FILE_ACCEPTED', - // 'ASSEMBLY_FILE_RESERVED', -]) - -export const assemblyStatusErrCodeSchema = z.enum([ - 'ADMIN_PERMISSIONS_REQUIRED', - 'ASSEMBLY_ACCOUNT_MISMATCH', - 'ASSEMBLY_CANNOT_BE_REPLAYED', - 'ASSEMBLY_COULD_NOT_BE_CREATED', - 'ASSEMBLY_CRASHED', - 'ASSEMBLY_DISALLOWED_ROBOTS_USED', - 'ASSEMBLY_EMPTY_STEPS', - 'ASSEMBLY_EXPIRED', - 'ASSEMBLY_FILE_NOT_RESERVED', - 'ASSEMBLY_INFINITE', - 'ASSEMBLY_INSTANCE_NOT_FOUND', - 'ASSEMBLY_INVALID_NOTIFY_URL', - 'ASSEMBLY_INVALID_NUM_EXPECTED_UPLOAD_FILES_PARAM', - 'ASSEMBLY_INVALID_STEPS', - 'ASSEMBLY_JOB_ENQUEUE_ERROR', - 'ASSEMBLY_LIST_ERROR', - 'ASSEMBLY_MEMORY_LIMIT_EXCEEDED', - 'ASSEMBLY_NO_CHARGEABLE_STEP', - 'ASSEMBLY_NO_STEPS', - 'ASSEMBLY_NOT_CAPABLE', - 'ASSEMBLY_NOT_FINISHED', - 'ASSEMBLY_NOT_FOUND', - 'ASSEMBLY_NOT_REPLAYED', - 'ASSEMBLY_NOTIFICATION_NOT_PERSISTED', - 'ASSEMBLY_ROBOT_MISSING', - 'ASSEMBLY_SATURATED', - 'ASSEMBLY_STATUS_NOT_FOUND', - 'ASSEMBLY_STATUS_PARSE_ERROR', - 'ASSEMBLY_STEP_INVALID_ROBOT', - 'ASSEMBLY_STEP_INVALID_USE', - 'ASSEMBLY_STEP_INVALID', - 'ASSEMBLY_STEP_NO_ROBOT', - 'ASSEMBLY_STEP_UNKNOWN_ROBOT', - 'ASSEMBLY_STEP_UNKNOWN_USE', - 'ASSEMBLY_URL_TRANSFORM_MISSING', - 'AUTH_EXPIRED', - 'AUTH_KEY_SCOPES_NOT_FOUND', - 'AUTH_KEYS_NOT_FOUND', - 'AUTH_SECRET_NOT_RETRIEVED', - 'AZURE_STORE_ACCESS_DENIED', - 'BACKBLAZE_IMPORT_ACCESS_DENIED', - 'BACKBLAZE_IMPORT_NOT_FOUND', - 'BACKBLAZE_STORE_ACCESS_DENIED', - 'BACKBLAZE_STORE_FAILURE', - 'BAD_PRICING', - 'BILL_LIMIT_EXCEEDED', - 'CANNOT_ACCEPT_NEW_ASSEMBLIES', - 'CANNOT_FETCH_ACTIVE_ASSEMBLIES', - 'CDN_REQUIRED', - 'CLOUDFILES_IMPORT_ACCESS_DENIED', - 'CLOUDFILES_IMPORT_NOT_FOUND', - 'CLOUDFILES_STORE_ACCESS_DENIED', - 'CLOUDFILES_STORE_ERROR', - 'CLOUDFLARE_IMPORT_VALIDATION', - 'DIGITALOCEAN_STORE_ACCESS_DENIED', - 'DO_NOT_REUSE_ASSEMBLY_IDS', - 'DOCUMENT_CONVERT_UNSUPPORTED_CONVERSION', - 'DOCUMENT_SPLIT_VALIDATION', - 'FILE_DOWNLOAD_ERROR', - 'FILE_FILTER_DECLINED_FILE', - 'FILE_FILTER_INVALID_OPERATOR', - 'FILE_FILTER_VALIDATION', - 'FILE_META_DATA_ERROR', - 'FILE_PREVIEW_VALIDATION', - 'FILE_READ_VALIDATION_ERROR', - 'FILE_VERIFY_INVALID_FILE', - 'FILE_VIRUSSCAN_DECLINED_FILE', - 'GET_ACCOUNT_DB_ERROR', - 'GET_ACCOUNT_UNKNOWN_AUTH_KEY', - 'GOOGLE_IMPORT_VALIDATION', - 'GOOGLE_STORE_VALIDATION', - 'HTML_CONVERT_VALIDATION', - 'HTTP_IMPORT_ACCESS_DENIED', - 'HTTP_IMPORT_FAILURE', - 'HTTP_IMPORT_NOT_FOUND', - 'HTTP_IMPORT_VALIDATION', - 'IMAGE_DESCRIBE_VALIDATION', - 'IMAGE_RESIZE_ERROR', - 'IMAGE_RESIZE_VALIDATION', - 'IMPORT_FILE_ERROR', - 'INCOMPLETE_PRICING', - 'INSUFFICIENT_AUTH_SCOPE', - 'INTERNAL_COMMAND_ERROR', - 'INTERNAL_COMMAND_TIMEOUT', - 'INVALID_ASSEMBLY_STATUS', - 'INVALID_AUTH_EXPIRES_PARAMETER', - 'INVALID_AUTH_KEY_PARAMETER', - 'INVALID_AUTH_MAX_SIZE_PARAMETER', - 'INVALID_AUTH_REFERER_PARAMETER', - 'INVALID_FILE_META_DATA', - 'INVALID_FORM_DATA', - 'INVALID_INPUT_ERROR', - 'INVALID_PARAMS_FIELD', - 'INVALID_SIGNATURE', - 'INVALID_STEP_NAME', - 'INVALID_TEMPLATE_FIELD', - 'INVALID_UPLOAD_HANDLE_STEP_NAME', - 'MAX_SIZE_EXCEEDED', - 'NO_AUTH_EXPIRES_PARAMETER', - 'NO_AUTH_KEY_PARAMETER', - 'NO_AUTH_PARAMETER', - 'NO_COUNTRY', - 'NO_OBJECT_AUTH_PARAMETER', - 'NO_OBJECT_PARAMS_FIELD', - 'NO_PARAMS_FIELD', - 'NO_PRICING', - 'NO_RESULT_STEP_FOUND', - 'NO_RPC_RESULT_FROM_IMAGE_RESIZER', - 'NO_SIGNATURE_FIELD', - 'NO_TEMPLATE_ID', - 'PLAN_LIMIT_EXCEEDED', - 'POSSIBLY_MALICIOUS_FILE_FOUND', - 'PRIORITY_JOB_SLOTS_NOT_FOUND', - 'RATE_LIMIT_REACHED', - 'REFERER_MISMATCH', - 'REQUEST_PREMATURE_CLOSED', - 'ROBOT_VALIDATION_BASE_ERROR', - 'S3_ACCESS_DENIED', - 'S3_IMPORT_ACCESS_DENIED', - 'S3_IMPORT_VALIDATION', - 'S3_NOT_FOUND', - 'S3_STORE_ACCESS_DENIED', - 'S3_STORE_VALIDATION', - 'SERVER_403', - 'SERVER_404', - 'SERVER_500', - 'SIGNATURE_REUSE_DETECTED', - 'TEMPLATE_CREDENTIALS_INJECTION_ERROR', - 'TEMPLATE_DB_ERROR', - 'TEMPLATE_DENIES_STEPS_OVERRIDE', - 'TEMPLATE_INVALID_JSON', - 'TEMPLATE_NOT_FOUND', - 'TMP_FILE_DOWNLOAD_ERROR', - 'USER_COMMAND_ERROR', - 'VERIFIED_EMAIL_REQUIRED', - 'VIDEO_ENCODE_VALIDATION', - 'VIMEO_IMPORT_FAILURE', - 'WORKER_JOB_ERROR', -]) - -const assemblyStatusMetaSchema = z - .object({ - width: z.union([z.number(), z.null()]).optional(), - height: z.union([z.number(), z.null()]).optional(), - date_file_modified: z.string().nullable().optional(), - aspect_ratio: z.union([z.number(), z.string(), z.null()]).optional(), - has_clipping_path: z.boolean().optional(), - frame_count: z.union([z.number(), z.null()]).optional(), - colorspace: z.string().nullable().optional(), - has_transparency: z.boolean().nullable().optional(), - average_color: z.string().nullable().optional(), - svgViewBoxWidth: z.union([z.number(), z.null()]).optional(), - svgViewBoxHeight: z.union([z.number(), z.null()]).optional(), - date_recorded: z.union([z.string(), z.number()]).nullable().optional(), - date_file_created: z.string().nullable().optional(), - title: z.union([z.string(), z.number()]).nullable().optional(), - description: z.string().nullable().optional(), - duration: z.union([z.number(), z.null()]).optional(), - location: z.string().nullable().optional(), - city: z.string().nullable().optional(), - state: z.string().nullable().optional(), - rights: z.union([z.string(), z.number()]).nullable().optional(), - country: z.string().nullable().optional(), - country_code: z.string().nullable().optional(), - keywords: z - .union([z.string(), z.array(z.union([z.string(), z.number()]))]) - .nullable() - .optional(), - aperture: z.union([z.number(), z.null()]).optional(), - exposure_compensation: z.union([z.number(), z.string()]).nullable().optional(), - exposure_mode: z.string().nullable().optional(), - exposure_time: z.union([z.number(), z.string()]).nullable().optional(), - flash: z.string().nullable().optional(), - focal_length: z.string().nullable().optional(), - f_number: z.union([z.number(), z.null()]).optional(), - iso: z.union([z.number(), z.null()]).optional(), - light_value: z.union([z.number(), z.null()]).optional(), - metering_mode: z.string().nullable().optional(), - shutter_speed: z.union([z.number(), z.string()]).nullable().optional(), - white_balance: z.string().nullable().optional(), - device_name: z.string().nullable().optional(), - device_vendor: z.string().nullable().optional(), - device_software: z.union([z.string(), z.number()]).nullable().optional(), - latitude: z.union([z.number(), z.null()]).optional(), - longitude: z.union([z.number(), z.null()]).optional(), - orientation: z.union([z.string(), z.number()]).nullable().optional(), - creator: z.string().nullable().optional(), - author: z.string().nullable().optional(), - copyright: z.string().nullable().optional(), - copyright_notice: z.union([z.string(), z.number()]).nullable().optional(), - dominant_colors: z.array(z.string()).nullable().optional(), - xp_title: z.string().nullable().optional(), - xp_comment: z.string().nullable().optional(), - xp_keywords: z.string().nullable().optional(), - xp_subject: z.string().nullable().optional(), - recognized_text: z - .union([ - z.array(z.string()), - z.array( - z - .object({ - text: z.string(), - boundingPolygon: z.array(z.object({ x: z.number(), y: z.number() })), - }) - .passthrough(), - ), - ]) - .optional(), - descriptions: z - .array( - z.union([z.string(), z.object({ name: z.string(), confidence: z.number() }).passthrough()]), - ) - .optional(), - framerate: z.union([z.number(), z.null()]).optional(), - mean_volume: z.union([z.number(), z.null()]).optional(), - video_bitrate: z.union([z.number(), z.null()]).optional(), - overall_bitrate: z.union([z.number(), z.null()]).optional(), - video_codec: z.string().nullable().optional(), - audio_bitrate: z.union([z.number(), z.null()]).optional(), - audio_samplerate: z.union([z.number(), z.null()]).optional(), - audio_channels: z.union([z.number(), z.null()]).optional(), - audio_channel_layout: z.union([z.string(), z.null()]).optional(), - audio_sample_format: z.union([z.string(), z.null()]).optional(), - audio_profile: z.union([z.string(), z.null()]).optional(), - audio_codec: z.union([z.string(), z.null()]).optional(), - num_audio_streams: z.union([z.number(), z.null()]).optional(), - num_video_streams: z.union([z.number(), z.null()]).optional(), - num_subtitles: z.union([z.number(), z.null()]).optional(), - bit_depth: z.union([z.number(), z.null()]).optional(), - seekable: z.union([z.boolean(), z.null()]).optional(), - pixel_format: z.union([z.string(), z.null()]).optional(), - reference_count: z.union([z.number(), z.null()]).optional(), - time_base: z.union([z.string(), z.null()]).optional(), - streams: z - .union([ - z.object({ - video: z.array(z.unknown()).optional(), - audio: z.array(z.unknown()).optional(), - subtitle: z.array(z.unknown()).optional(), - }), - z.null(), - ]) - .optional(), - rotation: z.union([z.number(), z.null()]).optional(), - album: z.string().nullable().optional(), - comment: z.string().nullable().optional(), - year: z.union([z.string(), z.number()]).nullable().optional(), - encoding_profile: z.string().nullable().optional(), - encoding_level: z.string().nullable().optional(), - has_artwork: z.union([z.boolean(), z.null()]).optional(), - has_alpha_channel: z.boolean().nullable().optional(), - beats_per_minute: z.union([z.number(), z.null()]).optional(), - genre: z.union([z.string(), z.number()]).nullable().optional(), - artist: z.string().nullable().optional(), - performer: z.string().nullable().optional(), - lyrics: z.string().nullable().optional(), - band: z.string().nullable().optional(), - disc: z.union([z.string(), z.number()]).nullable().optional(), - track: z.union([z.string(), z.number()]).nullable().optional(), - turbo: z.boolean().nullable().optional(), - encoder: z.string().nullable().optional(), - thumb_index: z.number().nullable().optional(), - thumb_offset: z - .preprocess((val) => (typeof val === 'string' ? Number.parseInt(val, 10) : val), z.number()) - .nullable() - .optional(), - page_count: z.union([z.number(), z.null()]).optional(), - page_size: z.string().nullable().optional(), - producer: z.string().nullable().optional(), - create_date: z.string().nullable().optional(), - modify_date: z.union([z.string(), z.number()]).nullable().optional(), - colortransfer: z.string().nullable().optional(), - colorprimaries: z.string().nullable().optional(), - archive_directory: z.string().nullable().optional(), - relative_path: z.string().nullable().optional(), - segment_index: z.number().nullable().optional(), - starts_at: z.string().nullable().optional(), - ends_at: z.string().nullable().optional(), - resolution: z.string().nullable().optional(), - bandwidth: z.number().nullable().optional(), - closed_captions: z.boolean().nullable().optional(), - codecs: z.string().nullable().optional(), - storage_url: z.string().optional(), - version_id: z.string().optional(), - faces: z - .array( - z - .object({ - x1: z.number(), - y1: z.number(), - x2: z.number(), - y2: z.number(), - confidence: z.number().optional(), - width: z.number(), - height: z.number(), - }) - .passthrough(), - ) - .nullable() - .optional(), - reason: z.string().optional(), - step: z.string().optional(), - previousStep: z.string().optional(), - exitCode: z.number().nullable().optional(), - exitSignal: z.string().nullable().optional(), - stdout: z.string().optional(), - stderr: z.string().optional(), - cmd: z.union([z.string(), z.array(z.union([z.string(), z.number()]))]).optional(), - worker: z.string().optional(), - word_count: z.union([z.number(), z.null()]).optional(), - character_count: z.union([z.number(), z.null()]).optional(), - character_count_with_spaces: z.union([z.number(), z.null()]).optional(), - line_count: z.union([z.number(), z.null()]).optional(), - paragraph_count: z.union([z.number(), z.null()]).optional(), - }) - .passthrough() -export type AssemblyStatusMeta = z.infer - -// Need to export the schema itself for assemblyStatusForTests.ts -export { assemblyStatusMetaSchema } - -const hlsNestedMetaSchema = z.object({ - relative_path: z.string().optional(), - duration: z.number().optional(), - width: z.number().optional(), - height: z.number().optional(), - framerate: z.number().optional(), - overall_bitrate: z.number().optional(), - aspect_ratio: z.number().optional(), - video_codec: z.string().optional(), - audio_samplerate: z.number().optional(), - audio_channels: z.number().optional(), - num_audio_streams: z.number().optional(), - audio_codec: z.string().optional(), - seekable: z.boolean().optional(), - date_file_modified: z.string().optional(), - encoding_profile: z.string().optional(), - encoding_level: z.string().optional(), - has_artwork: z.boolean().optional(), - has_alpha_channel: z.boolean().optional(), - version_id: z.string().optional(), -}) - -const hlsPlaylistSchema = z.object({ - name: z.union([z.string(), z.number()]).optional(), - content: z.string().optional(), - relative_path: z.string().optional(), - stream: z.string().optional(), - meta: hlsNestedMetaSchema.optional(), -}) - -export const assemblyStatusUploadSchema = z - .object({ - id: z.string(), - name: z.string(), - basename: z.string(), - ext: z.string(), - size: z.number(), - mime: z.string(), - type: z.string().nullable(), - field: z.string().nullable(), - md5hash: z.string().nullable(), - original_id: z.union([z.string(), z.array(z.string())]), - original_basename: z.string(), - original_name: z.string(), - original_path: z.string(), - original_md5hash: z.string().nullable(), - from_batch_import: z.boolean(), - is_tus_file: z.boolean(), - tus_upload_url: z.string().nullable(), - url: z.string().nullable(), - ssl_url: z.string().nullable(), - meta: assemblyStatusMetaSchema, - user_meta: z.record(z.unknown()).optional(), - as: z - .union([z.string(), z.array(z.string())]) - .nullable() - .optional(), - is_temp_url: z.boolean().optional(), - queue: z.string().nullable().optional(), - queue_time: z.number().optional(), - exec_time: z.number().optional(), - import_url: z.string().optional(), - cost: z.union([z.number(), z.null()]).optional(), - }) - .passthrough() -export type AssemblyStatusUpload = z.infer - -export const assemblyStatusUploadsSchema = z.array(assemblyStatusUploadSchema) -export type AssemblyStatusUploads = z.infer - -export const assemblyStatusResultSchema = z - .object({ - id: z.string().optional(), - basename: z.string().nullable().optional(), - field: z.string().nullable().optional(), - md5hash: z.string().nullable().optional(), - original_id: z.union([z.string(), z.array(z.string())]).optional(), - original_basename: z.string().nullable().optional(), - original_path: z.string().nullable().optional(), - original_md5hash: z.string().nullable().optional(), - from_batch_import: z.boolean().optional(), - is_tus_file: z.boolean().optional(), - tus_upload_url: z.string().nullable().optional(), - is_temp_url: z.boolean().optional(), - cost: z.number().nullable().optional(), - duration_human: z.string().nullable().optional(), - duration: z.number().nullable().optional(), - exec_time: z.number().nullable().optional(), - ext: z.string().nullable().optional(), - filepath: z.string().nullable().optional(), - path: z.string().nullable().optional(), - height: z.number().nullable().optional(), - meta: assemblyStatusMetaSchema.nullable().optional(), - mime: z.string().nullable().optional(), - name: z.string().nullable().optional(), - original_name: z.string().nullable().optional(), - preview: z.string().nullable().optional(), - queue_time: z.number().nullable().optional(), - queue: z.string().nullable().optional(), - size_human: z.string().nullable().optional(), - size: z.number().nullable().optional(), - ssl_url: z.string().nullable().optional(), - type: z.string().nullable().optional(), - url: z.string().nullable().optional(), - user_meta: z - .record(z.union([z.string(), z.number()])) - .nullable() - .optional(), - width: z.number().nullable().optional(), - as: z - .union([z.string(), z.array(z.string())]) - .nullable() - .optional(), - queueTime: z.number().nullable().optional(), - execTime: z.number().nullable().optional(), - import_url: z.string().optional(), - signed_url: z.string().optional(), - signed_ssl_url: z.string().optional(), - ios_url: z.string().optional(), - streaming_url: z.string().optional(), - remote_path: z.string().optional(), - playlists: z.array(hlsPlaylistSchema).optional(), - hls_url: z.string().optional(), - forcedFileExt: z.string().optional(), - // Robot-specific metadata added at runtime by /vimeo/import - vimeo: z - .object({ - title: z.string(), - uri: z.string(), - }) - .optional(), - }) - .passthrough() -export type AssemblyStatusResult = z.infer - -export const assemblyStatusResultsSchema = z.record(z.array(assemblyStatusResultSchema)) -export type AssemblyStatusResults = z.infer - -// Define a more specific schema for debuginfo if its structure is known -export const debugInfoSchema = z - .object({ - err: z.unknown().optional(), // Or a more specific error type if known - screenshot_ssl_url: z.string().optional(), - screenshot_filepath: z.string().optional(), - screenshot_s3_url: z.string().optional(), // Add s3 URL field - console_filepath: z.string().optional(), - console_ssl_url: z.string().optional(), // Add console SSL URL field - console_s3_url: z.string().optional(), // Add console s3 URL field - }) - .passthrough() - -export const assemblyStatusBaseSchema = z.object({ - message: z.string().optional(), - admin_cmd: z.unknown().optional(), - assemblyId: z.string().optional(), - assembly_id: z.string().optional(), - parent_id: z.string().nullable().optional(), - account_id: z.string().optional(), - account_name: z.string().nullable().optional(), - account_slug: z.string().nullable().optional(), - api_auth_key_id: z.string().nullable().optional(), - template_id: z.string().nullable().optional(), - template_name: z.string().nullable().optional(), - instance: z.string().optional(), - region: z.string().optional(), - assembly_url: z.string().optional(), - assembly_ssl_url: z.string().optional(), - uppyserver_url: z.string().optional(), - companion_url: z.string().optional(), - websocket_url: z.string().optional(), - update_stream_url: z.string().optional(), - tus_url: z.string().optional(), - bytes_received: z.number().optional(), - bytes_expected: z.number().nullable().optional(), - upload_duration: z.number().optional(), - client_agent: z.string().nullable().optional(), - client_ip: z.string().nullable().optional(), - client_referer: z.string().nullable().optional(), - transloadit_client: z.string().nullable().optional(), - start_date: z.string().optional(), - upload_meta_data_extracted: z.boolean().optional(), - warnings: z - .array( - z - .object({ level: z.literal('notice').or(z.literal('warning')), msg: z.string() }) - .passthrough(), - ) - .optional(), - is_infinite: z.boolean().optional(), - error: z.undefined().optional(), - has_dupe_jobs: z.boolean().optional(), - execution_start: z.string().nullable().optional(), - execution_duration: z.number().nullable().optional(), - queue_duration: z.number().optional(), - jobs_queue_duration: z.number().optional(), - notify_start: z.string().nullable().optional(), - notify_url: z.string().nullable().optional(), - notify_status: z.string().nullable().optional(), - notify_response_code: z.number().nullable().optional(), - notify_response_data: z.string().nullable().optional(), - notify_duration: z.number().nullable().optional(), - last_job_completed: z.string().nullable().optional(), - fields: z.record(z.unknown()).optional(), - running_jobs: z.array(z.string()).optional(), - bytes_usage: z.number().optional(), - usage_tags: z.string().optional(), - executing_jobs: z.array(z.string()).optional(), - started_jobs: z.array(z.string()).optional(), - parent_assembly_status: z.unknown().nullable().optional(), - params: z.string().nullable().optional(), - template: z.string().nullable().optional(), - merged_params: z.string().nullable().optional(), - num_input_files: z.number().optional(), - uploads: assemblyStatusUploadsSchema.optional(), - results: assemblyStatusResultsSchema.optional(), - build_id: z.string().optional(), - expected_tus_uploads: z.number().optional(), - started_tus_uploads: z.number().optional(), - finished_tus_uploads: z.number().optional(), - virusname: z.string().optional(), - tus_uploads: z - .array( - z - .object({ - filename: z.string(), - fieldname: z.string(), - user_meta: z.record(z.unknown()).optional(), - size: z.number(), - offset: z.number(), - finished: z.boolean(), - upload_url: z.string(), - local_path: z.string().optional(), - }) - .passthrough(), - ) - .optional(), - debuginfo: debugInfoSchema.optional(), - step: z.string().optional(), - previousStep: z.string().optional(), - worker: z.string().optional(), - info: z - .object({ - retryIn: z.number().optional(), - }) - .optional(), -}) - -export const assemblyStatusBusySchema = z - .object({ - ok: assemblyBusyCodeSchema, - // TODO: Does busy status also share base fields? Need example. - // Assuming for now it might share some base fields but not all recursively? - // Let's make it extend the *non-recursive* base for now. - }) - .extend(assemblyStatusBaseSchema.shape) - .passthrough() - -export const assemblyStatusOkSchema = assemblyStatusBaseSchema - .extend({ - ok: assemblyStatusOkCodeSchema, - }) - .passthrough() - -export const assemblyStatusErrSchema = assemblyStatusBaseSchema - .extend({ - error: assemblyStatusErrCodeSchema, - ok: z.null().optional(), - retries: z.number().optional(), - numRetries: z.number().optional(), - reason: z.string().optional(), - step: z.string().optional(), - previousStep: z.string().optional(), - path: z.string().optional(), - exitCode: z.number().nullable().optional(), - exitSignal: z.string().nullable().optional(), - stdout: z.string().optional(), - stderr: z.string().optional(), - cmd: z.union([z.string(), z.array(z.union([z.string(), z.number()]))]).optional(), - admin_cmd: z.union([z.string(), z.array(z.union([z.string(), z.number()]))]).optional(), - worker: z.string().optional(), - headers: z.record(z.unknown()).optional(), - retryable: z.boolean().optional(), - err: z.unknown().optional(), - }) - .passthrough() - -// Represents a low-level system error not mapped to standard assembly errors. -// Happened in Assemblies: -// - 13ca71f3b8714859b48ec11e49be10f1 -// - 14ef7ab868e84350b2c0b70c9f3b2df1 -// - 83b21b12c30b416f82651464635f05f1 -// - 9198732f03cf40adbf778ae28fd52ef1 -// - dfa372cef24a420092f1be42af6d1df1 -// - e975612bc76e4738b759d1b36bc527f1 -// All for Workspace: 6f86325febd14de4bfb38cbd04ee1f39 -export const assemblyStatusSysErrSchema = assemblyStatusBaseSchema - .extend({ - // Changed from .object() - // No 'ok' or 'error' discriminator - errno: z.number(), - code: z.string(), - syscall: z.string(), - path: z.string().optional(), // Path might be present - // Consider adding other potential sys error fields if observed later - }) - .passthrough() - -// Final schema defined lazily to handle recursion -// We break up inference to avoid: -// error TS7056: The inferred type of this node exceeds the maximum length the compiler will serialize. An explicit type annotation is needed. -export const assemblyStatusSchema: z.ZodUnion< - [ - typeof assemblyStatusBusySchema, - typeof assemblyStatusOkSchema, - typeof assemblyStatusErrSchema, - typeof assemblyStatusSysErrSchema, - ] -> = z.union([ - assemblyStatusBusySchema, // Use schema defined above - assemblyStatusOkSchema, // Use schema defined above - assemblyStatusErrSchema, // Use schema defined above - assemblyStatusSysErrSchema, // Add the new system error state -]) - -export type AssemblyStatus = z.infer - -/** - * Type guard to check if an assembly has an error. - */ -export function hasError( - assembly: AssemblyStatus | undefined | null, - particularErrorCode?: z.infer, -): assembly is AssemblyStatus & { error: string } { - const errorExists = - Boolean(assembly) && assembly != null && typeof assembly === 'object' && 'error' in assembly - - if (particularErrorCode) { - return errorExists && assembly.error === particularErrorCode - } - - return errorExists -} - -export function hasSpecificError( - assembly: AssemblyStatus | null | undefined, - errorType: string, -): boolean { - if (!assembly) return false - - if (hasError(assembly) && assembly.error === errorType) { - return true - } - - if (typeof assembly === 'object' && assembly !== null && errorType in assembly) { - const candidate = Reflect.get(assembly, errorType) - return Boolean(candidate) - } - - return false -} - -/** - * Type guard to check if an assembly has an ok status - */ -export function hasOk( - assembly: AssemblyStatus | undefined | null, - particularOkCode?: z.infer, -): assembly is AssemblyStatus & { ok: string } { - const okExists = - Boolean(assembly) && assembly != null && typeof assembly === 'object' && 'ok' in assembly - - if (particularOkCode) { - return okExists && assembly.ok === particularOkCode - } - return okExists -} - -/** - * Returns the error value if it exists or undefined - */ -export function getError(assembly: AssemblyStatus | undefined | null): string | undefined { - return assembly && assembly != null && typeof assembly === 'object' && 'error' in assembly - ? String(assembly.error) - : undefined -} - -/** - * Returns the ok value if it exists or undefined - */ -export function getOk(assembly: AssemblyStatus | undefined | null): string | undefined { - return assembly && assembly != null && typeof assembly === 'object' && 'ok' in assembly - ? String(assembly.ok) - : undefined -} - -/** - * This type and these functions below are compatibility helpers for - * working with partial assembly status objects during the transition - * from the old types to the new Zod-based schema. - */ -export type PartialAssemblyStatus = Partial - -export function hasErrorPartial( - assembly: PartialAssemblyStatus | undefined | null, -): assembly is PartialAssemblyStatus & { error: string } { - return ( - Boolean(assembly) && - assembly != null && - typeof assembly === 'object' && - 'error' in assembly && - Boolean(assembly.error) - ) -} - -export function hasOkPartial( - assembly: PartialAssemblyStatus | undefined | null, -): assembly is PartialAssemblyStatus & { ok: string } { - return ( - Boolean(assembly) && - assembly != null && - typeof assembly === 'object' && - 'ok' in assembly && - Boolean(assembly.ok) - ) -} - -// Schema for items returned by the List Assemblies endpoint -export const assemblyIndexItemSchema = z - .object({ - id: z.string(), // Likely always present for a list item - parent_id: assemblyStatusBaseSchema.shape.parent_id.optional(), - account_id: assemblyStatusBaseSchema.shape.account_id.unwrap().optional(), - template_id: assemblyStatusBaseSchema.shape.template_id.optional(), - instance: assemblyStatusBaseSchema.shape.instance.unwrap().optional(), - notify_url: assemblyStatusBaseSchema.shape.notify_url.optional(), - redirect_url: z.string().nullable().optional(), - files: z.string().nullable(), // JSON stringified, specific to list item, CAN BE NULL - warning_count: z.number().optional(), - execution_duration: assemblyStatusBaseSchema.shape.execution_duration.optional(), - execution_start: assemblyStatusBaseSchema.shape.execution_start.optional(), - region: assemblyStatusBaseSchema.shape.region.optional(), - num_input_files: assemblyStatusBaseSchema.shape.num_input_files.optional(), - bytes_usage: assemblyStatusBaseSchema.shape.bytes_usage.optional(), - ok: assemblyStatusOkCodeSchema.nullable().optional(), - error: assemblyStatusErrCodeSchema.nullable().optional(), - created: z.string(), - created_ts: z.number().optional(), - template_name: z.string().nullable().optional(), - }) - .passthrough() - -export type AssemblyIndexItem = z.infer - -export const assemblyIndexSchema = z.array(assemblyIndexItemSchema) -export type AssemblyIndex = z.infer diff --git a/packages/transloadit/src/alphalib/types/bill.ts b/packages/transloadit/src/alphalib/types/bill.ts deleted file mode 100644 index e8e08099..00000000 --- a/packages/transloadit/src/alphalib/types/bill.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { z } from 'zod' - -import { assemblyAuthInstructionsSchema } from './template.ts' - -export const billSchema = z - .object({ - auth: assemblyAuthInstructionsSchema, - }) - .strict() diff --git a/packages/transloadit/src/alphalib/types/robots/_index.ts b/packages/transloadit/src/alphalib/types/robots/_index.ts deleted file mode 100644 index 7cc47a21..00000000 --- a/packages/transloadit/src/alphalib/types/robots/_index.ts +++ /dev/null @@ -1,1201 +0,0 @@ -import { z } from 'zod' -import { - meta as aiChatMeta, - interpolatableRobotAiChatInstructionsSchema, - interpolatableRobotAiChatInstructionsWithHiddenFieldsSchema, -} from './ai-chat.ts' -import { - meta as audioArtworkMeta, - interpolatableRobotAudioArtworkInstructionsSchema, - interpolatableRobotAudioArtworkInstructionsWithHiddenFieldsSchema, -} from './audio-artwork.ts' -import { - meta as audioConcatMeta, - interpolatableRobotAudioConcatInstructionsSchema, - interpolatableRobotAudioConcatInstructionsWithHiddenFieldsSchema, -} from './audio-concat.ts' -import { - meta as audioEncodeMeta, - interpolatableRobotAudioEncodeInstructionsSchema, - interpolatableRobotAudioEncodeInstructionsWithHiddenFieldsSchema, -} from './audio-encode.ts' -import { - meta as audioLoopMeta, - interpolatableRobotAudioLoopInstructionsSchema, - interpolatableRobotAudioLoopInstructionsWithHiddenFieldsSchema, -} from './audio-loop.ts' -import { - meta as audioMergeMeta, - interpolatableRobotAudioMergeInstructionsSchema, - interpolatableRobotAudioMergeInstructionsWithHiddenFieldsSchema, -} from './audio-merge.ts' -import { - meta as audioWaveformMeta, - interpolatableRobotAudioWaveformInstructionsSchema, - interpolatableRobotAudioWaveformInstructionsWithHiddenFieldsSchema, -} from './audio-waveform.ts' -import { - meta as azureImportMeta, - interpolatableRobotAzureImportInstructionsSchema, - interpolatableRobotAzureImportInstructionsWithHiddenFieldsSchema, -} from './azure-import.ts' -import { - meta as azureStoreMeta, - interpolatableRobotAzureStoreInstructionsSchema, - interpolatableRobotAzureStoreInstructionsWithHiddenFieldsSchema, -} from './azure-store.ts' -import { - meta as backblazeImportMeta, - interpolatableRobotBackblazeImportInstructionsSchema, - interpolatableRobotBackblazeImportInstructionsWithHiddenFieldsSchema, -} from './backblaze-import.ts' -import { - meta as backblazeStoreMeta, - interpolatableRobotBackblazeStoreInstructionsSchema, - interpolatableRobotBackblazeStoreInstructionsWithHiddenFieldsSchema, -} from './backblaze-store.ts' -import { - meta as cloudfilesImportMeta, - interpolatableRobotCloudfilesImportInstructionsSchema, - interpolatableRobotCloudfilesImportInstructionsWithHiddenFieldsSchema, -} from './cloudfiles-import.ts' -import { - meta as cloudfilesStoreMeta, - interpolatableRobotCloudfilesStoreInstructionsSchema, - interpolatableRobotCloudfilesStoreInstructionsWithHiddenFieldsSchema, -} from './cloudfiles-store.ts' -import { - meta as cloudflareImportMeta, - interpolatableRobotCloudflareImportInstructionsSchema, - interpolatableRobotCloudflareImportInstructionsWithHiddenFieldsSchema, -} from './cloudflare-import.ts' -import { - meta as cloudflareStoreMeta, - interpolatableRobotCloudflareStoreInstructionsSchema, - interpolatableRobotCloudflareStoreInstructionsWithHiddenFieldsSchema, -} from './cloudflare-store.ts' -import { - meta as digitaloceanImportMeta, - interpolatableRobotDigitaloceanImportInstructionsSchema, - interpolatableRobotDigitaloceanImportInstructionsWithHiddenFieldsSchema, -} from './digitalocean-import.ts' -import { - meta as digitaloceanStoreMeta, - interpolatableRobotDigitaloceanStoreInstructionsSchema, - interpolatableRobotDigitaloceanStoreInstructionsWithHiddenFieldsSchema, -} from './digitalocean-store.ts' -import { - meta as documentAutorotateMeta, - interpolatableRobotDocumentAutorotateInstructionsSchema, - interpolatableRobotDocumentAutorotateInstructionsWithHiddenFieldsSchema, -} from './document-autorotate.ts' -import { - meta as documentConvertMeta, - interpolatableRobotDocumentConvertInstructionsSchema, - interpolatableRobotDocumentConvertInstructionsWithHiddenFieldsSchema, -} from './document-convert.ts' -import { - meta as documentMergeMeta, - interpolatableRobotDocumentMergeInstructionsSchema, - interpolatableRobotDocumentMergeInstructionsWithHiddenFieldsSchema, -} from './document-merge.ts' -import { - meta as documentOcrMeta, - interpolatableRobotDocumentOcrInstructionsSchema, - interpolatableRobotDocumentOcrInstructionsWithHiddenFieldsSchema, -} from './document-ocr.ts' -import { - meta as documentSplitMeta, - interpolatableRobotDocumentSplitInstructionsSchema, - interpolatableRobotDocumentSplitInstructionsWithHiddenFieldsSchema, -} from './document-split.ts' -import { - meta as documentThumbsMeta, - interpolatableRobotDocumentThumbsInstructionsSchema, - interpolatableRobotDocumentThumbsInstructionsWithHiddenFieldsSchema, -} from './document-thumbs.ts' -import { - meta as dropboxImportMeta, - interpolatableRobotDropboxImportInstructionsSchema, - interpolatableRobotDropboxImportInstructionsWithHiddenFieldsSchema, -} from './dropbox-import.ts' -import { - meta as dropboxStoreMeta, - interpolatableRobotDropboxStoreInstructionsSchema, - interpolatableRobotDropboxStoreInstructionsWithHiddenFieldsSchema, -} from './dropbox-store.ts' -import { - meta as edglyDeliverMeta, - interpolatableRobotEdglyDeliverInstructionsSchema, - interpolatableRobotEdglyDeliverInstructionsWithHiddenFieldsSchema, -} from './edgly-deliver.ts' -import { - meta as fileCompressMeta, - interpolatableRobotFileCompressInstructionsSchema, - interpolatableRobotFileCompressInstructionsWithHiddenFieldsSchema, -} from './file-compress.ts' -import { - meta as fileDecompressMeta, - interpolatableRobotFileDecompressInstructionsSchema, - interpolatableRobotFileDecompressInstructionsWithHiddenFieldsSchema, -} from './file-decompress.ts' -import { - meta as fileFilterMeta, - interpolatableRobotFileFilterInstructionsSchema, - interpolatableRobotFileFilterInstructionsWithHiddenFieldsSchema, -} from './file-filter.ts' -import { - meta as fileHashMeta, - interpolatableRobotFileHashInstructionsSchema, - interpolatableRobotFileHashInstructionsWithHiddenFieldsSchema, -} from './file-hash.ts' -import { - meta as filePreviewMeta, - interpolatableRobotFilePreviewInstructionsSchema, - interpolatableRobotFilePreviewInstructionsWithHiddenFieldsSchema, -} from './file-preview.ts' -import { - meta as fileReadMeta, - interpolatableRobotFileReadInstructionsSchema, - interpolatableRobotFileReadInstructionsWithHiddenFieldsSchema, -} from './file-read.ts' -import { - meta as fileServeMeta, - interpolatableRobotFileServeInstructionsSchema, - interpolatableRobotFileServeInstructionsWithHiddenFieldsSchema, -} from './file-serve.ts' -import { - meta as fileVerifyMeta, - interpolatableRobotFileVerifyInstructionsSchema, - interpolatableRobotFileVerifyInstructionsWithHiddenFieldsSchema, -} from './file-verify.ts' -import { - meta as fileVirusscanMeta, - interpolatableRobotFileVirusscanInstructionsSchema, - interpolatableRobotFileVirusscanInstructionsWithHiddenFieldsSchema, -} from './file-virusscan.ts' -import { - interpolatableRobotFileWatermarkInstructionsSchema, - interpolatableRobotFileWatermarkInstructionsWithHiddenFieldsSchema, -} from './file-watermark.ts' -import { - meta as ftpImportMeta, - interpolatableRobotFtpImportInstructionsSchema, - interpolatableRobotFtpImportInstructionsWithHiddenFieldsSchema, -} from './ftp-import.ts' -import { - meta as ftpStoreMeta, - interpolatableRobotFtpStoreInstructionsSchema, - interpolatableRobotFtpStoreInstructionsWithHiddenFieldsSchema, -} from './ftp-store.ts' -import { - meta as googleImportMeta, - interpolatableRobotGoogleImportInstructionsSchema, - interpolatableRobotGoogleImportInstructionsWithHiddenFieldsSchema, -} from './google-import.ts' -import { - meta as googleStoreMeta, - interpolatableRobotGoogleStoreInstructionsSchema, - interpolatableRobotGoogleStoreInstructionsWithHiddenFieldsSchema, -} from './google-store.ts' -import { - meta as htmlConvertMeta, - interpolatableRobotHtmlConvertInstructionsSchema, - interpolatableRobotHtmlConvertInstructionsWithHiddenFieldsSchema, -} from './html-convert.ts' -import { - meta as httpImportMeta, - interpolatableRobotHttpImportInstructionsSchema, - interpolatableRobotHttpImportInstructionsWithHiddenFieldsSchema, -} from './http-import.ts' -import { - meta as imageBgremoveMeta, - interpolatableRobotImageBgremoveInstructionsSchema, - interpolatableRobotImageBgremoveInstructionsWithHiddenFieldsSchema, -} from './image-bgremove.ts' -import { - meta as imageDescribeMeta, - interpolatableRobotImageDescribeInstructionsSchema, - interpolatableRobotImageDescribeInstructionsWithHiddenFieldsSchema, -} from './image-describe.ts' -import { - meta as imageFacedetectMeta, - interpolatableRobotImageFacedetectInstructionsSchema, - interpolatableRobotImageFacedetectInstructionsWithHiddenFieldsSchema, -} from './image-facedetect.ts' -import { - meta as imageGenerateMeta, - interpolatableRobotImageGenerateInstructionsSchema, - interpolatableRobotImageGenerateInstructionsWithHiddenFieldsSchema, -} from './image-generate.ts' -import { - meta as imageMergeMeta, - interpolatableRobotImageMergeInstructionsSchema, - interpolatableRobotImageMergeInstructionsWithHiddenFieldsSchema, -} from './image-merge.ts' -import { - meta as imageOcrMeta, - interpolatableRobotImageOcrInstructionsSchema, - interpolatableRobotImageOcrInstructionsWithHiddenFieldsSchema, -} from './image-ocr.ts' -import { - meta as imageOptimizeMeta, - interpolatableRobotImageOptimizeInstructionsSchema, - interpolatableRobotImageOptimizeInstructionsWithHiddenFieldsSchema, -} from './image-optimize.ts' -import { - meta as imageResizeMeta, - interpolatableRobotImageResizeInstructionsSchema, - interpolatableRobotImageResizeInstructionsWithHiddenFieldsSchema, -} from './image-resize.ts' -import { - interpolatableRobotMetaReadInstructionsSchema, - interpolatableRobotMetaReadInstructionsWithHiddenFieldsSchema, -} from './meta-read.ts' -import { - interpolatableRobotMetaWriteInstructionsSchema, - interpolatableRobotMetaWriteInstructionsWithHiddenFieldsSchema, - meta as metaWriteMeta, -} from './meta-write.ts' -import { - interpolatableRobotMinioImportInstructionsSchema, - interpolatableRobotMinioImportInstructionsWithHiddenFieldsSchema, - meta as minioImportMeta, -} from './minio-import.ts' -import { - interpolatableRobotMinioStoreInstructionsSchema, - interpolatableRobotMinioStoreInstructionsWithHiddenFieldsSchema, - meta as minioStoreMeta, -} from './minio-store.ts' -import { interpolatableRobotProgressSimulateInstructionsSchema } from './progress-simulate.ts' -import { - interpolatableRobotS3ImportInstructionsSchema, - interpolatableRobotS3ImportInstructionsWithHiddenFieldsSchema, - meta as s3ImportMeta, -} from './s3-import.ts' -import { - interpolatableRobotS3StoreInstructionsSchema, - interpolatableRobotS3StoreInstructionsWithHiddenFieldsSchema, - meta as s3StoreMeta, -} from './s3-store.ts' -import { - interpolatableRobotScriptRunInstructionsSchema, - interpolatableRobotScriptRunInstructionsWithHiddenFieldsSchema, - meta as scriptRunMeta, -} from './script-run.ts' -import { - interpolatableRobotSftpImportInstructionsSchema, - interpolatableRobotSftpImportInstructionsWithHiddenFieldsSchema, - meta as sftpImportMeta, -} from './sftp-import.ts' -import { - interpolatableRobotSftpStoreInstructionsSchema, - interpolatableRobotSftpStoreInstructionsWithHiddenFieldsSchema, - meta as sftpStoreMeta, -} from './sftp-store.ts' -import { - interpolatableRobotSpeechTranscribeInstructionsSchema, - interpolatableRobotSpeechTranscribeInstructionsWithHiddenFieldsSchema, - meta as speechTranscribeMeta, -} from './speech-transcribe.ts' -import { - interpolatableRobotSupabaseImportInstructionsSchema, - interpolatableRobotSupabaseImportInstructionsWithHiddenFieldsSchema, - meta as supabaseImportMeta, -} from './supabase-import.ts' -import { - interpolatableRobotSupabaseStoreInstructionsSchema, - interpolatableRobotSupabaseStoreInstructionsWithHiddenFieldsSchema, - meta as supabaseStoreMeta, -} from './supabase-store.ts' -import { - interpolatableRobotSwiftImportInstructionsSchema, - interpolatableRobotSwiftImportInstructionsWithHiddenFieldsSchema, - meta as swiftImportMeta, -} from './swift-import.ts' -import { - interpolatableRobotSwiftStoreInstructionsSchema, - interpolatableRobotSwiftStoreInstructionsWithHiddenFieldsSchema, - meta as swiftStoreMeta, -} from './swift-store.ts' -import { - interpolatableRobotTextSpeakInstructionsSchema, - interpolatableRobotTextSpeakInstructionsWithHiddenFieldsSchema, - meta as textSpeakMeta, -} from './text-speak.ts' -import { - interpolatableRobotTextTranslateInstructionsSchema, - interpolatableRobotTextTranslateInstructionsWithHiddenFieldsSchema, - meta as textTranslateMeta, -} from './text-translate.ts' -import { - interpolatableRobotTigrisImportInstructionsSchema, - interpolatableRobotTigrisImportInstructionsWithHiddenFieldsSchema, - meta as tigrisImport, -} from './tigris-import.ts' -import { - interpolatableRobotTigrisStoreInstructionsSchema, - interpolatableRobotTigrisStoreInstructionsWithHiddenFieldsSchema, - meta as tigrisStore, -} from './tigris-store.ts' -import { - interpolatableRobotTlcdnDeliverInstructionsSchema, - interpolatableRobotTlcdnDeliverInstructionsWithHiddenFieldsSchema, - meta as tlcdnDeliverMeta, -} from './tlcdn-deliver.ts' -import { - interpolatableRobotTusStoreInstructionsSchema, - interpolatableRobotTusStoreInstructionsWithHiddenFieldsSchema, - meta as tusStoreMeta, -} from './tus-store.ts' -import { - interpolatableRobotUploadHandleInstructionsSchema, - interpolatableRobotUploadHandleInstructionsWithHiddenFieldsSchema, - meta as uploadHandleMeta, -} from './upload-handle.ts' -import { - interpolatableRobotVideoAdaptiveInstructionsSchema, - interpolatableRobotVideoAdaptiveInstructionsWithHiddenFieldsSchema, - meta as videoAdaptiveMeta, -} from './video-adaptive.ts' -import { - interpolatableRobotVideoConcatInstructionsSchema, - interpolatableRobotVideoConcatInstructionsWithHiddenFieldsSchema, - meta as videoConcatMeta, -} from './video-concat.ts' -import { - interpolatableRobotVideoEncodeInstructionsSchema, - interpolatableRobotVideoEncodeInstructionsWithHiddenFieldsSchema, - meta as videoEncodeMeta, -} from './video-encode.ts' -import { - interpolatableRobotVideoMergeInstructionsSchema, - interpolatableRobotVideoMergeInstructionsWithHiddenFieldsSchema, - meta as videoMergeMeta, -} from './video-merge.ts' -import { - interpolatableRobotVideoOndemandInstructionsSchema, - interpolatableRobotVideoOndemandInstructionsWithHiddenFieldsSchema, - meta as videoOndemandMeta, -} from './video-ondemand.ts' -import { - interpolatableRobotVideoSubtitleInstructionsSchema, - interpolatableRobotVideoSubtitleInstructionsWithHiddenFieldsSchema, - meta as videoSubtitleMeta, -} from './video-subtitle.ts' -import { - interpolatableRobotVideoThumbsInstructionsSchema, - interpolatableRobotVideoThumbsInstructionsWithHiddenFieldsSchema, - meta as videoThumbsMeta, -} from './video-thumbs.ts' -import { - interpolatableRobotVimeoImportInstructionsSchema, - interpolatableRobotVimeoImportInstructionsWithHiddenFieldsSchema, - meta as vimeoImportMeta, -} from './vimeo-import.ts' -import { - interpolatableRobotVimeoStoreInstructionsSchema, - interpolatableRobotVimeoStoreInstructionsWithHiddenFieldsSchema, - meta as vimeoStoreMeta, -} from './vimeo-store.ts' -import { - interpolatableRobotWasabiImportInstructionsSchema, - interpolatableRobotWasabiImportInstructionsWithHiddenFieldsSchema, - meta as wasabiImportMeta, -} from './wasabi-import.ts' -import { - interpolatableRobotWasabiStoreInstructionsSchema, - interpolatableRobotWasabiStoreInstructionsWithHiddenFieldsSchema, - meta as wasabiStoreMeta, -} from './wasabi-store.ts' -import { - interpolatableRobotYoutubeStoreInstructionsSchema, - interpolatableRobotYoutubeStoreInstructionsWithHiddenFieldsSchema, - meta as youtubeStoreMeta, -} from './youtube-store.ts' - -const robotStepsInstructions = [ - interpolatableRobotAudioArtworkInstructionsSchema, - interpolatableRobotAudioConcatInstructionsSchema, - interpolatableRobotAudioEncodeInstructionsSchema, - interpolatableRobotAudioLoopInstructionsSchema, - interpolatableRobotAudioMergeInstructionsSchema, - interpolatableRobotAudioWaveformInstructionsSchema, - interpolatableRobotAzureImportInstructionsSchema, - interpolatableRobotAzureStoreInstructionsSchema, - interpolatableRobotBackblazeImportInstructionsSchema, - interpolatableRobotBackblazeStoreInstructionsSchema, - interpolatableRobotCloudfilesImportInstructionsSchema, - interpolatableRobotCloudfilesStoreInstructionsSchema, - interpolatableRobotCloudflareImportInstructionsSchema, - interpolatableRobotCloudflareStoreInstructionsSchema, - interpolatableRobotDigitaloceanImportInstructionsSchema, - interpolatableRobotDigitaloceanStoreInstructionsSchema, - interpolatableRobotDocumentAutorotateInstructionsSchema, - interpolatableRobotDocumentConvertInstructionsSchema, - interpolatableRobotDocumentMergeInstructionsSchema, - interpolatableRobotDocumentOcrInstructionsSchema, - interpolatableRobotFileReadInstructionsSchema, - interpolatableRobotDocumentSplitInstructionsSchema, - interpolatableRobotDocumentThumbsInstructionsSchema, - interpolatableRobotDropboxImportInstructionsSchema, - interpolatableRobotDropboxStoreInstructionsSchema, - interpolatableRobotEdglyDeliverInstructionsSchema, - interpolatableRobotFileCompressInstructionsSchema, - interpolatableRobotFileDecompressInstructionsSchema, - interpolatableRobotFileFilterInstructionsSchema, - interpolatableRobotFileHashInstructionsSchema, - interpolatableRobotFilePreviewInstructionsSchema, - interpolatableRobotFileServeInstructionsSchema, - interpolatableRobotFileVerifyInstructionsSchema, - interpolatableRobotFileVirusscanInstructionsSchema, - interpolatableRobotFtpImportInstructionsSchema, - interpolatableRobotFtpStoreInstructionsSchema, - interpolatableRobotGoogleImportInstructionsSchema, - interpolatableRobotGoogleStoreInstructionsSchema, - interpolatableRobotHtmlConvertInstructionsSchema, - interpolatableRobotHttpImportInstructionsSchema, - interpolatableRobotImageBgremoveInstructionsSchema, - interpolatableRobotImageDescribeInstructionsSchema, - interpolatableRobotImageFacedetectInstructionsSchema, - interpolatableRobotImageGenerateInstructionsSchema, - interpolatableRobotImageMergeInstructionsSchema, - interpolatableRobotImageOcrInstructionsSchema, - interpolatableRobotImageOptimizeInstructionsSchema, - interpolatableRobotImageResizeInstructionsSchema, - interpolatableRobotMetaWriteInstructionsSchema, - interpolatableRobotMinioImportInstructionsSchema, - interpolatableRobotMinioStoreInstructionsSchema, - interpolatableRobotS3ImportInstructionsSchema, - interpolatableRobotS3StoreInstructionsSchema, - interpolatableRobotScriptRunInstructionsSchema, - interpolatableRobotSftpImportInstructionsSchema, - interpolatableRobotSftpStoreInstructionsSchema, - interpolatableRobotSpeechTranscribeInstructionsSchema, - interpolatableRobotSupabaseImportInstructionsSchema, - interpolatableRobotSupabaseStoreInstructionsSchema, - interpolatableRobotSwiftImportInstructionsSchema, - interpolatableRobotSwiftStoreInstructionsSchema, - interpolatableRobotTextSpeakInstructionsSchema, - interpolatableRobotTextTranslateInstructionsSchema, - interpolatableRobotAiChatInstructionsSchema, - interpolatableRobotTigrisImportInstructionsSchema, - interpolatableRobotTigrisStoreInstructionsSchema, - interpolatableRobotTlcdnDeliverInstructionsSchema, - interpolatableRobotTusStoreInstructionsSchema, - interpolatableRobotUploadHandleInstructionsSchema, - interpolatableRobotVideoAdaptiveInstructionsSchema, - interpolatableRobotVideoConcatInstructionsSchema, - interpolatableRobotVideoEncodeInstructionsSchema, - interpolatableRobotVideoMergeInstructionsSchema, - interpolatableRobotVideoOndemandInstructionsSchema, - interpolatableRobotVideoSubtitleInstructionsSchema, - interpolatableRobotVideoThumbsInstructionsSchema, - interpolatableRobotVimeoImportInstructionsSchema, - interpolatableRobotVimeoStoreInstructionsSchema, - interpolatableRobotWasabiImportInstructionsSchema, - interpolatableRobotWasabiStoreInstructionsSchema, - interpolatableRobotYoutubeStoreInstructionsSchema, -] as const - -const robotStepsInstructionsWithHiddenFields = [ - interpolatableRobotAudioArtworkInstructionsWithHiddenFieldsSchema, - interpolatableRobotAudioConcatInstructionsWithHiddenFieldsSchema, - interpolatableRobotAudioEncodeInstructionsWithHiddenFieldsSchema, - interpolatableRobotAudioLoopInstructionsWithHiddenFieldsSchema, - interpolatableRobotAudioMergeInstructionsWithHiddenFieldsSchema, - interpolatableRobotAudioWaveformInstructionsWithHiddenFieldsSchema, - interpolatableRobotAzureImportInstructionsWithHiddenFieldsSchema, - interpolatableRobotAzureStoreInstructionsWithHiddenFieldsSchema, - interpolatableRobotBackblazeImportInstructionsWithHiddenFieldsSchema, - interpolatableRobotBackblazeStoreInstructionsWithHiddenFieldsSchema, - interpolatableRobotCloudfilesImportInstructionsWithHiddenFieldsSchema, - interpolatableRobotCloudfilesStoreInstructionsWithHiddenFieldsSchema, - interpolatableRobotCloudflareImportInstructionsWithHiddenFieldsSchema, - interpolatableRobotCloudflareStoreInstructionsWithHiddenFieldsSchema, - interpolatableRobotDigitaloceanImportInstructionsWithHiddenFieldsSchema, - interpolatableRobotDigitaloceanStoreInstructionsWithHiddenFieldsSchema, - interpolatableRobotDocumentAutorotateInstructionsWithHiddenFieldsSchema, - interpolatableRobotDocumentConvertInstructionsWithHiddenFieldsSchema, - interpolatableRobotDocumentMergeInstructionsWithHiddenFieldsSchema, - interpolatableRobotDocumentOcrInstructionsWithHiddenFieldsSchema, - interpolatableRobotFileReadInstructionsWithHiddenFieldsSchema, - interpolatableRobotDocumentSplitInstructionsWithHiddenFieldsSchema, - interpolatableRobotDocumentThumbsInstructionsWithHiddenFieldsSchema, - interpolatableRobotDropboxImportInstructionsWithHiddenFieldsSchema, - interpolatableRobotDropboxStoreInstructionsWithHiddenFieldsSchema, - interpolatableRobotEdglyDeliverInstructionsWithHiddenFieldsSchema, - interpolatableRobotFileCompressInstructionsWithHiddenFieldsSchema, - interpolatableRobotFileDecompressInstructionsWithHiddenFieldsSchema, - interpolatableRobotFileFilterInstructionsWithHiddenFieldsSchema, - interpolatableRobotFileHashInstructionsWithHiddenFieldsSchema, - interpolatableRobotFilePreviewInstructionsWithHiddenFieldsSchema, - interpolatableRobotFileServeInstructionsWithHiddenFieldsSchema, - interpolatableRobotFileVerifyInstructionsWithHiddenFieldsSchema, - interpolatableRobotFileVirusscanInstructionsWithHiddenFieldsSchema, - interpolatableRobotFileWatermarkInstructionsWithHiddenFieldsSchema, - interpolatableRobotFtpImportInstructionsWithHiddenFieldsSchema, - interpolatableRobotFtpStoreInstructionsWithHiddenFieldsSchema, - interpolatableRobotGoogleImportInstructionsWithHiddenFieldsSchema, - interpolatableRobotGoogleStoreInstructionsWithHiddenFieldsSchema, - interpolatableRobotHtmlConvertInstructionsWithHiddenFieldsSchema, - interpolatableRobotHttpImportInstructionsWithHiddenFieldsSchema, - interpolatableRobotImageBgremoveInstructionsWithHiddenFieldsSchema, - interpolatableRobotImageDescribeInstructionsWithHiddenFieldsSchema, - interpolatableRobotImageFacedetectInstructionsWithHiddenFieldsSchema, - interpolatableRobotImageGenerateInstructionsWithHiddenFieldsSchema, - interpolatableRobotImageMergeInstructionsWithHiddenFieldsSchema, - interpolatableRobotImageOcrInstructionsWithHiddenFieldsSchema, - interpolatableRobotImageOptimizeInstructionsWithHiddenFieldsSchema, - interpolatableRobotImageResizeInstructionsWithHiddenFieldsSchema, - interpolatableRobotMetaWriteInstructionsWithHiddenFieldsSchema, - interpolatableRobotMinioImportInstructionsWithHiddenFieldsSchema, - interpolatableRobotMinioStoreInstructionsWithHiddenFieldsSchema, - interpolatableRobotS3ImportInstructionsWithHiddenFieldsSchema, - interpolatableRobotS3StoreInstructionsWithHiddenFieldsSchema, - interpolatableRobotScriptRunInstructionsWithHiddenFieldsSchema, - interpolatableRobotSftpImportInstructionsWithHiddenFieldsSchema, - interpolatableRobotSftpStoreInstructionsWithHiddenFieldsSchema, - interpolatableRobotSpeechTranscribeInstructionsWithHiddenFieldsSchema, - interpolatableRobotSupabaseImportInstructionsWithHiddenFieldsSchema, - interpolatableRobotSupabaseStoreInstructionsWithHiddenFieldsSchema, - interpolatableRobotSwiftImportInstructionsWithHiddenFieldsSchema, - interpolatableRobotSwiftStoreInstructionsWithHiddenFieldsSchema, - interpolatableRobotTextSpeakInstructionsWithHiddenFieldsSchema, - interpolatableRobotTextTranslateInstructionsWithHiddenFieldsSchema, - interpolatableRobotAiChatInstructionsWithHiddenFieldsSchema, - interpolatableRobotTigrisImportInstructionsWithHiddenFieldsSchema, - interpolatableRobotTigrisStoreInstructionsWithHiddenFieldsSchema, - interpolatableRobotTlcdnDeliverInstructionsWithHiddenFieldsSchema, - interpolatableRobotTusStoreInstructionsWithHiddenFieldsSchema, - interpolatableRobotUploadHandleInstructionsWithHiddenFieldsSchema, - interpolatableRobotVideoAdaptiveInstructionsWithHiddenFieldsSchema, - interpolatableRobotVideoConcatInstructionsWithHiddenFieldsSchema, - interpolatableRobotVideoEncodeInstructionsWithHiddenFieldsSchema, - interpolatableRobotVideoMergeInstructionsWithHiddenFieldsSchema, - interpolatableRobotVideoOndemandInstructionsWithHiddenFieldsSchema, - interpolatableRobotVideoSubtitleInstructionsWithHiddenFieldsSchema, - interpolatableRobotVideoThumbsInstructionsWithHiddenFieldsSchema, - interpolatableRobotVimeoImportInstructionsWithHiddenFieldsSchema, - interpolatableRobotVimeoStoreInstructionsWithHiddenFieldsSchema, - interpolatableRobotWasabiImportInstructionsWithHiddenFieldsSchema, - interpolatableRobotWasabiStoreInstructionsWithHiddenFieldsSchema, - interpolatableRobotYoutubeStoreInstructionsWithHiddenFieldsSchema, -] as const - -/** - * Public robot instructions - */ -export type RobotsSchema = z.infer -export const robotsSchema = z.discriminatedUnion('robot', [...robotStepsInstructions]) -export const robotsWithHiddenFieldsSchema = z.discriminatedUnion('robot', [ - ...robotStepsInstructionsWithHiddenFields, -]) - -/** - * All robot instructions, including private ones. - */ -export const robotsWithHiddenBotsSchema = z.discriminatedUnion('robot', [ - ...robotStepsInstructions, - interpolatableRobotFileWatermarkInstructionsSchema, - interpolatableRobotMetaReadInstructionsSchema, - interpolatableRobotProgressSimulateInstructionsSchema, -]) -export const robotsWithHiddenBotsAndFieldsSchema = z.discriminatedUnion('robot', [ - ...robotStepsInstructionsWithHiddenFields, - interpolatableRobotMetaReadInstructionsWithHiddenFieldsSchema, - interpolatableRobotProgressSimulateInstructionsSchema, -]) - -export type RobotsWithHiddenBots = z.infer -export type RobotsWithHiddenBotsAndFields = z.infer - -export const robotsMeta = { - aiChatMeta, - audioArtworkMeta, - audioConcatMeta, - audioEncodeMeta, - audioLoopMeta, - audioMergeMeta, - audioWaveformMeta, - azureImportMeta, - azureStoreMeta, - backblazeImportMeta, - backblazeStoreMeta, - cloudfilesImportMeta, - cloudfilesStoreMeta, - cloudflareImportMeta, - cloudflareStoreMeta, - digitaloceanImportMeta, - digitaloceanStoreMeta, - documentAutorotateMeta, - documentConvertMeta, - documentMergeMeta, - documentOcrMeta, - documentSplitMeta, - documentThumbsMeta, - dropboxImportMeta, - dropboxStoreMeta, - edglyDeliverMeta, - fileCompressMeta, - fileDecompressMeta, - fileFilterMeta, - fileHashMeta, - filePreviewMeta, - fileReadMeta, - fileServeMeta, - fileVerifyMeta, - fileVirusscanMeta, - ftpImportMeta, - ftpStoreMeta, - googleImportMeta, - googleStoreMeta, - htmlConvertMeta, - httpImportMeta, - imageDescribeMeta, - imageFacedetectMeta, - imageBgremoveMeta, - imageGenerateMeta, - imageMergeMeta, - imageOcrMeta, - imageOptimizeMeta, - imageResizeMeta, - metaWriteMeta, - minioImportMeta, - minioStoreMeta, - s3ImportMeta, - s3StoreMeta, - scriptRunMeta, - sftpImportMeta, - sftpStoreMeta, - speechTranscribeMeta, - supabaseImportMeta, - supabaseStoreMeta, - swiftImportMeta, - swiftStoreMeta, - textSpeakMeta, - textTranslateMeta, - tigrisImport, - tigrisStore, - tlcdnDeliverMeta, - tusStoreMeta, - uploadHandleMeta, - videoAdaptiveMeta, - videoConcatMeta, - videoEncodeMeta, - videoMergeMeta, - videoOndemandMeta, - videoSubtitleMeta, - videoThumbsMeta, - vimeoImportMeta, - vimeoStoreMeta, - wasabiImportMeta, - wasabiStoreMeta, - youtubeStoreMeta, -} - -export type { - InterpolatableRobotAiChatInstructions, - InterpolatableRobotAiChatInstructionsInput, - InterpolatableRobotAiChatInstructionsWithHiddenFields, - InterpolatableRobotAiChatInstructionsWithHiddenFieldsInput, -} from './ai-chat.ts' -export type { - InterpolatableRobotAssemblySavejsonInstructions, - InterpolatableRobotAssemblySavejsonInstructionsInput, -} from './assembly-savejson.ts' -export type { - InterpolatableRobotAudioArtworkInstructions, - InterpolatableRobotAudioArtworkInstructionsInput, - InterpolatableRobotAudioArtworkInstructionsWithHiddenFields, - InterpolatableRobotAudioArtworkInstructionsWithHiddenFieldsInput, -} from './audio-artwork.ts' -export type { - InterpolatableRobotAudioConcatInstructions, - InterpolatableRobotAudioConcatInstructionsInput, - InterpolatableRobotAudioConcatInstructionsWithHiddenFields, - InterpolatableRobotAudioConcatInstructionsWithHiddenFieldsInput, -} from './audio-concat.ts' -export type { - InterpolatableRobotAudioEncodeInstructions, - InterpolatableRobotAudioEncodeInstructionsInput, - InterpolatableRobotAudioEncodeInstructionsWithHiddenFields, - InterpolatableRobotAudioEncodeInstructionsWithHiddenFieldsInput, -} from './audio-encode.ts' -export type { - InterpolatableRobotAudioLoopInstructions, - InterpolatableRobotAudioLoopInstructionsInput, - InterpolatableRobotAudioLoopInstructionsWithHiddenFields, - InterpolatableRobotAudioLoopInstructionsWithHiddenFieldsInput, -} from './audio-loop.ts' -export type { - InterpolatableRobotAudioMergeInstructions, - InterpolatableRobotAudioMergeInstructionsInput, - InterpolatableRobotAudioMergeInstructionsWithHiddenFields, - InterpolatableRobotAudioMergeInstructionsWithHiddenFieldsInput, -} from './audio-merge.ts' -export type { - InterpolatableRobotAudioWaveformInstructions, - InterpolatableRobotAudioWaveformInstructionsInput, - InterpolatableRobotAudioWaveformInstructionsWithHiddenFields, - InterpolatableRobotAudioWaveformInstructionsWithHiddenFieldsInput, -} from './audio-waveform.ts' -export type { - InterpolatableRobotAzureImportInstructions, - InterpolatableRobotAzureImportInstructionsInput, - InterpolatableRobotAzureImportInstructionsWithHiddenFields, - InterpolatableRobotAzureImportInstructionsWithHiddenFieldsInput, -} from './azure-import.ts' -export type { - InterpolatableRobotAzureStoreInstructions, - InterpolatableRobotAzureStoreInstructionsInput, - InterpolatableRobotAzureStoreInstructionsWithHiddenFields, - InterpolatableRobotAzureStoreInstructionsWithHiddenFieldsInput, -} from './azure-store.ts' -export type { - InterpolatableRobotBackblazeImportInstructions, - InterpolatableRobotBackblazeImportInstructionsInput, - InterpolatableRobotBackblazeImportInstructionsWithHiddenFields, - InterpolatableRobotBackblazeImportInstructionsWithHiddenFieldsInput, -} from './backblaze-import.ts' -export type { - InterpolatableRobotBackblazeStoreInstructions, - InterpolatableRobotBackblazeStoreInstructionsInput, - InterpolatableRobotBackblazeStoreInstructionsWithHiddenFields, - InterpolatableRobotBackblazeStoreInstructionsWithHiddenFieldsInput, -} from './backblaze-store.ts' -export type { - InterpolatableRobotCloudfilesImportInstructions, - InterpolatableRobotCloudfilesImportInstructionsInput, - InterpolatableRobotCloudfilesImportInstructionsWithHiddenFields, - InterpolatableRobotCloudfilesImportInstructionsWithHiddenFieldsInput, -} from './cloudfiles-import.ts' -export type { - InterpolatableRobotCloudfilesStoreInstructions, - InterpolatableRobotCloudfilesStoreInstructionsInput, - InterpolatableRobotCloudfilesStoreInstructionsWithHiddenFields, - InterpolatableRobotCloudfilesStoreInstructionsWithHiddenFieldsInput, -} from './cloudfiles-store.ts' -export type { - InterpolatableRobotCloudflareImportInstructions, - InterpolatableRobotCloudflareImportInstructionsInput, - InterpolatableRobotCloudflareImportInstructionsWithHiddenFields, - InterpolatableRobotCloudflareImportInstructionsWithHiddenFieldsInput, -} from './cloudflare-import.ts' -export type { - InterpolatableRobotCloudflareStoreInstructions, - InterpolatableRobotCloudflareStoreInstructionsInput, - InterpolatableRobotCloudflareStoreInstructionsWithHiddenFields, - InterpolatableRobotCloudflareStoreInstructionsWithHiddenFieldsInput, -} from './cloudflare-store.ts' -export type { - InterpolatableRobotDigitaloceanImportInstructions, - InterpolatableRobotDigitaloceanImportInstructionsInput, - InterpolatableRobotDigitaloceanImportInstructionsWithHiddenFields, - InterpolatableRobotDigitaloceanImportInstructionsWithHiddenFieldsInput, -} from './digitalocean-import.ts' -export type { - InterpolatableRobotDigitaloceanStoreInstructions, - InterpolatableRobotDigitaloceanStoreInstructionsInput, - InterpolatableRobotDigitaloceanStoreInstructionsWithHiddenFields, - InterpolatableRobotDigitaloceanStoreInstructionsWithHiddenFieldsInput, -} from './digitalocean-store.ts' -export type { - InterpolatableRobotDocumentAutorotateInstructions, - InterpolatableRobotDocumentAutorotateInstructionsInput, - InterpolatableRobotDocumentAutorotateInstructionsWithHiddenFields, - InterpolatableRobotDocumentAutorotateInstructionsWithHiddenFieldsInput, -} from './document-autorotate.ts' -export type { - InterpolatableRobotDocumentConvertInstructions, - InterpolatableRobotDocumentConvertInstructionsInput, - InterpolatableRobotDocumentConvertInstructionsWithHiddenFields, - InterpolatableRobotDocumentConvertInstructionsWithHiddenFieldsInput, -} from './document-convert.ts' -export type { - InterpolatableRobotDocumentMergeInstructions, - InterpolatableRobotDocumentMergeInstructionsInput, - InterpolatableRobotDocumentMergeInstructionsWithHiddenFields, - InterpolatableRobotDocumentMergeInstructionsWithHiddenFieldsInput, -} from './document-merge.ts' -export type { - InterpolatableRobotDocumentOcrInstructions, - InterpolatableRobotDocumentOcrInstructionsInput, - InterpolatableRobotDocumentOcrInstructionsWithHiddenFields, - InterpolatableRobotDocumentOcrInstructionsWithHiddenFieldsInput, -} from './document-ocr.ts' -export type { - InterpolatableRobotDocumentSplitInstructions, - InterpolatableRobotDocumentSplitInstructionsInput, - InterpolatableRobotDocumentSplitInstructionsWithHiddenFields, - InterpolatableRobotDocumentSplitInstructionsWithHiddenFieldsInput, -} from './document-split.ts' -export type { - InterpolatableRobotDocumentThumbsInstructions, - InterpolatableRobotDocumentThumbsInstructionsInput, - InterpolatableRobotDocumentThumbsInstructionsWithHiddenFields, - InterpolatableRobotDocumentThumbsInstructionsWithHiddenFieldsInput, -} from './document-thumbs.ts' -export type { - InterpolatableRobotDropboxImportInstructions, - InterpolatableRobotDropboxImportInstructionsInput, - InterpolatableRobotDropboxImportInstructionsWithHiddenFields, - InterpolatableRobotDropboxImportInstructionsWithHiddenFieldsInput, -} from './dropbox-import.ts' -export type { - InterpolatableRobotDropboxStoreInstructions, - InterpolatableRobotDropboxStoreInstructionsInput, - InterpolatableRobotDropboxStoreInstructionsWithHiddenFields, - InterpolatableRobotDropboxStoreInstructionsWithHiddenFieldsInput, -} from './dropbox-store.ts' -export type { - InterpolatableRobotEdglyDeliverInstructions, - InterpolatableRobotEdglyDeliverInstructionsInput, - InterpolatableRobotEdglyDeliverInstructionsWithHiddenFields, - InterpolatableRobotEdglyDeliverInstructionsWithHiddenFieldsInput, -} from './edgly-deliver.ts' -export type { - InterpolatableRobotFileCompressInstructions, - InterpolatableRobotFileCompressInstructionsInput, - InterpolatableRobotFileCompressInstructionsWithHiddenFields, - InterpolatableRobotFileCompressInstructionsWithHiddenFieldsInput, -} from './file-compress.ts' -export type { - InterpolatableRobotFileDecompressInstructions, - InterpolatableRobotFileDecompressInstructionsInput, - InterpolatableRobotFileDecompressInstructionsWithHiddenFields, - InterpolatableRobotFileDecompressInstructionsWithHiddenFieldsInput, -} from './file-decompress.ts' -export type { - InterpolatableRobotFileFilterInstructions, - InterpolatableRobotFileFilterInstructionsInput, - InterpolatableRobotFileFilterInstructionsWithHiddenFields, - InterpolatableRobotFileFilterInstructionsWithHiddenFieldsInput, -} from './file-filter.ts' -export type { - InterpolatableRobotFileHashInstructions, - InterpolatableRobotFileHashInstructionsInput, - InterpolatableRobotFileHashInstructionsWithHiddenFields, - InterpolatableRobotFileHashInstructionsWithHiddenFieldsInput, -} from './file-hash.ts' -export type { - InterpolatableRobotFilePreviewInstructions, - InterpolatableRobotFilePreviewInstructionsInput, - InterpolatableRobotFilePreviewInstructionsWithHiddenFields, - InterpolatableRobotFilePreviewInstructionsWithHiddenFieldsInput, -} from './file-preview.ts' -export type { - InterpolatableRobotFileReadInstructions, - InterpolatableRobotFileReadInstructionsInput, - InterpolatableRobotFileReadInstructionsWithHiddenFields, - InterpolatableRobotFileReadInstructionsWithHiddenFieldsInput, -} from './file-read.ts' -export type { - InterpolatableRobotFileServeInstructions, - InterpolatableRobotFileServeInstructionsInput, - InterpolatableRobotFileServeInstructionsWithHiddenFields, - InterpolatableRobotFileServeInstructionsWithHiddenFieldsInput, -} from './file-serve.ts' -export type { - InterpolatableRobotFileVerifyInstructions, - InterpolatableRobotFileVerifyInstructionsInput, - InterpolatableRobotFileVerifyInstructionsWithHiddenFields, - InterpolatableRobotFileVerifyInstructionsWithHiddenFieldsInput, -} from './file-verify.ts' -export type { - InterpolatableRobotFileVirusscanInstructions, - InterpolatableRobotFileVirusscanInstructionsInput, - InterpolatableRobotFileVirusscanInstructionsWithHiddenFields, - InterpolatableRobotFileVirusscanInstructionsWithHiddenFieldsInput, -} from './file-virusscan.ts' -export type { - InterpolatableRobotFileWatermarkInstructions, - InterpolatableRobotFileWatermarkInstructionsInput, - InterpolatableRobotFileWatermarkInstructionsWithHiddenFields, - InterpolatableRobotFileWatermarkInstructionsWithHiddenFieldsInput, -} from './file-watermark.ts' -export type { - InterpolatableRobotFtpImportInstructions, - InterpolatableRobotFtpImportInstructionsInput, - InterpolatableRobotFtpImportInstructionsWithHiddenFields, - InterpolatableRobotFtpImportInstructionsWithHiddenFieldsInput, -} from './ftp-import.ts' -export type { - InterpolatableRobotFtpStoreInstructions, - InterpolatableRobotFtpStoreInstructionsInput, - InterpolatableRobotFtpStoreInstructionsWithHiddenFields, - InterpolatableRobotFtpStoreInstructionsWithHiddenFieldsInput, -} from './ftp-store.ts' -export type { - InterpolatableRobotGoogleImportInstructions, - InterpolatableRobotGoogleImportInstructionsInput, - InterpolatableRobotGoogleImportInstructionsWithHiddenFields, - InterpolatableRobotGoogleImportInstructionsWithHiddenFieldsInput, -} from './google-import.ts' -export type { - InterpolatableRobotGoogleStoreInstructions, - InterpolatableRobotGoogleStoreInstructionsInput, - InterpolatableRobotGoogleStoreInstructionsWithHiddenFields, - InterpolatableRobotGoogleStoreInstructionsWithHiddenFieldsInput, -} from './google-store.ts' -export type { - InterpolatableRobotHtmlConvertInstructions, - InterpolatableRobotHtmlConvertInstructionsInput, - InterpolatableRobotHtmlConvertInstructionsWithHiddenFields, - InterpolatableRobotHtmlConvertInstructionsWithHiddenFieldsInput, -} from './html-convert.ts' -export type { - InterpolatableRobotHttpImportInstructions, - InterpolatableRobotHttpImportInstructionsInput, - InterpolatableRobotHttpImportInstructionsWithHiddenFields, - InterpolatableRobotHttpImportInstructionsWithHiddenFieldsInput, -} from './http-import.ts' -export type { - InterpolatableRobotImageBgremoveInstructions, - InterpolatableRobotImageBgremoveInstructionsInput, - InterpolatableRobotImageBgremoveInstructionsWithHiddenFields, - InterpolatableRobotImageBgremoveInstructionsWithHiddenFieldsInput, -} from './image-bgremove.ts' -export type { - InterpolatableRobotImageDescribeInstructions, - InterpolatableRobotImageDescribeInstructionsInput, - InterpolatableRobotImageDescribeInstructionsWithHiddenFields, - InterpolatableRobotImageDescribeInstructionsWithHiddenFieldsInput, -} from './image-describe.ts' -export type { - InterpolatableRobotImageFacedetectInstructions, - InterpolatableRobotImageFacedetectInstructionsInput, - InterpolatableRobotImageFacedetectInstructionsWithHiddenFields, - InterpolatableRobotImageFacedetectInstructionsWithHiddenFieldsInput, -} from './image-facedetect.ts' -export type { - InterpolatableRobotImageGenerateInstructions, - InterpolatableRobotImageGenerateInstructionsInput, - InterpolatableRobotImageGenerateInstructionsWithHiddenFields, - InterpolatableRobotImageGenerateInstructionsWithHiddenFieldsInput, -} from './image-generate.ts' -export type { - InterpolatableRobotImageMergeInstructions, - InterpolatableRobotImageMergeInstructionsInput, - InterpolatableRobotImageMergeInstructionsWithHiddenFields, - InterpolatableRobotImageMergeInstructionsWithHiddenFieldsInput, -} from './image-merge.ts' -export type { - InterpolatableRobotImageOcrInstructions, - InterpolatableRobotImageOcrInstructionsInput, - InterpolatableRobotImageOcrInstructionsWithHiddenFields, - InterpolatableRobotImageOcrInstructionsWithHiddenFieldsInput, -} from './image-ocr.ts' -export type { - InterpolatableRobotImageOptimizeInstructions, - InterpolatableRobotImageOptimizeInstructionsInput, - InterpolatableRobotImageOptimizeInstructionsWithHiddenFields, - InterpolatableRobotImageOptimizeInstructionsWithHiddenFieldsInput, -} from './image-optimize.ts' -export type { - InterpolatableRobotImageResizeInstructions, - InterpolatableRobotImageResizeInstructionsInput, - InterpolatableRobotImageResizeInstructionsWithHiddenFields, - InterpolatableRobotImageResizeInstructionsWithHiddenFieldsInput, -} from './image-resize.ts' -export type { - InterpolatableRobotMetaReadInstructions, - InterpolatableRobotMetaReadInstructionsInput, - InterpolatableRobotMetaReadInstructionsWithHiddenFields, -} from './meta-read.ts' -export type { - InterpolatableRobotMetaWriteInstructions, - InterpolatableRobotMetaWriteInstructionsInput, - InterpolatableRobotMetaWriteInstructionsWithHiddenFields, - InterpolatableRobotMetaWriteInstructionsWithHiddenFieldsInput, -} from './meta-write.ts' -export type { - InterpolatableRobotMinioImportInstructions, - InterpolatableRobotMinioImportInstructionsInput, - InterpolatableRobotMinioImportInstructionsWithHiddenFields, - InterpolatableRobotMinioImportInstructionsWithHiddenFieldsInput, -} from './minio-import.ts' -export type { - InterpolatableRobotMinioStoreInstructions, - InterpolatableRobotMinioStoreInstructionsInput, - InterpolatableRobotMinioStoreInstructionsWithHiddenFields, - InterpolatableRobotMinioStoreInstructionsWithHiddenFieldsInput, -} from './minio-store.ts' -export type { - InterpolatableRobotProgressSimulateInstructions, - InterpolatableRobotProgressSimulateInstructionsInput, -} from './progress-simulate.ts' -export type { - InterpolatableRobotS3ImportInstructions, - InterpolatableRobotS3ImportInstructionsInput, - InterpolatableRobotS3ImportInstructionsWithHiddenFields, - InterpolatableRobotS3ImportInstructionsWithHiddenFieldsInput, -} from './s3-import.ts' -export type { - InterpolatableRobotS3StoreInstructions, - InterpolatableRobotS3StoreInstructionsInput, - InterpolatableRobotS3StoreInstructionsWithHiddenFields, - InterpolatableRobotS3StoreInstructionsWithHiddenFieldsInput, -} from './s3-store.ts' -export type { - InterpolatableRobotScriptRunInstructions, - InterpolatableRobotScriptRunInstructionsInput, - InterpolatableRobotScriptRunInstructionsWithHiddenFields, - InterpolatableRobotScriptRunInstructionsWithHiddenFieldsInput, -} from './script-run.ts' -export type { - InterpolatableRobotSftpImportInstructions, - InterpolatableRobotSftpImportInstructionsInput, - InterpolatableRobotSftpImportInstructionsWithHiddenFields, - InterpolatableRobotSftpImportInstructionsWithHiddenFieldsInput, -} from './sftp-import.ts' -export type { - InterpolatableRobotSftpStoreInstructions, - InterpolatableRobotSftpStoreInstructionsInput, - InterpolatableRobotSftpStoreInstructionsWithHiddenFields, - InterpolatableRobotSftpStoreInstructionsWithHiddenFieldsInput, -} from './sftp-store.ts' -export type { - InterpolatableRobotSpeechTranscribeInstructions, - InterpolatableRobotSpeechTranscribeInstructionsInput, - InterpolatableRobotSpeechTranscribeInstructionsWithHiddenFields, - InterpolatableRobotSpeechTranscribeInstructionsWithHiddenFieldsInput, -} from './speech-transcribe.ts' -export type { - InterpolatableRobotSupabaseImportInstructions, - InterpolatableRobotSupabaseImportInstructionsInput, - InterpolatableRobotSupabaseImportInstructionsWithHiddenFields, - InterpolatableRobotSupabaseImportInstructionsWithHiddenFieldsInput, -} from './supabase-import.ts' -export type { - InterpolatableRobotSupabaseStoreInstructions, - InterpolatableRobotSupabaseStoreInstructionsInput, - InterpolatableRobotSupabaseStoreInstructionsWithHiddenFields, - InterpolatableRobotSupabaseStoreInstructionsWithHiddenFieldsInput, -} from './supabase-store.ts' -export type { - InterpolatableRobotSwiftImportInstructions, - InterpolatableRobotSwiftImportInstructionsInput, - InterpolatableRobotSwiftImportInstructionsWithHiddenFields, - InterpolatableRobotSwiftImportInstructionsWithHiddenFieldsInput, -} from './swift-import.ts' -export type { - InterpolatableRobotSwiftStoreInstructions, - InterpolatableRobotSwiftStoreInstructionsInput, - InterpolatableRobotSwiftStoreInstructionsWithHiddenFields, - InterpolatableRobotSwiftStoreInstructionsWithHiddenFieldsInput, -} from './swift-store.ts' -export type { - InterpolatableRobotTextSpeakInstructions, - InterpolatableRobotTextSpeakInstructionsInput, - InterpolatableRobotTextSpeakInstructionsWithHiddenFields, - InterpolatableRobotTextSpeakInstructionsWithHiddenFieldsInput, -} from './text-speak.ts' -export type { - InterpolatableRobotTextTranslateInstructions, - InterpolatableRobotTextTranslateInstructionsInput, - InterpolatableRobotTextTranslateInstructionsWithHiddenFields, - InterpolatableRobotTextTranslateInstructionsWithHiddenFieldsInput, -} from './text-translate.ts' -export type { - InterpolatableRobotTigrisImportInstructions, - InterpolatableRobotTigrisImportInstructionsInput, - InterpolatableRobotTigrisImportInstructionsWithHiddenFields, - InterpolatableRobotTigrisImportInstructionsWithHiddenFieldsInput, -} from './tigris-import.ts' -export type { - InterpolatableRobotTigrisStoreInstructions, - InterpolatableRobotTigrisStoreInstructionsInput, - InterpolatableRobotTigrisStoreInstructionsWithHiddenFields, - InterpolatableRobotTigrisStoreInstructionsWithHiddenFieldsInput, -} from './tigris-store.ts' -export type { - InterpolatableRobotTlcdnDeliverInstructions, - InterpolatableRobotTlcdnDeliverInstructionsInput, - InterpolatableRobotTlcdnDeliverInstructionsWithHiddenFields, - InterpolatableRobotTlcdnDeliverInstructionsWithHiddenFieldsInput, -} from './tlcdn-deliver.ts' -export type { - InterpolatableRobotTusStoreInstructions, - InterpolatableRobotTusStoreInstructionsInput, - InterpolatableRobotTusStoreInstructionsWithHiddenFields, - InterpolatableRobotTusStoreInstructionsWithHiddenFieldsInput, -} from './tus-store.ts' -export type { - InterpolatableRobotUploadHandleInstructions, - InterpolatableRobotUploadHandleInstructionsInput, - InterpolatableRobotUploadHandleInstructionsWithHiddenFields, - InterpolatableRobotUploadHandleInstructionsWithHiddenFieldsInput, -} from './upload-handle.ts' -export type { - InterpolatableRobotVideoAdaptiveInstructions, - InterpolatableRobotVideoAdaptiveInstructionsInput, - InterpolatableRobotVideoAdaptiveInstructionsWithHiddenFields, - InterpolatableRobotVideoAdaptiveInstructionsWithHiddenFieldsInput, -} from './video-adaptive.ts' -export type { - InterpolatableRobotVideoConcatInstructions, - InterpolatableRobotVideoConcatInstructionsInput, - InterpolatableRobotVideoConcatInstructionsWithHiddenFields, - InterpolatableRobotVideoConcatInstructionsWithHiddenFieldsInput, -} from './video-concat.ts' -export type { - InterpolatableRobotVideoEncodeInstructions, - InterpolatableRobotVideoEncodeInstructionsInput, - InterpolatableRobotVideoEncodeInstructionsWithHiddenFields, - InterpolatableRobotVideoEncodeInstructionsWithHiddenFieldsInput, -} from './video-encode.ts' -export type { - InterpolatableRobotVideoMergeInstructions, - InterpolatableRobotVideoMergeInstructionsInput, - InterpolatableRobotVideoMergeInstructionsWithHiddenFields, - InterpolatableRobotVideoMergeInstructionsWithHiddenFieldsInput, -} from './video-merge.ts' -export type { - InterpolatableRobotVideoOndemandInstructions, - InterpolatableRobotVideoOndemandInstructionsInput, - InterpolatableRobotVideoOndemandInstructionsWithHiddenFields, - InterpolatableRobotVideoOndemandInstructionsWithHiddenFieldsInput, -} from './video-ondemand.ts' -export type { - InterpolatableRobotVideoSubtitleInstructions, - InterpolatableRobotVideoSubtitleInstructionsInput, - InterpolatableRobotVideoSubtitleInstructionsWithHiddenFields, - InterpolatableRobotVideoSubtitleInstructionsWithHiddenFieldsInput, -} from './video-subtitle.ts' -export type { - InterpolatableRobotVideoThumbsInstructions, - InterpolatableRobotVideoThumbsInstructionsInput, - InterpolatableRobotVideoThumbsInstructionsWithHiddenFields, - InterpolatableRobotVideoThumbsInstructionsWithHiddenFieldsInput, -} from './video-thumbs.ts' -export type { - InterpolatableRobotVimeoImportInstructions, - InterpolatableRobotVimeoImportInstructionsInput, - InterpolatableRobotVimeoImportInstructionsWithHiddenFields, - InterpolatableRobotVimeoImportInstructionsWithHiddenFieldsInput, -} from './vimeo-import.ts' -export type { - InterpolatableRobotVimeoStoreInstructions, - InterpolatableRobotVimeoStoreInstructionsInput, - InterpolatableRobotVimeoStoreInstructionsWithHiddenFields, - InterpolatableRobotVimeoStoreInstructionsWithHiddenFieldsInput, -} from './vimeo-store.ts' -export type { - InterpolatableRobotWasabiImportInstructions, - InterpolatableRobotWasabiImportInstructionsInput, - InterpolatableRobotWasabiImportInstructionsWithHiddenFields, - InterpolatableRobotWasabiImportInstructionsWithHiddenFieldsInput, -} from './wasabi-import.ts' -export type { - InterpolatableRobotWasabiStoreInstructions, - InterpolatableRobotWasabiStoreInstructionsInput, - InterpolatableRobotWasabiStoreInstructionsWithHiddenFields, - InterpolatableRobotWasabiStoreInstructionsWithHiddenFieldsInput, -} from './wasabi-store.ts' -export type { - InterpolatableRobotYoutubeStoreInstructions, - InterpolatableRobotYoutubeStoreInstructionsInput, - InterpolatableRobotYoutubeStoreInstructionsWithHiddenFields, - InterpolatableRobotYoutubeStoreInstructionsWithHiddenFieldsInput, -} from './youtube-store.ts' diff --git a/packages/transloadit/src/alphalib/types/robots/_instructions-primitives.ts b/packages/transloadit/src/alphalib/types/robots/_instructions-primitives.ts deleted file mode 100644 index 50058199..00000000 --- a/packages/transloadit/src/alphalib/types/robots/_instructions-primitives.ts +++ /dev/null @@ -1,1770 +0,0 @@ -import type { Replace } from 'type-fest' -import { z } from 'zod' - -import { stackVersions } from '../stackVersions.ts' - -export const robotNames = z.enum([ - 'AiChatRobot', - 'UploadHandleRobot', - 'FileServeRobot', - 'FileWatermarkRobot', - 'FileVerifyRobot', - 'EdglyDeliverRobot', - 'TlcdnDeliverRobot', - 'VideoSubtitleRobot', - 'VideoEncodeRobot', - 'VideoAdaptiveRobot', - 'VideoMergeRobot', - 'VideoConcatRobot', - 'AudioWaveformRobot', - 'AudioEncodeRobot', - 'AudioLoopRobot', - 'AudioConcatRobot', - 'AudioMergeRobot', - 'AudioArtworkRobot', - 'ImageFacedetectRobot', - 'ImageDescribeRobot', - 'ImageOcrRobot', - 'ImageBgremoveRobot', - 'ImageGenerateRobot', - 'DocumentOcrRobot', - 'SpeechTranscribeRobot', - 'VideoThumbsRobot', - 'FileVirusscanRobot', - 'ImageOptimizeRobot', - 'FileCompressRobot', - 'MetaReadRobot', - 'FileDecompressRobot', - 'MetaWriteRobot', - 'DocumentThumbsRobot', - 'DocumentConvertRobot', - 'DocumentMergeRobot', - 'DocumentSplitRobot', - 'DocumentAutorotateRobot', - 'HtmlConvertRobot', - 'ImageResizeRobot', - 'ImageMergeRobot', - 'S3ImportRobot', - 'S3StoreRobot', - 'DigitalOceanImportRobot', - 'DigitalOceanStoreRobot', - 'BackblazeImportRobot', - 'BackblazeStoreRobot', - 'MinioImportRobot', - 'TigrisImportRobot', - 'CloudflareImportRobot', - 'SupabaseImportRobot', - 'MinioStoreRobot', - 'TigrisStoreRobot', - 'CloudflareStoreRobot', - 'SupabaseStoreRobot', - 'WasabiImportRobot', - 'WasabiStoreRobot', - 'SwiftImportRobot', - 'SwiftStoreRobot', - 'GoogleImportRobot', - 'GoogleStoreRobot', - 'DropboxImportRobot', - 'DropboxStoreRobot', - 'HttpImportRobot', - 'SftpImportRobot', - 'SftpStoreRobot', - 'FtpImportRobot', - 'FtpStoreRobot', - 'CloudfilesImportRobot', - 'CloudfilesStoreRobot', - 'AzureImportRobot', - 'AzureStoreRobot', - 'YoutubeStoreRobot', - 'VimeoImportRobot', - 'VimeoStoreRobot', - 'AssemblySavejsonRobot', - 'ScriptRunRobot', - 'FileHashRobot', - 'FileReadRobot', - 'VideoOndemandRobot', - 'FileFilterRobot', - 'TextSpeakRobot', - 'TextTranslateRobot', - 'FilePreviewRobot', - 'TusStoreRobot', - 'ProgressSimulateRobot', -]) - -export const robotMetaSchema = z.object({ - // Added keys from api2/lib/config.ts: - name: robotNames, - priceFactor: z.number(), - queueSlotCount: z.number(), - downloadInputFiles: z.boolean().optional(), - preserveInputFileUrls: z.boolean().optional(), - minimumCharge: z.number().optional(), - minimumChargeUsd: z.number().optional(), - minimumChargeUsdPerSpeechTranscribeMinute: z - .object({ - aws: z.number(), - gcp: z.number(), - }) - .optional(), - minimumChargeUsdPerDocumentOcrPage: z - .object({ - aws: z.number(), - gcp: z.number(), - }) - .optional(), - isAllowedForUrlTransform: z.boolean(), - removeJobResultFilesFromDiskRightAfterStoringOnS3: z.boolean(), - lazyLoad: z.boolean().optional(), - installVersionFile: z.string().optional(), - trackOutputFileSize: z.boolean().optional(), - isInternal: z.boolean(), - numDaemons: z.number().optional(), - stage: z.enum(['alpha', 'beta', 'ga', 'deprecated', 'removed']), - importRanges: z.array(z.string()).optional(), - extraChargeForImageResize: z.number().optional(), - - // Original keys from content repo: - allowed_for_url_transform: z.boolean(), - bytescount: z.number(), - description: z.string().optional(), - discount_factor: z.number(), - discount_pct: z.number(), - // To avoid a cycling dependency back to template.ts, we'll use any for now: - // example_code: assemblyInstructionsSchema.optional(), - example_code: z.any().optional(), - example_code_description: z.string().optional(), - extended_description: z.string().optional(), - has_small_icon: z.literal(true).optional(), - minimum_charge: z.number(), - minimum_charge_usd: z.union([z.number(), z.record(z.string(), z.number())]).optional(), - minimum_charge_usd_note: z.string().optional(), - ogimage: z.string().optional(), - marketing_intro: z.string().optional(), - output_factor: z.number(), - override_lvl1: z.string().optional(), - purpose_sentence: z.string(), - purpose_verb: z.enum([ - 'auto-rotate', - 'cache & deliver', - 'compress', - 'concatenate', - 'convert', - 'decompress', - 'detect', - 'encode', - 'export', - 'extract', - 'filter', - 'generate', - 'handle', - 'hash', - 'import', - 'loop', - 'merge', - 'optimize', - 'read', - 'recognize', - 'run', - 'scan', - 'serve', - 'speak', - 'subtitle', - 'take', - 'transcode', - 'transcribe', - 'translate', - 'verify', - 'remove', - 'write', - 'stream', - ]), - purpose_word: z.string(), - purpose_words: z.string(), - requires_credentials: z.literal(true).optional(), - service_slug: z.enum([ - 'artificial-intelligence', - 'audio-encoding', - 'code-evaluation', - 'content-delivery', - 'document-processing', - 'file-compressing', - 'file-exporting', - 'file-filtering', - 'file-importing', - 'handling-uploads', - 'image-manipulation', - 'media-cataloging', - 'video-encoding', - ]), - slot_count: z.number(), - title: z.string(), - typical_file_size_mb: z.number(), - typical_file_type: z.enum([ - 'audio file', - 'audio or video file', - 'document', - 'file', - 'image', - 'video', - 'webpage', - ]), - uses_tools: z.array(z.enum(['ffmpeg', 'imagemagick'])).optional(), -}) - -export type RobotMetaInput = z.input - -// These schemas can be reproduced with z.string().regex(). However, this causes some issues. -// We use this in combination with unions. Internally Zod normalizes unions. A string schema and -// enums merged in some way. Both are validated. Normally, if a Zod union has errors, all of them -// are surfaced. However, if the regex isn’t match, and none of the enum values overlap, then the -// regex error is raised instead of the union error. As a result, the best error we could give back -// to the user, is that there’s a problem with the interpolation syntax. But really the other error -// is more useful in pretty much every case. To work around this, we use z.custom() instead, as Zod -// can’t normalize that. -const interpolationRegexFull = /^\${.+}$/ -export const interpolationSchemaFull = z.custom<`\${${string}}`>( - (input) => typeof input === 'string' && interpolationRegexFull.test(input), - 'Must be a full interpolation string', -) -const interpolationRegexPartial = /\${.+}/ -export const interpolationSchemaPartial = z.custom( - (input) => typeof input === 'string' && interpolationRegexPartial.test(input), - 'Must be a partially interpolatable string', -) - -export const booleanStringSchema = z.enum(['true', 'false']) - -type InterpolatableTuple = Schemas extends readonly [ - infer Head extends z.ZodTypeAny, - ...infer Rest extends z.ZodTypeAny[], -] - ? [InterpolatableSchema, ...InterpolatableTuple] - : Schemas - -type InterpolatableSchema = Schema extends z.ZodString - ? Schema - : Schema extends - | z.ZodBoolean - | z.ZodEffects - | z.ZodEnum<[string, ...string[]]> - | z.ZodLiteral - | z.ZodNumber - ? z.ZodUnion<[z.ZodString, Schema]> - : Schema extends z.ZodArray - ? z.ZodUnion<[z.ZodString, z.ZodArray, Cardinality>]> - : Schema extends z.ZodDefault - ? z.ZodDefault> - : Schema extends z.ZodNullable - ? z.ZodNullable> - : Schema extends z.ZodOptional - ? z.ZodOptional> - : Schema extends z.ZodRecord - ? z.ZodRecord> - : Schema extends z.ZodTuple - ? z.ZodUnion< - [ - z.ZodString, - z.ZodTuple< - InterpolatableTuple, - Rest extends z.ZodTypeAny ? InterpolatableSchema : null - >, - ] - > - : Schema extends z.ZodObject - ? z.ZodUnion< - [ - z.ZodString, - z.ZodObject< - { [Key in keyof T]: InterpolatableSchema }, - UnknownKeys, - Catchall - >, - ] - > - : Schema extends z.ZodUnion - ? z.ZodUnion<[z.ZodString, ...InterpolatableTuple]> - : Schema - -export function interpolateRecursive( - schema: Schema, -): InterpolatableSchema { - const def = schema._def - - switch (def.typeName) { - case z.ZodFirstPartyTypeKind.ZodBoolean: - return z.union([ - interpolationSchemaFull, - z - .union([schema, booleanStringSchema]) - .transform((value) => value === true || value === false), - ]) as InterpolatableSchema - case z.ZodFirstPartyTypeKind.ZodArray: { - let replacement = z.array(interpolateRecursive(def.type), def) - - if (def.exactLength != null) { - replacement = replacement.min(def.exactLength.value, def.exactLength.message) - } - - if (def.maxLength != null) { - replacement = replacement.min(def.maxLength.value, def.maxLength.message) - } - - if (def.minLength != null) { - replacement = replacement.min(def.minLength.value, def.minLength.message) - } - - return z.union([interpolationSchemaFull, replacement]) as InterpolatableSchema - } - case z.ZodFirstPartyTypeKind.ZodDefault: { - const replacement = ( - interpolateRecursive(def.innerType) as InterpolatableSchema - ).default(def.defaultValue()) - - return ( - def.description ? replacement.describe(def.description) : replacement - ) as InterpolatableSchema - } - case z.ZodFirstPartyTypeKind.ZodEffects: - case z.ZodFirstPartyTypeKind.ZodEnum: - case z.ZodFirstPartyTypeKind.ZodLiteral: - return z.union([interpolationSchemaFull, schema], def) as InterpolatableSchema - case z.ZodFirstPartyTypeKind.ZodNumber: - return z.union( - [ - z - .string() - .regex(/^\d+(\.\d+)?$/) - .transform((value) => Number(value)), - interpolationSchemaFull, - schema, - ], - def, - ) as InterpolatableSchema - case z.ZodFirstPartyTypeKind.ZodNullable: - return interpolateRecursive(def.innerType) - .nullable() - .describe(def.description) as InterpolatableSchema - case z.ZodFirstPartyTypeKind.ZodObject: { - const replacement = z.object( - Object.fromEntries( - Object.entries(def.shape()).map(([key, nested]) => [ - key, - interpolateRecursive(nested as z.ZodFirstPartySchemaTypes), - ]), - ), - def, - ) - return z.union([ - interpolationSchemaFull, - def.unknownKeys === 'strict' - ? replacement.strict() - : def.unknownKeys === 'passthrough' - ? replacement.passthrough() - : replacement, - ]) as InterpolatableSchema - } - case z.ZodFirstPartyTypeKind.ZodOptional: - return z.optional(interpolateRecursive(def.innerType), def) as InterpolatableSchema - case z.ZodFirstPartyTypeKind.ZodRecord: - return z.record( - def.keyType, - interpolateRecursive(def.valueType), - def, - ) as InterpolatableSchema - case z.ZodFirstPartyTypeKind.ZodString: - return z.union([interpolationSchemaPartial, schema], def) as InterpolatableSchema - case z.ZodFirstPartyTypeKind.ZodTuple: { - const tuple = z.tuple(def.items.map(interpolateRecursive), def) - - return z.union([ - interpolationSchemaFull, - def.rest ? tuple.rest(def.rest) : tuple, - ]) as InterpolatableSchema - } - case z.ZodFirstPartyTypeKind.ZodUnion: - return z.union( - [interpolationSchemaFull, ...(def.options.map(interpolateRecursive) as z.ZodUnionOptions)], - def, - ) as InterpolatableSchema - default: - return schema as InterpolatableSchema - } -} - -/** - * The robot keys specified in this array can’t be interpolated. - */ -const uninterpolatableKeys = ['robot', 'use'] as const - -type InterpolatableRobot> = - Schema extends z.ZodObject - ? z.ZodObject< - { - [Key in keyof T]: Key extends (typeof uninterpolatableKeys)[number] - ? T[Key] - : InterpolatableSchema - }, - UnknownKeys, - Catchall - > - : never - -export function interpolateRobot>( - schema: Schema, -): InterpolatableRobot { - const def = schema._def - return z - .object( - Object.fromEntries( - Object.entries(def.shape()).map(([key, nested]) => [ - key, - (uninterpolatableKeys as readonly string[]).includes(key) - ? nested - : interpolateRecursive(nested as z.ZodFirstPartySchemaTypes), - ]), - ), - def, - ) - .strict() as InterpolatableRobot -} - -/** - * Fields that are shared by all Transloadit robots. - */ -export type RobotBase = z.infer -export const robotBase = z - .object({ - output_meta: z - .union([z.record(z.boolean()), z.boolean(), z.array(z.string())]) - .optional() - .describe(` -Allows you to specify a set of metadata that is more expensive on CPU power to calculate, and thus is disabled by default to keep your Assemblies processing fast. - -For images, you can add \`"has_transparency": true\` in this object to extract if the image contains transparent parts and \`"dominant_colors": true\` to extract an array of hexadecimal color codes from the image. - -For videos, you can add the \`"colorspace: true"\` parameter to extract the colorspace of the output video. - -For audio, you can add \`"mean_volume": true\` to get a single value representing the mean average volume of the audio file. - -You can also set this to \`false\` to skip metadata extraction and speed up transcoding. -`), - - result: z - .boolean() - .default(false) - .describe('Whether the results of this Step should be present in the Assembly Status JSON'), - - queue: z - .enum(['batch']) - .optional() - .describe( - `Setting the queue to 'batch', manually downgrades the priority of jobs for this step to avoid consuming Priority job slots for jobs that don't need zero queue waiting times`, - ), - - force_accept: z - .boolean() - .default(false) - .describe(`Force a Robot to accept a file type it would have ignored. - -By default, Robots ignore files they are not familiar with. -[🤖/video/encode](/docs/robots/video-encode/), for -example, will happily ignore input images. - -With the \`force_accept\` parameter set to \`true\`, you can force Robots to accept all files thrown at them. -This will typically lead to errors and should only be used for debugging or combatting edge cases. -`), - - ignore_errors: z - .union([z.boolean(), z.array(z.enum(['meta', 'execute']))]) - .transform((value) => (value === true ? ['meta', 'execute'] : value === false ? [] : value)) - .default([]) - .describe(` -Ignore errors during specific phases of processing. - -Setting this to \`["meta"]\` will cause the Robot to ignore errors during metadata extraction. - -Setting this to \`["execute"]\` will cause the Robot to ignore errors during the main execution phase. - -Setting this to \`true\` is equivalent to \`["meta", "execute"]\` and will ignore errors in both phases. -`), - }) - .strict() - -export const useParamObjectSchema = z - .object({ - name: z.string(), - fields: z.string().optional(), - as: z.string().optional(), - }) - .strict() - -export const useParamStringSchema = z.string() -export const useParamArrayOfStringsSchema = z.array(useParamStringSchema) -export const useParamArrayOfUseParamObjectSchema = z.array(useParamObjectSchema) -export const useParamStepsSchema = z.union([ - useParamStringSchema, - useParamArrayOfStringsSchema, - useParamArrayOfUseParamObjectSchema, -]) -export const useParamObjectOfStepsSchema = z - .object({ - steps: useParamStepsSchema, - bundle_steps: z.boolean().optional(), - group_by_original: z.boolean().optional(), - fields: z - .array(z.string()) - .optional() - .describe(` -Array of field names to filter input files by when using steps. -`), - }) - .strict() - -// Hidden fields variants for use parameters -export const useParamObjectWithHiddenFieldsSchema = useParamObjectSchema.extend({ - result: z.union([z.literal('debug'), z.boolean()]).optional(), -}) - -export const useParamArrayOfUseParamObjectWithHiddenFieldsSchema = z.array( - useParamObjectWithHiddenFieldsSchema, -) -export const useParamStepsWithHiddenFieldsSchema = z.union([ - useParamStringSchema, - useParamArrayOfStringsSchema, - useParamArrayOfUseParamObjectWithHiddenFieldsSchema, -]) -export const useParamObjectOfStepsWithHiddenFieldsSchema = z - .object({ - steps: useParamStepsWithHiddenFieldsSchema, - bundle_steps: z.boolean().optional(), - group_by_original: z.boolean().optional(), - fields: z - .array(z.string()) - .optional() - .describe(` -Array of field names to filter input files by when using steps. -`), - }) - .strict() - -/** - * A robot that uses another robot’s output as input. - */ -export type RobotUse = z.infer -export const robotUse = z - .object({ - use: z - .union([useParamStepsSchema, useParamObjectOfStepsSchema]) - .describe( - ` -Specifies which Step(s) to use as input. - -- You can pick any names for Steps except \`":original"\` (reserved for user uploads handled by Transloadit) -- You can provide several Steps as input with arrays: - \`\`\`json - { - "use": [ - ":original", - "encoded", - "resized" - ] - } - \`\`\` - -> [!Tip] -> That's likely all you need to know about \`use\`, but you can view [Advanced use cases](/docs/topics/use-parameter/). -`, - ) - .optional(), - }) - .strict() - -export type RobotUseWithHiddenFields = z.infer -export const robotUseWithHiddenFields = z - .object({ - use: z - .union([useParamStepsWithHiddenFieldsSchema, useParamObjectOfStepsWithHiddenFieldsSchema]) - .describe( - ` -Specifies which Step(s) to use as input. - -- You can pick any names for Steps except \`":original"\` (reserved for user uploads handled by Transloadit) -- You can provide several Steps as input with arrays: - \`\`\`json - { - "use": [ - ":original", - "encoded", - "resized" - ] - } - \`\`\` - -> [!Tip] -> That's likely all you need to know about \`use\`, but you can view [Advanced use cases](/docs/topics/use-parameter/). -`, - ) - .optional(), - }) - .strict() - -export const complexWidthSchema = z.preprocess((val) => { - if (typeof val === 'string' && val.startsWith('${')) { - return val - } - if (typeof val === 'string') { - const num = Number.parseInt(val, 10) - if (Number.isNaN(num) || val.includes('x')) { - return val - } - return num - } - return val -}, z.number().int().min(1).max(7680)) - -export const complexHeightSchema = z.preprocess((val) => { - if (typeof val === 'string' && val.startsWith('${')) { - return val - } - if (typeof val === 'string') { - const num = Number.parseInt(val, 10) - if (Number.isNaN(num) || val.includes('x')) { - return val - } - return num - } - return val -}, z.number().int().min(1).max(4320)) - -/** - * A robot that uses FFmpeg. - */ -export type FFmpeg = z.infer -export const robotFFmpeg = z.object({ - ffmpeg: z - .object({ - af: z.string().optional(), - 'b:a': z.union([z.string(), z.number()]).optional(), - 'b:v': z.union([z.string(), z.number()]).optional(), - 'c:a': z.string().optional(), - 'c:v': z.string().optional(), - 'codec:a': z.string().optional(), - 'codec:v': z.string().optional(), - 'filter:v': z.string().optional(), - 'filter:a': z.string().optional(), - bits_per_mb: z.union([z.string(), z.number()]).optional(), - ss: z.union([z.string(), z.number()]).optional(), - t: z.union([z.string(), z.number()]).optional(), - to: z.union([z.string(), z.number()]).optional(), - vendor: z.string().optional(), - shortest: z.boolean().nullish(), - filter_complex: z.union([z.string(), z.record(z.string())]).optional(), - 'level:v': z.union([z.string(), z.number()]).optional(), - 'profile:v': z.union([z.number(), z.enum(['baseline', 'main', 'high', 'main10'])]).optional(), - 'qscale:a': z.number().optional(), - 'qscale:v': z.number().optional(), - 'x264-params': z.string().optional(), - 'overshoot-pct': z.number().optional(), - deadline: z.string().optional(), - 'cpu-used': z.string().optional(), - 'undershoot-pct': z.number().optional(), - 'row-mt': z.number().optional(), - 'x265-params': z - .object({ - 'vbv-maxrate': z.number().optional(), - 'vbv-bufsize': z.number().optional(), - 'rc-lookahead': z.number().optional(), - 'b-adapt': z.number().optional(), - }) - .strict() - .optional(), - 'svtav1-params': z - .object({ - tune: z.number().optional(), - 'enable-qm': z.number().optional(), - 'fast-decode': z.number().optional(), - 'film-grain-denoise': z.number().optional(), - }) - .strict() - .optional(), - ac: z.number().optional(), - an: z.boolean().optional(), - ar: z.number().optional(), - async: z.number().optional(), - b: z - .union([ - z - .object({ - v: z.number().optional(), - a: z.number().optional(), - }) - .strict(), - z.string(), - ]) - .optional(), - bt: z.union([z.number(), z.string()]).optional(), - bufsize: z.union([z.string(), z.number()]).optional(), - c: z.string().optional(), - codec: z - .object({ - v: z.string().optional(), - a: z.string().optional(), - }) - .strict() - .optional(), - coder: z.number().optional(), - crf: z.number().optional(), - f: z.string().optional(), - flags: z.string().optional(), - g: z.number().optional(), - i_qfactor: z.union([z.string(), z.number()]).optional(), - keyint_min: z.number().optional(), - level: z.union([z.string(), z.number()]).optional(), - map: z.union([z.string(), z.array(z.string())]).optional(), - maxrate: z.union([z.string(), z.number()]).optional(), - me_range: z.number().optional(), - movflags: z.string().optional(), - partitions: z.string().optional(), - pix_fmt: z.string().optional(), - preset: z.union([z.string(), z.number()]).optional(), - profile: z.string().optional(), - 'q:a': z.number().optional(), - qcomp: z.union([z.string(), z.number()]).optional(), - qdiff: z.number().optional(), - qmax: z.number().optional(), - qmin: z.number().optional(), - r: z.union([z.number(), z.string()]).nullable().optional(), - rc_eq: z.string().optional(), - refs: z.number().optional(), - s: z.string().optional(), - sc_threshold: z.number().optional(), - sws_flags: z.string().optional(), - threads: z.number().optional(), - trellis: z.number().optional(), - transloaditffpreset: z.literal('empty').optional(), - vn: z.boolean().optional(), - vf: z.string().optional(), - x264opts: z.string().optional(), - vbr: z.union([z.string(), z.number()]).optional(), - }) - .passthrough() - .optional() - .describe(` -A parameter object to be passed to FFmpeg. If a preset is used, the options specified are merged on top of the ones from the preset. For available options, see the [FFmpeg documentation](https://ffmpeg.org/ffmpeg-doc.html). Options specified here take precedence over the preset options. -`), - - ffmpeg_stack: z - // Any semver in range is allowed and normalized. The enum is used for editor completions. - .union([z.enum(['v5', 'v6', 'v7']), z.string().regex(/^v?[567](\.\d+)?(\.\d+)?$/)]) - .default('v5.0.0') - .describe(` -Selects the FFmpeg stack version to use for encoding. These versions reflect real FFmpeg versions. We currently recommend to use "v6.0.0". -`), -}) - -/** - * Replace all underscores with hyphens. - * - * @param preset - * The input preset which may contain underscores. - * @returns - * The hyphenated preset. - */ -function transformPreset(preset: T): Replace { - return preset.replaceAll('_', '-') as Replace -} - -/** - * Convert a preset with hyphens to any underscore/hyphen combination. - * - * @template T - * The preset to process. - */ -type ReplacePreset = T extends `${infer T0}-${infer Tail}` - ? T | `${T0}-${ReplacePreset}` | `${T0}_${ReplacePreset}` - : T - -/** - * Generate all possible underscore/hyphen combinations of a preset. - * - * @param chunks - * A normalized preset split on hyphens. - * @returns - * An iterable that yields all possible combinations. - */ -function* generateCombinations(chunks: string[]): Iterable { - if (chunks.length === 0) { - return - } - - if (chunks.length === 1) { - yield chunks[0] - } - - const [head, ...remaining] = chunks - for (const result of generateCombinations(remaining)) { - yield `${head}-${result}` - yield `${head}_${result}` - } -} - -/** - * Create all possible preset combinations from a list of normalized presets. - * - * @param inputs - * The hyphenated presets. - * @returns - * An array of all possible combinations. - */ -function createPresets( - inputs: T[], -): readonly [ReplacePreset, ...ReplacePreset[]] { - const results: string[] = [] - for (const input of inputs) { - results.push(...generateCombinations(input.split('-'))) - } - - return [...results].sort() as [ReplacePreset, ...ReplacePreset[]] -} - -const audioPresets = createPresets([ - 'aac', - 'alac', - 'audio/aac', - 'audio/alac', - 'audio/flac', - 'audio/mp3', - 'audio/ogg', - 'dash-32k-audio', - 'dash-64k-audio', - 'dash-128k-audio', - 'dash-256k-audio', - 'dash/32k-audio', - 'dash/64k-audio', - 'dash/128k-audio', - 'dash/256k-audio', - 'empty', - 'flac', - 'hg-transformers-audio', - 'mp3', - 'ogg', - 'opus', - 'speech', - 'wav', -]) - -/** - * A robot that uses FFmpeg to **output** audio. - */ -export type FFmpegAudio = z.infer -export const robotFFmpegAudio = robotFFmpeg - .extend({ - preset: z - .enum(audioPresets) - .transform(transformPreset) - .optional() - .describe(` -Performs conversion using pre-configured settings. - -If you specify your own FFmpeg parameters using the Robot's \`ffmpeg\` parameter and you have not specified a preset, then the default \`mp3\` preset is not applied. This is to prevent you from having to override each of the MP3 preset's values manually. - -For a list of audio presets, see [audio presets](/docs/presets/audio/). -`), - }) - .strict() - -/** - * A robot that uses FFmpeg to **output** video. - */ -export type FFmpegVideo = z.infer -export const robotFFmpegVideo = robotFFmpeg - .extend({ - width: z - .number() - .int() - .min(1) - .nullish() - .describe(` -Width of the new video, in pixels. - -If the value is not specified and the \`preset\` parameter is available, the \`preset\`'s [supplied width](/docs/presets/video/) will be implemented. -`), - height: z - .number() - .int() - .min(1) - .nullish() - .describe(` -Height of the new video, in pixels. - -If the value is not specified and the \`preset\` parameter is available, the \`preset\`'s [supplied height](/docs/presets/video/) will be implemented. -`), - preset: z - .enum([ - ...createPresets([ - 'android', - 'android-high', - 'android-low', - 'dash-270p-video', - 'dash-360p-video', - 'dash-480p-video', - 'dash-540p-video', - 'dash-576p-video', - 'dash-720p-video', - 'dash-1080p-video', - 'dash/270p-video', - 'dash/360p-video', - 'dash/480p-video', - 'dash/540p-video', - 'dash/576p-video', - 'dash/720p-video', - 'dash/1080p-video', - 'flash', - 'gif', - 'hevc', - 'hls-270p', - 'hls-360p', - 'hls-480p', - 'hls-540p', - 'hls-576p', - 'hls-720p', - 'hls-1080p', - 'hls/270p', - 'hls/360p', - 'hls/480p', - 'hls/540p', - 'hls/720p', - 'hls/1080p', - 'hls/4k', - 'ipad', - 'ipad-high', - 'ipad-low', - 'iphone', - 'iphone-high', - 'iphone-low', - 'ogv', - 'vod/270p', - 'vod/480p', - 'vod/720p', - 'vod/1080p', - 'vp9', - 'vp9-270p', - 'vp9-360p', - 'vp9-480p', - 'vp9-540p', - 'vp9-576p', - 'vp9-720p', - 'vp9-1080p', - 'web/mp4-x265/240p', - 'web/mp4-x265/360p', - 'web/mp4-x265/480p', - 'web/mp4-x265/720p', - 'web/mp4-x265/1080p', - 'web/mp4-x265/4k', - 'web/mp4-x265/8k', - 'web/mp4/240p', - 'web/mp4/360p', - 'web/mp4/480p', - 'web/mp4/540p', - 'web/mp4/720p', - 'web/mp4/1080p', - 'web/mp4/4k', - 'web/mp4/8k', - 'web/webm-av1/240p', - 'web/webm-av1/360p', - 'web/webm-av1/480p', - 'web/webm-av1/720p', - 'web/webm-av1/1080p', - 'web/webm-av1/4k', - 'web/webm-av1/8k', - 'web/webm/240p', - 'web/webm/360p', - 'web/webm/480p', - 'web/webm/720p', - 'web/webm/1080p', - 'web/webm/4k', - 'web/webm/8k', - 'webm', - 'webm-270p', - 'webm-360p', - 'webm-480p', - 'webm-540p', - 'webm-576p', - 'webm-720p', - 'webm-1080p', - 'wmv', - ]), - ...audioPresets, - ]) - .transform(transformPreset) - .optional() - .describe(` -Converts a video according to [pre-configured settings](/docs/presets/video/). - -If you specify your own FFmpeg parameters using the Robot's and/or do not not want Transloadit to set any encoding setting, starting \`ffmpeg_stack: "${stackVersions.ffmpeg.recommendedVersion}"\`, you can use the value \`'empty'\` here. -`), - }) - .strict() - -export const unsafeCoordinatesSchema = z - .union([ - z - .object({ - x1: z.union([z.string(), z.number()]).nullish(), - y1: z.union([z.string(), z.number()]).nullish(), - x2: z.union([z.string(), z.number()]).nullish(), - y2: z.union([z.string(), z.number()]).nullish(), - }) - .strict(), - z.string(), - ]) - .describe(` -Coordinates for watermarking. -`) -export type UnsafeCoordinates = z.infer - -export const parsedCoordinatesSchema = z - .object({ - x1: z.number(), - y1: z.number(), - x2: z.number(), - y2: z.number(), - }) - .strict() -export type ParsedCoordinates = z.infer - -export const path = z.union([z.string(), z.array(z.string())]) - -export const next_page_token = z.string().default('') - -export const files_per_page = z.number().int().default(1000) - -export const page_number = z.number().int().default(1) - -export const recursive = z.boolean().default(false) - -export const return_file_stubs = z - .boolean() - .describe( - ` -If set to \`true\`, the Robot will not yet import the actual files but instead return an empty file stub that includes a URL from where the file can be imported by subsequent Robots. This is useful for cases where subsequent Steps need more control over the import process, such as with 🤖/video/ondemand. This parameter should only be set if all subsequent Steps use Robots that support file stubs. -`, - ) - .default(false) - -export const port = z.number().int().min(1).max(65535) - -// TODO: Use an enum. -export const preset = z.string() - -export const resize_strategy = z - .enum(['crop', 'fit', 'fillcrop', 'min_fit', 'pad', 'stretch']) - .default('pad') - -export const positionSchema = z.enum([ - 'bottom', - 'bottom-left', - 'bottom-right', - 'center', - 'left', - 'right', - 'top', - 'top-left', - 'top-right', -]) - -export const percentageSchema = z.string().regex(/^\d+%$/) - -export const color_with_alpha = z.string().regex(/^#?[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/) - -export const color_without_alpha = z.string().regex(/^#?[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$/) - -// Extended color schemas that also support named colors (for robots that support them) -export const color_with_alpha_with_named = z.union([ - color_with_alpha, // Extend the base hex color schema - z.enum([ - 'transparent', - 'none', - 'black', - 'white', - 'red', - 'green', - 'blue', - 'yellow', - 'cyan', - 'magenta', - 'gray', - 'grey', - 'opaque', - ]), // Named colors -]) - -export const color_without_alpha_with_named = z.union([ - color_without_alpha, // Extend the base hex color schema - z.enum([ - 'transparent', - 'none', - 'black', - 'white', - 'red', - 'green', - 'blue', - 'yellow', - 'cyan', - 'magenta', - 'gray', - 'grey', - 'opaque', - ]), // Named colors -]) - -export const bitrateSchema = z.number().int().min(1) - -export const sampleRateSchema = z.number().int().min(1) - -export const optimize_priority = z - .enum(['compression-ratio', 'conversion-speed']) - .default('conversion-speed') - -export type ImagemagickRobot = z.infer -export const robotImagemagick = z - .object({ - imagemagick_stack: z - // Any semver in range is allowed and normalized. The enum is used for editor completions. - .union([z.enum(['v3']), z.string().regex(/^v?[23](\.\d+)?(\.\d+)?$/)]) - .default('v3'), - }) - .strict() - -export const colorspaceSchema = z.enum([ - 'CMY', - 'CMYK', - 'Gray', - 'HCL', - 'HCLp', - 'HSB', - 'HSI', - 'HSL', - 'HSV', - 'HWB', - 'Jzazbz', - 'Lab', - 'LCHab', - 'LCHuv', - 'LMS', - 'Log', - 'Luv', - 'OHTA', - 'OkLab', - 'OkLCH', - 'Rec601YCbCr', - 'Rec709YCbCr', - 'RGB', - 'scRGB', - 'sRGB', - 'Transparent', - 'Undefined', - 'xyY', - 'XYZ', - 'YCbCr', - 'YCC', - 'YDbDr', - 'YIQ', - 'YPbPr', - 'YUV', -]) - -// TODO: add before and after images to the description. -export const imageQualitySchema = z - .number() - .int() - .min(1) - .max(100) - .default(92) - .describe(` -Controls the image compression for JPG and PNG images. Please also take a look at [🤖/image/optimize](/docs/robots/image-optimize/). -`) - -export const aiProviderSchema = z.enum(['aws', 'gcp', 'replicate', 'fal', 'transloadit']) - -export const granularitySchema = z.enum(['full', 'list']).default('full') - -/** - * A robot that imports data from a source. - */ -export type RobotImport = z.infer -export const robotImport = z - .object({ - force_name: z - .union([z.string(), z.array(z.string())]) - .nullable() - .default(null) - .describe( - 'Custom name for the imported file(s). By default file names are derived from the source.', - ), - ignore_errors: z - .union([z.boolean(), z.array(z.enum(['meta', 'import', 'execute']))]) - .transform((value) => - value === true ? ['meta', 'import', 'execute'] : value === false ? [] : value, - ) - .default([]), - }) - .strict() - -export type AzureBase = z.infer -export const azureBase = z - .object({ - credentials: z - .string() - .optional() - .describe(` -Please create your associated Template Credentials in your Transloadit account and use the name of your [Template Credentials](/c/template-credentials/) as this parameter's value. They will contain the values for your Azure Container, Account and Key. - -While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"account"\`, \`"key"\`, \`"container"\`. -`), - account: z.string().optional(), - container: z.string().optional(), - key: z.string().optional(), - }) - .strict() - -export type BackblazeBase = z.infer -export const backblazeBase = z - .object({ - credentials: z - .string() - .optional() - .describe(` -Please create your associated Template Credentials in your Transloadit account and use the name of your Template Credentials as this parameter's value. They will contain the values for your Backblaze Bucket Name, App Key ID, and App Key. - -To create your credential information, head over to Backblaze, sign in to your account, and select "Create a Bucket". Save the name of your bucket, and click on the "App Keys" tab, scroll to the bottom of the page then select “Add a New Application Key”. Allow access to your recently created bucket, select “Read and Write” as your type of access, and tick the “Allow List All Bucket Names” option. - -Now that everything is in place, create your key, and take note of the information you are given so you can input the information into your Template Credentials. - -⚠️ Your App Key will only be viewable once, so make sure you note this down. - -While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"bucket"\`, \`"app_key_id"\`, \`"app_key"\`. -`), - bucket: z.string().optional(), - app_key_id: z.string().optional(), - app_key: z.string().optional(), - }) - .strict() - -export type CloudfilesBase = z.infer -export const cloudfilesBase = z - .object({ - credentials: z - .string() - .optional() - .describe(` -Please create your associated Template Credentials in your Transloadit account and use the name of your [Template Credentials](/c/template-credentials/) as this parameter's value. They will contain the values for your Cloud Files Container, User, Key, Account type and Data center. - -While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"account_type"\` ("us" or "uk"), \`"data_center"\` ("dfw" for Dallas or "ord" for Chicago for example), \`"user"\`, \`"key"\`, \`"container"\`. -`), - account_type: z.enum(['uk', 'us']).optional(), - data_center: z.string().optional(), - user: z.string().optional(), - key: z.string().optional(), - container: z.string().optional(), - }) - .strict() - -export type CloudflareBase = z.infer -export const cloudflareBase = z - .object({ - credentials: z - .string() - .optional() - .describe(` -Please create your associated Template Credentials in your Transloadit account and use the name of your Template Credentials as this parameter's value. They will contain the values for your cloudflare bucket, Key, Secret and Bucket region. - -While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"bucket"\`, \`"host"\`, \`"key"\`, \`"secret"\`. -`), - bucket: z.string().optional(), - host: z.string().optional(), - key: z.string().optional(), - secret: z.string().optional(), - }) - .strict() - -export type DigitalOceanBase = z.infer -export const digitalOceanBase = z - .object({ - credentials: z - .string() - .optional() - .describe(` -Please create your associated Template Credentials in your Transloadit account and use the name of your [Template Credentials](/c/template-credentials/) as this parameter's value. They will contain the values for your DigitalOcean Space, Key, Secret and Region. - -While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"space"\`, \`"region"\` (for example: \`"fra1"\` or \`"nyc3"\`), \`"key"\`, \`"secret"\`. -`), - space: z.string().optional(), - region: z.string().optional(), - key: z.string().optional(), - secret: z.string().optional(), - }) - .strict() - -export type DropboxBase = z.infer -export const dropboxBase = z - .object({ - credentials: z - .string() - .optional() - .describe(` -Please create your associated Template Credentials in your Transloadit account and use the name of your Template Credentials as this parameter's value. They will contain the values for your Dropbox access token. -`), - }) - .strict() - -export type VimeoBase = z.infer -export const vimeoBase = z - .object({ - credentials: z - .string() - .optional() - .describe(` -Please create your associated Template Credentials in your Transloadit account and use the name of your Template Credentials as this parameter's value. They will contain the values for your Vimeo access token. -`), - }) - .strict() - -export type FtpBase = z.infer -export const ftpBase = z - .object({ - credentials: z - .string() - .optional() - .describe(` -Please create your associated Template Credentials in your Transloadit account and use the name of your Template Credentials as this parameter's value. They will contain the values for your FTP host, user and password. - -While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials with their static nature is too unwieldy. If you have this requirement, feel free to use the following parameters instead: \`"host"\`, \`"user"\`, \`"password"\`. -`), - host: z.string().optional(), - port: port.default(21).describe('The port to use for the FTP connection.'), - user: z.string().optional(), - password: z.string().optional(), - }) - .strict() - -export type GoogleBase = z.infer -export const googleBase = z - .object({ - credentials: z - .string() - .optional() - .describe(` -Create a new [Google service account](https://cloud.google.com/storage/docs/authentication). Set its role to "Storage Object Creator". Choose "JSON" for the key file format and download it to your computer. You will need to upload this file when creating your Template Credentials. - -Go back to your Google credentials project and enable the "Google Cloud Storage JSON API" for it. Wait around ten minutes for the action to propagate through the Google network. Grab the project ID from the dropdown menu in the header bar on the Google site. You will also need it later on. - -Now you can set up the \`storage.objects.create\` and \`storage.objects.delete\` permissions. The latter is optional and only required if you intend to overwrite existing paths. - -To do this from the Google Cloud console, navigate to "IAM & Admin" and select "Roles". From here, click "Create Role", enter a name, set the role launch stage to _General availability,_ and set the permissions stated above. - -Next, go to Storage browser and select the ellipsis on your bucket to edit bucket permissions. From here, select "Add Member", enter your service account as a new member, and select your newly created role. - -Then, create your associated [Template Credentials](/c/template-credentials/) in your Transloadit account and use the name of your Template Credentials as this parameter's value. -`), - }) - .strict() - -export type MinioBase = z.infer -export const minioBase = z - .object({ - credentials: z - .string() - .optional() - .describe(` -Please create your associated Template Credentials in your Transloadit account and use the name of your Template Credentials as this parameter's value. They will contain the values for your MinIO bucket, Key, Secret and Bucket region. - -While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"bucket"\`, \`"host"\`, \`"key"\`, \`"secret"\`. -`), - bucket: z.string().optional(), - host: z.string().optional(), - key: z.string().optional(), - secret: z.string().optional(), - }) - .strict() - -export type S3Base = z.infer -export const s3Base = z - .object({ - credentials: z - .string() - .optional() - .describe(` -Please create your associated Template Credentials in your Transloadit account and use the name of your Template Credentials as this parameter's value. They will contain the values for your S3 bucket, Key, Secret and Bucket region. - -While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"bucket"\`, \`"bucket_region"\` (for example: \`"us-east-1"\` or \`"eu-west-2"\`), \`"key"\`, \`"secret"\`. -`), - bucket: z.string().optional(), - bucket_region: z.string().optional(), - key: z.string().optional(), - secret: z.string().optional(), - }) - .strict() - -export type SftpBase = z.infer -export const sftpBase = z - .object({ - credentials: z - .string() - .optional() - .describe(` -Please create your associated Template Credentials in your Transloadit account and use the name of your Template Credentials as this parameter's value. They will contain the values for your SFTP host, user and optional custom public key. - -While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"host"\`, \`"port"\`, \`"user"\`, \`"public_key"\` (optional). -`), - host: z.string().optional(), - port: port.default(21).describe('The port to use for the FTP connection.'), - user: z.string().optional(), - public_key: z.string().optional(), - }) - .strict() - -export type SupabaseBase = z.infer -export const supabaseBase = z - .object({ - credentials: z - .string() - .optional() - .describe(` -Please create your associated Template Credentials in your Transloadit account and use the name of your Template Credentials as this parameter's value. They will contain the values for your Supabase bucket, Key, Secret and Bucket region. - -While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"bucket"\`, \`"host"\`, \`"key"\`, \`"secret"\`. - -If you do use these parameters, make sure to use the **Endpoint** value under \`Storage > S3 Connection\` in the Supabase console for the \`"host"\` value, and the values under **S3 Access Keys** on the same page for your \`"key"\` and \`"secret"\`. -`), - bucket: z.string().optional(), - bucket_region: z - .string() - .optional() - .describe(` -The region where the bucket is located. -`), - host: z.string().optional(), - key: z.string().optional(), - secret: z.string().optional(), - }) - .strict() - -export type SwiftBase = z.infer -export const swiftBase = z - .object({ - credentials: z - .string() - .optional() - .describe(` - Please create your associated Template Credentials in your Transloadit account and use the name of your Template Credentials as this parameter's value. They will contain the values for your Swift bucket, Key, Secret and Bucket region. - - While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"bucket"\`, \`"host"\`, \`"key"\`, \`"secret"\`. - `), - bucket: z.string().optional(), - bucket_region: z - .string() - .optional() - .describe(` -The region where the bucket is located. -`), - host: z.string().optional(), - key: z.string().optional(), - secret: z.string().optional(), - }) - .strict() - -export type TigrisBase = z.infer -export const tigrisBase = z - .object({ - credentials: z - .string() - .optional() - .describe(` -Please create your associated Template Credentials in your Transloadit account and use the name of your Template Credentials as this parameter's value. They will contain the values for your MinIO bucket, Key, Secret and Bucket region. - -While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"bucket"\`, \`"host"\`, \`"key"\`, \`"secret"\`. -`), - bucket: z.string().optional(), - bucket_region: z - .string() - .optional() - .describe(` -The region where the bucket is located. -`), - host: z.string().optional(), - key: z.string().optional(), - secret: z.string().optional(), - }) - .strict() - -export type WasabiBase = z.infer -export const wasabiBase = z - .object({ - credentials: z - .string() - .optional() - .describe(` -Please create your associated Template Credentials in your Transloadit account and use the name of your Template Credentials as this parameter's value. They will contain the values for your Wasabi bucket, Key, Secret and Bucket region. - -While we recommend to use Template Credentials at all times, some use cases demand dynamic credentials for which using Template Credentials is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"bucket"\`, \`"host"\`, \`"key"\`, \`"secret"\`. -`), - bucket: z.string().optional(), - bucket_region: z - .string() - .optional() - .describe(` -The region where the bucket is located. -`), - host: z.string().optional(), - key: z.string().optional(), - secret: z.string().optional(), - }) - .strict() - -export type FilterExpression = z.infer -export const filterExpression = z.union([ - z.string(), - z.number(), - z.null(), - z.array(z.union([z.string(), z.number(), z.null()])), -]) - -export type FilterCondition = z.infer -export const filterCondition = z.union([ - z.null(), - z.string(), - z.array( - z.tuple([ - filterExpression, - z.union([ - z.literal('=').describe('Equals without type check'), - z.literal('==').describe('Equals without type check'), - z.literal('===').describe('Strict equals with type check'), - z.literal('<').describe('Less than'), - z.literal('>').describe('Greater than'), - z.literal('<=').describe('Less or equal'), - z.literal('>=').describe('Greater or equal'), - z.literal('!=').describe('Simple inequality check without type check'), - z.literal('!==').describe('Strict inequality check with type check'), - z - .literal('regex') - .describe( - 'Case-insensitive regular expression based on [RE2](https://github.com/google/re2) `.match()`', - ), - z - .literal('!regex') - .describe( - 'Case-insensitive regular expression based on [RE2](https://github.com/google/re2) `!.match()`', - ), - z - .literal('includes') - .describe( - 'Check if the right element is included in the array, which is represented by the left element', - ), - z - .literal('!includes') - .describe( - 'Check if the right element is not included in the array, which is represented by the left element', - ), - z - .literal('empty') - .describe( - 'Check if the left element is an empty array, an object without properties, an empty string, the number zero or the boolean false. Leave the third element of the array to be an empty string. It won’t be evaluated.', - ), - z - .literal('!empty') - .describe( - 'Check if the left element is an array with members, an object with at least one property, a non-empty string, a number that does not equal zero or the boolean true. Leave the third element of the array to be an empty string. It won’t be evaluated.', - ), - ]), - filterExpression, - ]), - ), -]) - -/** - * Parameters specific to the /video/encode robot. Useful for typing robots that pass files to /video/encode. - */ -export const videoEncodeSpecificInstructionsSchema = robotFFmpegVideo - .extend({ - resize_strategy: resize_strategy.describe(` -See the [available resize strategies](/docs/topics/resize-strategies/). -`), - zoom: z - .boolean() - .default(true) - .describe(` -If this is set to \`false\`, smaller videos will not be stretched to the desired width and height. For details about the impact of zooming for your preferred resize strategy, see the list of available [resize strategies](/docs/topics/resize-strategies/). -`), - crop: unsafeCoordinatesSchema.optional().describe(` -Specify an object containing coordinates for the top left and bottom right corners of the rectangle to be cropped from the original video(s). Values can be integers for absolute pixel values or strings for percentage based values. - -For example: - -\`\`\`json -{ - "x1": 80, - "y1": 100, - "x2": "60%", - "y2": "80%" -} -\`\`\` - -This will crop the area from \`(80, 100)\` to \`(600, 800)\` from a 1000×1000 pixels video, which is a square whose width is 520px and height is 700px. If \`crop\` is set, the width and height parameters are ignored, and the \`resize_strategy\` is set to \`crop\` automatically. - -You can also use a JSON string of such an object with coordinates in similar fashion: - -\`\`\`json -"{\\"x1\\": , \\"y1\\": , \\"x2\\": , \\"y2\\": }" -\`\`\` -`), - background: color_with_alpha.default('#00000000').describe(` -The background color of the resulting video the \`"rrggbbaa"\` format (red, green, blue, alpha) when used with the \`"pad"\` resize strategy. The default color is black. -`), - rotate: z - // We can’t use enum. - // See https://github.com/colinhacks/zod/issues/2686 - .union([ - z.literal(0), - z.literal(90), - z.literal(180), - z.literal(270), - z.literal(360), - z.literal(false), - ]) - .optional() - .describe(` -Forces the video to be rotated by the specified degree integer. Currently, only multiples of \`90\` are supported. We automatically correct the orientation of many videos when the orientation is provided by the camera. This option is only useful for videos requiring rotation because it was not detected by the camera. If you set \`rotate\` to \`false\` no rotation is performed, even if the metadata contains such instructions. -`), - hint: z - .boolean() - .default(false) - .describe(` -Enables hinting for mp4 files, for RTP/RTSP streaming. -`), - turbo: z - .boolean() - .default(false) - .describe(` -Splits the video into multiple chunks so that each chunk can be encoded in parallel before all encoded chunks are stitched back together to form the result video. This comes at the expense of extra Priority Job Slots and may prove to be counter-productive for very small video files. -`), - chunk_duration: z - .number() - .int() - .min(1) - .optional() - .describe(` -Allows you to specify the duration of each chunk when \`turbo\` is set to \`true\`. This means you can take advantage of that feature while using fewer Priority Job Slots. For instance, the longer each chunk is, the fewer Encoding Jobs will need to be used. -`), - watermark_url: z - .string() - .default('') - .describe(` -A URL indicating a PNG image to be overlaid above this image. You can also [supply the watermark via another Assembly Step](/docs/topics/use-parameter/#supplying-the-watermark-via-an-assembly-step). -`), - watermark_position: z - .union([positionSchema, z.array(positionSchema)]) - .default('center') - .describe(` -The position at which the watermark is placed. - -An array of possible values can also be specified, in which case one value will be selected at random, such as \`[ "center", "left", "bottom-left", "bottom-right" ]\`. - -This setting puts the watermark in the specified corner. To use a specific pixel offset for the watermark, you will need to add the padding to the image itself. -`), - watermark_x_offset: z - .number() - .int() - .default(0) - .describe(` -The x-offset in number of pixels at which the watermark will be placed in relation to the position it has due to \`watermark_position\`. - -Values can be both positive and negative and yield different results depending on the \`watermark_position\` parameter. Positive values move the watermark closer to the image's center point, whereas negative values move the watermark further away from the image's center point. -`), - watermark_y_offset: z - .number() - .int() - .default(0) - .describe(` -The y-offset in number of pixels at which the watermark will be placed in relation to the position it has due to \`watermark_position\`. - -Values can be both positive and negative and yield different results depending on the \`watermark_position\` parameter. Positive values move the watermark closer to the image's center point, whereas negative values move the watermark further away from the image's center point. -`), - watermark_size: percentageSchema.optional().describe(` -The size of the watermark, as a percentage, such as \`"50%"\`. How the watermark is resized greatly depends on the \`watermark_resize_strategy\`. -`), - watermark_resize_strategy: z - .enum(['area', 'fit', 'stretch']) - .default('fit') - .describe(` -To explain how the resize strategies work, let's assume our target video size is 800×800 pixels and our watermark image is 400×300 pixels. Let's also assume, the \`watermark_size\` parameter is set to \`"25%"\`. - -For the \`"fit"\` resize strategy, the watermark is scaled so that the longer side of the watermark takes up 25% of the corresponding video side. And the other side is scaled according to the aspect ratio of the watermark image. So with our watermark, the width is the longer side, and 25% of the video size would be 200px. Hence, the watermark would be resized to 200×150 pixels. If the \`watermark_size\` was set to \`"50%"\`", it would be resized to 400×300 pixels (so just left at its original size). - -For the \`"stretch"\` resize strategy, the watermark image is stretched (meaning, it is resized without keeping its aspect ratio in mind) so that both sides take up 25% of the corresponding video side. Since our video is 800×800 pixels, for a watermark size of 25% the watermark would be resized to 200×200 pixels. Its height would appear stretched, because keeping the aspect ratio in mind it would be resized to 200×150 pixels instead. - -For the \`"area"\` resize strategy, the watermark is resized (keeping its aspect ratio in check) so that it covers \`"xx%"\` of the video's surface area. The value from \`watermark_size\` is used for the percentage area size. -`), - watermark_start_time: z - .number() - .default(0) - .describe(` -The delay in seconds from the start of the video for the watermark to appear. By default the watermark is immediately shown. -`), - watermark_duration: z - .number() - .default(-1) - .describe(` -The duration in seconds for the watermark to be shown. Can be used together with \`watermark_start_time\` to create nice effects. The default value is \`-1.0\`, which means that the watermark is shown for the entire duration of the video. -`), - watermark_opacity: z - .number() - .min(0) - .max(1) - .default(1) - .describe(` -The opacity of the watermark. Valid values are between \`0\` (invisible) and \`1.0\` (full visibility). -`), - segment: z - .boolean() - .default(false) - .describe(` -Splits the file into multiple parts, to be used for Apple's [HTTP Live Streaming](https://developer.apple.com/resources/http-streaming/). -`), - segment_duration: z - .number() - .int() - .min(1) - .default(10) - .describe(` -Specifies the length of each HTTP segment. This is optional, and the default value as recommended by Apple is \`10\`. Do not change this value unless you have a good reason. -`), - segment_prefix: z - .string() - .default('') - .describe(` -The prefix used for the naming. For example, a prefix of \`"segment_"\` would produce files named \`"segment_0.ts"\`, \`"segment_1.ts"\` and so on. This is optional, and defaults to the base name of the input file. Also see the related \`segment_name\` parameter. -`), - segment_name: z - .string() - .default('') - .describe(` -The name used for the final segment. Available variables are \`\${segment_prefix}\`, \`\${segment_number}\` and \`\${segment_id}\` (which is a UUIDv4 without dashes). -`), - segment_time_delta: z - .number() - .optional() - .describe(` -Delta to apply to segment duration. This is optional and allows fine-tuning of segment boundaries. -`), - }) - .strict() - -/** - * Type for the normalized use parameter from AssemblyNormalizer - * The steps array can contain either strings or objects with name property - */ -export interface NormalizedUse { - steps: Array<{ name: string; as?: string; fields?: string }> -} diff --git a/packages/transloadit/src/alphalib/types/robots/ai-chat.ts b/packages/transloadit/src/alphalib/types/robots/ai-chat.ts deleted file mode 100644 index da94e545..00000000 --- a/packages/transloadit/src/alphalib/types/robots/ai-chat.ts +++ /dev/null @@ -1,278 +0,0 @@ -import { z } from 'zod' -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -// We duplicate coreMessageSchema (and its related types) from structuredAiVercel.ts here -// so that we do not need to distribute structuredAiVercel.ts to for instance -// the node-sdk, which does rely on this ai-chat file to determine -// support Robot parameters. - -// Define JSONValue schema for proper type matching with AI SDK -const jsonValueSchema: z.ZodType = z.lazy(() => - z.union([ - z.string(), - z.number(), - z.boolean(), - z.null(), - z.array(jsonValueSchema), - z.record(jsonValueSchema), - ]), -) - -// Define provider metadata schema to match the AI SDK v5 -const providerMetadataSchema = z.record(z.record(jsonValueSchema)).optional() - -const textPartSchema = z.object({ - type: z.literal('text'), - text: z.string(), - experimental_providerMetadata: providerMetadataSchema, -}) -const imagePartSchema = z.object({ - type: z.literal('image'), - image: z.union([ - z.string(), - z.instanceof(Uint8Array), - z.instanceof(ArrayBuffer), - // Note: Buffer is not included here since it's Node.js-only and this code runs in browsers. - // Node.js Buffer extends Uint8Array, so Uint8Array validation handles Buffer values too. - z.instanceof(URL), - ]), - mimeType: z.string().optional(), - experimental_providerMetadata: providerMetadataSchema, -}) -const filePartSchema = z.object({ - type: z.literal('file'), - data: z.union([ - z.string(), - z.instanceof(Uint8Array), - z.instanceof(ArrayBuffer), - // Note: Buffer is not included here since it's Node.js-only and this code runs in browsers. - // Node.js Buffer extends Uint8Array, so Uint8Array validation handles Buffer values too. - z.instanceof(URL), - ]), - mediaType: z.string(), - experimental_providerMetadata: providerMetadataSchema, -}) -const toolCallPartSchema = z.object({ - type: z.literal('tool-call'), - toolCallId: z.string(), - toolName: z.string(), - args: z.record(jsonValueSchema), - experimental_providerMetadata: providerMetadataSchema, -}) -const toolResultPartSchema = z.object({ - type: z.literal('tool-result'), - toolCallId: z.string(), - toolName: z.string(), - result: z.unknown(), - experimental_content: z - .array( - z.union([ - z.object({ - type: z.literal('text'), - text: z.string(), - }), - z.object({ - type: z.literal('image'), - data: z.string(), - mimeType: z.string().optional(), - }), - ]), - ) - .optional(), - isError: z.boolean().optional(), - experimental_providerMetadata: providerMetadataSchema, -}) -const coreSystemMessageSchema = z.object({ - role: z.literal('system'), - content: z.string(), - experimental_providerMetadata: providerMetadataSchema, -}) -const coreUserMessageSchema = z.object({ - role: z.literal('user'), - content: z.union([ - z.string(), - z.array(z.union([textPartSchema, imagePartSchema, filePartSchema])), - ]), - experimental_providerMetadata: providerMetadataSchema, -}) -const coreAssistantMessageSchema = z.object({ - role: z.literal('assistant'), - content: z.union([z.string(), z.array(z.union([textPartSchema, toolCallPartSchema]))]), - experimental_providerMetadata: providerMetadataSchema, -}) -const coreToolMessageSchema = z.object({ - role: z.literal('tool'), - content: z.array(toolResultPartSchema), - experimental_providerMetadata: providerMetadataSchema, -}) -const coreMessageSchema = z.discriminatedUnion('role', [ - coreSystemMessageSchema, - coreUserMessageSchema, - coreAssistantMessageSchema, - coreToolMessageSchema, -]) - -export const meta: RobotMetaInput = { - name: 'AiChatRobot', - allowed_for_url_transform: true, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - minimum_charge: 0, - output_factor: 0.6, - purpose_sentence: 'generates AI chat responses from prompts', - purpose_verb: 'generate', - purpose_word: 'generate', - purpose_words: 'Generate AI chat responses', - service_slug: 'artificial-intelligence', - slot_count: 10, - title: 'Generate AI chat responses', - typical_file_size_mb: 0.01, - typical_file_type: 'document', - priceFactor: 1, - queueSlotCount: 10, - // Is this a sensbile minimum charge? What if the customer supplies their own keys? Is it low enough for these cases? - minimumChargeUsd: 0.06, - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'alpha', -} - -export const vendorModelSchema = z - .string() - .regex(/^[a-z]+\/[a-z0-9.-]+$/, 'Must be in format "vendor/model"') - .refine( - (val) => { - const [vendor, model] = val.split('/') - if (vendor === 'anthropic') { - return model === 'claude-4-sonnet-20250514' || model === 'claude-4-opus-20250514' - } - if (vendor === 'openai') { - return ( - model === 'gpt-4.1-2025-04-14' || - model === 'chatgpt-4o-latest' || - model === 'o3-2025-04-16' || - model === 'gpt-4o-audio-preview' - ) - } - if (vendor === 'google') { - return model === 'gemini-2.5-pro' - } - if (vendor === 'moonshot') { - return model === 'kimi-k2' - } - return false - }, - { - message: - 'Invalid vendor/model combination. Supported: anthropic/claude-4-sonnet-20250514, anthropic/claude-4-opus-20250514, openai/gpt-4.1-2025-04-14, openai/chatgpt-4o-latest, openai/o3-2025-04-16, openai/gpt-4o-audio-preview, google/gemini-2.5-pro, moonshot/kimi-k2', - }, - ) - -export type VendorModel = z.infer - -/** - * Model capabilities for /ai/chat. This centralizes which models support which input types. - * Key format: 'vendor/model' - */ -export const MODEL_CAPABILITIES: Record = { - 'anthropic/claude-4-sonnet-20250514': { pdf: true, image: true }, - 'anthropic/claude-4-opus-20250514': { pdf: true, image: true }, - 'google/gemini-2.5-pro': { pdf: true, image: true }, - 'openai/gpt-4.1-2025-04-14': { pdf: false, image: true }, - 'openai/chatgpt-4o-latest': { pdf: false, image: true }, - 'openai/o3-2025-04-16': { pdf: false, image: true }, - 'openai/gpt-4o-audio-preview': { pdf: false, image: false }, - 'moonshot/kimi-k2': { pdf: false, image: false }, -} - -export const robotAiChatInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/ai/chat'), - // TODO: Is the auto mode yet implemented? - model: z - .union([vendorModelSchema, z.literal('auto')]) - .default('auto') - .describe( - 'The model to use. Transloadit can pick the best model for the job if you set this to "auto".', - ), - format: z.enum(['json', 'text', 'meta']).default('json'), - return_messages: z.enum(['all', 'last']).default('last'), - schema: z.string().optional().describe('The JSON Schema that the LLM should output'), - messages: z - .union([z.string(), z.array(coreMessageSchema)]) - .describe('The prompt, or message history to send to the LLM.'), - system_message: z - .string() - .optional() - .describe('Set the system/developer prompt, if the model allows it'), - credentials: z - .union([z.string(), z.array(z.string())]) - .optional() - .describe('Names of template credentials to make available to the robot.'), - mcp_servers: z - .array( - z.object({ - type: z.enum(['sse', 'http']), - url: z.string(), - headers: z.record(z.string()).optional(), - }), - ) - .optional() - .describe('The MCP servers to use. This is used to call tools from the LLM.'), - }) - .strict() - -export const robotAiChatInstructionsWithHiddenFieldsSchema = robotAiChatInstructionsSchema.extend({ - result: z.union([z.literal('debug'), robotAiChatInstructionsSchema.shape.result]), - provider: z - .string() - .optional() - .describe( - 'Where to run the model. By the default, it is the vendor. For instance, anthropic:claude* runs on the Anthropic API. But, Claude could also be run on AWS Bedrock. This is a hidden placeholder for now, but will be used in the future to allow for more flexibility in where to run models. ', - ), - // These are listed here because we don't have these properties in the public documentation. - // They should set these keys using template credentials. - openai_api_key: z.string().optional().describe('The API key to use for the OpenAI API.'), - anthropic_api_key: z.string().optional().describe('The API key to use for the Anthropic API.'), - deepseek_api_key: z.string().optional().describe('The API key to use for the DeepSeek API.'), - google_generative_ai_api_key: z - .string() - .optional() - .describe('The API key to use for the Google Generative AI API.'), - xai_api_key: z.string().optional().describe('The API key to use for the xAI API.'), -}) - -export type RobotAiChatInstructions = z.infer - -export type RobotAiChatInstructionsWithHiddenFields = z.infer< - typeof robotAiChatInstructionsWithHiddenFieldsSchema -> - -export type RobotAiChatInstructionsWithHiddenFieldsInput = z.input< - typeof robotAiChatInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotAiChatInstructionsSchema = interpolateRobot( - robotAiChatInstructionsSchema, -) -export type InterpolatableRobotAiChatInstructions = z.infer< - typeof interpolatableRobotAiChatInstructionsSchema -> -export type InterpolatableRobotAiChatInstructionsInput = z.input< - typeof interpolatableRobotAiChatInstructionsSchema -> - -export const interpolatableRobotAiChatInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotAiChatInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotAiChatInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotAiChatInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotAiChatInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotAiChatInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/assembly-savejson.ts b/packages/transloadit/src/alphalib/types/robots/assembly-savejson.ts deleted file mode 100644 index 99428241..00000000 --- a/packages/transloadit/src/alphalib/types/robots/assembly-savejson.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { z } from 'zod' -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase } from './_instructions-primitives.ts' - -// @ts-expect-error - AssemblySavejsonRobot is not ready yet @TODO please supply missing keys -export const meta: RobotMetaInput = { - name: 'AssemblySavejsonRobot', - priceFactor: 0, - queueSlotCount: 5, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: true, - stage: 'ga', - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, -} - -export const robotAssemblySavejsonInstructionsSchema = robotBase - .extend({ - robot: z.literal('/assembly/savejson').describe(` -TODO: Add robot description here -`), - }) - .strict() - -export type RobotAssemblySavejsonInstructions = z.infer< - typeof robotAssemblySavejsonInstructionsSchema -> - -export const interpolatableRobotAssemblySavejsonInstructionsSchema = interpolateRobot( - robotAssemblySavejsonInstructionsSchema, -) -export type InterpolatableRobotAssemblySavejsonInstructions = - InterpolatableRobotAssemblySavejsonInstructionsInput - -export type InterpolatableRobotAssemblySavejsonInstructionsInput = z.input< - typeof interpolatableRobotAssemblySavejsonInstructionsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/audio-artwork.ts b/packages/transloadit/src/alphalib/types/robots/audio-artwork.ts deleted file mode 100644 index e57096f5..00000000 --- a/packages/transloadit/src/alphalib/types/robots/audio-artwork.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { z } from 'zod' - -import { stackVersions } from '../stackVersions.ts' -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - interpolateRobot, - robotBase, - robotFFmpegAudio, - robotUse, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - example_code: { - steps: { - artwork_extracted: { - robot: '/audio/artwork', - use: ':original', - ffmpeg_stack: stackVersions.ffmpeg.recommendedVersion, - }, - }, - }, - example_code_description: 'Extract embedded cover artwork from uploaded audio files:', - minimum_charge: 0, - output_factor: 0.8, - override_lvl1: 'Audio Encoding', - purpose_sentence: - 'extracts the embedded cover artwork from audio files and allows you to pipe it into other Steps, for example into /image/resize Steps. It can also insert images into audio files as cover artwork', - purpose_verb: 'extract', - purpose_word: 'extract/insert artwork', - purpose_words: 'Extract or insert audio artwork', - service_slug: 'audio-encoding', - slot_count: 20, - title: 'Extract or insert audio artwork', - typical_file_size_mb: 3.8, - typical_file_type: 'audio file', - uses_tools: ['ffmpeg'], - name: 'AudioArtworkRobot', - priceFactor: 1, - queueSlotCount: 20, - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotAudioArtworkInstructionsSchema = robotBase - .merge(robotUse) - .merge(robotFFmpegAudio) - .extend({ - robot: z.literal('/audio/artwork').describe(` -For extraction, this Robot uses the image format embedded within the audio file — most often, this is JPEG. - -If you need the image in a different format, pipe the result of this Robot into [🤖/image/resize](/docs/robots/image-resize/). - -The \`method\` parameter determines whether to extract or insert. -`), - method: z - .enum(['extract', 'insert']) - .default('extract') - .describe(` -What should be done with the audio file. A value of \`"extract"\` means audio artwork will be extracted. A value of \`"insert"\` means the provided image will be inserted as audio artwork. -`), - change_format_if_necessary: z - .boolean() - .default(false) - .describe(` -Whether the original file should be transcoded into a new format if there is an issue with the original file. -`), - }) - .strict() - -export const robotAudioArtworkInstructionsWithHiddenFieldsSchema = - robotAudioArtworkInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotAudioArtworkInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotAudioArtworkInstructions = z.infer -export type RobotAudioArtworkInstructionsWithHiddenFields = z.infer< - typeof robotAudioArtworkInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotAudioArtworkInstructionsSchema = interpolateRobot( - robotAudioArtworkInstructionsSchema, -) -export type InterpolatableRobotAudioArtworkInstructions = - InterpolatableRobotAudioArtworkInstructionsInput - -export type InterpolatableRobotAudioArtworkInstructionsInput = z.input< - typeof interpolatableRobotAudioArtworkInstructionsSchema -> - -export const interpolatableRobotAudioArtworkInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotAudioArtworkInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotAudioArtworkInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotAudioArtworkInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotAudioArtworkInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotAudioArtworkInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/audio-concat.ts b/packages/transloadit/src/alphalib/types/robots/audio-concat.ts deleted file mode 100644 index 2d223372..00000000 --- a/packages/transloadit/src/alphalib/types/robots/audio-concat.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { z } from 'zod' - -import { stackVersions } from '../stackVersions.ts' -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - bitrateSchema, - interpolateRobot, - robotBase, - robotFFmpegAudio, - robotUse, - robotUseWithHiddenFields, - sampleRateSchema, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: false, - bytescount: 4, - discount_factor: 0.25, - discount_pct: 75, - example_code: { - steps: { - concatenated: { - robot: '/audio/concat', - use: { - steps: [ - { - name: ':original', - fields: 'first_audio_file', - as: 'audio_1', - }, - { - name: ':original', - fields: 'second_audio_file', - as: 'audio_2', - }, - { - name: ':original', - fields: 'third_audio_file', - as: 'audio_3', - }, - ], - }, - ffmpeg_stack: stackVersions.ffmpeg.recommendedVersion, - }, - }, - }, - example_code_description: - 'If you have a form with 3 file input fields and want to concatenate the uploaded audios in a specific order, instruct Transloadit using the `name` attribute of each input field. Use this attribute as the value for the `fields` key in the JSON, and set `as` to `audio_[[index]]`. Transloadit will concatenate the files based on the ascending index order:', - minimum_charge: 0, - output_factor: 0.8, - override_lvl1: 'Audio Encoding', - purpose_sentence: 'concatenates several audio files together', - purpose_verb: 'concatenate', - purpose_word: 'concatenate', - purpose_words: 'Concatenate audio', - service_slug: 'audio-encoding', - slot_count: 20, - title: 'Concatenate audio', - typical_file_size_mb: 3.8, - typical_file_type: 'audio file', - uses_tools: ['ffmpeg'], - name: 'AudioConcatRobot', - priceFactor: 4, - queueSlotCount: 20, - isAllowedForUrlTransform: false, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotAudioConcatInstructionsSchema = robotBase - .merge(robotUse) - .merge(robotFFmpegAudio) - .extend({ - result: z - .boolean() - .optional() - .describe('Whether the results of this Step should be present in the Assembly Status JSON'), - robot: z.literal('/audio/concat').describe(` -This Robot can concatenate an almost infinite number of audio files. -`), - bitrate: bitrateSchema.optional().describe(` -Bit rate of the resulting audio file, in bits per second. If not specified will default to the bit rate of the input audio file. -`), - sample_rate: sampleRateSchema.optional().describe(` -Sample rate of the resulting audio file, in Hertz. If not specified will default to the sample rate of the input audio file. -`), - audio_fade_seconds: z - .number() - .default(1) - .describe(` -When used this adds an audio fade in and out effect between each section of your concatenated audio file. The float value is used, so if you want an audio delay effect of 500 milliseconds between each video section, you would select 0.5. Integer values can also be represented. - -This parameter does not add an audio fade effect at the beginning or end of your result audio file. If you want to do so, create an additional [🤖/audio/encode](/docs/robots/audio-encode/) Step and use our \`ffmpeg\` parameter as shown in this [demo](/demos/audio-encoding/ffmpeg-fade-in-and-out/). -`), - crossfade: z - .boolean() - .default(false) - .describe(` -When set to \`true\`, this parameter enables crossfading between concatenated audio files using FFmpeg's \`acrossfade\` filter. This creates a smooth transition where the end of one audio file overlaps and blends with the beginning of the next file. - -The duration of the crossfade is controlled by the \`audio_fade_seconds\` parameter (defaults to 1 second if \`audio_fade_seconds\` is 0). - -Note: This parameter requires at least 2 audio files to concatenate and only works with audio files, not video files. -`), - }) - .strict() - -export const robotAudioConcatInstructionsWithHiddenFieldsSchema = robotAudioConcatInstructionsSchema - .omit({ use: true }) - .merge(robotUseWithHiddenFields) - .extend({ - result: z - .union([z.literal('debug'), robotAudioConcatInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotAudioConcatInstructions = z.infer -export type RobotAudioConcatInstructionsWithHiddenFields = z.infer< - typeof robotAudioConcatInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotAudioConcatInstructionsSchema = interpolateRobot( - robotAudioConcatInstructionsSchema, -) -export type InterpolatableRobotAudioConcatInstructions = - InterpolatableRobotAudioConcatInstructionsInput - -export type InterpolatableRobotAudioConcatInstructionsInput = z.input< - typeof interpolatableRobotAudioConcatInstructionsSchema -> - -export const interpolatableRobotAudioConcatInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotAudioConcatInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotAudioConcatInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotAudioConcatInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotAudioConcatInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotAudioConcatInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/audio-encode.ts b/packages/transloadit/src/alphalib/types/robots/audio-encode.ts deleted file mode 100644 index 2476cee8..00000000 --- a/packages/transloadit/src/alphalib/types/robots/audio-encode.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { z } from 'zod' - -import { stackVersions } from '../stackVersions.ts' -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - bitrateSchema, - interpolateRobot, - robotBase, - robotFFmpegAudio, - robotUse, - sampleRateSchema, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: false, - bytescount: 4, - discount_factor: 0.25, - discount_pct: 75, - example_code: { - steps: { - mp3_encoded: { - robot: '/audio/encode', - use: ':original', - preset: 'mp3', - bitrate: 256000, - ffmpeg_stack: stackVersions.ffmpeg.recommendedVersion, - }, - }, - }, - example_code_description: 'Encode uploaded audio to MP3 format at a 256 kbps bitrate:', - minimum_charge: 0, - output_factor: 0.8, - override_lvl1: 'Audio Encoding', - purpose_sentence: - 'converts audio files into all kinds of formats for you. We provide encoding presets for the most common formats', - purpose_verb: 'encode', - purpose_word: 'encode', - purpose_words: 'Encode audio', - service_slug: 'audio-encoding', - slot_count: 20, - title: 'Encode audio', - typical_file_size_mb: 3.8, - typical_file_type: 'audio file', - uses_tools: ['ffmpeg'], - name: 'AudioEncodeRobot', - priceFactor: 4, - queueSlotCount: 20, - isAllowedForUrlTransform: false, - trackOutputFileSize: true, - isInternal: false, - stage: 'ga', - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, -} - -export const robotAudioEncodeInstructionsSchema = robotBase - .merge(robotUse) - .merge(robotFFmpegAudio) - .extend({ - result: z - .boolean() - .optional() - .describe('Whether the results of this Step should be present in the Assembly Status JSON'), - robot: z.literal('/audio/encode'), - bitrate: bitrateSchema.optional().describe(` -Bit rate of the resulting audio file, in bits per second. If not specified will default to the bit rate of the input audio file. -`), - sample_rate: sampleRateSchema.optional().describe(` -Sample rate of the resulting audio file, in Hertz. If not specified will default to the sample rate of the input audio file. -`), - }) - .strict() - -export const robotAudioEncodeInstructionsWithHiddenFieldsSchema = - robotAudioEncodeInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotAudioEncodeInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotAudioEncodeInstructions = z.infer -export type RobotAudioEncodeInstructionsWithHiddenFields = z.infer< - typeof robotAudioEncodeInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotAudioEncodeInstructionsSchema = interpolateRobot( - robotAudioEncodeInstructionsSchema, -) -export type InterpolatableRobotAudioEncodeInstructions = - InterpolatableRobotAudioEncodeInstructionsInput - -export type InterpolatableRobotAudioEncodeInstructionsInput = z.input< - typeof interpolatableRobotAudioEncodeInstructionsSchema -> - -export const interpolatableRobotAudioEncodeInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotAudioEncodeInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotAudioEncodeInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotAudioEncodeInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotAudioEncodeInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotAudioEncodeInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/audio-loop.ts b/packages/transloadit/src/alphalib/types/robots/audio-loop.ts deleted file mode 100644 index d0f4b872..00000000 --- a/packages/transloadit/src/alphalib/types/robots/audio-loop.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { z } from 'zod' - -import { stackVersions } from '../stackVersions.ts' -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - bitrateSchema, - interpolateRobot, - robotBase, - robotFFmpegAudio, - robotUse, - sampleRateSchema, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: false, - bytescount: 4, - discount_factor: 0.25, - discount_pct: 75, - example_code: { - steps: { - looped: { - robot: '/audio/loop', - use: ':original', - duration: 300, - ffmpeg_stack: stackVersions.ffmpeg.recommendedVersion, - }, - }, - }, - example_code_description: 'Loop uploaded audio to achieve a target duration of 300 seconds:', - marketing_intro: - 'Whether you’re producing beats, white-noise, or just empty segments as fillers between audio tracks that you’re to stringing together with [🤖/audio/concat](/docs/robots/audio-concat/), [🤖/audio/loop](/docs/robots/audio-loop/) has got your back.', - minimum_charge: 0, - output_factor: 0.8, - override_lvl1: 'Audio Encoding', - purpose_sentence: 'loops one audio file as often as is required to match a given duration', - purpose_verb: 'loop', - purpose_word: 'loop', - purpose_words: 'Loop audio', - service_slug: 'audio-encoding', - slot_count: 20, - title: 'Loop audio', - typical_file_size_mb: 3.8, - typical_file_type: 'audio file', - uses_tools: ['ffmpeg'], - name: 'AudioLoopRobot', - priceFactor: 4, - queueSlotCount: 20, - isAllowedForUrlTransform: false, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotAudioLoopInstructionsSchema = robotBase - .merge(robotUse) - .merge(robotFFmpegAudio) - .extend({ - robot: z.literal('/audio/loop'), - bitrate: bitrateSchema.optional().describe(` -Bit rate of the resulting audio file, in bits per second. If not specified will default to the bit rate of the input audio file. -`), - sample_rate: sampleRateSchema.optional().describe(` -Sample rate of the resulting audio file, in Hertz. If not specified will default to the sample rate of the input audio file. -`), - duration: z - .number() - .default(60) - .describe(` -Target duration for the whole process in seconds. The Robot will loop the input audio file for as long as this target duration is not reached yet. -`), - }) - .strict() - -export const robotAudioLoopInstructionsWithHiddenFieldsSchema = - robotAudioLoopInstructionsSchema.extend({ - result: z.union([z.literal('debug'), robotAudioLoopInstructionsSchema.shape.result]).optional(), - }) - -export type RobotAudioLoopInstructions = z.infer -export type RobotAudioLoopInstructionsWithHiddenFields = z.infer< - typeof robotAudioLoopInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotAudioLoopInstructionsSchema = interpolateRobot( - robotAudioLoopInstructionsSchema, -) -export type InterpolatableRobotAudioLoopInstructions = InterpolatableRobotAudioLoopInstructionsInput - -export type InterpolatableRobotAudioLoopInstructionsInput = z.input< - typeof interpolatableRobotAudioLoopInstructionsSchema -> - -export const interpolatableRobotAudioLoopInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotAudioLoopInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotAudioLoopInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotAudioLoopInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotAudioLoopInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotAudioLoopInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/audio-merge.ts b/packages/transloadit/src/alphalib/types/robots/audio-merge.ts deleted file mode 100644 index e1059540..00000000 --- a/packages/transloadit/src/alphalib/types/robots/audio-merge.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { z } from 'zod' - -import { stackVersions } from '../stackVersions.ts' -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - bitrateSchema, - interpolateRobot, - robotBase, - robotFFmpegAudio, - robotUse, - robotUseWithHiddenFields, - sampleRateSchema, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: false, - bytescount: 4, - discount_factor: 0.25, - discount_pct: 75, - example_code: { - steps: { - merged: { - robot: '/audio/merge', - preset: 'mp3', - use: { - steps: [ - { - name: ':original', - fields: 'first_audio_file', - as: 'audio', - }, - { - name: ':original', - fields: 'second_audio_file', - as: 'audio', - }, - { - name: ':original', - fields: 'third_audio_file', - as: 'audio', - }, - ], - }, - ffmpeg_stack: stackVersions.ffmpeg.recommendedVersion, - }, - }, - }, - example_code_description: - 'If you have a form with 3 file input fields and wish to overlay the uploaded audios, instruct Transloadit using the `name` attribute of each input field. Use this attribute as the value for the `fields` key in the JSON, and set `as` to `audio`:', - minimum_charge: 0, - output_factor: 0.8, - override_lvl1: 'Audio Encoding', - purpose_sentence: 'overlays several audio files on top of each other', - purpose_verb: 'merge', - purpose_word: 'merge', - purpose_words: 'Merge audio files into one', - service_slug: 'audio-encoding', - slot_count: 20, - title: 'Merge audio files into one', - typical_file_size_mb: 3.8, - typical_file_type: 'audio file', - uses_tools: ['ffmpeg'], - name: 'AudioMergeRobot', - priceFactor: 4, - queueSlotCount: 20, - isAllowedForUrlTransform: false, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotAudioMergeInstructionsSchema = robotBase - .merge(robotUse) - .merge(robotFFmpegAudio) - .extend({ - robot: z.literal('/audio/merge'), - bitrate: bitrateSchema.optional().describe(` -Bit rate of the resulting audio file, in bits per second. If not specified will default to the bit rate of the input audio file. -`), - sample_rate: sampleRateSchema.optional().describe(` -Sample rate of the resulting audio file, in Hertz. If not specified will default to the sample rate of the input audio file. -`), - duration: z - .enum(['first', 'longest', 'shortest']) - .default('longest') - .describe(` -Duration of the output file compared to the duration of all merged audio files. Can be \`"first"\` (duration of the first input file), \`"shortest"\` (duration of the shortest audio file) or \`"longest"\` for the duration of the longest input file. -`), - loop: z - .boolean() - .default(false) - .describe(` -Specifies if any input files that do not match the target duration should be looped to match it. Useful for audio merging where your overlay file is typically much shorter than the main audio file. -`), - volume: z - .enum(['average', 'sum']) - .default('average') - .describe(` -Valid values are \`"average"\` and \`"sum"\` here. \`"average"\` means each input is scaled 1/n (n is the number of inputs) or \`"sum"\` which means each individual audio stays on the same volume, but since we merge tracks 'on top' of each other, this could result in very loud output. -`), - }) - .strict() - -export const robotAudioMergeInstructionsWithHiddenFieldsSchema = robotAudioMergeInstructionsSchema - .omit({ use: true }) - .merge(robotUseWithHiddenFields) - .extend({ - result: z - .union([z.literal('debug'), robotAudioMergeInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotAudioMergeInstructions = z.infer -export type RobotAudioMergeInstructionsWithHiddenFields = z.infer< - typeof robotAudioMergeInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotAudioMergeInstructionsSchema = interpolateRobot( - robotAudioMergeInstructionsSchema, -) -export type InterpolatableRobotAudioMergeInstructions = - InterpolatableRobotAudioMergeInstructionsInput - -export type InterpolatableRobotAudioMergeInstructionsInput = z.input< - typeof interpolatableRobotAudioMergeInstructionsSchema -> - -export const interpolatableRobotAudioMergeInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotAudioMergeInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotAudioMergeInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotAudioMergeInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotAudioMergeInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotAudioMergeInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/audio-waveform.ts b/packages/transloadit/src/alphalib/types/robots/audio-waveform.ts deleted file mode 100644 index a48c8795..00000000 --- a/packages/transloadit/src/alphalib/types/robots/audio-waveform.ts +++ /dev/null @@ -1,280 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - color_with_alpha, - interpolateRobot, - robotBase, - robotFFmpeg, - robotUse, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: false, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - example_code: { - steps: { - waveformed: { - robot: '/audio/waveform', - use: ':original', - width: 400, - height: 200, - outer_color: '0099ccff', - center_color: '0099ccff', - }, - }, - }, - example_code_description: - 'Generate a 400×200 waveform in `#0099cc` color from an uploaded audio file:', - extended_description: ` -Here is an example waveform image: - -{% assign hotDemo = collections.demos |find: "url", "/demos/audio-encoding/generate-a-waveform-image-from-an-audio-file/" %} - -Example waveform image -`, - minimum_charge: 1048576, - output_factor: 0.07, - override_lvl1: 'Audio Encoding', - purpose_sentence: - 'generates waveform images for your audio files and allows you to change their colors and dimensions', - purpose_verb: 'generate', - purpose_word: 'generate waveforms', - purpose_words: 'Generate waveform images from audio', - service_slug: 'audio-encoding', - slot_count: 20, - title: 'Generate waveform images from audio', - typical_file_size_mb: 3.8, - typical_file_type: 'audio file', - name: 'AudioWaveformRobot', - priceFactor: 1, - queueSlotCount: 20, - minimumCharge: 1048576, - isAllowedForUrlTransform: false, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -// Base schema with common fields -const robotAudioWaveformInstructionsBaseSchema = robotBase - .merge(robotUse) - .merge(robotFFmpeg) - .extend({ - robot: z.literal('/audio/waveform').describe(` -We recommend that you use an [🤖/audio/encode](/docs/robots/audio-encode/) Step prior to your waveform Step to convert audio files to MP3. This way it is guaranteed that [🤖/audio/waveform](/docs/robots/audio-waveform/) accepts your audio file and you can also down-sample large audio files and save some money. - -Similarly, if you need the output image in a different format, please pipe the result of this Robot into [🤖/image/resize](/docs/robots/image-resize/). -`), - format: z - .enum(['image', 'json']) - .default('image') - .describe(` -The format of the result file. Can be \`"image"\` or \`"json"\`. If \`"image"\` is supplied, a PNG image will be created, otherwise a JSON file. -`), - width: z - .number() - .int() - .min(1) - .default(256) - .describe(` -The width of the resulting image if the format \`"image"\` was selected. -`), - height: z - .number() - .int() - .min(1) - .default(64) - .describe(` -The height of the resulting image if the format \`"image"\` was selected. -`), - antialiasing: z - .union([z.literal(0), z.literal(1), z.boolean()]) - .default(0) - .describe(` -Either a value of \`0\` or \`1\`, or \`true\`/\`false\`, corresponding to if you want to enable antialiasing to achieve smoother edges in the waveform graph or not. -`), - background_color: color_with_alpha.default('#00000000').describe(` -The background color of the resulting image in the "rrggbbaa" format (red, green, blue, alpha), if the format \`"image"\` was selected. -`), - center_color: color_with_alpha.default('000000ff').describe(` -The color used in the center of the gradient. The format is "rrggbbaa" (red, green, blue, alpha). -`), - outer_color: color_with_alpha.default('000000ff').describe(` -The color used in the outer parts of the gradient. The format is "rrggbbaa" (red, green, blue, alpha). -`), - }) - -const styleSchema = z.preprocess( - (val) => { - // Backwards compatibility: historically this robot used numeric styles 0/1/2. - // The new API is `style: "v0" | "v1"`. Old v2 values are mapped to v1. - if (val === 'v1' || val === 1 || val === '1') return 'v1' - if (val === 'v0' || val === 0 || val === '0') return 'v0' - return val - }, - z.enum(['v0', 'v1']).default('v0'), -) - -// Unified schema: all parameters exist for both styles, but v1-only parameters only apply when -// `style` is `v1` (they are accepted for v0 but have no effect). -export const robotAudioWaveformInstructionsSchema = robotAudioWaveformInstructionsBaseSchema - .extend({ - style: styleSchema.describe(` -Waveform style version. - -- \`"v0"\`: Legacy waveform generation (default). -- \`"v1"\`: Advanced waveform generation with additional parameters. - -For backwards compatibility, numeric values \`0\`, \`1\`, \`2\` are also accepted and mapped to \`"v0"\` (0) and \`"v1"\` (1/2). -`), - - // v1-only parameters (accepted for v0 but have no effect) - split_channels: z - .boolean() - .optional() - .describe(` -Available when style is \`"v1"\`. If set to \`true\`, outputs multi-channel waveform data or image files, one per channel. -`), - zoom: z - .number() - .int() - .min(1) - .optional() - .describe(` -Available when style is \`"v1"\`. Zoom level in samples per pixel. This parameter cannot be used together with \`pixels_per_second\`. -`), - pixels_per_second: z - .number() - .positive() - .optional() - .describe(` -Available when style is \`"v1"\`. Zoom level in pixels per second. This parameter cannot be used together with \`zoom\`. -`), - bits: z - .union([z.literal(8), z.literal(16)]) - .optional() - .describe(` -Available when style is \`"v1"\`. Bit depth for waveform data. Can be 8 or 16. -`), - start: z - .number() - .min(0) - .optional() - .describe(` -Available when style is \`"v1"\`. Start time in seconds. -`), - end: z - .number() - .min(0) - .optional() - .describe(` -Available when style is \`"v1"\`. End time in seconds (0 means end of audio). -`), - colors: z - .enum(['audition', 'audacity']) - .optional() - .describe(` -Available when style is \`"v1"\`. Color scheme to use. Can be "audition" or "audacity". -`), - border_color: color_with_alpha.optional().describe(` -Available when style is \`"v1"\`. Border color in "rrggbbaa" format. -`), - waveform_style: z - .enum(['normal', 'bars']) - .optional() - .describe(` -Available when style is \`"v1"\`. Waveform style. Can be "normal" or "bars". -`), - bar_width: z - .number() - .int() - .positive() - .optional() - .describe(` -Available when style is \`"v1"\`. Width of bars in pixels when waveform_style is "bars". -`), - bar_gap: z - .number() - .int() - .min(0) - .optional() - .describe(` -Available when style is \`"v1"\`. Gap between bars in pixels when waveform_style is "bars". -`), - bar_style: z - .enum(['square', 'rounded']) - .optional() - .describe(` -Available when style is \`"v1"\`. Bar style when waveform_style is "bars". -`), - axis_label_color: color_with_alpha.optional().describe(` -Available when style is \`"v1"\`. Color for axis labels in "rrggbbaa" format. -`), - no_axis_labels: z - .boolean() - .optional() - .describe(` -Available when style is \`"v1"\`. If set to \`true\`, renders waveform image without axis labels. -`), - with_axis_labels: z - .boolean() - .optional() - .describe(` -Available when style is \`"v1"\`. If set to \`true\`, renders waveform image with axis labels. -`), - amplitude_scale: z - .number() - .positive() - .optional() - .describe(` -Available when style is \`"v1"\`. Amplitude scale factor. -`), - compression: z - .number() - .int() - .min(-1) - .max(9) - .optional() - .describe(` -Available when style is \`"v1"\`. PNG compression level: 0 (none) to 9 (best), or -1 (default). Only applicable when format is "image". -`), - }) - .strict() - -export const robotAudioWaveformInstructionsWithHiddenFieldsSchema = - robotAudioWaveformInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotAudioWaveformInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotAudioWaveformInstructions = z.infer -export type RobotAudioWaveformInstructionsWithHiddenFields = z.infer< - typeof robotAudioWaveformInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotAudioWaveformInstructionsSchema = interpolateRobot( - robotAudioWaveformInstructionsSchema, -) - -export type InterpolatableRobotAudioWaveformInstructions = z.input< - typeof interpolatableRobotAudioWaveformInstructionsSchema -> -export type InterpolatableRobotAudioWaveformInstructionsInput = z.input< - typeof interpolatableRobotAudioWaveformInstructionsSchema -> - -export const interpolatableRobotAudioWaveformInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotAudioWaveformInstructionsWithHiddenFieldsSchema, -) - -export type InterpolatableRobotAudioWaveformInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotAudioWaveformInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotAudioWaveformInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotAudioWaveformInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/azure-import.ts b/packages/transloadit/src/alphalib/types/robots/azure-import.ts deleted file mode 100644 index 806277d2..00000000 --- a/packages/transloadit/src/alphalib/types/robots/azure-import.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - azureBase, - files_per_page, - interpolateRobot, - next_page_token, - path, - recursive, - robotBase, - robotImport, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 10, - discount_factor: 0.1, - discount_pct: 90, - example_code: { - steps: { - imported: { - robot: '/azure/import', - credentials: 'YOUR_AZURE_CREDENTIALS', - path: 'path/to/files/', - }, - }, - }, - example_code_description: - 'Import files from the `path/to/files` directory and its subdirectories:', - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Importing', - purpose_sentence: 'imports whole directories of files from your Azure container', - purpose_verb: 'import', - purpose_word: 'Azure', - purpose_words: 'Import files from Azure', - service_slug: 'file-importing', - requires_credentials: true, - slot_count: 20, - title: 'Import files from Azure', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'AzureImportRobot', - priceFactor: 6.6666, - queueSlotCount: 20, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: true, - stage: 'ga', -} - -export const robotAzureImportInstructionsSchema = robotBase - .merge(robotImport) - .merge(azureBase) - .extend({ - robot: z.literal('/azure/import'), - path: path.describe(` -The path in your container to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. - -If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are descendants of this directory are recursively imported. For example: \`images/\`. - -If you want to import all files from the root directory, please use \`/\` as the value here. - -You can also use an array of path strings here to import multiple paths in the same Robot's Step. -`), - recursive: recursive.describe(` - Setting this to \`true\` will enable importing files from subdirectories and sub-subdirectories (etc.) of the given path. - `), - next_page_token: next_page_token.describe(` -A string token used for pagination. The returned files of one paginated call have the next page token inside of their meta data, which needs to be used for the subsequent paging call. -`), - files_per_page: files_per_page.describe(` -The pagination page size. -`), - }) - .strict() - -export const robotAzureImportInstructionsWithHiddenFieldsSchema = - robotAzureImportInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotAzureImportInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotAzureImportInstructions = z.infer -export type RobotAzureImportInstructionsWithHiddenFields = z.infer< - typeof robotAzureImportInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotAzureImportInstructionsSchema = interpolateRobot( - robotAzureImportInstructionsSchema, -) -export type InterpolatableRobotAzureImportInstructions = - InterpolatableRobotAzureImportInstructionsInput - -export type InterpolatableRobotAzureImportInstructionsInput = z.input< - typeof interpolatableRobotAzureImportInstructionsSchema -> - -export const interpolatableRobotAzureImportInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotAzureImportInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotAzureImportInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotAzureImportInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotAzureImportInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotAzureImportInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/azure-store.ts b/packages/transloadit/src/alphalib/types/robots/azure-store.ts deleted file mode 100644 index dcb1e678..00000000 --- a/packages/transloadit/src/alphalib/types/robots/azure-store.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { azureBase, interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 6, - discount_factor: 0.15000150001500018, - discount_pct: 84.99984999849998, - example_code: { - steps: { - exported: { - robot: '/azure/store', - use: ':original', - credentials: 'YOUR_AZURE_CREDENTIALS', - path: 'my_target_folder/${unique_prefix}/${file.url_name}', - }, - }, - }, - example_code_description: 'Export uploaded files to `my_target_folder` on Azure:', - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Exporting', - purpose_sentence: 'exports encoding results to Microsoft Azure', - purpose_verb: 'export', - purpose_word: 'Azure', - purpose_words: 'Export files to Microsoft Azure', - service_slug: 'file-exporting', - slot_count: 10, - title: 'Export files to Microsoft Azure', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'AzureStoreRobot', - priceFactor: 6.6666, - queueSlotCount: 10, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotAzureStoreInstructionsSchema = robotBase - .merge(robotUse) - .merge(azureBase) - .extend({ - robot: z.literal('/azure/store'), - path: z - .string() - .default('${unique_prefix}/${file.url_name}') - .describe(` -The path at which the file is to be stored. This may include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). -`), - content_type: z - .string() - .optional() - .describe(` -The content type with which to store the file. By default this will be guessed by Azure. -`), - content_encoding: z - .string() - .optional() - .describe(` -The content encoding with which to store the file. By default this will be guessed by Azure. -`), - content_language: z - .string() - .optional() - .describe(` -The content language with which to store the file. By default this will be guessed by Azure. -`), - content_disposition: z - .string() - .optional() - .describe(` -The content disposition with which to store the file. By default this will be guessed by Azure. -`), - cache_control: z - .string() - .optional() - .describe(` -The cache control header with which to store the file. -`), - // TODO: verify if this is correct. - metadata: z - .record(z.string()) - .default({}) - .describe(` -A JavaScript object containing a list of metadata to be set for this file on Azure, such as \`{ FileURL: "\${file.url_name}" }\`. This can also include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). -`), - sas_expires_in: z - .number() - .int() - .min(0) - .optional() - .describe(` -Set this to a number to enable shared access signatures for your stored object. This reflects the number of seconds that the signature will be valid for once the object is stored. Enabling this will attach the shared access signature (SAS) to the result URL of your object. -`), - sas_permissions: z - .string() - .regex(/^[rdw]+$/) - .min(0) - .max(3) - .optional() - .describe(` -Set this to a combination of \`r\` (read), \`w\` (write) and \`d\` (delete) for your shared access signatures (SAS) permissions. -`), - }) - .strict() - -export const robotAzureStoreInstructionsWithHiddenFieldsSchema = - robotAzureStoreInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotAzureStoreInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotAzureStoreInstructions = z.infer -export type RobotAzureStoreInstructionsWithHiddenFields = z.infer< - typeof robotAzureStoreInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotAzureStoreInstructionsSchema = interpolateRobot( - robotAzureStoreInstructionsSchema, -) -export type InterpolatableRobotAzureStoreInstructions = - InterpolatableRobotAzureStoreInstructionsInput - -export type InterpolatableRobotAzureStoreInstructionsInput = z.input< - typeof interpolatableRobotAzureStoreInstructionsSchema -> - -export const interpolatableRobotAzureStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotAzureStoreInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotAzureStoreInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotAzureStoreInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotAzureStoreInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotAzureStoreInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/backblaze-import.ts b/packages/transloadit/src/alphalib/types/robots/backblaze-import.ts deleted file mode 100644 index 1062f462..00000000 --- a/packages/transloadit/src/alphalib/types/robots/backblaze-import.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - backblazeBase, - files_per_page, - interpolateRobot, - path, - recursive, - robotBase, - robotImport, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 10, - discount_factor: 0.1, - discount_pct: 90, - example_code: { - steps: { - imported: { - robot: '/backblaze/import', - credentials: 'YOUR_BACKBLAZE_CREDENTIALS', - path: 'path/to/files/', - recursive: true, - }, - }, - }, - example_code_description: - 'Import files from the `path/to/files` directory and its subdirectories:', - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Importing', - purpose_sentence: 'imports whole directories of files from your Backblaze bucket', - purpose_verb: 'import', - purpose_word: 'Backblaze', - purpose_words: 'Import files from Backblaze', - requires_credentials: true, - service_slug: 'file-importing', - slot_count: 20, - title: 'Import files from Backblaze', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'BackblazeImportRobot', - priceFactor: 6.6666, - queueSlotCount: 20, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: true, - stage: 'ga', -} - -export const robotBackblazeImportInstructionsSchema = robotBase - .merge(robotImport) - .merge(backblazeBase) - .extend({ - robot: z.literal('/backblaze/import'), - path: path.describe(` -The path in your bucket to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. - -If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are direct descendants of this directory will be imported. For example: \`images/\`. - -Directories are **not** imported recursively. If you want to import files from subdirectories and sub-subdirectories, enable the \`recursive\` parameter. - -If you want to import all files from the root directory, please use \`/\` as the value here. In this case, make sure all your objects belong to a path. If you have objects in the root of your bucket that aren't prefixed with \`/\`, you'll receive a 404 \`BACKBLAZE_IMPORT_NOT_FOUND\` error. - -You can also use an array of path strings here to import multiple paths in the same Robot's Step. -`), - recursive: recursive.describe(` -Setting this to \`true\` will enable importing files from subdirectories and sub-subdirectories (etc.) of the given path. - -Please use the pagination parameters \`start_file_name\` and \`files_per_page\` wisely here. -`), - start_file_name: z - .string() - .default('') - .describe(` -The name of the last file from the previous paging call. This tells the Robot to ignore all files up to and including this file. -`), - files_per_page: files_per_page.describe(` -The pagination page size. This only works when recursive is \`true\` for now, in order to not break backwards compatibility in non-recursive imports. -`), - }) - .strict() - -export const robotBackblazeImportInstructionsWithHiddenFieldsSchema = - robotBackblazeImportInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotBackblazeImportInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotBackblazeImportInstructions = z.infer< - typeof robotBackblazeImportInstructionsSchema -> -export type RobotBackblazeImportInstructionsWithHiddenFields = z.infer< - typeof robotBackblazeImportInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotBackblazeImportInstructionsSchema = interpolateRobot( - robotBackblazeImportInstructionsSchema, -) -export type InterpolatableRobotBackblazeImportInstructions = - InterpolatableRobotBackblazeImportInstructionsInput - -export type InterpolatableRobotBackblazeImportInstructionsInput = z.input< - typeof interpolatableRobotBackblazeImportInstructionsSchema -> - -export const interpolatableRobotBackblazeImportInstructionsWithHiddenFieldsSchema = - interpolateRobot(robotBackblazeImportInstructionsWithHiddenFieldsSchema) -export type InterpolatableRobotBackblazeImportInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotBackblazeImportInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotBackblazeImportInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotBackblazeImportInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/backblaze-store.ts b/packages/transloadit/src/alphalib/types/robots/backblaze-store.ts deleted file mode 100644 index 84bbd13e..00000000 --- a/packages/transloadit/src/alphalib/types/robots/backblaze-store.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { backblazeBase, interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 6, - discount_factor: 0.15000150001500018, - discount_pct: 84.99984999849998, - example_code: { - steps: { - exported: { - robot: '/backblaze/store', - use: ':original', - credentials: 'YOUR_BACKBLAZE_CREDENTIALS', - path: 'my_target_folder/${unique_prefix}/${file.url_name}', - }, - }, - }, - example_code_description: 'Export uploaded files to `my_target_folder` on Backblaze:', - extended_description: ` -## Access - -Your Backblaze buckets need to have the \`listBuckets\` (to obtain a bucket ID from a bucket name), \`writeFiles\` and \`listFiles\` permissions. -`, - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Exporting', - purpose_sentence: 'exports encoding results to Backblaze', - purpose_verb: 'export', - purpose_word: 'Backblaze', - purpose_words: 'Export files to Backblaze', - service_slug: 'file-exporting', - slot_count: 10, - title: 'Export files to Backblaze', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'BackblazeStoreRobot', - priceFactor: 6.6666, - queueSlotCount: 10, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotBackblazeStoreInstructionsSchema = robotBase - .merge(robotUse) - .merge(backblazeBase) - .extend({ - robot: z.literal('/backblaze/store'), - path: z - .string() - .default('${unique_prefix}/${file.url_name}') - .describe(` -The path at which the file is to be stored. This may include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). -`), - headers: z - .record(z.string()) - .default({}) - .describe(` -An object containing a list of headers to be set for this file on backblaze, such as \`{ FileURL: "\${file.url_name}" }\`. This can also include any available [Assembly Variables](/docs/topics/assembly-instructions/#assembly-variables). - -[Here](https://www.backblaze.com/b2/docs/b2_upload_file.html) you can find a list of available headers. - -Object Metadata can be specified using \`X-Bz-Info-*\` headers. -`), - }) - .strict() - -export const robotBackblazeStoreInstructionsWithHiddenFieldsSchema = - robotBackblazeStoreInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotBackblazeStoreInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotBackblazeStoreInstructions = z.infer -export type RobotBackblazeStoreInstructionsWithHiddenFields = z.infer< - typeof robotBackblazeStoreInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotBackblazeStoreInstructionsSchema = interpolateRobot( - robotBackblazeStoreInstructionsSchema, -) -export type InterpolatableRobotBackblazeStoreInstructions = - InterpolatableRobotBackblazeStoreInstructionsInput - -export type InterpolatableRobotBackblazeStoreInstructionsInput = z.input< - typeof interpolatableRobotBackblazeStoreInstructionsSchema -> - -export const interpolatableRobotBackblazeStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotBackblazeStoreInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotBackblazeStoreInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotBackblazeStoreInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotBackblazeStoreInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotBackblazeStoreInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/cloudfiles-import.ts b/packages/transloadit/src/alphalib/types/robots/cloudfiles-import.ts deleted file mode 100644 index 3f380511..00000000 --- a/packages/transloadit/src/alphalib/types/robots/cloudfiles-import.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - cloudfilesBase, - files_per_page, - interpolateRobot, - page_number, - path, - robotBase, - robotImport, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 10, - discount_factor: 0.1, - discount_pct: 90, - example_code: { - steps: { - imported: { - robot: '/cloudfiles/import', - credentials: 'YOUR_CLOUDFILES_CREDENTIALS', - path: 'path/to/files/', - recursive: true, - }, - }, - }, - example_code_description: - 'Import files from the `path/to/files` directory and its subdirectories:', - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Importing', - purpose_sentence: 'imports whole directories of files from your Rackspace Cloud Files container', - purpose_verb: 'import', - purpose_word: 'Rackspace Cloud Files', - purpose_words: 'Import files from Rackspace Cloud Files', - requires_credentials: true, - service_slug: 'file-importing', - slot_count: 20, - title: 'Import files from Rackspace Cloud Files', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'CloudfilesImportRobot', - priceFactor: 6.6666, - queueSlotCount: 20, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: true, - stage: 'ga', -} - -export const robotCloudfilesImportInstructionsSchema = robotBase - .merge(robotImport) - .merge(cloudfilesBase) - .extend({ - robot: z.literal('/cloudfiles/import'), - path: path.describe(` -The path in your bucket to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. - -If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are direct descendants of this directory will be imported. For example: \`images/\`. - -Directories are **not** imported recursively. If you want to import files from subdirectories and sub-subdirectories, enable the \`recursive\` parameter. - -You can also use an array of path strings here to import multiple paths in the same Robot's Step. -`), - recursive: z - .boolean() - .default(false) - .describe(` -Setting this to \`true\` will enable importing files from subdirectories and sub-subdirectories (etc.) of the given path. - -Please use the pagination parameters \`page_number\` and \`files_per_page\`wisely here. -`), - page_number: page_number.describe(` -The pagination page number. For now, in order to not break backwards compatibility in non-recursive imports, this only works when recursive is set to \`true\`. - -When doing big imports, make sure no files are added or removed from other scripts within your path, otherwise you might get weird results with the pagination. -`), - files_per_page: files_per_page.describe(` -The pagination page size. This only works when recursive is \`true\` for now, in order to not break backwards compatibility in non-recursive imports. -`), - }) - .strict() - -export const robotCloudfilesImportInstructionsWithHiddenFieldsSchema = - robotCloudfilesImportInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotCloudfilesImportInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotCloudfilesImportInstructions = z.infer< - typeof robotCloudfilesImportInstructionsSchema -> -export type RobotCloudfilesImportInstructionsWithHiddenFields = z.infer< - typeof robotCloudfilesImportInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotCloudfilesImportInstructionsSchema = interpolateRobot( - robotCloudfilesImportInstructionsSchema, -) -export type InterpolatableRobotCloudfilesImportInstructions = - InterpolatableRobotCloudfilesImportInstructionsInput - -export type InterpolatableRobotCloudfilesImportInstructionsInput = z.input< - typeof interpolatableRobotCloudfilesImportInstructionsSchema -> - -export const interpolatableRobotCloudfilesImportInstructionsWithHiddenFieldsSchema = - interpolateRobot(robotCloudfilesImportInstructionsWithHiddenFieldsSchema) -export type InterpolatableRobotCloudfilesImportInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotCloudfilesImportInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotCloudfilesImportInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotCloudfilesImportInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/cloudfiles-store.ts b/packages/transloadit/src/alphalib/types/robots/cloudfiles-store.ts deleted file mode 100644 index eec34b63..00000000 --- a/packages/transloadit/src/alphalib/types/robots/cloudfiles-store.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - cloudfilesBase, - interpolateRobot, - robotBase, - robotUse, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 6, - discount_factor: 0.15000150001500018, - discount_pct: 84.99984999849998, - example_code: { - steps: { - exported: { - robot: '/cloudfiles/store', - use: ':original', - credentials: 'YOUR_CLOUDFILES_CREDENTIALS', - path: 'my_target_folder/${unique_prefix}/${file.url_name}', - }, - }, - }, - example_code_description: 'Export uploaded files to `my_target_folder` on Rackspace Cloud Files:', - extended_description: ` - - -## A note about URLs - -If your container is CDN-enabled, the resulting \`file.url\` indicates the path to the file in your -CDN container, or is \`null\` otherwise. - -The storage container URL for this file is always available via \`file.meta.storage_url\`. -`, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Exporting', - purpose_sentence: 'exports encoding results to Rackspace Cloud Files', - purpose_verb: 'export', - purpose_word: 'Rackspace Cloud Files', - purpose_words: 'Export files to Rackspace Cloud Files', - service_slug: 'file-exporting', - slot_count: 10, - title: 'Export files to Rackspace Cloud Files', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'CloudfilesStoreRobot', - priceFactor: 6.6666, - queueSlotCount: 10, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotCloudfilesStoreInstructionsSchema = robotBase - .merge(robotUse) - .merge(cloudfilesBase) - .extend({ - robot: z.literal('/cloudfiles/store'), - path: z - .string() - .default('${unique_prefix}/${file.url_name}') - .describe(` -The path at which to store the file. This value can also contain [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). -`), - }) - .strict() - -export const robotCloudfilesStoreInstructionsWithHiddenFieldsSchema = - robotCloudfilesStoreInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotCloudfilesStoreInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotCloudfilesStoreInstructions = z.infer< - typeof robotCloudfilesStoreInstructionsSchema -> -export type RobotCloudfilesStoreInstructionsWithHiddenFields = z.infer< - typeof robotCloudfilesStoreInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotCloudfilesStoreInstructionsSchema = interpolateRobot( - robotCloudfilesStoreInstructionsSchema, -) -export type InterpolatableRobotCloudfilesStoreInstructions = - InterpolatableRobotCloudfilesStoreInstructionsInput - -export type InterpolatableRobotCloudfilesStoreInstructionsInput = z.input< - typeof interpolatableRobotCloudfilesStoreInstructionsSchema -> - -export const interpolatableRobotCloudfilesStoreInstructionsWithHiddenFieldsSchema = - interpolateRobot(robotCloudfilesStoreInstructionsWithHiddenFieldsSchema) -export type InterpolatableRobotCloudfilesStoreInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotCloudfilesStoreInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotCloudfilesStoreInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotCloudfilesStoreInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/cloudflare-import.ts b/packages/transloadit/src/alphalib/types/robots/cloudflare-import.ts deleted file mode 100644 index 3ac9036c..00000000 --- a/packages/transloadit/src/alphalib/types/robots/cloudflare-import.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - cloudflareBase, - files_per_page, - interpolateRobot, - page_number, - path, - recursive, - return_file_stubs, - robotBase, - robotImport, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 10, - discount_factor: 0.1, - discount_pct: 90, - example_code: { - steps: { - imported: { - robot: '/cloudflare/import', - credentials: 'YOUR_CLOUDFLARE_CREDENTIALS', - path: 'path/to/files/', - recursive: true, - }, - }, - }, - example_code_description: - 'Import files from the `path/to/files` directory and its subdirectories:', - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Importing', - purpose_sentence: 'imports whole directories of files from your cloudflare r2 bucket', - purpose_verb: 'import', - purpose_word: 'cloudflare', - purpose_words: 'Import files from Cloudflare R2', - requires_credentials: true, - service_slug: 'file-importing', - slot_count: 20, - title: 'Import files from Cloudflare R2', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'CloudflareImportRobot', - priceFactor: 6.6666, - queueSlotCount: 20, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: true, - stage: 'ga', -} - -export const robotCloudflareImportInstructionsSchema = robotBase - .merge(robotImport) - .merge(cloudflareBase) - .extend({ - robot: z.literal('/cloudflare/import'), - path: path.describe(` -The path in your bucket to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. - -If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are direct descendants of this directory will be imported. For example: \`images/\`. - -Directories are **not** imported recursively. If you want to import files from subdirectories and sub-subdirectories, enable the \`recursive\` parameter. - -If you want to import all files from the root directory, please use \`/\` as the value here. In this case, make sure all your objects belong to a path. If you have objects in the root of your bucket that aren't prefixed with \`/\`, you'll receive an error: \`A client error (NoSuchKey) occurred when calling the GetObject operation: The specified key does not exist.\` - -You can also use an array of path strings here to import multiple paths in the same Robot's Step. -`), - recursive: recursive.describe(` -Setting this to \`true\` will enable importing files from subfolders and sub-subfolders, etc. of the given path. - -Please use the pagination parameters \`page_number\` and \`files_per_page\` wisely here. -`), - page_number: page_number.describe(` -The pagination page number. For now, in order to not break backwards compatibility in non-recursive imports, this only works when recursive is set to \`true\`. - -When doing big imports, make sure no files are added or removed from other scripts within your path, otherwise you might get weird results with the pagination. -`), - files_per_page: files_per_page.describe(` -The pagination page size. This only works when recursive is \`true\` for now, in order to not break backwards compatibility in non-recursive imports. -`), - return_file_stubs, - }) - .strict() - -export const robotCloudflareImportInstructionsWithHiddenFieldsSchema = - robotCloudflareImportInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotCloudflareImportInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotCloudflareImportInstructions = z.infer< - typeof robotCloudflareImportInstructionsSchema -> -export type RobotCloudflareImportInstructionsWithHiddenFields = z.infer< - typeof robotCloudflareImportInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotCloudflareImportInstructionsSchema = interpolateRobot( - robotCloudflareImportInstructionsSchema, -) -export type InterpolatableRobotCloudflareImportInstructions = - InterpolatableRobotCloudflareImportInstructionsInput - -export type InterpolatableRobotCloudflareImportInstructionsInput = z.input< - typeof interpolatableRobotCloudflareImportInstructionsSchema -> - -export const interpolatableRobotCloudflareImportInstructionsWithHiddenFieldsSchema = - interpolateRobot(robotCloudflareImportInstructionsWithHiddenFieldsSchema) -export type InterpolatableRobotCloudflareImportInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotCloudflareImportInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotCloudflareImportInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotCloudflareImportInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/cloudflare-store.ts b/packages/transloadit/src/alphalib/types/robots/cloudflare-store.ts deleted file mode 100644 index 905ba306..00000000 --- a/packages/transloadit/src/alphalib/types/robots/cloudflare-store.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - cloudflareBase, - interpolateRobot, - robotBase, - robotUse, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 6, - discount_factor: 0.15000150001500018, - discount_pct: 84.99984999849998, - example_code: { - steps: { - exported: { - robot: '/cloudflare/store', - use: ':original', - credentials: 'YOUR_CLOUDFLARE_CREDENTIALS', - path: 'my_target_folder/${unique_prefix}/${file.url_name}', - }, - }, - }, - example_code_description: 'Export uploaded files to `my_target_folder` on cloudflare R2:', - extended_description: ` -The URL to the result file will be returned in the Assembly Status JSON. -`, - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Exporting', - purpose_sentence: 'exports encoding results to cloudflare r2 buckets', - purpose_verb: 'export', - purpose_word: 'cloudflare', - purpose_words: 'Export files to Cloudflare R2', - service_slug: 'file-exporting', - slot_count: 10, - title: 'Export files to Cloudflare R2', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'CloudflareStoreRobot', - priceFactor: 6.6666, - queueSlotCount: 10, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotCloudflareStoreInstructionsSchema = robotBase - .merge(robotUse) - .merge(cloudflareBase) - .extend({ - robot: z.literal('/cloudflare/store'), - path: z - .string() - .default('${unique_prefix}/${file.url_name}') - .describe(` -The path at which the file is to be stored. This may include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). The path must not be a directory. -`), - headers: z - .record(z.string()) - .default({ 'Content-Type': '${file.mime}' }) - .describe(` -An object containing a list of headers to be set for this file on cloudflare Spaces, such as \`{ FileURL: "\${file.url_name}" }\`. This can also include any available [Assembly Variables](/docs/topics/assembly-instructions/#assembly-variables). - -Object Metadata can be specified using \`x-amz-meta-*\` headers. Note that these headers [do not support non-ASCII metadata values](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html#UserMetadata). -`), - sign_urls_for: z - .number() - .int() - .min(0) - .optional() - .describe(` -This parameter provides signed URLs in the result JSON (in the \`signed_ssl_url\` property). The number that you set this parameter to is the URL expiry time in seconds. If this parameter is not used, no URL signing is done. -`), - url_prefix: z - .string() - .optional() - .describe(` -The URL prefix used for accessing files from your Cloudflare R2 bucket. This is typically the custom public URL access host set up in your Cloudflare account. -`), - }) - .strict() - -export const robotCloudflareStoreInstructionsWithHiddenFieldsSchema = - robotCloudflareStoreInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotCloudflareStoreInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotCloudflareStoreInstructions = z.infer< - typeof robotCloudflareStoreInstructionsSchema -> -export type RobotCloudflareStoreInstructionsWithHiddenFields = z.infer< - typeof robotCloudflareStoreInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotCloudflareStoreInstructionsSchema = interpolateRobot( - robotCloudflareStoreInstructionsSchema, -) -export type InterpolatableRobotCloudflareStoreInstructions = - InterpolatableRobotCloudflareStoreInstructionsInput - -export type InterpolatableRobotCloudflareStoreInstructionsInput = z.input< - typeof interpolatableRobotCloudflareStoreInstructionsSchema -> - -export const interpolatableRobotCloudflareStoreInstructionsWithHiddenFieldsSchema = - interpolateRobot(robotCloudflareStoreInstructionsWithHiddenFieldsSchema) -export type InterpolatableRobotCloudflareStoreInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotCloudflareStoreInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotCloudflareStoreInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotCloudflareStoreInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/digitalocean-import.ts b/packages/transloadit/src/alphalib/types/robots/digitalocean-import.ts deleted file mode 100644 index a63a7dc9..00000000 --- a/packages/transloadit/src/alphalib/types/robots/digitalocean-import.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - digitalOceanBase, - files_per_page, - interpolateRobot, - page_number, - path, - recursive, - return_file_stubs, - robotBase, - robotImport, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 10, - discount_factor: 0.1, - discount_pct: 90, - example_code: { - steps: { - imported: { - robot: '/digitalocean/import', - credentials: 'YOUR_DIGITALOCEAN_CREDENTIALS', - path: 'path/to/files/', - recursive: true, - }, - }, - }, - example_code_description: - 'Import files from the `path/to/files` directory and its subdirectories:', - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Importing', - purpose_sentence: 'imports whole directories of files from DigitalOcean Spaces', - purpose_verb: 'import', - purpose_word: 'DigitalOcean Spaces', - purpose_words: 'Import files from DigitalOcean Spaces', - service_slug: 'file-importing', - slot_count: 20, - title: 'Import files from DigitalOcean Spaces', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'DigitalOceanImportRobot', - priceFactor: 6.6666, - queueSlotCount: 20, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: true, - stage: 'ga', -} - -export const robotDigitaloceanImportInstructionsSchema = robotBase - .merge(robotImport) - .merge(digitalOceanBase) - .extend({ - robot: z.literal('/digitalocean/import'), - path: path.describe(` -The path in your bucket to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. - -If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are direct descendants of this directory will be imported. For example: \`images/\`. - -Directories are **not** imported recursively. If you want to import files from subdirectories and sub-subdirectories, enable the \`recursive\` parameter. - -You can also use an array of path strings here to import multiple paths in the same Robot's Step. -`), - recursive: recursive.describe(` -Setting this to \`true\` will enable importing files from subdirectories and sub-subdirectories (etc.) of the given path. - -Please use the pagination parameters \`page_number\` and \`files_per_page\` wisely here. -`), - page_number: page_number.describe(` -The pagination page number. For now, in order to not break backwards compatibility in non-recursive imports, this only works when recursive is set to \`true\`. - -When doing big imports, make sure no files are added or removed from other scripts within your path, otherwise you might get weird results with the pagination. -`), - files_per_page: files_per_page.describe(` -The pagination page size. This only works when recursive is \`true\` for now, in order to not break backwards compatibility in non-recursive imports. -`), - return_file_stubs, - }) - .strict() - -export const robotDigitaloceanImportInstructionsWithHiddenFieldsSchema = - robotDigitaloceanImportInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotDigitaloceanImportInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotDigitaloceanImportInstructions = z.infer< - typeof robotDigitaloceanImportInstructionsSchema -> -export type RobotDigitaloceanImportInstructionsWithHiddenFields = z.infer< - typeof robotDigitaloceanImportInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotDigitaloceanImportInstructionsSchema = interpolateRobot( - robotDigitaloceanImportInstructionsSchema, -) -export type InterpolatableRobotDigitaloceanImportInstructions = - InterpolatableRobotDigitaloceanImportInstructionsInput - -export type InterpolatableRobotDigitaloceanImportInstructionsInput = z.input< - typeof interpolatableRobotDigitaloceanImportInstructionsSchema -> - -export const interpolatableRobotDigitaloceanImportInstructionsWithHiddenFieldsSchema = - interpolateRobot(robotDigitaloceanImportInstructionsWithHiddenFieldsSchema) -export type InterpolatableRobotDigitaloceanImportInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotDigitaloceanImportInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotDigitaloceanImportInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotDigitaloceanImportInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/digitalocean-store.ts b/packages/transloadit/src/alphalib/types/robots/digitalocean-store.ts deleted file mode 100644 index 99aeedb4..00000000 --- a/packages/transloadit/src/alphalib/types/robots/digitalocean-store.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - digitalOceanBase, - interpolateRobot, - robotBase, - robotUse, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 6, - discount_factor: 0.15000150001500018, - discount_pct: 84.99984999849998, - example_code: { - steps: { - exported: { - robot: '/digitalocean/store', - use: ':original', - credentials: 'YOUR_DIGITALOCEAN_CREDENTIALS', - path: 'my_target_folder/${unique_prefix}/${file.url_name}', - }, - }, - }, - example_code_description: 'Export uploaded files to `my_target_folder` on DigitalOcean Spaces:', - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Exporting', - purpose_sentence: 'exports encoding results to DigitalOcean Spaces', - purpose_verb: 'export', - purpose_word: 'DigitalOcean Spaces', - purpose_words: 'Export files to DigitalOcean Spaces', - service_slug: 'file-exporting', - slot_count: 10, - title: 'Export files to DigitalOcean Spaces', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'DigitalOceanStoreRobot', - priceFactor: 6.6666, - queueSlotCount: 10, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotDigitaloceanStoreInstructionsSchema = robotBase - .merge(robotUse) - .merge(digitalOceanBase) - .extend({ - robot: z.literal('/digitalocean/store'), - path: z - .string() - .default('${unique_prefix}/${file.url_name}') - .describe(` -The path at which the file is to be stored. This may include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). The path must not be a directory. -`), - url_prefix: z - .string() - .default('https://{space}.{region}.digitaloceanspaces.com/') - .describe(` -The URL prefix used for the returned URL, such as \`"https://my.cdn.com/some/path"\`. -`), - acl: z - .enum(['private', 'public-read']) - .default('public-read') - .describe(` -The permissions used for this file. -`), - headers: z - .record(z.string()) - .default({ 'Content-Type': '${file.mime}' }) - .describe(` -An object containing a list of headers to be set for this file on DigitalOcean Spaces, such as \`{ FileURL: "\${file.url_name}" }\`. This can also include any available [Assembly Variables](/docs/topics/assembly-instructions/#assembly-variables). - -[Here](https://developers.digitalocean.com/documentation/spaces/#object) you can find a list of available headers. - -Object Metadata can be specified using \`x-amz-meta-*\` headers. Note that these headers [do not support non-ASCII metadata values](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html#UserMetadata). -`), - sign_urls_for: z - .number() - .int() - .min(0) - .optional() - .describe(` -This parameter provides signed URLs in the result JSON (in the \`signed_ssl_url\` property). The number that you set this parameter to is the URL expiry time in seconds. If this parameter is not used, no URL signing is done. -`), - }) - .strict() - -export const robotDigitaloceanStoreInstructionsWithHiddenFieldsSchema = - robotDigitaloceanStoreInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotDigitaloceanStoreInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotDigitaloceanStoreInstructions = z.infer< - typeof robotDigitaloceanStoreInstructionsSchema -> -export type RobotDigitaloceanStoreInstructionsWithHiddenFields = z.infer< - typeof robotDigitaloceanStoreInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotDigitaloceanStoreInstructionsSchema = interpolateRobot( - robotDigitaloceanStoreInstructionsSchema, -) -export type InterpolatableRobotDigitaloceanStoreInstructions = - InterpolatableRobotDigitaloceanStoreInstructionsInput - -export type InterpolatableRobotDigitaloceanStoreInstructionsInput = z.input< - typeof interpolatableRobotDigitaloceanStoreInstructionsSchema -> - -export const interpolatableRobotDigitaloceanStoreInstructionsWithHiddenFieldsSchema = - interpolateRobot(robotDigitaloceanStoreInstructionsWithHiddenFieldsSchema) -export type InterpolatableRobotDigitaloceanStoreInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotDigitaloceanStoreInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotDigitaloceanStoreInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotDigitaloceanStoreInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/document-autorotate.ts b/packages/transloadit/src/alphalib/types/robots/document-autorotate.ts deleted file mode 100644 index f1c4ab83..00000000 --- a/packages/transloadit/src/alphalib/types/robots/document-autorotate.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - example_code_description: - 'Auto-rotate individual pages of a documents to the correction orientation:', - minimum_charge: 2097152, - output_factor: 1, - override_lvl1: 'Document Processing', - purpose_sentence: 'corrects the orientation of documents', - purpose_verb: 'auto-rotate', - purpose_word: 'auto-rotate documents', - purpose_words: 'Auto-rotate documents', - service_slug: 'document-processing', - slot_count: 10, - title: 'Auto-rotate documents to the correct orientation', - typical_file_size_mb: 0.8, - typical_file_type: 'document', - name: 'DocumentAutorotateRobot', - priceFactor: 1, - queueSlotCount: 10, - minimumCharge: 2097152, - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotDocumentAutorotateInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/document/autorotate'), - }) - .strict() - -export const robotDocumentAutorotateInstructionsWithHiddenFieldsSchema = - robotDocumentAutorotateInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotDocumentAutorotateInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotDocumentAutorotateInstructions = z.infer< - typeof robotDocumentAutorotateInstructionsSchema -> -export type RobotDocumentAutorotateInstructionsWithHiddenFields = z.infer< - typeof robotDocumentAutorotateInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotDocumentAutorotateInstructionsSchema = interpolateRobot( - robotDocumentAutorotateInstructionsSchema, -) -export type InterpolatableRobotDocumentAutorotateInstructions = - InterpolatableRobotDocumentAutorotateInstructionsInput - -export type InterpolatableRobotDocumentAutorotateInstructionsInput = z.input< - typeof interpolatableRobotDocumentAutorotateInstructionsSchema -> - -export const interpolatableRobotDocumentAutorotateInstructionsWithHiddenFieldsSchema = - interpolateRobot(robotDocumentAutorotateInstructionsWithHiddenFieldsSchema) -export type InterpolatableRobotDocumentAutorotateInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotDocumentAutorotateInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotDocumentAutorotateInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotDocumentAutorotateInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/document-convert.ts b/packages/transloadit/src/alphalib/types/robots/document-convert.ts deleted file mode 100644 index 94ac9507..00000000 --- a/packages/transloadit/src/alphalib/types/robots/document-convert.ts +++ /dev/null @@ -1,302 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - example_code: { - steps: { - converted: { - robot: '/document/convert', - use: ':original', - format: 'pdf', - }, - }, - }, - example_code_description: 'Convert uploaded files to PDF documents:', - extended_description: ` -> [!Note] -> This Robot can convert files to PDF, but cannot convert PDFs to different formats. If you want to convert PDFs to say, JPEG or TIFF, use [🤖/image/resize](/docs/robots/image-resize/). If you want to turn them into text files or recognize (OCR) them to make them searchable, reach out, as we have a new Robot in the works for this. - -Sometimes, a certain file type might not support what you are trying to accomplish. Perhaps your company is trying to automate document formatting, but it only works with docx, so all your docs need to be converted. Or maybe your stored jpg files are taking up too much space and you want a lighter format. Whatever the case, we have you covered. - -Using this Robot, you can bypass the issues that certain file types may bring, by converting your file into the most suitable format. This also works in conjunction with our other Robots, allowing for even greater versatility when using our services. - -> ![Warning] -> A general rule of this Robot is that converting files into an alien format category will result in an error. For example, SRT files can be converted into the VTT format, but not to an image. - -The following file formats can be converted from: - -- \`ai\` -- \`csv\` -- \`doc\` -- \`docx\` -- \`eps\` -- \`gif\` -- \`html\` -- \`jpg\` -- \`latex\` -- \`md\` -- \`oda\` -- \`odd\` -- \`odt\` -- \`ott\` -- \`png\` -- \`pot\` -- \`pps\` -- \`ppt\` -- \`pptx\` -- \`ppz\` -- \`ps\` -- \`rtf\` -- \`rtx\` -- \`svg\` -- \`text\` -- \`txt\` -- \`xhtml\` -- \`xla\` -- \`xls\` -- \`xlsx\` -- \`xml\` -`, - minimum_charge: 1048576, - output_factor: 1, - override_lvl1: 'Document Processing', - purpose_sentence: 'converts documents into different formats', - purpose_verb: 'convert', - purpose_word: 'convert', - purpose_words: 'Convert documents into different formats', - service_slug: 'document-processing', - slot_count: 12, - title: 'Convert documents into different formats', - typical_file_size_mb: 0.8, - typical_file_type: 'document', - name: 'DocumentConvertRobot', - priceFactor: 1, - // This slot count needs to be unique, because unoconv can only process one document at a time, - // and is also only included in WorkerSlotCalculator::slotsThatFit() when - // we have enough idle unoconv daemons. - // We do not want a queue of this Robot to block any other Robot's jobs. - queueSlotCount: 32, - minimumCharge: 1048576, - lazyLoad: true, - installVersionFile: process.env.API2_UNOCONV_INSTALL_VERSION_FILE || '', - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - // we cannot use coreConfig.numUnoconvDaemons, because it does not live in alphalib - numDaemons: 8, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotDocumentConvertInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/document/convert').describe(` -> [!Note] -> This Robot can convert files to PDF, but cannot convert PDFs to different formats. If you want to convert PDFs to say, JPEG or TIFF, use [🤖/image/resize](/docs/robots/image-resize/). If you want to turn them into text files or recognize (OCR) them to make them searchable, reach out, as we have a new Robot in the works for this. - -Sometimes, a certain file type might not support what you are trying to accomplish. Perhaps your company is trying to automate document formatting, but it only works with docx, so all your docs need to be converted. Or maybe your stored jpg files are taking up too much space and you want a lighter format. Whatever the case, we have you covered. - -Using this Robot, you can bypass the issues that certain file types may bring, by converting your file into the most suitable format. This also works in conjunction with our other Robots, allowing for even greater versatility when using our services. - -> [!Warning] -> A general rule of this Robot is that converting files into an alien format category will result in an error. For example, SRT files can be converted into the VTT format, but not to an image. - -The following file formats can be converted from: - -- \`ai\` -- \`csv\` -- \`doc\` -- \`docx\` -- \`eps\` -- \`gif\` -- \`html\` -- \`jpg\` -- \`latex\` -- \`md\` -- \`oda\` -- \`odd\` -- \`odt\` -- \`ott\` -- \`png\` -- \`pot\` -- \`pps\` -- \`ppt\` -- \`pptx\` -- \`ppz\` -- \`ps\` -- \`rtf\` -- \`rtx\` -- \`svg\` -- \`text\` -- \`txt\` -- \`xhtml\` -- \`xla\` -- \`xls\` -- \`xlsx\` -- \`xml\` -`), - format: z - .enum([ - 'ai', - 'csv', - 'doc', - 'docx', - 'eps', - 'gif', - 'html', - 'jpeg', - 'jpg', - 'latex', - 'oda', - 'odd', - 'odt', - 'ott', - 'pdf', - 'png', - 'pot', - 'pps', - 'ppt', - 'pptx', - 'ppz', - 'ps', - 'rtf', - 'rtx', - 'srt', - 'svg', - 'text', - 'txt', - 'vtt', - 'xhtml', - 'xla', - 'xls', - 'xlsx', - 'xml', - ]) - .describe(` -The desired format for document conversion. -`), - markdown_format: z - .enum(['commonmark', 'gfm']) - .default('gfm') - .describe(` -Markdown can be represented in several [variants](https://www.iana.org/assignments/markdown-variants/markdown-variants.xhtml), so when using this Robot to transform Markdown into HTML please specify which revision is being used. -`), - markdown_theme: z - .enum(['bare', 'github']) - .default('github') - .describe(` -This parameter overhauls your Markdown files styling based on several canned presets. -`), - pdf_margin: z - .string() - .default('6.25mm,6.25mm,14.11mm,6.25mm') - .describe(` -PDF Paper margins, separated by \`,\` and with units. - -We support the following unit values: \`px\`, \`in\`, \`cm\`, \`mm\`. - -Currently this parameter is only supported when converting from \`html\`. -`), - pdf_print_background: z - .boolean() - .default(true) - .describe(` -Print PDF background graphics. - -Currently this parameter is only supported when converting from \`html\`. -`), - pdf_format: z - .enum(['A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'Ledger', 'Legal', 'Letter', 'Tabloid']) - .default('Letter') - .describe(` -PDF paper format. - -Currently this parameter is only supported when converting from \`html\`. -`), - pdf_display_header_footer: z - .boolean() - .default(false) - .describe(` -Display PDF header and footer. - -Currently this parameter is only supported when converting from \`html\`. -`), - pdf_header_template: z - .string() - .optional() - .describe(` -HTML template for the PDF print header. - -Should be valid HTML markup with following classes used to inject printing values into them: -- \`date\` formatted print date -- \`title\` document title -- \`url\` document location -- \`pageNumber\` current page number -- \`totalPages\` total pages in the document - -Currently this parameter is only supported when converting from \`html\`, and requires \`pdf_display_header_footer\` to be enabled. - -To change the formatting of the HTML element, the \`font-size\` must be specified in a wrapper. For example, to center the page number at the top of a page you'd use the following HTML for the header template: - -\`\`\`html -
-\`\`\` -`), - pdf_footer_template: z - .string() - .optional() - .describe(` -HTML template for the PDF print footer. - -Should use the same format as the \`pdf_header_template\`. - -Currently this parameter is only supported when converting from \`html\`, and requires \`pdf_display_header_footer\` to be enabled. - -To change the formatting of the HTML element, the \`font-size\` must be specified in a wrapper. For example, to center the page number in the footer you'd use the following HTML for the footer template: - -\`\`\`html -
-\`\`\` -`), - }) - .strict() - -export const robotDocumentConvertInstructionsWithHiddenFieldsSchema = - robotDocumentConvertInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotDocumentConvertInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotDocumentConvertInstructions = z.infer< - typeof robotDocumentConvertInstructionsSchema -> -export type RobotDocumentConvertInstructionsWithHiddenFields = z.infer< - typeof robotDocumentConvertInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotDocumentConvertInstructionsSchema = interpolateRobot( - robotDocumentConvertInstructionsSchema, -) -export type InterpolatableRobotDocumentConvertInstructions = - InterpolatableRobotDocumentConvertInstructionsInput - -export type InterpolatableRobotDocumentConvertInstructionsInput = z.input< - typeof interpolatableRobotDocumentConvertInstructionsSchema -> - -export const interpolatableRobotDocumentConvertInstructionsWithHiddenFieldsSchema = - interpolateRobot(robotDocumentConvertInstructionsWithHiddenFieldsSchema) -export type InterpolatableRobotDocumentConvertInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotDocumentConvertInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotDocumentConvertInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotDocumentConvertInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/document-merge.ts b/packages/transloadit/src/alphalib/types/robots/document-merge.ts deleted file mode 100644 index 0a8340b3..00000000 --- a/packages/transloadit/src/alphalib/types/robots/document-merge.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - example_code: { - steps: { - merged: { - robot: '/document/merge', - use: { - steps: [':original'], - bundle_steps: true, - }, - }, - }, - }, - example_code_description: 'Merge all uploaded PDF documents into one:', - extended_description: ` -> ![Note] -> This Robot can merge PDF files only at the moment. - -Input files are sorted alphanumerically unless you provide the as-syntax in the "use" parameter. For example: - -\`\`\`json -{ - "use": [ - { "name": "my_step_name", "as": "document_2" }, - { "name": "my_other_step_name", "as": "document_1" } - ] -} -\`\`\` -`, - minimum_charge: 1048576, - output_factor: 1, - override_lvl1: 'Document Processing', - purpose_sentence: 'concatenates several PDF documents into a single file', - purpose_verb: 'convert', - purpose_word: 'convert', - purpose_words: 'Merge documents into one', - service_slug: 'document-processing', - slot_count: 10, - title: 'Merge documents into one', - typical_file_size_mb: 0.8, - typical_file_type: 'document', - name: 'DocumentMergeRobot', - priceFactor: 1, - queueSlotCount: 10, - minimumCharge: 1048576, - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotDocumentMergeInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/document/merge'), - input_passwords: z - .array(z.string()) - .default([]) - .describe(` -An array of passwords for the input documents, in case they are encrypted. The order of passwords must match the order of the documents as they are passed to the /document/merge step. - -This can be achieved via our as-syntax using "document_1", "document_2", etc if provided. See the demos below. - -If the as-syntax is not used in the "use" parameter, the documents are sorted alphanumerically based on their filename, and in that order input passwords should be provided. -`), - output_password: z - .string() - .optional() - .describe(` -If not empty, encrypts the output file and makes it accessible only by typing in this password. -`), - }) - .strict() - -export const robotDocumentMergeInstructionsWithHiddenFieldsSchema = - robotDocumentMergeInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotDocumentMergeInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotDocumentMergeInstructions = z.infer -export type RobotDocumentMergeInstructionsWithHiddenFields = z.infer< - typeof robotDocumentMergeInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotDocumentMergeInstructionsSchema = interpolateRobot( - robotDocumentMergeInstructionsSchema, -) -export type InterpolatableRobotDocumentMergeInstructions = - InterpolatableRobotDocumentMergeInstructionsInput - -export type InterpolatableRobotDocumentMergeInstructionsInput = z.input< - typeof interpolatableRobotDocumentMergeInstructionsSchema -> - -export const interpolatableRobotDocumentMergeInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotDocumentMergeInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotDocumentMergeInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotDocumentMergeInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotDocumentMergeInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotDocumentMergeInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/document-ocr.ts b/packages/transloadit/src/alphalib/types/robots/document-ocr.ts deleted file mode 100644 index 5a443ea3..00000000 --- a/packages/transloadit/src/alphalib/types/robots/document-ocr.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - aiProviderSchema, - granularitySchema, - interpolateRobot, - robotBase, - robotUse, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - example_code: { - steps: { - recognized: { - robot: '/document/ocr', - use: ':original', - provider: 'gcp', - }, - }, - }, - example_code_description: 'Recognize text in an uploaded document and save it to a JSON file:', - extended_description: ` -> [!Warning] -> Transloadit aims to be deterministic, but this Robot uses third-party AI services. The providers (AWS, GCP) will evolve their models over time, giving different responses for the same input PDFs. Avoid relying on exact responses in your tests and application. - -> [!Note] -> Currently, this Robot only supports character recognition for PDFs. To use this Robot with other document formats, use [/document/convert](/docs/robots/document-convert/) first to convert the document into a PDF. -`, - minimum_charge: 1048576, - output_factor: 1, - override_lvl1: 'Artificial Intelligence', - purpose_sentence: 'recognizes text in documents and returns it in a machine-readable format', - purpose_verb: 'recognize', - purpose_word: 'recognize text', - purpose_words: 'Recognize text in documents (OCR)', - service_slug: 'artificial-intelligence', - slot_count: 10, - title: 'Recognize text in documents', - typical_file_size_mb: 0.8, - typical_file_type: 'document', - name: 'DocumentOcrRobot', - priceFactor: 1, - queueSlotCount: 10, - minimumChargeUsdPerDocumentOcrPage: { - aws: 0.02, - gcp: 0.015, - }, - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotDocumentOcrInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/document/ocr').describe(` -With this Robot, you can detect and extract text from PDFs using optical character recognition (OCR). - -For example, you can use the results to obtain the content of invoices, legal documents or restaurant menus. You can also pass the text down to other Robots to filter documents that contain (or do not contain) certain phrases. -`), - provider: aiProviderSchema.describe(` -Which AI provider to leverage. Valid values are \`"aws"\` and \`"gcp"\`. - -Transloadit outsources this task and abstracts the interface so you can expect the same data structures, but different latencies and information being returned. Different cloud vendors have different areas they shine in, and we recommend to try out and see what yields the best results for your use case. - -AWS supports detection for the following languages: English, Arabic, Russian, German, French, Italian, Portuguese and Spanish. GCP allows for a wider range of languages, with varying levels of support which can be found on the [official documentation](https://cloud.google.com/vision/docs/languages/). -`), - granularity: granularitySchema.describe(` -Whether to return a full response including coordinates for the text (\`"full"\`), or a flat list of the extracted phrases (\`"list"\`). This parameter has no effect if the \`format\` parameter is set to \`"text"\`. -`), - format: z - .enum(['json', 'meta', 'text']) - .default('json') - .describe(` -In what format to return the extracted text. -- \`"json"\` returns a JSON file. -- \`"meta"\` does not return a file, but stores the data inside Transloadit's file object (under \`\${file.meta.recognized_text}\`, which is an array of strings) that's passed around between encoding Steps, so that you can use the values to burn the data into videos, filter on them, etc. -- \`"text"\` returns the recognized text as a plain UTF-8 encoded text file. -`), - }) - .strict() - -export const robotDocumentOcrInstructionsWithHiddenFieldsSchema = - robotDocumentOcrInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotDocumentOcrInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotDocumentOcrInstructions = z.infer -export type RobotDocumentOcrInstructionsWithHiddenFields = z.infer< - typeof robotDocumentOcrInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotDocumentOcrInstructionsSchema = interpolateRobot( - robotDocumentOcrInstructionsSchema, -) -export type InterpolatableRobotDocumentOcrInstructions = - InterpolatableRobotDocumentOcrInstructionsInput - -export type InterpolatableRobotDocumentOcrInstructionsInput = z.input< - typeof interpolatableRobotDocumentOcrInstructionsSchema -> - -export const interpolatableRobotDocumentOcrInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotDocumentOcrInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotDocumentOcrInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotDocumentOcrInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotDocumentOcrInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotDocumentOcrInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/document-split.ts b/packages/transloadit/src/alphalib/types/robots/document-split.ts deleted file mode 100644 index a76b836d..00000000 --- a/packages/transloadit/src/alphalib/types/robots/document-split.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - example_code_description: 'Extract single or multiple pages from a PDF document:', - minimum_charge: 2097152, - output_factor: 1, - override_lvl1: 'Document Processing', - purpose_sentence: 'extracts pages from documents', - purpose_verb: 'extract', - purpose_word: 'extracts pages', - purpose_words: 'Extracts pages', - service_slug: 'document-processing', - slot_count: 10, - title: 'Extract pages from a document', - typical_file_size_mb: 0.8, - typical_file_type: 'document', - name: 'DocumentSplitRobot', - priceFactor: 1, - queueSlotCount: 10, - minimumCharge: 1048576, - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotDocumentSplitInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/document/split'), - pages: z - .union([z.string(), z.array(z.string())]) - .describe( - 'The pages to select from the input PDF and to be included in the output PDF. Each entry can be a single page number (e.g. 5), or a range (e.g. `5-10`). Page numbers start at 1. By default all pages are extracted.', - ) - .optional(), - }) - .strict() - -export const robotDocumentSplitInstructionsWithHiddenFieldsSchema = - robotDocumentSplitInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotDocumentSplitInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotDocumentSplitInstructions = z.infer -export type RobotDocumentSplitInstructionsWithHiddenFields = z.infer< - typeof robotDocumentSplitInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotDocumentSplitInstructionsSchema = interpolateRobot( - robotDocumentSplitInstructionsSchema, -) -export type InterpolatableRobotDocumentSplitInstructions = - InterpolatableRobotDocumentSplitInstructionsInput - -export type InterpolatableRobotDocumentSplitInstructionsInput = z.input< - typeof interpolatableRobotDocumentSplitInstructionsSchema -> - -export const interpolatableRobotDocumentSplitInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotDocumentSplitInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotDocumentSplitInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotDocumentSplitInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotDocumentSplitInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotDocumentSplitInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/document-thumbs.ts b/packages/transloadit/src/alphalib/types/robots/document-thumbs.ts deleted file mode 100644 index 2aff4c71..00000000 --- a/packages/transloadit/src/alphalib/types/robots/document-thumbs.ts +++ /dev/null @@ -1,231 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - colorspaceSchema, - interpolateRobot, - robotBase, - robotImagemagick, - robotUse, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - example_code: { - steps: { - thumbnailed: { - use: ':original', - robot: '/document/thumbs', - width: 200, - resize_strategy: 'fit', - trim_whitespace: false, - }, - }, - }, - example_code_description: 'Convert all pages of a PDF document into separate 200px-wide images:', - minimum_charge: 524288, - output_factor: 1, - override_lvl1: 'Document Processing', - purpose_sentence: - 'generates an image for each page in a PDF file or an animated GIF file that loops through all pages', - purpose_verb: 'extract', - purpose_word: 'thumbnail', - purpose_words: 'Extract thumbnail images from documents', - service_slug: 'document-processing', - slot_count: 10, - title: 'Extract thumbnail images from documents', - typical_file_size_mb: 0.8, - typical_file_type: 'document', - uses_tools: ['imagemagick'], - name: 'DocumentThumbsRobot', - priceFactor: 1, - queueSlotCount: 60, - minimumCharge: 524288, - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotDocumentThumbsInstructionsSchema = robotBase - .merge(robotUse) - .merge(robotImagemagick) - .extend({ - robot: z.literal('/document/thumbs').describe(` -## Things to keep in mind - -- If you convert a multi-page PDF file into several images, all result images will be sorted with the first image being the thumbnail of the first document page, etc. -- You can also check the \`meta.thumb_index\` key of each result image to find out which page it corresponds to. Keep in mind that these thumb indices **start at 0,** not at 1. -`), - page: z - .number() - .int() - .nullable() - .default(null) - .describe(` -The PDF page that you want to convert to an image. By default the value is \`null\` which means that all pages will be converted into images. -`), - format: z - .enum(['gif', 'jpeg', 'jpg', 'png']) - .default('png') - .describe(` -The format of the extracted image(s). - -If you specify the value \`"gif"\`, then an animated gif cycling through all pages is created. Please check out [this demo](/demos/document-processing/convert-all-pages-of-a-document-into-an-animated-gif/) to learn more about this. -`), - delay: z - .number() - .int() - .min(0) - .optional() - .describe(` -If your output format is \`"gif"\` then this parameter sets the number of 100th seconds to pass before the next frame is shown in the animation. Set this to \`100\` for example to allow 1 second to pass between the frames of the animated gif. - -If your output format is not \`"gif"\`, then this parameter does not have any effect. -`), - width: z - .number() - .int() - .min(1) - .max(5000) - .optional() - .describe(` -Width of the new image, in pixels. If not specified, will default to the width of the input image -`), - height: z - .number() - .int() - .min(1) - .max(5000) - .optional() - .describe(` -Height of the new image, in pixels. If not specified, will default to the height of the input image -`), - resize_strategy: z - .enum(['crop', 'fillcrop', 'fit', 'min_fit', 'pad', 'stretch']) - .default('pad') - .describe(` -One of the [available resize strategies](/docs/topics/resize-strategies/). -`), - // TODO: Determine the allowed colors - background: z - .string() - .default('#FFFFFF') - .describe(` -Either the hexadecimal code or [name](https://www.imagemagick.org/script/color.php#color_names) of the color used to fill the background (only used for the pad resize strategy). - -By default, the background of transparent images is changed to white. For details about how to preserve transparency across all image types, see [this demo](/demos/image-manipulation/properly-preserve-transparency-across-all-image-types/). -`), - // TODO: Update options list. Why are they capitalized? They are lowercase in th ImageMagick docs. - alpha: z - .enum(['Remove', 'Set']) - .optional() - .describe(` -Change how the alpha channel of the resulting image should work. Valid values are \`"Set"\` to enable transparency and \`"Remove"\` to remove transparency. - -For a list of all valid values please check the ImageMagick documentation [here](http://www.imagemagick.org/script/command-line-options.php#alpha). -`), - density: z - .string() - .regex(/\d+(x\d+)?/) - .optional() - .describe(` -While in-memory quality and file format depth specifies the color resolution, the density of an image is the spatial (space) resolution of the image. That is the density (in pixels per inch) of an image and defines how far apart (or how big) the individual pixels are. It defines the size of the image in real world terms when displayed on devices or printed. - -You can set this value to a specific \`width\` or in the format \`width\`x\`height\`. - -If your converted image has a low resolution, please try using the density parameter to resolve that. -`), - antialiasing: z - .boolean() - .default(false) - .describe(` -Controls whether or not antialiasing is used to remove jagged edges from text or images in a document. -`), - colorspace: colorspaceSchema.optional().describe(` -Sets the image colorspace. For details about the available values, see the [ImageMagick documentation](https://www.imagemagick.org/script/command-line-options.php#colorspace). - -Please note that if you were using \`"RGB"\`, we recommend using \`"sRGB"\`. ImageMagick might try to find the most efficient \`colorspace\` based on the color of an image, and default to e.g. \`"Gray"\`. To force colors, you might then have to use this parameter. -`), - trim_whitespace: z - .boolean() - .default(true) - .describe(` -This determines if additional whitespace around the PDF should first be trimmed away before it is converted to an image. If you set this to \`true\` only the real PDF page contents will be shown in the image. - -If you need to reflect the PDF's dimensions in your image, it is generally a good idea to set this to \`false\`. -`), - pdf_use_cropbox: z - .boolean() - .default(true) - .describe(` -Some PDF documents lie about their dimensions. For instance they'll say they are landscape, but when opened in decent Desktop readers, it's really in portrait mode. This can happen if the document has a cropbox defined. When this option is enabled (by default), the cropbox is leading in determining the dimensions of the resulting thumbnails. -`), - turbo: z - .boolean() - .default(true) - .describe(` -If you set this to \`false\`, the robot will not emit files as they become available. This is useful if you are only interested in the final result and not in the intermediate steps. - -Also, extracted pages will be resized a lot faster as they are sent off to other machines for the resizing. This is especially useful for large documents with many pages to get up to 20 times faster processing. - -Turbo Mode increases pricing, though, in that the input document's file size is added for every extracted page. There are no performance benefits nor increased charges for single-page documents. -`), - }) - .strict() - -export const robotDocumentThumbsInstructionsWithHiddenFieldsSchema = - robotDocumentThumbsInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotDocumentThumbsInstructionsSchema.shape.result]) - .optional(), - stack: z - .string() - .optional() - .describe(` -The image processing stack to use. Defaults to the robot's preferred stack (ImageMagick). -`), - // Override to support lowercase for BC: - alpha: z - .enum(['Remove', 'Set', 'remove', 'set']) - .optional() - .describe(` -Change how the alpha channel of the resulting image should work. Valid values are \`"Set"\` to enable transparency and \`"Remove"\` to remove transparency. Lowercase values are also accepted for backwards compatibility. -`), - // Override to support 'none' for BC - resize_strategy: z - .enum(['crop', 'fillcrop', 'fit', 'min_fit', 'pad', 'stretch', 'none']) - .optional() - .describe(` -One of the [available resize strategies](/docs/transcoding/image-manipulation/image-resize/#resize-strategies). The 'none' value is supported for backwards compatibility. -`), - }) - -export type RobotDocumentThumbsInstructions = z.infer -export type RobotDocumentThumbsInstructionsWithHiddenFields = z.infer< - typeof robotDocumentThumbsInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotDocumentThumbsInstructionsSchema = interpolateRobot( - robotDocumentThumbsInstructionsSchema, -) -export type InterpolatableRobotDocumentThumbsInstructions = - InterpolatableRobotDocumentThumbsInstructionsInput - -export type InterpolatableRobotDocumentThumbsInstructionsInput = z.input< - typeof interpolatableRobotDocumentThumbsInstructionsSchema -> - -export const interpolatableRobotDocumentThumbsInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotDocumentThumbsInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotDocumentThumbsInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotDocumentThumbsInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotDocumentThumbsInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotDocumentThumbsInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/dropbox-import.ts b/packages/transloadit/src/alphalib/types/robots/dropbox-import.ts deleted file mode 100644 index b18d0d9e..00000000 --- a/packages/transloadit/src/alphalib/types/robots/dropbox-import.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - dropboxBase, - interpolateRobot, - path, - robotBase, - robotImport, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 10, - discount_factor: 0.1, - discount_pct: 90, - example_code: { - steps: { - imported: { - robot: '/dropbox/import', - credentials: 'YOUR_DROPBOX_CREDENTIALS', - path: 'path/to/files/', - }, - }, - }, - example_code_description: - 'Import files from the `path/to/files` directory and its subdirectories:', - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Importing', - purpose_sentence: 'imports whole directories of files from your Dropbox', - purpose_verb: 'import', - purpose_word: 'Dropbox', - purpose_words: 'Import files from Dropbox', - requires_credentials: true, - service_slug: 'file-importing', - slot_count: 20, - title: 'Import files from Dropbox', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'DropboxImportRobot', - priceFactor: 6.6666, - queueSlotCount: 20, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: true, - stage: 'ga', -} - -export const robotDropboxImportInstructionsSchema = robotBase - .merge(robotImport) - .merge(dropboxBase) - .extend({ - robot: z.literal('/dropbox/import'), - path: path.describe(` -The path in your Dropbox to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. - -If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are descendants of this directory are recursively imported. For example: \`images/\`. - -If you want to import all files from the root directory, please use \`/\` as the value here. - -You can also use an array of path strings here to import multiple paths in the same Robot's Step. -`), - }) - .strict() - -export const robotDropboxImportInstructionsWithHiddenFieldsSchema = - robotDropboxImportInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotDropboxImportInstructionsSchema.shape.result]) - .optional(), - access_token: z.string().optional(), // Legacy field for backward compatibility - }) - -export type RobotDropboxImportInstructions = z.infer -export type RobotDropboxImportInstructionsWithHiddenFields = z.infer< - typeof robotDropboxImportInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotDropboxImportInstructionsSchema = interpolateRobot( - robotDropboxImportInstructionsSchema, -) -export type InterpolatableRobotDropboxImportInstructions = - InterpolatableRobotDropboxImportInstructionsInput - -export type InterpolatableRobotDropboxImportInstructionsInput = z.input< - typeof interpolatableRobotDropboxImportInstructionsSchema -> - -export const interpolatableRobotDropboxImportInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotDropboxImportInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotDropboxImportInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotDropboxImportInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotDropboxImportInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotDropboxImportInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/dropbox-store.ts b/packages/transloadit/src/alphalib/types/robots/dropbox-store.ts deleted file mode 100644 index 95c1b992..00000000 --- a/packages/transloadit/src/alphalib/types/robots/dropbox-store.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { dropboxBase, interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 6, - discount_factor: 0.15000150001500018, - discount_pct: 84.99984999849998, - example_code: { - steps: { - exported: { - robot: '/dropbox/store', - use: ':original', - credentials: 'YOUR_DROPBOX_CREDENTIALS', - path: 'my_target_folder/${unique_prefix}/${file.url_name}', - }, - }, - }, - example_code_description: 'Export uploaded files to `my_target_folder` on Dropbox:', - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Exporting', - purpose_sentence: 'exports encoding results to Dropbox', - purpose_verb: 'export', - purpose_word: 'Dropbox', - purpose_words: 'Export files to Dropbox', - service_slug: 'file-exporting', - slot_count: 10, - title: 'Export files to Dropbox', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'DropboxStoreRobot', - priceFactor: 6.6666, - queueSlotCount: 10, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotDropboxStoreInstructionsSchema = robotBase - .merge(robotUse) - .merge(dropboxBase) - .extend({ - robot: z.literal('/dropbox/store'), - path: z - .string() - .default('${unique_prefix}/${file.url_name}') - .describe(` -The path at which the file is to be stored. This may include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). -`), - create_sharing_link: z - .boolean() - .default(false) - .describe(` -Whether to create a URL to this file for sharing with other people. This will overwrite the file's \`"url"\` property. -`), - }) - .strict() - -export const robotDropboxStoreInstructionsWithHiddenFieldsSchema = - robotDropboxStoreInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotDropboxStoreInstructionsSchema.shape.result]) - .optional(), - access_token: z.string().optional(), // Legacy field for backward compatibility - }) - -export type RobotDropboxStoreInstructions = z.infer -export type RobotDropboxStoreInstructionsInput = z.input -export type RobotDropboxStoreInstructionsWithHiddenFields = z.infer< - typeof robotDropboxStoreInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotDropboxStoreInstructionsSchema = interpolateRobot( - robotDropboxStoreInstructionsSchema, -) -export type InterpolatableRobotDropboxStoreInstructions = - InterpolatableRobotDropboxStoreInstructionsInput - -export type InterpolatableRobotDropboxStoreInstructionsInput = z.input< - typeof interpolatableRobotDropboxStoreInstructionsSchema -> - -export const interpolatableRobotDropboxStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotDropboxStoreInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotDropboxStoreInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotDropboxStoreInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotDropboxStoreInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotDropboxStoreInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/edgly-deliver.ts b/packages/transloadit/src/alphalib/types/robots/edgly-deliver.ts deleted file mode 100644 index cfd19d16..00000000 --- a/packages/transloadit/src/alphalib/types/robots/edgly-deliver.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { z } from 'zod' -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: false, - bytescount: 20, - discount_factor: 0.05, - discount_pct: 95, - minimum_charge: 102400, - output_factor: 1, - override_lvl1: 'Content Delivery', - purpose_sentence: 'caches and delivers files globally', - purpose_verb: 'cache & deliver', - purpose_word: 'Cache and deliver files', - purpose_words: 'Cache and deliver files globally', - service_slug: 'content-delivery', - slot_count: 0, - title: 'Cache and deliver files globally', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'EdglyDeliverRobot', - priceFactor: 20, - queueSlotCount: 0, - minimumCharge: 102400, - downloadInputFiles: false, - preserveInputFileUrls: true, - isAllowedForUrlTransform: false, - trackOutputFileSize: false, - isInternal: true, - stage: 'removed', - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, -} - -export const robotEdglyDeliverInstructionsSchema = robotBase - .extend({ - robot: z.literal('/edgly/deliver').describe(` -When you want Transloadit to tranform files on the fly, this Robot can cache and deliver the results close to your end-user, saving on latency and encoding volume. The use of this Robot is implicit when you use the edgly.net domain. -`), - }) - .strict() - -export const robotEdglyDeliverInstructionsWithHiddenFieldsSchema = - robotEdglyDeliverInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotEdglyDeliverInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotEdglyDeliverInstructions = z.infer -export type RobotEdglyDeliverInstructionsWithHiddenFields = z.infer< - typeof robotEdglyDeliverInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotEdglyDeliverInstructionsSchema = interpolateRobot( - robotEdglyDeliverInstructionsSchema, -) -export type InterpolatableRobotEdglyDeliverInstructions = - InterpolatableRobotEdglyDeliverInstructionsInput - -export type InterpolatableRobotEdglyDeliverInstructionsInput = z.input< - typeof interpolatableRobotEdglyDeliverInstructionsSchema -> - -export const interpolatableRobotEdglyDeliverInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotEdglyDeliverInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotEdglyDeliverInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotEdglyDeliverInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotEdglyDeliverInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotEdglyDeliverInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/file-compress.ts b/packages/transloadit/src/alphalib/types/robots/file-compress.ts deleted file mode 100644 index f329ba6e..00000000 --- a/packages/transloadit/src/alphalib/types/robots/file-compress.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: false, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - example_code: { - steps: { - compressed: { - robot: '/file/compress', - use: { - steps: [':original'], - bundle_steps: true, - }, - format: 'zip', - }, - }, - }, - example_code_description: 'Compress uploaded files into a ZIP archive:', - extended_description: ` -### Archive structure for the \`"advanced"\` file layout. - -There are a few things that we kept in mind when designing the \`"advanced"\` archive structure: - -- There could be naming collisions. -- You want to know which Step a result file belongs to. -- You want to know from which originally uploaded file a result file was generated. -- Ideally, you want subfolders for a better structure of files. - -To achieve all this, we have created the following archive file structure. - -- There is a subfolder for each Step name that has result files in the archive. -- Files are named according to the first two letters of the unique original prefix + "_" + the first two letters of the unique prefix + "_" + the original file name. If you do not know what the original prefixes are, please check [our available Assembly variables](/docs/topics/assembly-instructions/#assembly-variables) and look for \`\${unique_original_prefix}\` and \`\${unique_prefix}\`. -- Files that belong to the \`:original\` Step (originally uploaded files) do **not** include the first two letters of the \`unique_original_prefix\`. -- If you are dealing with thumbnails from [🤖/video/thumbs](/docs/robots/video-thumbs/), there is an additional digit representing the order in the file name. - -Here is an example: - -\`\`\`yaml -":original": - - gh_a.mov # "gh" are the first 2 letters of the unique prefix. - # "a.mov" was the file name of the uploaded file. - - ff_b.mov -"thumbed": - - gh_e8_thumb_1.jpg # "gh" is the unique original prefix, meaning it's a result of a.mov. - # "e8" is the file's unique prefix. - # The "1" shows the thumbnail order. - - gh_cv_thumb_2.jpg - - ff_9b_thumb_3.jpg -"resized": - - gh_ll_thumb.jpg - - gh_df_thumb.jpg - - ff_jk_thumb.jpg # is a child of b.mov, as it starts with "ff" -\`\`\` -`, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Compressing', - purpose_sentence: 'creates archives of files or file conversion results', - purpose_verb: 'compress', - purpose_word: 'compress', - purpose_words: 'Compress files', - service_slug: 'file-compressing', - slot_count: 15, - title: 'Compress files', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'FileCompressRobot', - priceFactor: 1, - queueSlotCount: 15, - isAllowedForUrlTransform: false, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotFileCompressInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/file/compress'), - format: z - .enum(['tar', 'zip']) - .default('tar') - .describe(` -The format of the archive to be created. Supported values are \`"tar"\` and \`"zip"\`. - -Note that \`"tar"\` without setting \`gzip\` to \`true\` results in an archive that's not compressed in any way. -`), - gzip: z - .boolean() - .default(false) - .describe(` -Determines if the result archive should also be gzipped. Gzip compression is only applied if you use the \`"tar"\` format. -`), - password: z - .string() - .nullable() - .default(null) - .describe(` -This allows you to encrypt all archive contents with a password and thereby protect it against unauthorized use. To unzip the archive, the user will need to provide the password in a text input field prompt. - -This parameter has no effect if the format parameter is anything other than \`"zip"\`. -`), - compression_level: z - .number() - .int() - .min(-9) - .max(0) - .default(-6) - .describe(` -Determines how fiercely to try to compress the archive. \`-0\` is compressionless, which is suitable for media that is already compressed. \`-1\` is fastest with lowest compression. \`-9\` is slowest with the highest compression. - -If you are using \`-0\` in combination with the \`tar\` format with \`gzip\` enabled, consider setting \`gzip: false\` instead. This results in a plain Tar archive, meaning it already has no compression. -`), - file_layout: z - .enum(['advanced', 'simple', 'relative-path']) - .default('advanced') - .describe(` -Determines if the result archive should contain all files in one directory (value for this is \`"simple"\`) or in subfolders according to the explanation below (value for this is \`"advanced"\`). The \`"relative-path"\` option preserves the relative directory structure of the input files. - -Files with same names are numbered in the \`"simple"\` file layout to avoid naming collisions. -`), - archive_name: z - .string() - .optional() - .describe(` -The name of the archive file to be created (without the file extension). -`), - }) - .strict() - -export const robotFileCompressInstructionsWithHiddenFieldsSchema = - robotFileCompressInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotFileCompressInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotFileCompressInstructions = z.infer -export type RobotFileCompressInstructionsWithHiddenFields = z.infer< - typeof robotFileCompressInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotFileCompressInstructionsSchema = interpolateRobot( - robotFileCompressInstructionsSchema, -) -export type InterpolatableRobotFileCompressInstructions = - InterpolatableRobotFileCompressInstructionsInput - -export type InterpolatableRobotFileCompressInstructionsInput = z.input< - typeof interpolatableRobotFileCompressInstructionsSchema -> - -export const interpolatableRobotFileCompressInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotFileCompressInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotFileCompressInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotFileCompressInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotFileCompressInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotFileCompressInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/file-decompress.ts b/packages/transloadit/src/alphalib/types/robots/file-decompress.ts deleted file mode 100644 index 17ee0e23..00000000 --- a/packages/transloadit/src/alphalib/types/robots/file-decompress.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 1, - discount_factor: 0.8, - discount_pct: 20, - example_code: { - steps: { - decompressed: { - robot: '/file/decompress', - use: ':original', - }, - }, - }, - example_code_description: 'Decompress an uploaded archive:', - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Compressing', - purpose_sentence: - 'extracts entire archives of files to be consumed by other Robots or exported as individual files', - purpose_verb: 'decompress', - purpose_word: 'decompress', - purpose_words: 'Decompress archives', - service_slug: 'file-compressing', - slot_count: 10, - title: 'Decompress archives', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'FileDecompressRobot', - priceFactor: 1.25, - queueSlotCount: 10, - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: true, - stage: 'ga', -} - -export const robotFileDecompressInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/file/decompress').describe(` -This Robot supports the following archive formats: - -- ZIP archives (with uncompressed or "deflate"-compressed entries) -- 7-Zip archives -- RAR archives -- GNU tar format (including GNU long filenames, long link names, and sparse files) -- Solaris 9 extended tar format (including ACLs) -- Old V7 tar archives -- POSIX ustar -- POSIX pax interchange format -- POSIX octet-oriented cpio -- SVR4 ASCII cpio -- POSIX octet-oriented cpio -- Binary cpio (big-endian or little-endian) -- ISO9660 CD-ROM images (with optional Rockridge or Joliet extensions) -- GNU and BSD "ar" archives -- "mtree" format -- Microsoft CAB format -- LHA and LZH archives -- XAR archives - -This Robot also detects and handles any of the following before evaluating the archive file: - -- uuencoded files -- Files with RPM wrapper -- gzip compression -- bzip2 compression -- compress/LZW compression -- lzma, lzip, and xz compression - -For security reasons, archives that contain symlinks to outside the archived dir, will error out the Assembly. Decompressing password-protected archives (encrypted archives) is currently not fully supported but will not cause an Assembly to fail. -`), - ignore_errors: z - .union([z.boolean(), z.array(z.enum(['meta', 'execute']))]) - .transform((ignoreErrors): ('meta' | 'execute')[] => - ignoreErrors === true ? ['meta', 'execute'] : ignoreErrors === false ? [] : ignoreErrors, - ) - .default([]) - .describe(` -A possible array member is only \`"meta"\`. - -You might see an error when trying to extract metadata from the files inside your archive. This happens, for example, for files with a size of zero bytes. Setting this to \`true\` will cause the Robot to not stop the file decompression (and the entire Assembly) when that happens. - -To keep backwards compatibility, setting this parameter to \`true\` will set it to \`["meta"]\` internally. -`), - }) - .strict() - -export const robotFileDecompressInstructionsWithHiddenFieldsSchema = - robotFileDecompressInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotFileDecompressInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotFileDecompressInstructions = z.infer -export type RobotFileDecompressInstructionsWithHiddenFields = z.infer< - typeof robotFileDecompressInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotFileDecompressInstructionsSchema = interpolateRobot( - robotFileDecompressInstructionsSchema, -) -export type InterpolatableRobotFileDecompressInstructions = - InterpolatableRobotFileDecompressInstructionsInput - -export type InterpolatableRobotFileDecompressInstructionsInput = z.input< - typeof interpolatableRobotFileDecompressInstructionsSchema -> - -export const interpolatableRobotFileDecompressInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotFileDecompressInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotFileDecompressInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotFileDecompressInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotFileDecompressInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotFileDecompressInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/file-filter.ts b/packages/transloadit/src/alphalib/types/robots/file-filter.ts deleted file mode 100644 index 0bf6f8c9..00000000 --- a/packages/transloadit/src/alphalib/types/robots/file-filter.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - filterCondition, - interpolateRobot, - robotBase, - robotUse, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 0, - discount_factor: 0, - discount_pct: 100, - example_code: { - steps: { - filtered: { - robot: '/file/filter', - use: ':original', - declines: [['${file.size}', '>', '20971520']], - error_on_decline: true, - error_msg: 'File size must not exceed 20 MB', - }, - }, - }, - example_code_description: 'Reject files that are larger than 20 MB:', - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Filtering', - purpose_sentence: 'directs files to different encoding Steps based on your conditions', - purpose_verb: 'filter', - purpose_word: 'filter', - purpose_words: 'Filter files', - service_slug: 'file-filtering', - slot_count: 0, - title: 'Filter files', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'FileFilterRobot', - priceFactor: 100, - queueSlotCount: 0, - downloadInputFiles: false, - preserveInputFileUrls: true, - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotFileFilterInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/file/filter').describe(` -Think of this Robot as an \`if/else\` condition for building advanced file conversion workflows. With it, you can filter and direct certain uploaded files depending on their metadata. - -The Robot has two modes of operation: - -- Constructing conditions out of arrays with 3 members each. For example, \`["\${file.size}", "<=", "720"]\` -- Writing conditions in JavaScript. For example, \`\${file.size <= 720}\`. See also [Dynamic Evaluation](/docs/topics/dynamic-evaluation/). - -Passing JavaScript allows you to implement logic as complex as you wish, however it’s slower than combining arrays of conditions, and will be charged for per invocation via [🤖/script/run](/docs/robots/script-run/). - -### Conditions as arrays - -The \`accepts\` and \`declines\` parameters can each be set to an array of arrays with three members: - -1. A value or job variable, such as \`\${file.mime}\` -2. One of the following operators: \`==\`, \`===\`, \`<\`, \`>\`, \`<=\`, \`>=\`, \`!=\`, \`!==\`, \`regex\`, \`!regex\` -3. A value or job variable, such as \`50\` or \`"foo"\` - -Examples: - -- \`[["\${file.meta.width}", ">", "\${file.meta.height}"]]\` -- \`[["\${file.size}", "<=", "720"]]\` -- \`[["720", ">=", "\${file.size}"]]\` -- \`[["\${file.mime}", "regex", "image"]]\` - -> [!Warning] -> If you would like to match against a \`null\` value or a value that is not present (like an audio file does not have a \`video_codec\` property in its metadata), match against \`""\` (an empty string) instead. We’ll support proper matching against \`null\` in the future, but we cannot easily do so right now without breaking backwards compatibility. - -### Conditions as JavaScript - -The \`accepts\` and \`declines\` parameters can each be set to strings of JavaScript, which return a boolean value. - -Examples: - -- \`\${file.meta.width > file.meta.height}\` -- \`\${file.size <= 720}\` -- \`\${/image/.test(file.mime)}\` -- \`\${Math.max(file.meta.width, file.meta.height) > 100}\` - -As indicated, we charge for this via [🤖/script/run](/docs/robots/script-run/). See also [Dynamic Evaluation](/docs/topics/dynamic-evaluation/) for more details on allowed syntax and behavior. -`), - accepts: filterCondition - .describe( - ` -Files that match at least one requirement will be accepted, or declined otherwise. If the value is \`null\`, all files will be accepted. If the array is empty, no files will be accepted. Example: - -\`[["\${file.mime}", "==", "image/gif"]]\`. - -If the \`condition_type\` parameter is set to \`"and"\`, then all requirements must match for the file to be accepted. - -If \`accepts\` and \`declines\` are both provided, the requirements in \`accepts\` will be evaluated first, before the conditions in \`declines\`. -`, - ) - .optional(), - declines: filterCondition - .describe( - ` -Files that match at least one requirement will be declined, or accepted otherwise. If the value is \`null\` or an empty array, no files will be declined. Example: - -\`[["\${file.size}",">","1024"]]\`. - -If the \`condition_type\` parameter is set to \`"and"\`, then all requirements must match for the file to be declined. - -If \`accepts\` and \`declines\` are both provided, the requirements in \`accepts\` will be evaluated first, before the conditions in \`declines\`. -`, - ) - .optional(), - condition_type: z - .enum(['and', 'or']) - .default('or') - .describe(` -Specifies the condition type according to which the members of the \`accepts\` or \`declines\` arrays should be evaluated. Can be \`"or"\` or \`"and"\`. -`), - error_on_decline: z - .boolean() - .default(false) - .describe(` -If this is set to \`true\` and one or more files are declined, the Assembly will be stopped and marked with an error. -`), - error_msg: z - .string() - .default('One of your files was declined') - .describe(` -The error message shown to your users (such as by Uppy) when a file is declined and \`error_on_decline\` is set to \`true\`. -`), - }) - .strict() - -export const robotFileFilterInstructionsWithHiddenFieldsSchema = - robotFileFilterInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotFileFilterInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotFileFilterInstructions = z.infer -export type RobotFileFilterInstructionsWithHiddenFields = z.infer< - typeof robotFileFilterInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotFileFilterInstructionsSchema = interpolateRobot( - robotFileFilterInstructionsSchema, -) -export type InterpolatableRobotFileFilterInstructions = - InterpolatableRobotFileFilterInstructionsInput - -export type InterpolatableRobotFileFilterInstructionsInput = z.input< - typeof interpolatableRobotFileFilterInstructionsSchema -> - -export const interpolatableRobotFileFilterInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotFileFilterInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotFileFilterInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotFileFilterInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotFileFilterInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotFileFilterInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/file-hash.ts b/packages/transloadit/src/alphalib/types/robots/file-hash.ts deleted file mode 100644 index 50058e66..00000000 --- a/packages/transloadit/src/alphalib/types/robots/file-hash.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: false, - bytescount: 5, - discount_factor: 0.2, - discount_pct: 80, - example_code: { - steps: { - hashed: { - robot: '/file/hash', - use: ':original', - algorithm: 'sha1', - }, - }, - }, - example_code_description: 'Hash each uploaded file using the SHA-1 algorithm:', - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'Media Cataloging', - purpose_sentence: 'hashes files in Assemblies', - purpose_verb: 'hash', - purpose_word: 'file', - purpose_words: 'Hash files', - service_slug: 'media-cataloging', - slot_count: 60, - title: 'Hash Files', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'FileHashRobot', - priceFactor: 5, - queueSlotCount: 60, - isAllowedForUrlTransform: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotFileHashInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/file/hash').describe(` -This Robot allows you to hash any file as part of the Assembly execution process. This can be useful for verifying the integrity of a file for example. -`), - algorithm: z - .enum(['b2', 'md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512']) - .default('sha256') - .describe(` -The hashing algorithm to use. - -The file hash is exported as \`file.meta.hash\`. -`), - }) - .strict() - -export const robotFileHashInstructionsWithHiddenFieldsSchema = - robotFileHashInstructionsSchema.extend({ - result: z.union([z.literal('debug'), robotFileHashInstructionsSchema.shape.result]).optional(), - }) - -export type RobotFileHashInstructions = z.infer -export type RobotFileHashInstructionsWithHiddenFields = z.infer< - typeof robotFileHashInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotFileHashInstructionsSchema = interpolateRobot( - robotFileHashInstructionsSchema, -) -export type InterpolatableRobotFileHashInstructions = InterpolatableRobotFileHashInstructionsInput - -export type InterpolatableRobotFileHashInstructionsInput = z.input< - typeof interpolatableRobotFileHashInstructionsSchema -> - -export const interpolatableRobotFileHashInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotFileHashInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotFileHashInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotFileHashInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotFileHashInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotFileHashInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/file-preview.ts b/packages/transloadit/src/alphalib/types/robots/file-preview.ts deleted file mode 100644 index a4307225..00000000 --- a/packages/transloadit/src/alphalib/types/robots/file-preview.ts +++ /dev/null @@ -1,260 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - color_with_alpha, - complexHeightSchema, - complexWidthSchema, - interpolateRobot, - optimize_priority, - resize_strategy, - robotBase, - robotUse, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - example_code: { - steps: { - previewed: { - robot: '/file/preview', - use: ':original', - height: 400, - width: 300, - format: 'png', - }, - }, - }, - example_code_description: 'Generate a preview thumbnail for any uploaded file:', - minimum_charge: 1048576, - output_factor: 1, - override_lvl1: 'Media Cataloging', - purpose_sentence: - 'generates a thumbnail for any uploaded file to preview its content, similar to the thumbnails in desktop file managers', - purpose_verb: 'generate', - purpose_word: 'generate', - purpose_words: 'Generate a preview thumbnail', - service_slug: 'media-cataloging', - slot_count: 15, - title: 'Generate a preview thumbnail', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'FilePreviewRobot', - priceFactor: 1, - queueSlotCount: 15, - minimumCharge: 1048576, - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - importRanges: ['0-19999999', '-1000000'], - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'beta', -} - -export const robotFilePreviewInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/file/preview').describe(` -This Robot's purpose is to generate a meaningful preview image for any file, in such a way that the resulting thumbnail highlights the file's content. The goal is not to losslessly present the original media in a smaller way. Instead, it is to maximize the chance of a person recognizing the media at a glance, while being visually pleasing and consistent with other previews. The generation process depends on the file type. For example, the Robot can extract artwork from media files, frames from videos, generate a waveform for audio files, and preview the content of documents and images. The details of all available strategies are provided in the next section. - -If no file-specific thumbnail can be generated because the file type is not supported, a generic icon containing the file extension will be generated. - -The default parameters ensure that the Robot always generates a preview image with the predefined dimensions and formats, to allow an easy integration into your application's UI. In addition, the generated preview images are optimized by default to reduce their file size while keeping their quality. -`), - format: z - .enum(['gif', 'jpg', 'png']) - .default('png') - .describe(` -The output format for the generated thumbnail image. If a short video clip is generated using the \`clip\` strategy, its format is defined by \`clip_format\`. -`), - width: complexWidthSchema.default(300).describe(` -Width of the thumbnail, in pixels. -`), - height: complexHeightSchema.default(200).describe(` -Height of the thumbnail, in pixels. -`), - resize_strategy: resize_strategy.describe(` -To achieve the desired dimensions of the preview thumbnail, the Robot might have to resize the generated image. This happens, for example, when the dimensions of a frame extracted from a video do not match the chosen \`width\` and \`height\` parameters. - -See the list of available [resize strategies](/docs/topics/resize-strategies/) for more details. -`), - background: color_with_alpha.default('#ffffffff').describe(` -The hexadecimal code of the color used to fill the background (only used for the pad resize strategy). The format is \`#rrggbb[aa]\` (red, green, blue, alpha). Use \`#00000000\` for a transparent padding. -`), - strategy: z - .object({ - archive: z.array(z.string()).default(['icon']), - audio: z.array(z.string()).default(['artwork', 'waveform', 'icon']), - document: z.array(z.string()).default(['page', 'icon']), - image: z.array(z.string()).default(['image', 'icon']), - unknown: z.array(z.string()).default(['icon']), - video: z.array(z.string()).default(['artwork', 'frame', 'icon']), - webpage: z.array(z.string()).default(['render', 'icon']), - }) - .optional() - .describe(` -Definition of the thumbnail generation process per file category. The parameter must be an object whose keys can be one of the file categories: \`audio\`, \`video\`, \`image\`, \`document\`, \`archive\`, \`webpage\`, and \`unknown\`. The corresponding value is an array of strategies for the specific file category. See the above section for a list of all available strategies. - -For each file, the Robot will attempt to use the first strategy to generate the thumbnail. If this process fails (e.g., because no artwork is available in a video file), the next strategy is attempted. This is repeated until either a thumbnail is generated or the list is exhausted. Selecting the \`icon\` strategy as the last entry provides a fallback mechanism to ensure that an appropriate strategy is always available. - -The parameter defaults to the following definition: - -\`\`\`json -{ - "audio": ["artwork", "waveform", "icon"], - "video": ["artwork", "frame", "icon"], - "document": ["page", "icon"], - "image": ["image", "icon"], - "webpage": ["render", "icon"], - "archive": ["icon"], - "unknown": ["icon"] -} -\`\`\` -`), - artwork_outer_color: color_with_alpha.optional().describe(` - The color used in the outer parts of the artwork's gradient. - `), - artwork_center_color: color_with_alpha.optional().describe(` - The color used in the center of the artwork's gradient. - `), - waveform_center_color: color_with_alpha.default('#000000ff').describe(` -The color used in the center of the waveform's gradient. The format is \`#rrggbb[aa]\` (red, green, blue, alpha). Only used if the \`waveform\` strategy for audio files is applied. -`), - waveform_outer_color: color_with_alpha.default('#000000ff').describe(` -The color used in the outer parts of the waveform's gradient. The format is \`#rrggbb[aa]\` (red, green, blue, alpha). Only used if the \`waveform\` strategy for audio files is applied. -`), - waveform_height: z - .number() - .int() - .min(1) - .max(5000) - .default(100) - .describe(` -Height of the waveform, in pixels. Only used if the \`waveform\` strategy for audio files is applied. It can be utilized to ensure that the waveform only takes up a section of the preview thumbnail. -`), - waveform_width: z - .number() - .int() - .min(1) - .max(5000) - .default(300) - .describe(` -Width of the waveform, in pixels. Only used if the \`waveform\` strategy for audio files is applied. It can be utilized to ensure that the waveform only takes up a section of the preview thumbnail. -`), - icon_style: z - .enum(['square', 'with-text']) - .default('with-text') - .describe(` -The style of the icon generated if the \`icon\` strategy is applied. The default style, \`with-text\`, includes an icon showing the file type and a text box below it, whose content can be controlled by the \`icon_text_content\` parameter and defaults to the file extension (e.g. MP4, JPEG). The \`square\` style only includes a square variant of the icon showing the file type. Below are exemplary previews generated for a text file utilizing the different styles: - -

\`with-text\` style:
-![Image with text style]({{site.asset_cdn}}/assets/images/file-preview/icon-with-text.png) -

\`square\` style:
-![Image with square style]({{site.asset_cdn}}/assets/images/file-preview/icon-square.png) -`), - icon_text_color: color_with_alpha.default('#a2a2a2').describe(` -The color of the text used in the icon. The format is \`#rrggbb[aa]\`. Only used if the \`icon\` strategy is applied. -`), - // TODO: Determine the font enum. - icon_text_font: z - .string() - .default('Roboto') - .describe(` -The font family of the text used in the icon. Only used if the \`icon\` strategy is applied. [Here](/docs/supported-formats/fonts/) is a list of all supported fonts. -`), - icon_text_content: z - .enum(['extension', 'none']) - .default('extension') - .describe(` -The content of the text box in generated icons. Only used if the \`icon_style\` parameter is set to \`with-text\`. The default value, \`extension\`, adds the file extension (e.g. MP4, JPEG) to the icon. The value \`none\` can be used to render an empty text box, which is useful if no text should not be included in the raster image, but some place should be reserved in the image for later overlaying custom text over the image using HTML etc. -`), - optimize: z - .boolean() - .default(true) - .describe(` -Specifies whether the generated preview image should be optimized to reduce the image's file size while keeping their quaility. If enabled, the images will be optimized using [🤖/image/optimize](/docs/robots/image-optimize/). -`), - optimize_priority: optimize_priority.describe(` -Specifies whether conversion speed or compression ratio is prioritized when optimizing images. Only used if \`optimize\` is enabled. Please see the [🤖/image/optimize documentation](/docs/robots/image-optimize/#param-priority) for more details. -`), - optimize_progressive: z - .boolean() - .default(false) - .describe(` -Specifies whether images should be interlaced, which makes the result image load progressively in browsers. Only used if \`optimize\` is enabled. Please see the [🤖/image/optimize documentation](/docs/robots/image-optimize/#param-progressive) for more details. -`), - clip_format: z - .enum(['apng', 'avif', 'gif', 'webp']) - .default('webp') - .describe(` -The animated image format for the generated video clip. Only used if the \`clip\` strategy for video files is applied. - -Please consult the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types) for detailed information about the image formats and their characteristics. GIF enjoys the broadest support in software, but only supports a limit color palette. APNG supports a variety of color depths, but its lossless compression produces large images for videos. AVIF is a modern image format that offers great compression, but proper support for animations is still lacking in some browsers. WebP on the other hand, enjoys broad support while offering a great balance between small file sizes and good visual quality, making it the default clip format. -`), - clip_offset: z - .number() - .min(0) - .default(1) - .describe(` -The start position in seconds of where the clip is cut. Only used if the \`clip\` strategy for video files is applied. Be aware that for larger video only the first few MBs of the file may be imported to improve speed. Larger offsets may seek to a position outside of the imported part and thus fail to generate a clip. -`), - clip_duration: z - .number() - .min(0) - .default(5) - .describe(` -The duration in seconds of the generated video clip. Only used if the \`clip\` strategy for video files is applied. Be aware that a longer clip duration also results in a larger file size, which might be undesirable for previews. -`), - clip_framerate: z - .number() - .int() - .min(1) - .max(60) - .default(5) - .describe(` -The framerate of the generated video clip. Only used if the \`clip\` strategy for video files is applied. Be aware that a higher framerate appears smoother but also results in a larger file size, which might be undesirable for previews. -`), - clip_loop: z - .boolean() - .default(true) - .describe(` -Specifies whether the generated animated image should loop forever (\`true\`) or stop after playing the animation once (\`false\`). Only used if the \`clip\` strategy for video files is applied. -`), - }) - .strict() - -export const robotFilePreviewInstructionsWithHiddenFieldsSchema = - robotFilePreviewInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotFilePreviewInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotFilePreviewInstructions = z.infer -export type RobotFilePreviewInstructionsInput = z.input -export type RobotFilePreviewInstructionsWithHiddenFields = z.infer< - typeof robotFilePreviewInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotFilePreviewInstructionsSchema = interpolateRobot( - robotFilePreviewInstructionsSchema, -) -export type InterpolatableRobotFilePreviewInstructions = - InterpolatableRobotFilePreviewInstructionsInput - -export type InterpolatableRobotFilePreviewInstructionsInput = z.input< - typeof interpolatableRobotFilePreviewInstructionsSchema -> - -export const interpolatableRobotFilePreviewInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotFilePreviewInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotFilePreviewInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotFilePreviewInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotFilePreviewInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotFilePreviewInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/file-read.ts b/packages/transloadit/src/alphalib/types/robots/file-read.ts deleted file mode 100644 index 4fabd6c7..00000000 --- a/packages/transloadit/src/alphalib/types/robots/file-read.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 5, - discount_factor: 0.2, - discount_pct: 80, - minimum_charge: 512000, - output_factor: 1, - override_lvl1: 'Document Processing', - purpose_sentence: 'reads file contents from supported file-types', - purpose_verb: 'read', - purpose_word: 'read files', - purpose_words: 'Read file contents', - service_slug: 'document-processing', - slot_count: 5, - title: 'Read file contents', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'FileReadRobot', - priceFactor: 5, - queueSlotCount: 5, - minimumCharge: 512000, - isAllowedForUrlTransform: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotFileReadInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/file/read').describe(` -This Robot accepts any file, and will read the file using UTF-8 encoding. The result is outputted to \`file.meta.content\` to be accessed in later Steps. - -The Robot currently only accepts files under 500KB. -`), - }) - .strict() - -export const robotFileReadInstructionsWithHiddenFieldsSchema = - robotFileReadInstructionsSchema.extend({ - result: z.union([z.literal('debug'), robotFileReadInstructionsSchema.shape.result]).optional(), - }) - -export type RobotFileReadInstructions = z.infer -export type RobotFileReadInstructionsWithHiddenFields = z.infer< - typeof robotFileReadInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotFileReadInstructionsSchema = interpolateRobot( - robotFileReadInstructionsSchema, -) -export type InterpolatableRobotFileReadInstructions = InterpolatableRobotFileReadInstructionsInput - -export type InterpolatableRobotFileReadInstructionsInput = z.input< - typeof interpolatableRobotFileReadInstructionsSchema -> - -export const interpolatableRobotFileReadInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotFileReadInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotFileReadInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotFileReadInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotFileReadInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotFileReadInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/file-serve.ts b/packages/transloadit/src/alphalib/types/robots/file-serve.ts deleted file mode 100644 index 7c4e2a63..00000000 --- a/packages/transloadit/src/alphalib/types/robots/file-serve.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 4, - discount_factor: 0.25, - discount_pct: 75, - minimum_charge: 0, - output_factor: 1, - purpose_sentence: 'serves files to web browsers', - purpose_verb: 'serve', - purpose_word: 'Serve files', - purpose_words: 'Serve files to web browsers', - service_slug: 'content-delivery', - slot_count: 0, - title: 'Serve files to web browsers', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'FileServeRobot', - priceFactor: 4, - queueSlotCount: 0, - downloadInputFiles: false, - preserveInputFileUrls: true, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - stage: 'ga', - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, -} - -export const robotFileServeInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/file/serve').describe(` -When you want Transloadit to tranform files on the fly, you can use this Robot to determine which Step of a Template should be served to the end-user (via a CDN), as well as set extra information on the served files, such as headers. This way you can for instance suggest the CDN for how long to keep cached copies of the result around. By default, as you can see in the \`headers\` parameter, we instruct browsers to cache the result for 72h (\`259200\` seconds) and CDNs to cache the content for 24h (\`86400\` seconds). These values should be adjusted to suit your use case. - -🤖/file/serve merely acts as the glue layer between our Assembly engine and serving files over HTTP. It let's you pick the proper result of a series of Steps via the \`use\` parameter and configure headers on the original content. That is where its responsibilies end, and 🤖/tlcdn/deliver, then takes over to globally distribute this original content across the globe, and make sure that is cached close to your end-users, when they make requests such as , another. 🤖/tlcdn/deliver is not a part of your Assembly Instructions, but it may appear on your invoices as bandwidth charges incur when distributing the cached copies. 🤖/file/serve only charges when the CDN does not have a cached copy and requests to regenerate the original content, which depending on your caching settings could be just once a month, or year, per file/transformation. - -While theoretically possible, you could use [🤖/file/serve](/docs/robots/file-serve/) directly in HTML files, but we strongly recommend against this, because if your site gets popular and the media URL that /file/serve is handling gets hit one million times, that is one million new image resizes. Wrapping it with a CDN (and thanks to the caching that comes with it) makes sure encoding charges stay low, as well as latencies. - -Also consider configuring caching headers and cache-control directives to control how content is cached and invalidated on the CDN edge servers, balancing between freshness and efficiency. - -## Smart CDN Security with Signature Authentication - -You can leverage [Signature Authentication](/docs/api/authentication/#smart-cdn) to avoid abuse of our encoding platform. Below is a quick Node.js example using our Node SDK, but there are [examples for other languages and SDKs](/docs/api/authentication/#example-code) as well. - -\`\`\`javascript -// yarn add transloadit -// or -// npm install --save transloadit - -import { Transloadit } from 'transloadit' - -const transloadit = new Transloadit({ - authKey: 'YOUR_TRANSLOADIT_KEY', - authSecret: 'YOUR_TRANSLOADIT_SECRET', -}) - -const url = transloadit.getSignedSmartCDNUrl({ - workspace: 'YOUR_WORKSPACE', - template: 'YOUR_TEMPLATE', - input: 'image.png', - urlParams: { height: 100, width: 100 }, -}) - -console.log(url) -\`\`\` - -This will generate a signed Smart CDN URL that includes authentication parameters, preventing unauthorized access to your transformation endpoints. - -## More information - -- [Content Delivery](/services/content-delivery/) -- [🤖/file/serve](/docs/robots/file-serve/) pricing -- [🤖/tlcdn/deliver](/docs/robots/tlcdn-deliver/) pricing -- [File Preview Feature](/blog/2024/06/file-preview-with-smart-cdn/) blog post -`), - headers: z - .record(z.string()) - .default({ - 'Access-Control-Allow-Headers': - 'X-Requested-With, Content-Type, Cache-Control, Accept, Content-Length, Transloadit-Client, Authorization', - 'Access-Control-Allow-Methods': 'POST, GET, PUT, DELETE, OPTIONS', - 'Access-Control-Allow-Origin': '*', - 'Cache-Control': 'public, max-age=259200, s-max-age=86400', - 'Content-Type': '${file.mime}; charset=utf-8', - 'Transfer-Encoding': 'chunked', - 'Transloadit-Assembly': '…', - 'Transloadit-RequestID': '…', - }) - .describe(` -An object containing a list of headers to be set for a file as we serve it to a CDN/web browser, such as \`{ FileURL: "\${file.url_name}" }\` which will be merged over the defaults, and can include any available [Assembly Variable](/docs/topics/assembly-instructions/#assembly-variables). -`), - }) - .strict() - -export const robotFileServeInstructionsWithHiddenFieldsSchema = - robotFileServeInstructionsSchema.extend({ - result: z.union([z.literal('debug'), robotFileServeInstructionsSchema.shape.result]).optional(), - }) - -export type RobotFileServeInstructions = z.infer -export type RobotFileServeInstructionsInput = z.input -export type RobotFileServeInstructionsWithHiddenFields = z.infer< - typeof robotFileServeInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotFileServeInstructionsSchema = interpolateRobot( - robotFileServeInstructionsSchema, -) -export type InterpolatableRobotFileServeInstructions = InterpolatableRobotFileServeInstructionsInput - -export type InterpolatableRobotFileServeInstructionsInput = z.input< - typeof interpolatableRobotFileServeInstructionsSchema -> - -export const interpolatableRobotFileServeInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotFileServeInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotFileServeInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotFileServeInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotFileServeInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotFileServeInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/file-verify.ts b/packages/transloadit/src/alphalib/types/robots/file-verify.ts deleted file mode 100644 index 15b55908..00000000 --- a/packages/transloadit/src/alphalib/types/robots/file-verify.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 4, - description: - '/file/verify is a simple Robot that helps ensure that the files you upload are of the type you initially intended. This is especially useful when handling user-generated content, where you may not want to run certain Steps in your Template if the user hasn’t uploaded a file of the correct type. Another use case for /file/verify is when a user uploads a ZIP file, but we find that it has a few damaged files inside when we extract it. Perhaps you don’t want to error out, but only send the good files to a next processing step. With /file/verify, you can do exactly that (assuming the default of `error_on_decline`: `true`).', - discount_factor: 0.25, - discount_pct: 75, - example_code: { - steps: { - scanned: { - robot: '/file/verify', - use: ':original', - error_on_decline: true, - error_msg: 'At least one of the uploaded files was not the desired type', - verify_to_be: 'image', - }, - }, - }, - example_code_description: 'Scan the uploaded files and throw an error if they are not images:', - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Filtering', - purpose_sentence: 'verifies your files are the type that you want', - purpose_verb: 'verify', - purpose_word: 'verify the file type', - purpose_words: 'Verify the file type', - service_slug: 'file-filtering', - slot_count: 10, - title: 'Verify the file type', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'FileVerifyRobot', - priceFactor: 4, - queueSlotCount: 10, - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotFileVerifyInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/file/verify'), - error_on_decline: z - .boolean() - .default(false) - .describe(` -If this is set to \`true\` and one or more files are declined, the Assembly will be stopped and marked with an error. -`), - error_msg: z - .string() - .default('One of your files was declined') - .describe(` -The error message shown to your users (such as by Uppy) when a file is declined and \`error_on_decline\` is set to \`true\`. -`), - verify_to_be: z - .string() - .default('pdf') - .describe(` -The type that you want to match against to ensure your file is of this type. For example, \`image\` will verify whether uploaded files are images. This also works against file media types, in this case \`image/png\` would also work to match against specifically \`png\` files. -`), - }) - .strict() - -export const robotFileVerifyInstructionsWithHiddenFieldsSchema = - robotFileVerifyInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotFileVerifyInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotFileVerifyInstructions = z.infer -export type RobotFileVerifyInstructionsWithHiddenFields = z.infer< - typeof robotFileVerifyInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotFileVerifyInstructionsSchema = interpolateRobot( - robotFileVerifyInstructionsSchema, -) -export type InterpolatableRobotFileVerifyInstructions = - InterpolatableRobotFileVerifyInstructionsInput - -export type InterpolatableRobotFileVerifyInstructionsInput = z.input< - typeof interpolatableRobotFileVerifyInstructionsSchema -> - -export const interpolatableRobotFileVerifyInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotFileVerifyInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotFileVerifyInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotFileVerifyInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotFileVerifyInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotFileVerifyInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/file-virusscan.ts b/packages/transloadit/src/alphalib/types/robots/file-virusscan.ts deleted file mode 100644 index 11e7d886..00000000 --- a/packages/transloadit/src/alphalib/types/robots/file-virusscan.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: false, - bytescount: 1, - description: - 'While 100% security is a myth, having /file/virusscan as a gatekeeper bot helps reject millions of trojans, viruses, malware & other malicious threats before they reach your platform.', - discount_factor: 1, - discount_pct: 0, - example_code: { - steps: { - scanned: { - robot: '/file/virusscan', - use: ':original', - error_on_decline: true, - error_msg: 'At least one of the uploaded files is malicious and was declined', - }, - }, - }, - example_code_description: - 'Scan uploaded files and throw an error if a malicious file is detected:', - minimum_charge: 1048576, - ogimage: '/assets/images/robots/ogimages/file-virusscan.jpg', - output_factor: 1, - override_lvl1: 'File Filtering', - purpose_sentence: - 'rejects millions of trojans, viruses, malware & other malicious threats before they reach your platform', - purpose_verb: 'scan', - purpose_word: 'scan for viruses and reject malware', - purpose_words: 'Scan files for viruses', - service_slug: 'file-filtering', - slot_count: 38, - title: 'Scan files for viruses', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'FileVirusscanRobot', - priceFactor: 1, - queueSlotCount: 38, - minimumCharge: 1048576, - lazyLoad: true, - installVersionFile: process.env.API2_CLAMD_INSTALL_VERSION_FILE || '', - isAllowedForUrlTransform: false, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotFileVirusscanInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/file/virusscan').describe(` - This Robot is built on top of [ClamAV](https://www.clamav.net/), the best open source antivirus engine available. We update its signatures on a daily basis. - -By default, this Robot excludes all malicious files from further processing without any additional notification. This behavior can be changed by setting \`error_on_decline\` to \`true\`, which will stop Assemblies as soon as malicious files are found. Such Assemblies will then be marked with an error. - -We allow the use of industry standard [EICAR files](https://www.eicar.org/download-anti-malware-testfile/) for integration testing without needing to use potentially dangerous live virus samples. -`), - error_on_decline: z - .boolean() - .default(false) - .describe(` -If this is set to \`true\` and one or more files are declined, the Assembly will be stopped and marked with an error. -`), - error_msg: z - .string() - .default('One of your files was declined') - .describe(` -The error message shown to your users (such as by Uppy) when a file is declined and \`error_on_decline\` is set to \`true\`. -`), - }) - .strict() - -export const robotFileVirusscanInstructionsWithHiddenFieldsSchema = - robotFileVirusscanInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotFileVirusscanInstructionsSchema.shape.result]) - .optional(), - can_use_daemon_fallback: z - .boolean() - .optional() - .describe(` -Allow the robot to use a daemon fallback mechanism if the primary scanning method fails. -`), - }) - -export type RobotFileVirusscanInstructions = z.infer -export type RobotFileVirusscanInstructionsWithHiddenFields = z.infer< - typeof robotFileVirusscanInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotFileVirusscanInstructionsSchema = interpolateRobot( - robotFileVirusscanInstructionsSchema, -) -export type InterpolatableRobotFileVirusscanInstructions = - InterpolatableRobotFileVirusscanInstructionsInput - -export type InterpolatableRobotFileVirusscanInstructionsInput = z.input< - typeof interpolatableRobotFileVirusscanInstructionsSchema -> - -export const interpolatableRobotFileVirusscanInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotFileVirusscanInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotFileVirusscanInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotFileVirusscanInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotFileVirusscanInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotFileVirusscanInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/file-watermark.ts b/packages/transloadit/src/alphalib/types/robots/file-watermark.ts deleted file mode 100644 index 70e03dd8..00000000 --- a/packages/transloadit/src/alphalib/types/robots/file-watermark.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -// @ts-expect-error - FileWatermarkRobot is not ready yet @TODO please supply missing keys -export const meta: RobotMetaInput = { - name: 'FileWatermarkRobot', - priceFactor: 4, - queueSlotCount: 20, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotFileWatermarkInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/file/watermark'), - randomize: z.boolean().optional(), - }) - .strict() - -export const robotFileWatermarkInstructionsWithHiddenFieldsSchema = - robotFileWatermarkInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotFileWatermarkInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotFileWatermarkInstructions = z.infer -export type RobotFileWatermarkInstructionsWithHiddenFields = z.infer< - typeof robotFileWatermarkInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotFileWatermarkInstructionsSchema = interpolateRobot( - robotFileWatermarkInstructionsSchema, -) -export type InterpolatableRobotFileWatermarkInstructions = - InterpolatableRobotFileWatermarkInstructionsInput - -export type InterpolatableRobotFileWatermarkInstructionsInput = z.input< - typeof interpolatableRobotFileWatermarkInstructionsSchema -> - -export const interpolatableRobotFileWatermarkInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotFileWatermarkInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotFileWatermarkInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotFileWatermarkInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotFileWatermarkInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotFileWatermarkInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/ftp-import.ts b/packages/transloadit/src/alphalib/types/robots/ftp-import.ts deleted file mode 100644 index 5791c338..00000000 --- a/packages/transloadit/src/alphalib/types/robots/ftp-import.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - ftpBase, - interpolateRobot, - path, - robotBase, - robotImport, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 10, - discount_factor: 0.1, - discount_pct: 90, - example_code: { - steps: { - imported: { - robot: '/ftp/import', - credentials: 'YOUR_FTP_CREDENTIALS', - path: 'path/to/files/', - }, - }, - }, - example_code_description: - 'Import files from the `path/to/files` directory and its subdirectories:', - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Importing', - purpose_sentence: - 'imports whole libraries of files from your FTP servers into Transloadit. This Robot relies on password access. For more security, consider our /sftp/import Robot', - purpose_verb: 'import', - purpose_word: 'FTP servers', - purpose_words: 'Import files from FTP servers', - service_slug: 'file-importing', - slot_count: 20, - title: 'Import files from FTP servers', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'FtpImportRobot', - priceFactor: 6.6666, - queueSlotCount: 20, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: true, - stage: 'ga', -} - -export const robotFtpImportInstructionsSchema = robotBase - .merge(robotImport) - .merge(ftpBase) - .extend({ - robot: z.literal('/ftp/import'), - path: path.describe(` -The path on your FTP server where to search for files. Files are imported recursively from all sub-directories and sub-sub-directories (and so on) from this path. -`), - passive_mode: z - .boolean() - .default(true) - .describe(` -Determines if passive mode should be used for the FTP connection. -`), - }) - .strict() - -export const robotFtpImportInstructionsWithHiddenFieldsSchema = - robotFtpImportInstructionsSchema.extend({ - result: z.union([z.literal('debug'), robotFtpImportInstructionsSchema.shape.result]).optional(), - }) - -export type RobotFtpImportInstructions = z.infer -export type RobotFtpImportInstructionsWithHiddenFields = z.infer< - typeof robotFtpImportInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotFtpImportInstructionsSchema = interpolateRobot( - robotFtpImportInstructionsSchema, -) -export type InterpolatableRobotFtpImportInstructions = InterpolatableRobotFtpImportInstructionsInput - -export type InterpolatableRobotFtpImportInstructionsInput = z.input< - typeof interpolatableRobotFtpImportInstructionsSchema -> - -export const interpolatableRobotFtpImportInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotFtpImportInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotFtpImportInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotFtpImportInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotFtpImportInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotFtpImportInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/ftp-store.ts b/packages/transloadit/src/alphalib/types/robots/ftp-store.ts deleted file mode 100644 index 85c33773..00000000 --- a/packages/transloadit/src/alphalib/types/robots/ftp-store.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { ftpBase, interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 6, - discount_factor: 0.15000150001500018, - discount_pct: 84.99984999849998, - example_code: { - steps: { - exported: { - robot: '/ftp/store', - use: ':original', - credentials: 'YOUR_FTP_CREDENTIALS', - path: 'my_target_folder/${unique_prefix}/${file.url_name}', - }, - }, - }, - example_code_description: 'Export uploaded files to `my_target_folder` on an FTP server:', - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Exporting', - purpose_sentence: - 'exports encoding results to your FTP servers. This Robot relies on password access. For more security, consider our /sftp/store Robot', - purpose_verb: 'export', - purpose_word: 'FTP servers', - purpose_words: 'Export files to FTP servers', - service_slug: 'file-exporting', - slot_count: 10, - title: 'Export files to FTP servers', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'FtpStoreRobot', - priceFactor: 6.6666, - queueSlotCount: 10, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotFtpStoreInstructionsSchema = robotBase - .merge(robotUse) - .merge(ftpBase) - .extend({ - robot: z.literal('/ftp/store'), - path: z - .string() - .default('${unique_prefix}/${file.url_name}') - .describe(` -The path at which the file is to be stored. This can contain any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). - -Please note that you might need to include your homedir at the beginning of the path. -`), - url_template: z - .string() - .default('https://{HOST}/{PATH}') - .describe(` -The URL of the file in the result JSON. The following [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables) are supported. -`), - ssl_url_template: z - .string() - .default('https://{HOST}/{PATH}') - .describe(` -The SSL URL of the file in the result JSON. The following [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables) are supported. -`), - secure: z - .boolean() - .default(false) - .describe(` -Determines whether to establish a secure connection to the FTP server using SSL. -`), - }) - .strict() - -export const robotFtpStoreInstructionsWithHiddenFieldsSchema = - robotFtpStoreInstructionsSchema.extend({ - result: z.union([z.literal('debug'), robotFtpStoreInstructionsSchema.shape.result]).optional(), - use_remote_utime: z - .boolean() - .optional() - .describe(` -Use the remote file's modification time instead of the current time when storing the file. -`), - version: z - .union([z.string(), z.number()]) - .optional() - .describe(` -Version identifier for the underlying tool used (2 is ncftp, 1 is ftp). -`), - allowNetwork: z.string().optional(), // For internal test purposes - }) - -export type RobotFtpStoreInstructions = z.infer -export type RobotFtpStoreInstructionsWithHiddenFields = z.infer< - typeof robotFtpStoreInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotFtpStoreInstructionsSchema = interpolateRobot( - robotFtpStoreInstructionsSchema, -) -export type InterpolatableRobotFtpStoreInstructions = InterpolatableRobotFtpStoreInstructionsInput - -export type InterpolatableRobotFtpStoreInstructionsInput = z.input< - typeof interpolatableRobotFtpStoreInstructionsSchema -> - -export const interpolatableRobotFtpStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotFtpStoreInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotFtpStoreInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotFtpStoreInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotFtpStoreInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotFtpStoreInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/google-import.ts b/packages/transloadit/src/alphalib/types/robots/google-import.ts deleted file mode 100644 index b031fbd3..00000000 --- a/packages/transloadit/src/alphalib/types/robots/google-import.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - files_per_page, - googleBase, - interpolateRobot, - next_page_token, - path, - recursive, - robotBase, - robotImport, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 10, - discount_factor: 0.1, - discount_pct: 90, - example_code: { - steps: { - imported: { - robot: '/google/import', - credentials: 'YOUR_GOOGLE_CREDENTIALS', - path: 'path/to/files/', - recursive: true, - }, - }, - }, - example_code_description: - 'Import files from the `path/to/files` directory and its subdirectories:', - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Importing', - purpose_sentence: 'imports whole directories of files from Google Storage', - purpose_verb: 'import', - purpose_word: 'Google Storage', - purpose_words: 'Import files from Google Storage', - service_slug: 'file-importing', - slot_count: 20, - title: 'Import files from Google Storage', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'GoogleImportRobot', - priceFactor: 6.6666, - queueSlotCount: 20, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: true, - stage: 'ga', -} - -export const robotGoogleImportInstructionsSchema = robotBase - .merge(robotImport) - .merge(googleBase) - .extend({ - robot: z.literal('/google/import'), - path: path.describe(` -The path in your bucket to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. - -If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are direct descendants of this directory will be imported. For example: \`images/\`. - -Directories are **not** imported recursively. If you want to import files from subdirectories and sub-subdirectories, enable the \`recursive\` parameter. - -If you want to import all files from the root directory, please use \`/\` as the value here. In this case, make sure all your objects belong to a path. If you have objects in the root of your bucket that aren't prefixed with \`/\`, you'll receive a 404 \`GOOGLE_IMPORT_NOT_FOUND\` error. - -You can also use an array of path strings here to import multiple paths in the same Robot's Step. -`), - recursive: recursive.describe(` -Setting this to \`true\` will enable importing files from subdirectories and sub-subdirectories (etc.) of the given path. - -Please use the pagination parameters \`start_file_name\` and \`files_per_page\` wisely here. -`), - next_page_token: next_page_token.describe(` -A string token used for pagination. The returned files of one paginated call have the next page token inside of their meta data, which needs to be used for the subsequent paging call. -`), - files_per_page: files_per_page.describe(` -The pagination page size. This only works when recursive is \`true\` for now, in order to not break backwards compatibility in non-recursive imports. -`), - }) - .strict() - -export const robotGoogleImportInstructionsWithHiddenFieldsSchema = - robotGoogleImportInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotGoogleImportInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotGoogleImportInstructions = z.infer -export type RobotGoogleImportInstructionsWithHiddenFields = z.infer< - typeof robotGoogleImportInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotGoogleImportInstructionsSchema = interpolateRobot( - robotGoogleImportInstructionsSchema, -) -export type InterpolatableRobotGoogleImportInstructions = - InterpolatableRobotGoogleImportInstructionsInput - -export type InterpolatableRobotGoogleImportInstructionsInput = z.input< - typeof interpolatableRobotGoogleImportInstructionsSchema -> - -export const interpolatableRobotGoogleImportInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotGoogleImportInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotGoogleImportInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotGoogleImportInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotGoogleImportInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotGoogleImportInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/google-store.ts b/packages/transloadit/src/alphalib/types/robots/google-store.ts deleted file mode 100644 index 9101626b..00000000 --- a/packages/transloadit/src/alphalib/types/robots/google-store.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { googleBase, interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 6, - discount_factor: 0.15000150001500018, - discount_pct: 84.99984999849998, - example_code: { - steps: { - exported: { - robot: '/google/store', - use: ':original', - credentials: 'YOUR_GOOGLE_CREDENTIALS', - path: 'my_target_folder/${unique_prefix}/${file.url_name}', - }, - }, - }, - example_code_description: 'Export uploaded files to `my_target_folder` on Google Storage:', - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Exporting', - purpose_sentence: 'exports encoding results to Google Storage', - purpose_verb: 'export', - purpose_word: 'Google Storage', - purpose_words: 'Export files to Google Storage', - service_slug: 'file-exporting', - slot_count: 10, - title: 'Export files to Google Storage', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'GoogleStoreRobot', - priceFactor: 6.6666, - queueSlotCount: 10, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotGoogleStoreInstructionsSchema = robotBase - .merge(robotUse) - .merge(googleBase) - .extend({ - robot: z.literal('/google/store').describe(` -The URL to the exported file in your Google bucket will be presented in the Transloadit Assembly Status JSON. This Robot can also be used to export encoded files to Google's Firebase as demonstrated in [this blogpost](/blog/2018/12/2h-youtube-clone/). -`), - result: z - .boolean() - .optional() - .describe('Whether the results of this Step should be present in the Assembly Status JSON'), - credentials: z.string().describe(` -Create a new [Google service account](https://cloud.google.com/storage/docs/authentication). Set its role to "Storage Object Creator". Choose "JSON" for the key file format and download it to your computer. You will need to upload this file when creating your Template Credentials. - -Go back to your Google credentials project and enable the "Google Cloud Storage JSON API" for it. Wait around ten minutes for the action to propagate through the Google network. Grab the project ID from the dropdown menu in the header bar on the Google site. You will also need it later on. - -Now you can set up the \`storage.objects.create\` and \`storage.objects.delete\` permissions. The latter is optional and only required if you intend to overwrite existing paths. - -To do this from the Google Cloud console, navigate to "IAM & Admin" and select "Roles". From here, click "Create Role", enter a name, set the role launch stage to _General availability,_ and set the permissions stated above. - -Next, go to Storage browser and select the ellipsis on your bucket to edit bucket permissions. From here, select "Add Member", enter your service account as a new member, and select your newly created role. - -Then, create your associated [Template Credentials](/c/template-credentials/) in your Transloadit account and use the name of your Template Credentials as this parameter's value. -`), - path: z - .string() - .default('${unique_prefix}/${file.url_name}') - .describe(` -The path at which the file is to be stored. This may include any available [Assembly Variables](/docs/topics/assembly-instructions/#assembly-variables). -`), - acl: z - .enum([ - 'authenticated-read', - 'bucket-owner-full-control', - 'private', - 'project-private', - 'public-read', - ]) - .nullable() - .default('public-read') - .describe(` -The permissions used for this file. -`), - cache_control: z - .string() - .optional() - .describe(` -The \`Cache-Control\` header determines how long browsers are allowed to cache your object for. Values specified with this parameter will be added to the object's metadata under the \`Cache-Control\` header. For more information on valid values, take a look at the [official Google documentation](https://cloud.google.com/storage/docs/metadata#cache-control). -`), - url_template: z - .string() - .default('https://{HOST}/{PATH}') - .describe(` -The URL of the file in the result JSON. This may include any of the following supported [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). -`), - ssl_url_template: z - .string() - .default('https://{HOST}/{PATH}') - .describe(` -The SSL URL of the file in the result JSON. The following [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables) are supported. -`), - }) - .strict() - -export const robotGoogleStoreInstructionsWithHiddenFieldsSchema = - robotGoogleStoreInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotGoogleStoreInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotGoogleStoreInstructions = z.infer -export type RobotGoogleStoreInstructionsWithHiddenFields = z.infer< - typeof robotGoogleStoreInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotGoogleStoreInstructionsSchema = interpolateRobot( - robotGoogleStoreInstructionsSchema, -) -export type InterpolatableRobotGoogleStoreInstructions = - InterpolatableRobotGoogleStoreInstructionsInput - -export type InterpolatableRobotGoogleStoreInstructionsInput = z.input< - typeof interpolatableRobotGoogleStoreInstructionsSchema -> - -export const interpolatableRobotGoogleStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotGoogleStoreInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotGoogleStoreInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotGoogleStoreInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotGoogleStoreInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotGoogleStoreInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/html-convert.ts b/packages/transloadit/src/alphalib/types/robots/html-convert.ts deleted file mode 100644 index 2960a07f..00000000 --- a/packages/transloadit/src/alphalib/types/robots/html-convert.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - example_code: { - steps: { - captured: { - robot: '/html/convert', - url: 'https://transloadit.com', - }, - }, - }, - example_code_description: 'Take a full screenshot of the Transloadit homepage:', - extended_description: ` -> [!Warning] -> A validation error will occur if neither an HTML file is uploaded nor a URL parameter is given. - -> [!Note] -> Any files imported within the HTML page will be included in the cost. -`, - minimum_charge: 1048576, - output_factor: 0.5, - override_lvl1: 'Document Processing', - purpose_sentence: 'takes screenshots of web pages or uploaded HTML pages', - purpose_verb: 'take', - purpose_word: 'take screenshots of a webpage', - purpose_words: 'Take screenshots of webpages or HTML files', - service_slug: 'document-processing', - slot_count: 10, - title: 'Take screenshots of webpages or uploaded HTML files', - typical_file_size_mb: 0.6, - typical_file_type: 'webpage', - name: 'HtmlConvertRobot', - priceFactor: 1, - queueSlotCount: 30, - minimumCharge: 1048576, - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotHtmlConvertInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/html/convert').describe(` -A URL can be provided instead of an input HTML file, to capture a screenshot from the website referenced by the URL. - -Use [🤖/image/resize](/docs/robots/image-resize/) to resize or crop the screenshot as needed. -`), - url: z - .string() - .nullable() - .default(null) - .describe(` -The URL of the web page to be converted. Optional, as you can also upload/import HTML files and pass it to this Robot. -`), - format: z - .enum(['jpeg', 'jpg', 'pdf', 'png']) - .default('png') - .describe(` -The format of the resulting image. -`), - fullpage: z - .boolean() - .default(true) - .describe(` -Determines if a screenshot of the full page should be taken or not. - -If set to \`true\`, the \`height\` parameter will not have any effect, as heights of websites vary. You can control the size of the resulting image somewhat, though, by setting the \`width\` parameter. - -If set to \`false\`, an image will be cropped from the top of the webpage according to your \`width\` and \`height\` parameters. -`), - omit_background: z - .boolean() - .default(false) - .describe(` -Determines whether to preserve a transparent background in HTML pages. Useful if you're generating artwork in HTML that you want to overlay on e.g. a video. - -The default of \`false\` fills transparent areas with a white background, for easier reading/printing. - -This parameter is only used when \`format\` is not \`pdf\`. -`), - width: z - .number() - .int() - .min(1) - .default(1024) - .describe(` -The screen width that will be used, in pixels. Change this to change the dimensions of the resulting image. -`), - height: z - .number() - .int() - .min(1) - .optional() - .describe(` -The screen height that will be used, in pixels. By default this equals the length of the web page in pixels if \`fullpage\` is set to \`true\`. If \`fullpage\` is set to \`false\`, the height parameter takes effect and defaults to the value \`768\`. -`), - delay: z - .number() - .int() - .min(0) - .default(0) - .describe(` -The delay (in milliseconds) applied to allow the page and all of its JavaScript to render before taking the screenshot. -`), - headers: z - .record(z.string()) - .optional() - .describe(` -An object containing optional headers that will be passed along with the original request to the website. For example, this parameter can be used to pass along an authorization token along with the request. -`), - wait_until: z - .enum(['domcontentloaded', 'load', 'networkidle', 'commit']) - .default('networkidle') - .describe(` -The event to wait for before taking the screenshot. Used for loading Javascript, and images. - -See [Playwright's documentation](https://playwright.dev/docs/api/class-page#page-wait-for-load-state) for more information. -`), - }) - .strict() - -export const robotHtmlConvertInstructionsWithHiddenFieldsSchema = - robotHtmlConvertInstructionsSchema.extend({ - debuginfo: z.boolean().optional(), - timeouts: z.record(z.unknown()).optional(), - actions: z.array(z.record(z.unknown())).optional(), - result: z - .union([z.literal('debug'), robotHtmlConvertInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotHtmlConvertInstructions = z.infer -export type RobotHtmlConvertInstructionsWithHiddenFields = z.infer< - typeof robotHtmlConvertInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotHtmlConvertInstructionsSchema = interpolateRobot( - robotHtmlConvertInstructionsSchema, -) -export type InterpolatableRobotHtmlConvertInstructions = - InterpolatableRobotHtmlConvertInstructionsInput - -export type InterpolatableRobotHtmlConvertInstructionsInput = z.input< - typeof interpolatableRobotHtmlConvertInstructionsSchema -> - -export const interpolatableRobotHtmlConvertInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotHtmlConvertInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotHtmlConvertInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotHtmlConvertInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotHtmlConvertInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotHtmlConvertInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/http-import.ts b/packages/transloadit/src/alphalib/types/robots/http-import.ts deleted file mode 100644 index 1dec3319..00000000 --- a/packages/transloadit/src/alphalib/types/robots/http-import.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - interpolateRobot, - return_file_stubs, - robotBase, - robotImport, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 10, - discount_factor: 0.1, - discount_pct: 90, - example_code: { - steps: { - imported: { - robot: '/http/import', - url: 'https://demos.transloadit.com/inputs/chameleon.jpg', - }, - }, - }, - example_code_description: 'Import an image from a specific URL:', - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Importing', - purpose_sentence: 'imports any file that is publicly available via a web URL into Transloadit', - purpose_verb: 'import', - purpose_word: 'Webservers', - purpose_words: 'Import files from web servers', - service_slug: 'file-importing', - slot_count: 10, - title: 'Import files from web servers', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'HttpImportRobot', - priceFactor: 10, - queueSlotCount: 10, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: true, - stage: 'ga', -} - -export const robotHttpImportInstructionsSchema = robotBase - .merge(robotImport) - .extend({ - robot: z.literal('/http/import').describe(` -The result of this Robot will carry a field \`import_url\` in their metadata, which references the URL from which they were imported. Further conversion results that use this file will also carry this \`import_url\` field. This allows you to to match conversion results with the original import URL that you used. - -This Robot knows to interpret links to files on these services: - -- Dropbox -- Google Drive -- Google Docs -- OneDrive - -Instead of downloading the HTML page previewing the file, the actual file itself will be imported. -`), - url: z.union([z.string().url(), z.array(z.string().url())]).describe(` -The URL from which the file to be imported can be retrieved. - -You can also specify an array of URLs or a string of \`|\` delimited URLs to import several files at once. Please also check the \`url_delimiter\` parameter for that. -`), - url_delimiter: z - .string() - .default('|') - .describe(` -Provides the delimiter that is used to split the URLs in your \`url\` parameter value. -`), - headers: z - .union([ - z.array(z.string()), - z.array(z.record(z.string())), - z.string(), // For JSON strings like '{"X-Database":"volt"}' - ]) - .default([]) - .describe(` -Custom headers to be sent for file import. - -This is an empty array by default, such that no additional headers except the necessary ones (e.g. Host) are sent. - -Headers can be specified as: -- An array of strings in the format "Header-Name: value" -- An array of objects with header names as keys and values as values -- A JSON string that will be parsed into an object -`), - import_on_errors: z - .array(z.string()) - .default([]) - .describe(` -Setting this to \`"meta"\` will still import the file on metadata extraction errors. \`ignore_errors\` is similar, it also ignores the error and makes sure the Robot doesn't stop, but it doesn't import the file. -`), - fail_fast: z - .boolean() - .default(false) - .describe(` -Disable the internal retry mechanism, and fail immediately if a resource can't be imported. This can be useful for performance critical applications. -`), - return_file_stubs, - range: z - .union([z.string(), z.array(z.string())]) - .optional() - .describe(` -Allows you to specify one or more byte ranges to import from the file. The server must support range requests for this to work. - -**Single range**: Use a string like \`"0-99"\` to import bytes 0-99 (the first 100 bytes). - -**Multiple ranges**: Use an array like \`["0-99", "200-299"]\` to import multiple separate ranges. The resulting file will contain all requested ranges concatenated together, with zero bytes (\\0) filling any gaps between non-contiguous ranges. - -**Range formats**: -- \`"0-99"\`: Bytes 0 through 99 (inclusive) -- \`"100-199"\`: Bytes 100 through 199 (inclusive) -- \`"-100"\`: The last 100 bytes of the file - -**Important notes**: -- The server must support HTTP range requests (respond with 206 Partial Content) -- If the server doesn't support range requests, the entire file will be imported instead -- Overlapping ranges are allowed and will be included as requested -- The resulting file size will be the highest byte position requested, with gaps filled with zero bytes -`), - }) - .strict() - -export const robotHttpImportInstructionsWithHiddenFieldsSchema = - robotHttpImportInstructionsSchema.extend({ - force_original_id: z.string().optional(), - force_name: z - .union([z.string(), z.record(z.string())]) - .optional() - .describe(` -Force a specific filename for imported files. Can be a string to apply to all imports, or an object mapping URLs to filenames. -`), - result: z - .union([z.literal('debug'), robotHttpImportInstructionsSchema.shape.result]) - .optional(), - credentials: z.string().optional(), // For test purposes - bucket: z.string().optional(), // For test purposes - // Override url to support relative URLs in tests with bucket - url: z.union([z.string(), z.array(z.string())]).optional(), - }) - -export type RobotHttpImportInstructions = z.infer -export type RobotHttpImportInstructionsWithHiddenFields = z.infer< - typeof robotHttpImportInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotHttpImportInstructionsSchema = interpolateRobot( - robotHttpImportInstructionsSchema, -) -export type InterpolatableRobotHttpImportInstructions = - InterpolatableRobotHttpImportInstructionsInput - -export type InterpolatableRobotHttpImportInstructionsInput = z.input< - typeof interpolatableRobotHttpImportInstructionsSchema -> - -export const interpolatableRobotHttpImportInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotHttpImportInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotHttpImportInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotHttpImportInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotHttpImportInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotHttpImportInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/image-bgremove.ts b/packages/transloadit/src/alphalib/types/robots/image-bgremove.ts deleted file mode 100644 index 178a052c..00000000 --- a/packages/transloadit/src/alphalib/types/robots/image-bgremove.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - discount_factor: 1, - bytescount: 1, - discount_pct: 0, - example_code: { - steps: { - remove_background: { - robot: '/image/bgremove', - use: ':original', - }, - }, - }, - example_code_description: 'Remove the background from the uploaded image:', - minimum_charge: 0, - output_factor: 0.6, - override_lvl1: 'Image Manipulation', - purpose_sentence: 'removes the background from images', - purpose_verb: 'remove', - purpose_word: 'remove', - purpose_words: 'Remove the background from images', - service_slug: 'image-manipulation', - slot_count: 10, - title: 'Remove the background from images', - typical_file_size_mb: 0.8, - typical_file_type: 'image', - name: 'ImageBgremoveRobot', - priceFactor: 1, - queueSlotCount: 10, - minimumChargeUsd: 0.006, - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotImageBgremoveInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/image/bgremove'), - select: z - .enum(['foreground', 'background']) - .optional() - .describe('Region to select and keep in the image. The other region is removed.'), - format: z.enum(['png', 'gif', 'webp']).optional().describe('Format of the generated image.'), - provider: z - .enum(['transloadit', 'replicate', 'fal']) - .optional() - .describe('Provider to use for removing the background.'), - model: z - .string() - .optional() - .describe( - 'Provider-specific model to use for removing the background. Mostly intended for testing and evaluation.', - ), - }) - .strict() - -export const robotImageBgremoveInstructionsWithHiddenFieldsSchema = - robotImageBgremoveInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotImageBgremoveInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotImageBgremoveInstructions = z.infer -export type RobotImageBgremoveInstructionsWithHiddenFields = z.infer< - typeof robotImageBgremoveInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotImageBgremoveInstructionsSchema = interpolateRobot( - robotImageBgremoveInstructionsSchema, -) -export type InterpolatableRobotImageBgremoveInstructions = - InterpolatableRobotImageBgremoveInstructionsInput - -export type InterpolatableRobotImageBgremoveInstructionsInput = z.input< - typeof interpolatableRobotImageBgremoveInstructionsSchema -> - -export const interpolatableRobotImageBgremoveInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotImageBgremoveInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotImageBgremoveInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotImageBgremoveInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotImageBgremoveInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotImageBgremoveInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/image-describe.ts b/packages/transloadit/src/alphalib/types/robots/image-describe.ts deleted file mode 100644 index d6a618c8..00000000 --- a/packages/transloadit/src/alphalib/types/robots/image-describe.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - aiProviderSchema, - granularitySchema, - interpolateRobot, - robotBase, - robotUse, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - example_code: { - steps: { - described: { - robot: '/image/describe', - use: ':original', - provider: 'aws', - }, - }, - }, - example_code_description: - 'Recognize objects in an uploaded image and store the labels in a JSON file:', - extended_description: ` -> [!Warning] -> Transloadit aims to be deterministic, but this Robot uses third-party AI services. The providers (AWS, GCP) will evolve their models over time, giving different responses for the same input images. Avoid relying on exact responses in your tests and application. -`, - minimum_charge: 1572864, - output_factor: 0.05, - override_lvl1: 'Artificial Intelligence', - purpose_sentence: 'recognizes objects in images and returns them as English words', - purpose_verb: 'recognize', - purpose_word: 'recognize objects', - purpose_words: 'Recognize objects in images', - service_slug: 'artificial-intelligence', - slot_count: 10, - title: 'Recognize objects in images', - typical_file_size_mb: 0.8, - typical_file_type: 'image', - name: 'ImageDescribeRobot', - priceFactor: 1, - queueSlotCount: 10, - minimumChargeUsd: 0.0013, - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotImageDescribeInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/image/describe').describe(` -You can use the labels that we return in your application to automatically classify images. You can also pass the labels down to other Robots to filter images that contain (or do not contain) certain content. -`), - provider: aiProviderSchema.optional().describe(` -Which AI provider to leverage. - -Transloadit outsources this task and abstracts the interface so you can expect the same data structures, but different latencies and information being returned. Different cloud vendors have different areas they shine in, and we recommend to try out and see what yields the best results for your use case. -`), - granularity: granularitySchema.describe(` -Whether to return a full response (\`"full"\`) including confidence percentages for each found label, or just a flat list of labels (\`"list"\`). -`), - format: z - .enum(['json', 'meta', 'text']) - .default('json') - .describe(` -In what format to return the descriptions. - -- \`"json"\` returns a JSON file. -- \`"meta"\` does not return a file, but stores the data inside Transloadit's file object (under \`\${file.meta.descriptions}\`) that's passed around between encoding Steps, so that you can use the values to burn the data into videos, filter on them, etc. -`), - explicit_descriptions: z - .boolean() - .default(false) - .describe(` -Whether to return only explicit or only non-explicit descriptions of the provided image. Explicit descriptions include labels for NSFW content (nudity, violence, etc). If set to \`false\`, only non-explicit descriptions (such as human or chair) will be returned. If set to \`true\`, only explicit descriptions will be returned. - -The possible descriptions depend on the chosen provider. The list of labels from AWS can be found [in their documentation](https://docs.aws.amazon.com/rekognition/latest/dg/moderation.html#moderation-api). GCP labels the image based on five categories, as described [in their documentation](https://cloud.google.com/vision/docs/detecting-safe-search). - -For an example of how to automatically reject NSFW content and malware, please check out this [blog post](/blog/2022/07/deny-image-uploads/). -`), - }) - .strict() - -export const robotImageDescribeInstructionsWithHiddenFieldsSchema = - robotImageDescribeInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotImageDescribeInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotImageDescribeInstructions = z.infer -export type RobotImageDescribeInstructionsWithHiddenFields = z.infer< - typeof robotImageDescribeInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotImageDescribeInstructionsSchema = interpolateRobot( - robotImageDescribeInstructionsSchema, -) -export type InterpolatableRobotImageDescribeInstructions = - InterpolatableRobotImageDescribeInstructionsInput - -export type InterpolatableRobotImageDescribeInstructionsInput = z.input< - typeof interpolatableRobotImageDescribeInstructionsSchema -> - -export const interpolatableRobotImageDescribeInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotImageDescribeInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotImageDescribeInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotImageDescribeInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotImageDescribeInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotImageDescribeInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/image-facedetect.ts b/packages/transloadit/src/alphalib/types/robots/image-facedetect.ts deleted file mode 100644 index fad9a026..00000000 --- a/packages/transloadit/src/alphalib/types/robots/image-facedetect.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - aiProviderSchema, - interpolateRobot, - robotBase, - robotUse, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - example_code: { - steps: { - faces_detected: { - robot: '/image/facedetect', - use: ':original', - crop: true, - faces: 'each', - crop_padding: '10px', - }, - }, - }, - example_code_description: - 'Detect all faces in uploaded images, crop them, and save as separate images:', - minimum_charge: 5242880, - output_factor: 0.2, - override_lvl1: 'Artificial Intelligence', - purpose_sentence: - 'detects faces in images and can return either their coordinates or the faces themselves as new images', - purpose_verb: 'detect', - purpose_word: 'detect faces', - purpose_words: 'Detect faces in images', - service_slug: 'artificial-intelligence', - slot_count: 20, - title: 'Detect faces in images', - typical_file_size_mb: 0.8, - typical_file_type: 'image', - name: 'ImageFacedetectRobot', - priceFactor: 1, - queueSlotCount: 20, - minimumChargeUsd: 0.0013, - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotImageFacedetectInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/image/facedetect').describe(` -You can specify padding around the extracted faces, tailoring the output for your needs. - -This Robot works well together with [🤖/image/resize](/docs/robots/image-resize/) to bring the full power of resized and optimized images to your website or app. - -
- -**How to improve the accuracy:** - -- Ensure that your pictures have the correct orientation. This Robot achieves the best performance when the faces in the image are oriented upright and not rotated. -- If the Robot detects objects other than a face, you can use \`"faces": "max-confidence"\` within your Template for selecting only the detection with the highest confidence. -- The number of returned detections can also be controlled using the \`min_confidence\` parameter. Increasing its value will yield less results but each with a higher confidence. Decreasing the value, on the other hand, will provide more results but may also include objects other than faces. - -
-`), - provider: aiProviderSchema.optional().describe(` -Which AI provider to leverage. - -Transloadit outsources this task and abstracts the interface so you can expect the same data structures, but different latencies and information being returned. Different cloud vendors have different areas they shine in, and we recommend to try out and see what yields the best results for your use case. -`), - crop: z - .boolean() - .default(false) - .describe(` -Determine if the detected faces should be extracted. If this option is set to \`false\`, then the Robot returns the input image again, but with the coordinates of all detected faces attached to \`file.meta.faces\` in the result JSON. If this parameter is set to \`true\`, the Robot will output all detected faces as images. -`), - crop_padding: z - .string() - .regex(/^\d+(px|%)$/) - .default('5px') - .describe(` -Specifies how much padding is added to the extracted face images if \`crop\` is set to \`true\`. Values can be in \`px\` (pixels) or \`%\` (percentage of the width and height of the particular face image). -`), - format: z - .enum(['jpg', 'png', 'preserve', 'tiff']) - .default('preserve') - .describe(` -Determines the output format of the extracted face images if \`crop\` is set to \`true\`. - -The default value \`"preserve"\` means that the input image format is re-used. -`), - min_confidence: z - .number() - .int() - .min(0) - .max(100) - .default(70) - .describe(` -Specifies the minimum confidence that a detected face must have. Only faces which have a higher confidence value than this threshold will be included in the result. -`), - faces: z - .union([z.enum(['each', 'group', 'max-confidence', 'max-size']), z.number().int()]) - .default('each') - .describe(` -Determines which of the detected faces should be returned. Valid values are: - -- \`"each"\` — each face is returned individually. -- \`"max-confidence"\` — only the face with the highest confidence value is returned. -- \`"max-size"\` — only the face with the largest area is returned. -- \`"group"\` — all detected faces are grouped together into one rectangle that contains all faces. -- any integer — the faces are sorted by their top-left corner and the integer determines the index of the returned face. Be aware the values are zero-indexed, meaning that \`faces: 0\` will return the first face. If no face for a given index exists, no output is produced. - -For the following examples, the input image is: - -![](/assets/images/abbas-malek-hosseini-22NnY93qaOk-unsplash.jpg) - -
- -\`faces: "each"\` applied: - -![](/assets/images/abbas-malek-hosseini-22NnY93qaOk-face-0.jpg) -![](/assets/images/abbas-malek-hosseini-22NnY93qaOk-face-1.jpg) - -
- -\`faces: "max-confidence"\` applied: - -![](/assets/images/abbas-malek-hosseini-22NnY93qaOk-face-1.jpg) - -
- -\`faces: "max-size"\` applied: - -![](/assets/images/abbas-malek-hosseini-22NnY93qaOk-face-1.jpg) - -
- -\`faces: "group"\` applied: - -![](/assets/images/abbas-malek-hosseini-22NnY93qaOk-face-group.jpg) - -
- -\`faces: 0\` applied: - -![](/assets/images/abbas-malek-hosseini-22NnY93qaOk-face-0.jpg) -`), - }) - .strict() - -export const robotImageFacedetectInstructionsWithHiddenFieldsSchema = - robotImageFacedetectInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotImageFacedetectInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotImageFacedetectInstructions = z.infer< - typeof robotImageFacedetectInstructionsSchema -> -export type RobotImageFacedetectInstructionsWithHiddenFields = z.infer< - typeof robotImageFacedetectInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotImageFacedetectInstructionsSchema = interpolateRobot( - robotImageFacedetectInstructionsSchema, -) -export type InterpolatableRobotImageFacedetectInstructions = - InterpolatableRobotImageFacedetectInstructionsInput - -export type InterpolatableRobotImageFacedetectInstructionsInput = z.input< - typeof interpolatableRobotImageFacedetectInstructionsSchema -> - -export const interpolatableRobotImageFacedetectInstructionsWithHiddenFieldsSchema = - interpolateRobot(robotImageFacedetectInstructionsWithHiddenFieldsSchema) -export type InterpolatableRobotImageFacedetectInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotImageFacedetectInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotImageFacedetectInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotImageFacedetectInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/image-generate.ts b/packages/transloadit/src/alphalib/types/robots/image-generate.ts deleted file mode 100644 index 2b120099..00000000 --- a/packages/transloadit/src/alphalib/types/robots/image-generate.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - minimum_charge: 0, - output_factor: 0.6, - purpose_sentence: 'generates images from text prompts using AI', - purpose_verb: 'generate', - purpose_word: 'generate', - purpose_words: 'Generate images from text prompts', - service_slug: 'artificial-intelligence', - slot_count: 10, - title: 'Generate images from text prompts', - typical_file_size_mb: 1.2, - typical_file_type: 'image', - name: 'ImageGenerateRobot', - priceFactor: 1, - queueSlotCount: 10, - minimumChargeUsd: 0.06, - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotImageGenerateInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/image/generate'), - model: z - .string() - .optional() - .describe('The AI model to use for image generation. Defaults to google/nano-banana.'), - prompt: z.string().describe('The prompt describing the desired image content.'), - format: z - .enum(['jpeg', 'jpg', 'png', 'gif', 'webp', 'svg']) - .optional() - .describe('Format of the generated image.'), - seed: z.number().optional().describe('Seed for the random number generator.'), - aspect_ratio: z.string().optional().describe('Aspect ratio of the generated image.'), - height: z.number().optional().describe('Height of the generated image.'), - width: z.number().optional().describe('Width of the generated image.'), - style: z.string().optional().describe('Style of the generated image.'), - num_outputs: z - .number() - .int() - .min(1) - .max(10) - .optional() - .describe('Number of image variants to generate.'), - }) - .strict() - -export const robotImageGenerateInstructionsWithHiddenFieldsSchema = - robotImageGenerateInstructionsSchema.extend({ - provider: z.string().optional().describe('Provider for generating the image.'), - result: z - .union([z.literal('debug'), robotImageGenerateInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotImageGenerateInstructions = z.infer -export type RobotImageGenerateInstructionsWithHiddenFields = z.infer< - typeof robotImageGenerateInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotImageGenerateInstructionsSchema = interpolateRobot( - robotImageGenerateInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotImageGenerateInstructions = - InterpolatableRobotImageGenerateInstructionsInput - -export type InterpolatableRobotImageGenerateInstructionsInput = z.input< - typeof interpolatableRobotImageGenerateInstructionsSchema -> - -export const interpolatableRobotImageGenerateInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotImageGenerateInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotImageGenerateInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotImageGenerateInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotImageGenerateInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotImageGenerateInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/image-merge.ts b/packages/transloadit/src/alphalib/types/robots/image-merge.ts deleted file mode 100644 index 535d05ba..00000000 --- a/packages/transloadit/src/alphalib/types/robots/image-merge.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - color_without_alpha, - imageQualitySchema, - interpolateRobot, - robotBase, - robotUse, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - example_code: { - steps: { - merged: { - robot: '/image/merge', - use: { - steps: [':original'], - bundle_steps: true, - }, - border: 5, - }, - }, - }, - example_code_description: - 'Merge uploaded images into one, with a 5px gap between them on the spritesheet:', - minimum_charge: 0, - output_factor: 0.6, - override_lvl1: 'Image Manipulation', - purpose_sentence: 'merges several images into a single spritesheet', - purpose_verb: 'merge', - purpose_word: 'merge', - purpose_words: 'Merge several images into one image', - service_slug: 'image-manipulation', - slot_count: 10, - title: 'Merge several images into a single image', - typical_file_size_mb: 0.8, - typical_file_type: 'image', - name: 'ImageMergeRobot', - priceFactor: 1, - queueSlotCount: 10, - isAllowedForUrlTransform: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotImageMergeInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/image/merge').describe(` -The final result will be a spritesheet, with the images displayed horizontally or vertically. - -It's recommended to use this Robot with -[🤖/image/resize](/docs/robots/image-resize/) so your images are of a -similar size before merging them. -`), - format: z - .enum(['jpg', 'png']) - .default('png') - .describe('The output format for the modified image.'), - direction: z - .enum(['horizontal', 'vertical']) - .default('horizontal') - .describe('Specifies the direction which the images are displayed.'), - // TODO: default is not between 1 and 10 - border: z - .number() - .int() - .default(0) - .describe(` -An integer value which defines the gap between images on the spritesheet. - -A value of \`10\` would cause the images to have the largest gap between them, while a value of \`1\` would place the images side-by-side. -`), - background: color_without_alpha.default('#FFFFFF').describe(` -Either the hexadecimal code or [name](https://www.imagemagick.org/script/color.php#color_names) of the color used to fill the background (only shown with a border > 1). - -By default, the background of transparent images is changed to white. - -For details about how to preserve transparency across all image types, see [this demo](/demos/image-manipulation/properly-preserve-transparency-across-all-image-types/). -`), - adaptive_filtering: z - .boolean() - .default(false) - .describe(` -Controls the image compression for PNG images. Setting to \`true\` results in smaller file size, while increasing processing time. It is encouraged to keep this option disabled. -`), - quality: imageQualitySchema, - }) - .strict() - -export const robotImageMergeInstructionsWithHiddenFieldsSchema = - robotImageMergeInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotImageMergeInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotImageMergeInstructions = z.infer -export type RobotImageMergeInstructionsWithHiddenFields = z.infer< - typeof robotImageMergeInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotImageMergeInstructionsSchema = interpolateRobot( - robotImageMergeInstructionsSchema, -) -export type InterpolatableRobotImageMergeInstructions = - InterpolatableRobotImageMergeInstructionsInput - -export type InterpolatableRobotImageMergeInstructionsInput = z.input< - typeof interpolatableRobotImageMergeInstructionsSchema -> - -export const interpolatableRobotImageMergeInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotImageMergeInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotImageMergeInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotImageMergeInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotImageMergeInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotImageMergeInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/image-ocr.ts b/packages/transloadit/src/alphalib/types/robots/image-ocr.ts deleted file mode 100644 index e4d4a862..00000000 --- a/packages/transloadit/src/alphalib/types/robots/image-ocr.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - aiProviderSchema, - granularitySchema, - interpolateRobot, - robotBase, - robotUse, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - example_code: { - steps: { - recognized: { - robot: '/image/ocr', - use: ':original', - provider: 'gcp', - format: 'text', - }, - }, - }, - example_code_description: 'Recognize text in an uploaded image and save it to a text file:', - extended_description: ` -> [!Warning] -> Transloadit aims to be deterministic, but this Robot uses third-party AI services. The providers (AWS, GCP) will evolve their models over time, giving different responses for the same input images. Avoid relying on exact responses in your tests and application. -`, - minimum_charge: 1048576, - output_factor: 0.6, - override_lvl1: 'Artificial Intelligence', - purpose_sentence: 'recognizes text in images and returns it in a machine-readable format', - purpose_verb: 'recognize', - purpose_word: 'recognize text', - purpose_words: 'Recognize text in images (OCR)', - service_slug: 'artificial-intelligence', - slot_count: 10, - title: 'Recognize text in images', - typical_file_size_mb: 0.8, - typical_file_type: 'image', - name: 'ImageOcrRobot', - priceFactor: 1, - queueSlotCount: 10, - minimumChargeUsd: 0.0013, - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotImageOcrInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/image/ocr').describe(` -With this Robot you can detect and extract text from images using optical character recognition (OCR). - -For example, you can use the results to obtain the content of traffic signs, name tags, package labels and many more. You can also pass the text down to other Robots to filter images that contain (or do not contain) certain phrases. For images of dense documents, results may vary and be less accurate than for small pieces of text in photos. -`), - provider: aiProviderSchema.describe(` -Which AI provider to leverage. - -Transloadit outsources this task and abstracts the interface so you can expect the same data structures, but different latencies and information being returned. Different cloud vendors have different areas they shine in, and we recommend to try out and see what yields the best results for your use case. - -AWS supports detection for the following languages: English, Arabic, Russian, German, French, Italian, Portuguese and Spanish. GCP allows for a wider range of languages, with varying levels of support which can be found on the [official documentation](https://cloud.google.com/vision/docs/languages/). -`), - granularity: granularitySchema.describe(` -Whether to return a full response including coordinates for the text (\`"full"\`), or a flat list of the extracted phrases (\`"list"\`). This parameter has no effect if the \`format\` parameter is set to \`"text"\`. -`), - format: z - .enum(['json', 'meta', 'text']) - .default('json') - .describe(` -In what format to return the extracted text. -- \`"json"\` returns a JSON file. -- \`"meta"\` does not return a file, but stores the data inside Transloadit's file object (under \`\${file.meta.recognized_text}\`, which is an array of strings) that's passed around between encoding Steps, so that you can use the values to burn the data into videos, filter on them, etc. -- \`"text"\` returns the recognized text as a plain UTF-8 encoded text file. -`), - }) - .strict() - -export const robotImageOcrInstructionsWithHiddenFieldsSchema = - robotImageOcrInstructionsSchema.extend({ - result: z.union([z.literal('debug'), robotImageOcrInstructionsSchema.shape.result]).optional(), - }) - -export type RobotImageOcrInstructions = z.infer -export type RobotImageOcrInstructionsWithHiddenFields = z.infer< - typeof robotImageOcrInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotImageOcrInstructionsSchema = interpolateRobot( - robotImageOcrInstructionsSchema, -) -export type InterpolatableRobotImageOcrInstructions = InterpolatableRobotImageOcrInstructionsInput - -export type InterpolatableRobotImageOcrInstructionsInput = z.input< - typeof interpolatableRobotImageOcrInstructionsSchema -> - -export const interpolatableRobotImageOcrInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotImageOcrInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotImageOcrInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotImageOcrInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotImageOcrInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotImageOcrInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/image-optimize.ts b/packages/transloadit/src/alphalib/types/robots/image-optimize.ts deleted file mode 100644 index f4d027c5..00000000 --- a/packages/transloadit/src/alphalib/types/robots/image-optimize.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - interpolateRobot, - optimize_priority, - robotBase, - robotUse, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - example_code: { - steps: { - optimized: { - robot: '/image/optimize', - use: ':original', - }, - }, - }, - example_code_description: 'Optimize uploaded images:', - minimum_charge: 0, - output_factor: 0.6, - override_lvl1: 'Image Manipulation', - purpose_sentence: 'reduces the size of images while maintaining the same visual quality', - purpose_verb: 'optimize', - purpose_word: 'optimize', - purpose_words: 'Optimize images without quality loss', - service_slug: 'image-manipulation', - slot_count: 15, - title: 'Optimize images without quality loss', - typical_file_size_mb: 0.8, - typical_file_type: 'image', - name: 'ImageOptimizeRobot', - priceFactor: 1, - queueSlotCount: 15, - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotImageOptimizeInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/image/optimize').describe(` -With this Robot it's possible to reduce the file size of your JPEG, PNG, GIF, WEBP and SVG images by up to 80% for big images and 65% for small to medium sized ones — while keeping their original quality! - -This Robot enables you to lower your storage and bandwidth costs, and improves your user experience and monetization by reducing the load time of image-intensive web pages. - -It works well together with [🤖/image/resize](/docs/robots/image-resize/) to bring the full power of resized and optimized images to your website or app. - -> [!Note] -> This Robot accepts all image types and will just pass on unsupported image types unoptimized. Hence, there is no need to set up [🤖/file/filter](/docs/robots/file-filter/) workflows for this. -`), - priority: optimize_priority.describe(` -Provides different algorithms for better or worse compression for your images, but that run slower or faster. The value \`"conversion-speed"\` will result in an average compression ratio of 18%. \`"compression-ratio"\` will result in an average compression ratio of 31%. -`), - progressive: z - .boolean() - .default(false) - .describe(` -Interlaces the image if set to \`true\`, which makes the result image load progressively in browsers. Instead of rendering the image from top to bottom, the browser will first show a low-res blurry version of the image which is then quickly replaced with the actual image as the data arrives. This greatly increases the user experience, but comes at a loss of about 10% of the file size reduction. -`), - preserve_meta_data: z - .boolean() - .default(true) - .describe(` -Specifies if the image's metadata should be preserved during the optimization, or not. If it is not preserved, the file size is even further reduced. But be aware that this could strip a photographer's copyright information, which for obvious reasons can be frowned upon. -`), - fix_breaking_images: z - .boolean() - .default(true) - .describe(` -If set to \`true\` this parameter tries to fix images that would otherwise make the underlying tool error out and thereby break your Assemblies. This can sometimes result in a larger file size, though. -`), - }) - .strict() - -export const robotImageOptimizeInstructionsWithHiddenFieldsSchema = - robotImageOptimizeInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotImageOptimizeInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotImageOptimizeInstructions = z.infer -export type RobotImageOptimizeInstructionsWithHiddenFields = z.infer< - typeof robotImageOptimizeInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotImageOptimizeInstructionsSchema = interpolateRobot( - robotImageOptimizeInstructionsSchema, -) -export type InterpolatableRobotImageOptimizeInstructions = - InterpolatableRobotImageOptimizeInstructionsInput - -export type InterpolatableRobotImageOptimizeInstructionsInput = z.input< - typeof interpolatableRobotImageOptimizeInstructionsSchema -> - -export const interpolatableRobotImageOptimizeInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotImageOptimizeInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotImageOptimizeInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotImageOptimizeInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotImageOptimizeInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotImageOptimizeInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/image-resize.ts b/packages/transloadit/src/alphalib/types/robots/image-resize.ts deleted file mode 100644 index ff632d2f..00000000 --- a/packages/transloadit/src/alphalib/types/robots/image-resize.ts +++ /dev/null @@ -1,653 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - color_without_alpha_with_named, - colorspaceSchema, - complexHeightSchema, - complexWidthSchema, - imageQualitySchema, - interpolateRobot, - percentageSchema, - positionSchema, - robotBase, - robotImagemagick, - robotUse, - unsafeCoordinatesSchema, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - example_code: { - steps: { - resized: { - robot: '/image/resize', - use: ':original', - width: 200, - }, - }, - }, - example_code_description: - 'Resize uploaded images to a width of 200px while keeping their original aspect ratio:', - minimum_charge: 0, - output_factor: 0.6, - override_lvl1: 'Image Manipulation', - purpose_sentence: - 'resizes, crops, changes colorization, rotation, and applies text and watermarks to images', - purpose_verb: 'convert', - purpose_word: 'convert/resize/watermark', - purpose_words: 'Convert, resize, or watermark images', - service_slug: 'image-manipulation', - slot_count: 5, - title: 'Convert, resize, or watermark images', - typical_file_size_mb: 0.8, - typical_file_type: 'image', - uses_tools: ['imagemagick'], - name: 'ImageResizeRobot', - priceFactor: 1, - queueSlotCount: 5, - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const oneTextSchema = z.object({ - // TODO: Determine valid fonts - text: z.string(), - font: z - .string() - .default('Arial') - .describe(` -The font family to use. Also includes boldness and style of the font. - -[Here](/docs/supported-formats/fonts/) is a list of all -supported fonts. -`), - size: z - .number() - .int() - .min(1) - .default(12) - .describe(` -The text size in pixels. -`), - rotate: z - .number() - .int() - .default(0) - .describe(` -The rotation angle in degrees. -`), - color: color_without_alpha_with_named.default('#000000').describe(` -The text color. All hex colors in the form \`"#xxxxxx"\` are supported, where each x can be \`0-9\` or \`a-f\`. Named colors like \`"black"\`, \`"white"\`, \`"transparent"\` etc. are also supported. If you want a transparent text color, use "stroke" instead, otherwise your text will not be visible. -`), - background_color: color_without_alpha_with_named.default('transparent').describe(` -The background color behind the text. All hex colors in the form \`"#xxxxxx"\` are supported, where each x can be \`0-9\` or \`a-f\`. Named colors like \`"black"\`, \`"white"\`, \`"transparent"\` etc. are also supported. -`), - stroke_width: z - .number() - .int() - .min(0) - .default(0) - .describe(` -The stroke's width in pixels. -`), - stroke_color: color_without_alpha_with_named.default('transparent').describe(` -The stroke's color. All hex colors in the form \`"#xxxxxx"\` are supported, where each x can be \`0-9\` or \`a-f\`. Named colors like \`"black"\`, \`"white"\`, \`"transparent"\` etc. are also supported. -`), - align: z - .enum(['center', 'left', 'right']) - .default('center') - .describe(` -The horizontal text alignment. Can be \`"left"\`, \`"center"\` and \`"right"\`. -`), - valign: z - .enum(['bottom', 'center', 'top']) - .default('center') - .describe(` -The vertical text alignment. Can be \`"top"\`, \`"center"\` and \`"bottom"\`. -`), - x_offset: z - .number() - .int() - .default(0) - .describe(` -The horizontal offset for the text in pixels that is added (positive integer) or removed (negative integer) from the horizontal alignment. -`), - y_offset: z - .number() - .int() - .default(0) - .describe(` -The vertical offset for the text in pixels that is added (positive integer) or removed (negative integer) from the vertical alignment. -`), -}) - -const TEXT_DESCRIPTION = ` -Text overlays to be applied to the image. Can be either a single text object or an array of text objects. Each text object contains text rules. The following text parameters are intended to be used as properties for your text overlays. Here is an example: - -\`\`\`json -"watermarked": { - "use": "resized", - "robot": "/image/resize", - "text": [ - { - "text": "© 2018 Transloadit.com", - "size": 12, - "font": "Ubuntu", - "color": "#eeeeee", - "valign": "bottom", - "align": "right", - "x_offset": 16, - "y_offset": -10 - } - ] -} -\`\`\`` - -export const robotImageResizeInstructionsSchema = robotBase - .merge(robotUse) - .merge(robotImagemagick) - .extend({ - robot: z.literal('/image/resize'), - // TODO: Use an enum - format: z - .string() - .nullable() - .default(null) - .describe(` -The output format for the modified image. - -Some of the most important available formats are \`"jpg"\`, \`"png"\`, \`"gif"\`, and \`"tiff"\`. For a complete lists of all formats that we can write to please check [our supported image formats list](/docs/supported-formats/image-formats/). - -If \`null\` (default), then the input image's format will be used as the output format. - -If you wish to convert to \`"pdf"\`, please consider [🤖/document/convert](/docs/robots/document-convert/) instead. -`), - width: complexWidthSchema.optional().describe(` -Width of the result in pixels. If not specified, will default to the width of the original. -`), - height: complexHeightSchema.optional().describe(` -Height of the new image, in pixels. If not specified, will default to the height of the input image. -`), - resize_strategy: z - .union([ - z - .literal('crop') - .describe(`Cuts an area out of an image, discarding any overlapping parts. If the source image is smaller than the crop frame, it will be zoomed. This strategy is implied when you specify coordinates in the \`crop\` parameter, and cannot be used without it. - -To crop around human faces, see [🤖/image/facedetect](https://transloadit.com/docs/robots/image-facedetect/) instead.`), - z - .literal('fillcrop') - .describe(`Scales the image to fit into our 100×100 target while preserving aspect ratio, while trimming away any excess surface. This means both sides will become exactly 100 pixels, at the tradeoff of destroying parts of the image. - -By default the resulting image is horizontally/vertically centered to fill the target rectangle. Use the \`gravity\` parameter to change where to crop the image, such as \`"bottom\`" or \`"left\`".`), - z - .literal('fit') - .describe(`Uses the larger side of the original image as a base for the resize. Aspect ratio is preserved. Either side will become at most 100 pixels. - -For example: resizing a 400×300 image into 100×100, would produce a 100×75 image.`), - z - .literal('min_fit') - .describe(`Uses the **smaller** side of the original image as a base for the resize. After resizing, the larger side will have a larger value than specified. Aspect ratio is preserved. Either side will become at least 100 pixels. - -For example: resizing a 400×300 image into 100×100, would produce a 133×100 image.`), - z - .literal('pad') - .describe(`Scales the image to fit while preserving aspect ratio. Both sides of the resized image become exactly 100 pixels, and any remaining surface is filled with a background color. - -In this example, the background color is determined by the [Assembly Variable](https://transloadit.com/docs/topics/assembly-instructions/#assembly-variables) \`\${file.meta.average_color}\`. If you set \`zoom\` to \`false\` (default is \`true\`), smaller images will be centered horizontally and vertically, and have the background padding all around them.`), - z - .literal('stretch') - .describe( - 'Ignores aspect ratio, resizing the image to the exact width and height specified. This may result in a stretched or distorted image.', - ), - ]) - .default('fit') - .describe(` -See the list of available [resize strategies](/docs/topics/resize-strategies/). -`), - zoom: z - .boolean() - .default(true) - .describe(` -If this is set to \`false\`, smaller images will not be stretched to the desired width and height. For details about the impact of zooming for your preferred resize strategy, see the list of available [resize strategies](/docs/topics/resize-strategies/). -`), - crop: unsafeCoordinatesSchema.optional().describe(` -Specify an object containing coordinates for the top left and bottom right corners of the rectangle to be cropped from the original image(s). The coordinate system is rooted in the top left corner of the image. Values can be integers for absolute pixel values or strings for percentage based values. - -For example: - -\`\`\`json -{ - "x1": 80, - "y1": 100, - "x2": "60%", - "y2": "80%" -} -\`\`\` - -This will crop the area from \`(80, 100)\` to \`(600, 800)\` from a 1000×1000 pixels image, which is a square whose width is 520px and height is 700px. If \`crop\` is set, the width and height parameters are ignored, and the \`resize_strategy\` is set to \`crop\` automatically. - -You can also use a JSON string of such an object with coordinates in similar fashion: - -\`\`\`json -"{\\"x1\\": , \\"y1\\": , \\"x2\\": , \\"y2\\": }" -\`\`\` - -To crop around human faces, see [🤖/image/facedetect](/docs/robots/image-facedetect/). -`), - gravity: positionSchema.default('center').describe(` -The direction from which the image is to be cropped, when \`"resize_strategy"\` is set to \`"crop"\`, but no crop coordinates are defined. -`), - strip: z - .boolean() - .default(false) - .describe(` -Strips all metadata from the image. This is useful to keep thumbnails as small as possible. -`), - alpha: z - .enum([ - 'Activate', - 'Background', - 'Copy', - 'Deactivate', - 'Extract', - 'Off', - 'On', - 'Opaque', - 'Remove', - 'Set', - 'Shape', - 'Transparent', - ]) - .optional() - .describe(` -Gives control of the alpha/matte channel of an image. -`), - preclip_alpha: z - .enum([ - 'Activate', - 'Background', - 'Copy', - 'Deactivate', - 'Extract', - 'Off', - 'On', - 'Opaque', - 'Remove', - 'Set', - 'Shape', - 'Transparent', - ]) - .optional() - .describe(` -Gives control of the alpha/matte channel of an image before applying the clipping path via \`clip: true\`. -`), - flatten: z - .boolean() - .default(true) - .describe(` -Flattens all layers onto the specified background to achieve better results from transparent formats to non-transparent formats, as explained in the [ImageMagick documentation](https://www.imagemagick.org/script/command-line-options.php#layers). - -To preserve animations, GIF files are not flattened when this is set to \`true\`. To flatten GIF animations, use the \`frame\` parameter. -`), - correct_gamma: z - .boolean() - .default(false) - .describe(` -Prevents gamma errors [common in many image scaling algorithms](https://www.4p8.com/eric.brasseur/gamma.html). -`), - quality: imageQualitySchema, - adaptive_filtering: z - .boolean() - .default(false) - .describe(` -Controls the image compression for PNG images. Setting to \`true\` results in smaller file size, while increasing processing time. It is encouraged to keep this option disabled. -`), - background: color_without_alpha_with_named.default('#FFFFFF').describe(` -Either the hexadecimal code or [name](https://www.imagemagick.org/script/color.php#color_names) of the color used to fill the background (used for the \`pad\` resize strategy). - -**Note:** By default, the background of transparent images is changed to white. To preserve transparency, set \`"background"\` to \`"none"\`. -`), - frame: z - .number() - .int() - .min(1) - .nullable() - .default(null) - .describe(` -Use this parameter when dealing with animated GIF files to specify which frame of the GIF is used for the operation. Specify \`1\` to use the first frame, \`2\` to use the second, and so on. \`null\` means all frames. -`), - colorspace: colorspaceSchema.optional().describe(` -Sets the image colorspace. For details about the available values, see the [ImageMagick documentation](https://www.imagemagick.org/script/command-line-options.php#colorspace). Please note that if you were using \`"RGB"\`, we recommend using \`"sRGB"\` instead as of 2014-02-04. ImageMagick might try to find the most efficient \`colorspace\` based on the color of an image, and default to e.g. \`"Gray"\`. To force colors, you might have to use this parameter in combination with \`type: "TrueColor"\`. -`), - type: z - .enum([ - 'Bilevel', - 'ColorSeparation', - 'ColorSeparationAlpha', - 'Grayscale', - 'GrayscaleAlpha', - 'Palette', - 'PaletteAlpha', - 'TrueColor', - 'TrueColorAlpha', - ]) - .optional() - .describe(` -Sets the image color type. For details about the available values, see the [ImageMagick documentation](https://www.imagemagick.org/script/command-line-options.php#type). If you're using \`colorspace\`, ImageMagick might try to find the most efficient based on the color of an image, and default to e.g. \`"Gray"\`. To force colors, you could e.g. set this parameter to \`"TrueColor"\` -`), - sepia: z - .number() - .int() - .min(0) - .max(99) - .nullable() - .default(null) - .describe(` -Applies a sepia tone effect in percent. -`), - rotation: z - .union([ - z.number(), // Support any numeric rotation value (including precise angles like 2.9) - z.boolean(), - z.literal('auto'), // Support 'auto' string value - ]) - .default(true) - .describe(` -Determines whether the image should be rotated. Use any number to specify the rotation angle in degrees (e.g., \`90\`, \`180\`, \`270\`, \`360\`, or precise values like \`2.9\`). Use the value \`true\` or \`"auto"\` to auto-rotate images that are rotated incorrectly or depend on EXIF rotation settings. Otherwise, use \`false\` to disable auto-fixing altogether. -`), - compress: z - .enum(['BZip', 'Fax', 'Group4', 'JPEG', 'JPEG2000', 'Lossless', 'LZW', 'None', 'RLE', 'Zip']) - .nullable() - .default(null) - .describe(` -Specifies pixel compression for when the image is written. Compression is disabled by default. - -Please also take a look at [🤖/image/optimize](/docs/robots/image-optimize/). -`), - blur: z - .string() - .regex(/^\d+(\.\d+)?x\d+(\.\d+)?$/) - .nullable() - .default(null) - .describe(` -Specifies gaussian blur, using a value with the form \`{radius}x{sigma}\`. The radius value specifies the size of area the operator should look at when spreading pixels, and should typically be either \`"0"\` or at least two times the sigma value. The sigma value is an approximation of how many pixels the image is "spread"; think of it as the size of the brush used to blur the image. This number is a floating point value, enabling small values like \`"0.5"\` to be used. -`), - blur_regions: z - .array( - z.object({ - // TODO: These types are not documented. - x: complexWidthSchema, - y: complexHeightSchema, - width: complexWidthSchema, - height: complexHeightSchema, - }), - ) - .nullable() - .default(null) - .describe(` -Specifies an array of ellipse objects that should be blurred on the image. Each object has the following keys: \`x\`, \`y\`, \`width\`, \`height\`. If \`blur_regions\` has a value, then the \`blur\` parameter is used as the strength of the blur for each region. -`), - // TODO: An int according to the docs, a float in the example - brightness: z - .number() - .min(0) - .default(1) - .describe(` -Increases or decreases the brightness of the image by using a multiplier. For example \`1.5\` would increase the brightness by 50%, and \`0.75\` would decrease the brightness by 25%. -`), - // TODO: An int according to the docs, a float in the example - saturation: z - .number() - .min(0) - .default(1) - .describe(` -Increases or decreases the saturation of the image by using a multiplier. For example \`1.5\` would increase the saturation by 50%, and \`0.75\` would decrease the saturation by 25%. -`), - hue: z - .number() - .min(0) - .default(100) - .describe(` -Changes the hue by rotating the color of the image. The value \`100\` would produce no change whereas \`0\` and \`200\` will negate the colors in the image. -`), - contrast: z - .number() - .min(0) - .max(2) - .default(1) - .describe(` -Adjusts the contrast of the image. A value of \`1\` produces no change. Values below \`1\` decrease contrast (with \`0\` being minimum contrast), and values above \`1\` increase contrast (with \`2\` being maximum contrast). This works like the \`brightness\` parameter. -`), - watermark_url: z - .string() - .optional() - .describe(` -A URL indicating a PNG image to be overlaid above this image. Please note that you can also [supply the watermark via another Assembly Step](/docs/topics/use-parameter/#supplying-the-watermark-via-an-assembly-step). With watermarking you can add an image onto another image. This is usually used for logos. -`), - watermark_position: z - .union([positionSchema, z.array(positionSchema)]) - .default('center') - .describe(` -The position at which the watermark is placed. The available options are \`"center"\`, \`"top"\`, \`"bottom"\`, \`"left"\`, and \`"right"\`. You can also combine options, such as \`"bottom-right"\`. - -An array of possible values can also be specified, in which case one value will be selected at random, such as \`[ "center", "left", "bottom-left", "bottom-right" ]\`. - -This setting puts the watermark in the specified corner. To use a specific pixel offset for the watermark, you will need to add the padding to the image itself. -`), - watermark_x_offset: z - .number() - .int() - .default(0) - .describe(` -The x-offset in number of pixels at which the watermark will be placed in relation to the position it has due to \`watermark_position\`. - -Values can be both positive and negative and yield different results depending on the \`watermark_position\` parameter. Positive values move the watermark closer to the image's center point, whereas negative values move the watermark further away from the image's center point. -`), - watermark_y_offset: z - .number() - .int() - .default(0) - .describe(` -The y-offset in number of pixels at which the watermark will be placed in relation to the position it has due to \`watermark_position\`. - -Values can be both positive and negative and yield different results depending on the \`watermark_position\` parameter. Positive values move the watermark closer to the image's center point, whereas negative values move the watermark further away from the image's center point. -`), - watermark_size: percentageSchema.optional().describe(` -The size of the watermark, as a percentage. - -For example, a value of \`"50%"\` means that size of the watermark will be 50% of the size of image on which it is placed. The exact sizing depends on \`watermark_resize_strategy\`, too. -`), - watermark_resize_strategy: z - .enum(['area', 'fit', 'min_fit', 'stretch']) - .default('fit') - .describe(` -Available values are \`"fit"\`, \`"min_fit"\`, \`"stretch"\` and \`"area"\`. - -To explain how the resize strategies work, let's assume our target image size is 800×800 pixels and our watermark image is 400×300 pixels. Let's also assume, the \`watermark_size\` parameter is set to \`"25%"\`. - -For the \`"fit"\` resize strategy, the watermark is scaled so that the longer side of the watermark takes up 25% of the corresponding image side. And the other side is scaled according to the aspect ratio of the watermark image. So with our watermark, the width is the longer side, and 25% of the image size would be 200px. Hence, the watermark would be resized to 200×150 pixels. If the \`watermark_size\` was set to \`"50%"\`, it would be resized to 400×300 pixels (so just left at its original size). - -For the \`"min_fit"\` resize strategy, the watermark is scaled so that the shorter side of the watermark takes up 25% of the corresponding image side. And the other side is scaled according to the aspect ratio of the watermark image. So with our watermark, the height is the shorter side, and 25% of the image size would be 200px. Hence, the watermark would be resized to 267×200 pixels. If the \`watermark_size\` was set to \`"50%"\`, it would be resized to 533×400 pixels (so larger than its original size). - -For the \`"stretch"\` resize strategy, the watermark is stretched (meaning, it is resized without keeping its aspect ratio in mind) so that both sides take up 25% of the corresponding image side. Since our image is 800×800 pixels, for a watermark size of 25% the watermark would be resized to 200×200 pixels. Its height would appear stretched, because keeping the aspect ratio in mind it would be resized to 200×150 pixels instead. - -For the \`"area"\` resize strategy, the watermark is resized (keeping its aspect ratio in check) so that it covers \`"xx%"\` of the image's surface area. The value from \`watermark_size\` is used for the percentage area size. -`), - watermark_opacity: z - .number() - .min(0) - .max(1) - .default(1.0) - .describe(` -The opacity of the watermark, where \`0.0\` is fully transparent and \`1.0\` is fully opaque. - -For example, a value of \`0.5\` means the watermark will be 50% transparent, allowing the underlying image to show through. This is useful for subtle branding or when you want the watermark to be less obtrusive. -`), - watermark_repeat_x: z - .boolean() - .default(false) - .describe(` -When set to \`true\`, the watermark will be repeated horizontally across the entire width of the image. - -This is useful for creating tiled watermark patterns that cover the full image and make it more difficult to crop out the watermark. -`), - watermark_repeat_y: z - .boolean() - .default(false) - .describe(` -When set to \`true\`, the watermark will be repeated vertically across the entire height of the image. - -This is useful for creating tiled watermark patterns that cover the full image. Can be combined with \`watermark_repeat_x\` to tile in both directions. -`), - text: z - .union([ - // Support single text object (backward compatibility) - oneTextSchema, - // Support array of text objects (current schema) - z.array(oneTextSchema), - ]) - .optional() - .describe(TEXT_DESCRIPTION), - progressive: z - .boolean() - .default(false) - .describe(` -Interlaces the image if set to \`true\`, which makes the image load progressively in browsers. Instead of rendering the image from top to bottom, the browser will first show a low-res blurry version of the images which is then quickly replaced with the actual image as the data arrives. This greatly increases the user experience, but comes at a cost of a file size increase by around 10%. -`), - transparent: z - .union([color_without_alpha_with_named, z.string().regex(/^\d+,\d+,\d+$/)]) - .optional() - .describe(` -Make this color transparent within the image. Example: \`"255,255,255"\`. -`), - trim_whitespace: z - .boolean() - .default(false) - .describe(` -This determines if additional whitespace around the image should first be trimmed away. If you set this to \`true\` this parameter removes any edges that are exactly the same color as the corner pixels. -`), - clip: z - .union([z.string(), z.boolean()]) - .default(false) - .describe(` -Apply the clipping path to other operations in the resize job, if one is present. If set to \`true\`, it will automatically take the first clipping path. If set to a String it finds a clipping path by that name. -`), - negate: z - .boolean() - .default(false) - .describe(` -Replace each pixel with its complementary color, effectively negating the image. Especially useful when testing clipping. -`), - density: z - .string() - .regex(/\d+(x\d+)?/) - .nullable() - .default(null) - .describe(` -While in-memory quality and file format depth specifies the color resolution, the density of an image is the spatial (space) resolution of the image. That is the density (in pixels per inch) of an image and defines how far apart (or how big) the individual pixels are. It defines the size of the image in real world terms when displayed on devices or printed. - -You can set this value to a specific \`width\` or in the format \`width\`x\`height\`. - -If your converted image is unsharp, please try increasing density. -`), - monochrome: z - .boolean() - .default(false) - .describe(` -Transform the image to black and white. This is a shortcut for setting the colorspace to Gray and type to Bilevel. -`), - shave: z - .union([ - z.string().regex(/^\d+(x\d+)?$/), - z - .number() - .int() - .min(0) - .transform(String), // Accept numbers and convert to string - ]) - .optional() - .describe(` -Shave pixels from the image edges. The value should be in the format \`width\` or \`width\`x\`height\` to specify the number of pixels to remove from each side. -`), - }) - .strict() - -export const robotImageResizeInstructionsWithHiddenFieldsSchema = - robotImageResizeInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotImageResizeInstructionsSchema.shape.result]) - .optional(), - stack: z.string().optional().describe('Legacy parameter, use imagemagick_stack instead'), - text: z - .union([ - // Support single text object (backward compatibility) - oneTextSchema.extend({ - gravity: positionSchema - .default('top-left') - .optional() - .describe(` - Legacy. The direction from which to start the offsets. - `), - }), - // Support array of text objects (current schema) - z.array( - oneTextSchema.extend({ - gravity: positionSchema - .default('top-left') - .optional() - .describe(` - Legacy. The direction from which to start the offsets. - `), - }), - ), - ]) - .optional() - .describe(TEXT_DESCRIPTION), - watermark_position_x: z - .number() - .int() - .optional() - .describe(` - Legacy alias for \`watermark_x_offset\`. The x-offset in number of pixels at which the watermark will be placed. - `), - watermark_position_y: z - .number() - .int() - .optional() - .describe(` - Legacy alias for \`watermark_y_offset\`. The y-offset in number of pixels at which the watermark will be placed. - `), - }) - -export type RobotImageResizeInstructions = z.infer -export type RobotImageResizeInstructionsWithHiddenFields = z.infer< - typeof robotImageResizeInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotImageResizeInstructionsSchema = interpolateRobot( - robotImageResizeInstructionsSchema, -) -export type InterpolatableRobotImageResizeInstructions = - InterpolatableRobotImageResizeInstructionsInput - -export type InterpolatableRobotImageResizeInstructionsInput = z.input< - typeof interpolatableRobotImageResizeInstructionsSchema -> - -export const interpolatableRobotImageResizeInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotImageResizeInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotImageResizeInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotImageResizeInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotImageResizeInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotImageResizeInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/meta-read.ts b/packages/transloadit/src/alphalib/types/robots/meta-read.ts deleted file mode 100644 index 544c578a..00000000 --- a/packages/transloadit/src/alphalib/types/robots/meta-read.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { z } from 'zod' -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase } from './_instructions-primitives.ts' - -// @ts-expect-error - MetaReadRobot is not ready yet @TODO please supply missing keys -export const meta: RobotMetaInput = { - name: 'MetaReadRobot', - priceFactor: 0, - queueSlotCount: 15, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: true, - stage: 'ga', - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, -} - -export const robotMetaReadInstructionsSchema = robotBase - .extend({ - robot: z.literal('/meta/read').describe('Reads metadata from a file.'), - }) - .strict() - -export type RobotMetaReadInstructions = z.infer - -export const robotMetaReadInstructionsWithHiddenFieldsSchema = - robotMetaReadInstructionsSchema.extend({ - result: z.union([z.literal('debug'), robotMetaReadInstructionsSchema.shape.result]).optional(), - }) - -export const interpolatableRobotMetaReadInstructionsSchema = interpolateRobot( - robotMetaReadInstructionsSchema, -) -export type InterpolatableRobotMetaReadInstructions = InterpolatableRobotMetaReadInstructionsInput - -export type InterpolatableRobotMetaReadInstructionsInput = z.input< - typeof interpolatableRobotMetaReadInstructionsSchema -> - -export const interpolatableRobotMetaReadInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotMetaReadInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotMetaReadInstructionsWithHiddenFields = z.input< - typeof interpolatableRobotMetaReadInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/meta-write.ts b/packages/transloadit/src/alphalib/types/robots/meta-write.ts deleted file mode 100644 index 46852e8d..00000000 --- a/packages/transloadit/src/alphalib/types/robots/meta-write.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { z } from 'zod' - -import { stackVersions } from '../stackVersions.ts' -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotFFmpeg, robotUse } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - example_code: { - steps: { - attributed: { - robot: '/meta/write', - use: ':original', - data_to_write: { - copyright: '© Transloadit', - }, - ffmpeg_stack: stackVersions.ffmpeg.recommendedVersion, - }, - }, - }, - example_code_description: 'Add a copyright notice to uploaded images:', - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'Media Cataloging', - purpose_sentence: 'writes metadata into files', - purpose_verb: 'write', - purpose_word: 'write metadata', - purpose_words: 'Write metadata to media', - service_slug: 'media-cataloging', - slot_count: 10, - title: 'Write metadata to media', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - uses_tools: ['ffmpeg'], - name: 'MetaWriteRobot', - priceFactor: 1, - queueSlotCount: 10, - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotMetaWriteInstructionsSchema = robotBase - .merge(robotUse) - .merge(robotFFmpeg) - .extend({ - robot: z.literal('/meta/write').describe(` -**Note:** This Robot currently accepts images, videos and audio files. -`), - data_to_write: z - .record(z.unknown()) - .default({}) - .describe(` -A key/value map defining the metadata to write into the file. - -Valid metadata keys can be found [here](https://exiftool.org/TagNames/EXIF.html). For example: \`ProcessingSoftware\`. -`), - }) - .strict() - -export const robotMetaWriteInstructionsWithHiddenFieldsSchema = - robotMetaWriteInstructionsSchema.extend({ - result: z.union([z.literal('debug'), robotMetaWriteInstructionsSchema.shape.result]).optional(), - }) - -export type RobotMetaWriteInstructions = z.infer -export type RobotMetaWriteInstructionsWithHiddenFields = z.infer< - typeof robotMetaWriteInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotMetaWriteInstructionsSchema = interpolateRobot( - robotMetaWriteInstructionsSchema, -) -export type InterpolatableRobotMetaWriteInstructions = InterpolatableRobotMetaWriteInstructionsInput - -export type InterpolatableRobotMetaWriteInstructionsInput = z.input< - typeof interpolatableRobotMetaWriteInstructionsSchema -> - -export const interpolatableRobotMetaWriteInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotMetaWriteInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotMetaWriteInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotMetaWriteInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotMetaWriteInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotMetaWriteInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/minio-import.ts b/packages/transloadit/src/alphalib/types/robots/minio-import.ts deleted file mode 100644 index 617f6990..00000000 --- a/packages/transloadit/src/alphalib/types/robots/minio-import.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - files_per_page, - interpolateRobot, - minioBase, - page_number, - path, - recursive, - return_file_stubs, - robotBase, - robotImport, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 10, - discount_factor: 0.1, - discount_pct: 90, - example_code: { - steps: { - imported: { - robot: '/minio/import', - credentials: 'YOUR_MINIO_CREDENTIALS', - path: 'path/to/files/', - recursive: true, - }, - }, - }, - example_code_description: - 'Import files from the `path/to/files` directory and its subdirectories:', - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Importing', - purpose_sentence: 'imports whole directories of files from your MinIO bucket', - purpose_verb: 'import', - purpose_word: 'MinIO', - purpose_words: 'Import files from MinIO', - requires_credentials: true, - service_slug: 'file-importing', - slot_count: 20, - title: 'Import files from MinIO', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'MinioImportRobot', - priceFactor: 6.6666, - queueSlotCount: 20, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: true, - stage: 'ga', -} - -export const robotMinioImportInstructionsSchema = robotBase - .merge(robotImport) - .merge(minioBase) - .extend({ - robot: z.literal('/minio/import'), - path: path.describe(` -The path in your bucket to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. - -If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are direct descendants of this directory will be imported. For example: \`images/\`. - -Directories are **not** imported recursively. If you want to import files from subdirectories and sub-subdirectories, enable the \`recursive\` parameter. - -If you want to import all files from the root directory, please use \`/\` as the value here. In this case, make sure all your objects belong to a path. If you have objects in the root of your bucket that aren't prefixed with \`/\`, you'll receive an error: \`A client error (NoSuchKey) occurred when calling the GetObject operation: The specified key does not exist.\` - -You can also use an array of path strings here to import multiple paths in the same Robot's Step. -`), - recursive: recursive.describe(` -Setting this to \`true\` will enable importing files from subdirectories and sub-subdirectories (etc.) of the given path. - -Please use the pagination parameters \`page_number\` and \`files_per_page\` wisely here. -`), - page_number: page_number.describe(` -The pagination page number. For now, in order to not break backwards compatibility in non-recursive imports, this only works when recursive is set to \`true\`. - -When doing big imports, make sure no files are added or removed from other scripts within your path, otherwise you might get weird results with the pagination. -`), - files_per_page: files_per_page.describe(` -The pagination page size. This only works when recursive is \`true\` for now, in order to not break backwards compatibility in non-recursive imports. -`), - return_file_stubs, - }) - .strict() - -export const robotMinioImportInstructionsWithHiddenFieldsSchema = - robotMinioImportInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotMinioImportInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotMinioImportInstructions = z.infer -export type RobotMinioImportInstructionsWithHiddenFields = z.infer< - typeof robotMinioImportInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotMinioImportInstructionsSchema = interpolateRobot( - robotMinioImportInstructionsSchema, -) -export type InterpolatableRobotMinioImportInstructions = - InterpolatableRobotMinioImportInstructionsInput - -export type InterpolatableRobotMinioImportInstructionsInput = z.input< - typeof interpolatableRobotMinioImportInstructionsSchema -> - -export const interpolatableRobotMinioImportInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotMinioImportInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotMinioImportInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotMinioImportInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotMinioImportInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotMinioImportInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/minio-store.ts b/packages/transloadit/src/alphalib/types/robots/minio-store.ts deleted file mode 100644 index 6003f609..00000000 --- a/packages/transloadit/src/alphalib/types/robots/minio-store.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, minioBase, robotBase, robotUse } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 6, - discount_factor: 0.15000150001500018, - discount_pct: 84.99984999849998, - example_code: { - steps: { - exported: { - robot: '/minio/store', - use: ':original', - credentials: 'YOUR_MINIO_CREDENTIALS', - path: 'my_target_folder/${unique_prefix}/${file.url_name}', - }, - }, - }, - example_code_description: 'Export uploaded files to `my_target_folder` on MinIO:', - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Exporting', - purpose_sentence: 'exports encoding results to MinIO buckets', - purpose_verb: 'export', - purpose_word: 'MinIO', - purpose_words: 'Export files to MinIO', - service_slug: 'file-exporting', - slot_count: 10, - title: 'Export files to MinIO', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'MinioStoreRobot', - priceFactor: 6.6666, - queueSlotCount: 10, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotMinioStoreInstructionsSchema = robotBase - .merge(robotUse) - .merge(minioBase) - .extend({ - robot: z.literal('/minio/store').describe(` -The URL to the result file will be returned in the Assembly Status JSON. -`), - path: z - .string() - .default('${unique_prefix}/${file.url_name}') - .describe(` -The path at which the file is to be stored. This may include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). The path must not be a directory. -`), - acl: z - .enum(['private', 'public-read']) - .default('public-read') - .describe(` -The permissions used for this file. -`), - headers: z - .record(z.string()) - .default({ 'Content-Type': '${file.mime}' }) - .describe(` -An object containing a list of headers to be set for this file on MinIO Spaces, such as \`{ FileURL: "\${file.url_name}" }\`. This can also include any available [Assembly Variables](/docs/topics/assembly-instructions/#assembly-variables). - -Object Metadata can be specified using \`x-amz-meta-*\` headers. Note that these headers [do not support non-ASCII metadata values](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html#UserMetadata). -`), - sign_urls_for: z - .number() - .int() - .min(0) - .optional() - .describe(` -This parameter provides signed URLs in the result JSON (in the \`signed_ssl_url\` property). The number that you set this parameter to is the URL expiry time in seconds. - -If this parameter is not used, no URL signing is done. -`), - }) - .strict() - -export const robotMinioStoreInstructionsWithHiddenFieldsSchema = - robotMinioStoreInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotMinioStoreInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotMinioStoreInstructions = z.infer -export type RobotMinioStoreInstructionsWithHiddenFields = z.infer< - typeof robotMinioStoreInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotMinioStoreInstructionsSchema = interpolateRobot( - robotMinioStoreInstructionsSchema, -) -export type InterpolatableRobotMinioStoreInstructions = - InterpolatableRobotMinioStoreInstructionsInput - -export type InterpolatableRobotMinioStoreInstructionsInput = z.input< - typeof interpolatableRobotMinioStoreInstructionsSchema -> - -export const interpolatableRobotMinioStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotMinioStoreInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotMinioStoreInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotMinioStoreInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotMinioStoreInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotMinioStoreInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/progress-simulate.ts b/packages/transloadit/src/alphalib/types/robots/progress-simulate.ts deleted file mode 100644 index 350f2bc5..00000000 --- a/packages/transloadit/src/alphalib/types/robots/progress-simulate.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -// @ts-expect-error - ProgressSimulateRobot is not ready yet @TODO please supply missing keys -export const meta: RobotMetaInput = { - name: 'ProgressSimulateRobot', - priceFactor: 1, - queueSlotCount: 20, - isAllowedForUrlTransform: false, - trackOutputFileSize: true, - isInternal: true, - stage: 'ga', - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, -} - -export const robotProgressSimulateInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/progress/simulate'), - duration: z.number(), - output_files: z.number(), - emit_progress: z.boolean(), - predict_output: z.boolean(), - }) - .strict() -export type RobotProgressSimulateInstructions = z.infer< - typeof robotProgressSimulateInstructionsSchema -> - -export const interpolatableRobotProgressSimulateInstructionsSchema = interpolateRobot( - robotProgressSimulateInstructionsSchema, -) -export type InterpolatableRobotProgressSimulateInstructions = - InterpolatableRobotProgressSimulateInstructionsInput - -export type InterpolatableRobotProgressSimulateInstructionsInput = z.input< - typeof interpolatableRobotProgressSimulateInstructionsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/s3-import.ts b/packages/transloadit/src/alphalib/types/robots/s3-import.ts deleted file mode 100644 index 5d435821..00000000 --- a/packages/transloadit/src/alphalib/types/robots/s3-import.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - files_per_page, - interpolateRobot, - page_number, - path, - recursive, - return_file_stubs, - robotBase, - robotImport, - s3Base, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 10, - discount_factor: 0.1, - discount_pct: 90, - example_code: { - steps: { - imported: { - robot: '/s3/import', - credentials: 'YOUR_AWS_CREDENTIALS', - path: 'path/to/files/', - recursive: true, - }, - }, - }, - example_code_description: - 'Import files from the `path/to/files` directory and its subdirectories:', - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Importing', - purpose_sentence: 'imports whole directories of files from your S3 bucket', - purpose_verb: 'import', - purpose_word: 'Amazon S3', - purpose_words: 'Import files from Amazon S3', - requires_credentials: true, - service_slug: 'file-importing', - slot_count: 10, - title: 'Import files from Amazon S3', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'S3ImportRobot', - priceFactor: 10, - queueSlotCount: 10, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: true, - stage: 'ga', -} - -export const robotS3ImportInstructionsSchema = robotBase - .merge(robotImport) - .merge(s3Base) - .extend({ - robot: z.literal('/s3/import').describe(` -If you are new to Amazon S3, see our tutorial on [using your own S3 bucket](/docs/faq/how-to-set-up-an-amazon-s3-bucket/). - -The URL to the result file in your S3 bucket will be returned in the Assembly Status JSON. - -> [!Warning] -> **Use DNS-compliant bucket names**. Your bucket name [must be DNS-compliant](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html) and must not contain uppercase letters. Any non-alphanumeric characters in the file names will be replaced with an underscore, and spaces will be replaced with dashes. If your existing S3 bucket contains uppercase letters or is otherwise not DNS-compliant, rewrite the result URLs using the Robot’s \`url_prefix\` parameter. - - - -## Limit access - -You will also need to add permissions to your bucket so that Transloadit can access it properly. Here is an example IAM policy that you can use. Following the [principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege), it contains the **minimum required permissions** to export a file to your S3 bucket using Transloadit. You may require more permissions (especially viewing permissions) depending on your application. - -Please change \`{BUCKET_NAME}\` in the values for \`Sid\` and \`Resource\` accordingly. Also, this policy will grant the minimum required permissions to all your users. We advise you to create a separate Amazon IAM user, and use its User ARN (can be found in the "Summary" tab of a user [here](https://console.aws.amazon.com/iam/home#users)) for the \`Principal\` value. More information about this can be found [here](https://docs.aws.amazon.com/AmazonS3/latest/dev/AccessPolicyLanguage_UseCases_s3_a.html). - -\`\`\`json -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "AllowTransloaditToImportFilesIn{BUCKET_NAME}Bucket", - "Effect": "Allow", - "Action": ["s3:GetBucketLocation", "s3:ListBucket"], - "Resource": ["arn:aws:s3:::{BUCKET_NAME}", "arn:aws:s3:::{BUCKET_NAME}/*"] - } - ] -} -\`\`\` - -The \`Sid\` value is just an identifier for you to recognize the rule later. You can name it anything you like. - -The policy needs to be separated into two parts, because the \`ListBucket\` action requires permissions on the bucket while the other actions require permissions on the objects in the bucket. When targeting the objects there's a trailing slash and an asterisk in the \`Resource\` parameter, whereas when the policy targets the bucket, the slash and the asterisk are omitted. - -In order to build proper result URLs we need to know the region in which your S3 bucket resides. For this we require the \`GetBucketLocation\` permission. Figuring out your bucket's region this way will also slow down your Assemblies. To make this much faster and to also not require the \`GetBucketLocation\` permission, we have added the \`bucket_region\` parameter to the /s3/store and /s3/import Robots. We recommend using them at all times. - -Please keep in mind that if you use bucket encryption you may also need to add \`"sts:*"\` and \`"kms:*"\` to the bucket policy. Please read [here](https://docs.aws.amazon.com/kms/latest/developerguide/kms-api-permissions-reference.html) and [here](https://aws.amazon.com/blogs/security/how-to-restrict-amazon-s3-bucket-access-to-a-specific-iam-role/) in case you run into trouble with our example bucket policy. -`), - path: path.describe(` -The path in your bucket to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. - -If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are direct descendants to this directory will be imported. For example: \`images/\`. - -Directories are **not** imported recursively. If you want to import files from subdirectories and sub-subdirectories, enable the \`recursive\` parameter. - -If you want to import all files from the root directory, please use \`/\` as the value here. In this case, make sure all your objects belong to a path. If you have objects in the root of your bucket that aren't prefixed with \`/\`, you'll receive an error: \`A client error (NoSuchKey) occurred when calling the GetObject operation: The specified key does not exist.\` - -You can also use an array of path strings here to import multiple paths in the same Robot's Step. -`), - recursive: recursive.describe(` -Setting this to \`true\` will enable importing files from subdirectories and sub-subdirectories (etc.) of the given path. - -Please use the pagination parameters \`page_number\` and \`files_per_page\` wisely here. -`), - page_number: page_number.optional().describe(` -The pagination page number. For now, in order to not break backwards compatibility in non-recursive imports, this only works when recursive is set to \`true\`. - -When doing big imports, make sure no files are added or removed from other scripts within your path, otherwise you might get weird results with the pagination. -`), - files_per_page: files_per_page.optional().describe(` -The pagination page size. This only works when recursive is \`true\` for now, in order to not break backwards compatibility in non-recursive imports. -`), - return_file_stubs, - range: z - .union([z.string(), z.array(z.string())]) - .optional() - .describe(` -Allows you to specify one or more byte ranges to import from the file. S3 must support range requests for this to work. - -**Single range**: Use a string like \`"0-99"\` to import bytes 0-99 (the first 100 bytes). - -**Multiple ranges**: Use an array like \`["0-99", "200-299"]\` to import multiple separate ranges. The resulting file will contain all requested ranges concatenated together, with zero bytes (\\0) filling any gaps between non-contiguous ranges. - -**Range formats**: -- \`"0-99"\`: Bytes 0 through 99 (inclusive) -- \`"100-199"\`: Bytes 100 through 199 (inclusive) -- \`"-100"\`: The last 100 bytes of the file - -**Important notes**: -- S3 supports range requests by default -- Overlapping ranges are allowed and will be included as requested -- The resulting file size will be the highest byte position requested, with gaps filled with zero bytes -- Each range is fetched in a separate request to ensure compatibility with S3 -`), - }) - .strict() - -export const robotS3ImportInstructionsWithHiddenFieldsSchema = - robotS3ImportInstructionsSchema.extend({ - result: z.union([z.literal('debug'), robotS3ImportInstructionsSchema.shape.result]).optional(), - }) - -export type RobotS3ImportInstructions = z.infer -export type RobotS3ImportInstructionsWithHiddenFields = z.infer< - typeof robotS3ImportInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotS3ImportInstructionsSchema = interpolateRobot( - robotS3ImportInstructionsSchema, -) -export type InterpolatableRobotS3ImportInstructions = InterpolatableRobotS3ImportInstructionsInput - -export type InterpolatableRobotS3ImportInstructionsInput = z.input< - typeof interpolatableRobotS3ImportInstructionsSchema -> - -export const interpolatableRobotS3ImportInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotS3ImportInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotS3ImportInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotS3ImportInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotS3ImportInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotS3ImportInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/s3-store.ts b/packages/transloadit/src/alphalib/types/robots/s3-store.ts deleted file mode 100644 index 69946f32..00000000 --- a/packages/transloadit/src/alphalib/types/robots/s3-store.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse, s3Base } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 10, - discount_factor: 0.1, - discount_pct: 90, - example_code: { - steps: { - exported: { - robot: '/s3/store', - use: ':original', - credentials: 'YOUR_AWS_CREDENTIALS', - path: 'my_target_folder/${unique_prefix}/${file.url_name}', - }, - }, - }, - example_code_description: 'Export uploaded files to `my_target_folder` in an S3 bucket:', - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Exporting', - purpose_sentence: 'exports encoding results to Amazon S3', - purpose_verb: 'export', - purpose_word: 'Amazon S3', - purpose_words: 'Export files to Amazon S3', - service_slug: 'file-exporting', - slot_count: 2, - title: 'Export files to Amazon S3', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'S3StoreRobot', - priceFactor: 10, - queueSlotCount: 2, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - stage: 'ga', - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, -} - -export const robotS3StoreInstructionsSchema = robotBase - .merge(robotUse) - .merge(s3Base) - .extend({ - robot: z.literal('/s3/store').describe(` -If you are new to Amazon S3, see our tutorial on [using your own S3 bucket](/docs/faq/how-to-set-up-an-amazon-s3-bucket/). - -The URL to the result file in your S3 bucket will be returned in the Assembly Status JSON. If your S3 bucket has versioning enabled, the version ID of the file will be returned within \`meta.version_id\` - -> [!Warning] -> **Avoid permission errors.** By default, \`acl\` is set to \`"public"\`. AWS S3 has a bucket setting called "Block new public ACLs and uploading public objects". Set this to False in your bucket if you intend to leave \`acl\` as \`"public"\`. Otherwise, you’ll receive permission errors in your Assemblies despite your S3 credentials being configured correctly. - -> [!Warning] -> **Use DNS-compliant bucket names.** Your bucket name [must be DNS-compliant](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html) and must not contain uppercase letters. Any non-alphanumeric characters in the file names will be replaced with an underscore, and spaces will be replaced with dashes. If your existing S3 bucket contains uppercase letters or is otherwise not DNS-compliant, rewrite the result URLs using the Robot’s \`url_prefix\` parameter. - - - -## Limit access - -You will also need to add permissions to your bucket so that Transloadit can access it properly. Here is an example IAM policy that you can use. Following the [principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege), it contains the **minimum required permissions** to export a file to your S3 bucket using Transloadit. You may require more permissions (especially viewing permissions) depending on your application. - -Please change \`{BUCKET_NAME}\` in the values for \`Sid\` and \`Resource\` accordingly. Also, this policy will grant the minimum required permissions to all your users. We advise you to create a separate Amazon IAM user, and use its User ARN (can be found in the "Summary" tab of a user [here](https://console.aws.amazon.com/iam/home#users)) for the \`Principal\` value. More information about this can be found [here](https://docs.aws.amazon.com/AmazonS3/latest/dev/AccessPolicyLanguage_UseCases_s3_a.html). - -\`\`\`json -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "AllowTransloaditToStoreFilesIn{BUCKET_NAME}Bucket", - "Effect": "Allow", - "Action": ["s3:GetBucketLocation", "s3:ListBucket", "s3:PutObject", "s3:PutObjectAcl"], - "Resource": ["arn:aws:s3:::{BUCKET_NAME}", "arn:aws:s3:::{BUCKET_NAME}/*"] - } - ] -} -\`\`\` - -The \`Sid\` value is just an identifier for you to recognize the rule later. You can name it anything you like. - -The policy needs to be separated into two parts, because the \`ListBucket\` action requires permissions on the bucket while the other actions require permissions on the objects in the bucket. When targeting the objects there's a trailing slash and an asterisk in the \`Resource\` parameter, whereas when the policy targets the bucket, the slash and the asterisk are omitted. - -Please note that if you give the Robot's \`acl\` parameter a value of \`"bucket-default"\`, then you do not need the \`"s3:PutObjectAcl"\` permission in your bucket policy. - -In order to build proper result URLs we need to know the region in which your S3 bucket resides. For this we require the \`GetBucketLocation\` permission. Figuring out your bucket's region this way will also slow down your Assemblies. To make this much faster and to also not require the \`GetBucketLocation\` permission, we have added the \`bucket_region\` parameter to the /s3/store and /s3/import Robots. We recommend using them at all times. - -Please keep in mind that if you use bucket encryption you may also need to add \`"sts:*"\` and \`"kms:*"\` to the bucket policy. Please read [here](https://docs.aws.amazon.com/kms/latest/developerguide/kms-api-permissions-reference.html) and [here](https://aws.amazon.com/blogs/security/how-to-restrict-amazon-s3-bucket-access-to-a-specific-iam-role/) in case you run into trouble with our example bucket policy. -`), - path: z - .string() - .default('${unique_prefix}/${file.url_name}') - .describe(` -The path at which the file is to be stored. This may include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). The path must not be a directory. -`), - url_prefix: z - .string() - .default('http://{bucket}.s3.amazonaws.com/') - .describe(` -The URL prefix used for the returned URL, such as \`"http://my.cdn.com/some/path/"\`. -`), - acl: z - .enum(['bucket-default', 'private', 'public', 'public-read']) - .default('public-read') - .describe(` -The permissions used for this file. - -Please keep in mind that the default value \`"public-read"\` can lead to permission errors due to the \`"Block all public access"\` checkbox that is checked by default when creating a new Amazon S3 Bucket in the AWS console. -`), - check_integrity: z - .boolean() - .default(false) - .describe(` -Calculate and submit the file's checksum in order for S3 to verify its integrity after uploading, which can help with occasional file corruption issues. - -Enabling this option adds to the overall execution time, as integrity checking can be CPU intensive, especially for larger files. -`), - headers: z - .record(z.string()) - .default({ 'Content-Type': '${file.mime}' }) - .describe(` -An object containing a list of headers to be set for this file on S3, such as \`{ FileURL: "\${file.url_name}" }\`. This can also include any available [Assembly Variables](/docs/topics/assembly-instructions/#assembly-variables). You can find a list of available headers [here](https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUT.html). - -Object Metadata can be specified using \`x-amz-meta-*\` headers. Note that these headers [do not support non-ASCII metadata values](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html#UserMetadata). -`), - tags: z - .record(z.string()) - .default({}) - .describe(` -Object tagging allows you to categorize storage. You can associate up to 10 tags with an object. Tags that are associated with an object must have unique tag keys. -`), - host: z - .string() - .default('s3.amazonaws.com') - .describe(` -The host of the storage service used. This only needs to be set when the storage service used is not Amazon S3, but has a compatible API (such as hosteurope.de). The default protocol used is HTTP, for anything else the protocol needs to be explicitly specified. For example, prefix the host with \`https://\` or \`s3://\` to use either respective protocol. -`), - no_vhost: z - .boolean() - .default(false) - .describe(` -Set to \`true\` if you use a custom host and run into access denied errors. -`), - sign_urls_for: z - .number() - .int() - .min(0) - .optional() - .describe(` -This parameter provides signed URLs in the result JSON (in the \`signed_url\` and \`signed_ssl_url\` properties). The number that you set this parameter to is the URL expiry time in seconds. If this parameter is not used, no URL signing is done. -`), - session_token: z - .string() - .optional() - .describe(` -The session token to use for the S3 store. This is only used if the credentials are from an IAM user with the \`sts:AssumeRole\` permission. -`), - }) - .strict() - -export const robotS3StoreInstructionsWithHiddenFieldsSchema = robotS3StoreInstructionsSchema.extend( - { - result: z.union([z.literal('debug'), robotS3StoreInstructionsSchema.shape.result]).optional(), - skip_region_lookup: z - .boolean() - .optional() - .describe(` -Internal parameter to skip region lookup for testing purposes. -`), - }, -) - -export type RobotS3StoreInstructions = z.infer -export type RobotS3StoreInstructionsInput = z.input -export type RobotS3StoreInstructionsWithHiddenFields = z.infer< - typeof robotS3StoreInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotS3StoreInstructionsSchema = interpolateRobot( - robotS3StoreInstructionsSchema, -) -export type InterpolatableRobotS3StoreInstructions = InterpolatableRobotS3StoreInstructionsInput - -export type InterpolatableRobotS3StoreInstructionsInput = z.input< - typeof interpolatableRobotS3StoreInstructionsSchema -> - -export const interpolatableRobotS3StoreInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotS3StoreInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotS3StoreInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotS3StoreInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotS3StoreInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotS3StoreInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/script-run.ts b/packages/transloadit/src/alphalib/types/robots/script-run.ts deleted file mode 100644 index 1cabe774..00000000 --- a/packages/transloadit/src/alphalib/types/robots/script-run.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 10, - discount_factor: 0.1, - discount_pct: 90, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'Code Evaluation', - purpose_sentence: 'runs scripts in Assemblies', - purpose_verb: 'run', - purpose_word: 'script', - purpose_words: 'Run scripts in Assemblies', - service_slug: 'code-evaluation', - slot_count: 5, - title: 'Run Scripts', - typical_file_size_mb: 0.0001, - typical_file_type: 'file', - name: 'ScriptRunRobot', - priceFactor: 10, - queueSlotCount: 5, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotScriptRunInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/script/run').describe(` -This Robot allows you to run arbitrary \`JavaScript\` as part of the Assembly -execution process. The Robot is invoked automatically when there are Assembly -Instructions containing \`\${...}\`: - -\`\`\`json -{ - "robot": "/image/resize", - "width": "\${Math.max(file.meta.width, file.meta.height)}" -} -\`\`\` - -You can also invoke this Robot directly, leaving out the \`\${...}\`: - -\`\`\`json -{ - "robot": "/script/run", - "script": "Math.max(file.meta.width, file.meta.height)" -} -\`\`\` - -When accessing arrays, the syntax is the same as in any JavaScript program: - -\`\`\`json -{ - "robot": "/image/resize", - "width": "\${file.meta.faces[0].width * 2}" -} -\`\`\` - -Compared to only accessing an Assembly Variable: - -\`\`\`json -{ - "robot": "/image/resize", - "width": "\${file.meta.faces[0].width}" -} -\`\`\` - -For more information, see [Dynamic Evaluation](/docs/topics/dynamic-evaluation/). -`), - script: z.string().describe(` -A string of JavaScript to evaluate. It has access to all JavaScript features available in a modern browser environment. - -The script is expected to return a \`JSON.stringify\`-able value in the same tick, so no \`await\` or callbacks are allowed (yet). - -If the script does not finish within 1000ms it times out with an error. The return value or error is exported as \`file.meta.result\`. If there was an error, \`file.meta.isError\` is \`true\`. Note that the Assembly will not crash in this case. If you need it to crash, you can check this value with a [🤖/file/filter](/docs/robots/file-filter/) Step, setting \`error_on_decline\` to \`true\`. - -You can check whether evaluating this script was free by inspecting \`file.meta.isFree\`. It is recommended to do this during development as to not see sudden unexpected costs in production. -`), - }) - .strict() - -export const robotScriptRunInstructionsWithHiddenFieldsSchema = - robotScriptRunInstructionsSchema.extend({ - result: z.union([z.literal('debug'), robotScriptRunInstructionsSchema.shape.result]).optional(), - contextJSON: z - .string() - .optional() - .describe(` -A JSON string that provides additional context data to the script. This will be parsed and made available to the script as a \`context\` variable. For example, if you pass \`'{"foo":{"bar":"baz"}}'\`, the script can access \`context.foo.bar\` to get the value \`"baz"\`. -`), - }) - -export type RobotScriptRunInstructions = z.infer -export type RobotScriptRunInstructionsWithHiddenFields = z.infer< - typeof robotScriptRunInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotScriptRunInstructionsSchema = interpolateRobot( - robotScriptRunInstructionsSchema, -) -export type InterpolatableRobotScriptRunInstructions = InterpolatableRobotScriptRunInstructionsInput - -export type InterpolatableRobotScriptRunInstructionsInput = z.input< - typeof interpolatableRobotScriptRunInstructionsSchema -> - -export const interpolatableRobotScriptRunInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotScriptRunInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotScriptRunInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotScriptRunInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotScriptRunInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotScriptRunInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/sftp-import.ts b/packages/transloadit/src/alphalib/types/robots/sftp-import.ts deleted file mode 100644 index 8ea81bbb..00000000 --- a/packages/transloadit/src/alphalib/types/robots/sftp-import.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotImport, sftpBase } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 10, - discount_factor: 0.1, - discount_pct: 90, - example_code: { - steps: { - imported: { - robot: '/sftp/import', - credentials: 'YOUR_SFTP_CREDENTIALS', - path: 'path/to/files/', - }, - }, - }, - example_code_description: - 'Import files from the `path/to/files` directory and its subdirectories:', - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Importing', - purpose_sentence: - 'imports whole libraries of files from your SFTP servers into Transloadit. This Robot relies on public key authentication', - purpose_verb: 'import', - purpose_word: 'SFTP servers', - purpose_words: 'Import files from SFTP servers', - service_slug: 'file-importing', - slot_count: 20, - title: 'Import files from SFTP servers', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'SftpImportRobot', - priceFactor: 6.6666, - queueSlotCount: 20, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: true, - stage: 'ga', -} - -export const robotSftpImportInstructionsSchema = robotBase - .merge(robotImport) - .merge(sftpBase) - .extend({ - robot: z.literal('/sftp/import'), - path: z.string().describe(` -The path on your SFTP server where to search for files. -`), - }) - .strict() - -export const robotSftpImportInstructionsWithHiddenFieldsSchema = - robotSftpImportInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotSftpImportInstructionsSchema.shape.result]) - .optional(), - allowNetwork: z - .string() - .optional() - .describe(` -Network access permission for the SFTP connection. This is used to control which networks the SFTP robot can access. -`), - }) - -export type RobotSftpImportInstructions = z.infer -export type RobotSftpImportInstructionsWithHiddenFields = z.infer< - typeof robotSftpImportInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotSftpImportInstructionsSchema = interpolateRobot( - robotSftpImportInstructionsSchema, -) -export type InterpolatableRobotSftpImportInstructions = - InterpolatableRobotSftpImportInstructionsInput - -export type InterpolatableRobotSftpImportInstructionsInput = z.input< - typeof interpolatableRobotSftpImportInstructionsSchema -> - -export const interpolatableRobotSftpImportInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotSftpImportInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotSftpImportInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotSftpImportInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotSftpImportInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotSftpImportInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/sftp-store.ts b/packages/transloadit/src/alphalib/types/robots/sftp-store.ts deleted file mode 100644 index 11c4f53c..00000000 --- a/packages/transloadit/src/alphalib/types/robots/sftp-store.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse, sftpBase } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 6, - discount_factor: 0.15000150001500018, - discount_pct: 84.99984999849998, - example_code: { - steps: { - exported: { - robot: '/sftp/store', - use: ':original', - credentials: 'YOUR_SFTP_CREDENTIALS', - path: 'my_target_folder/${unique_prefix}/${file.url_name}', - }, - }, - }, - example_code_description: 'Export uploaded files to `my_target_folder` on an SFTP server:', - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Exporting', - purpose_sentence: 'exports encoding results to your own SFTP server', - purpose_verb: 'export', - purpose_word: 'SFTP servers', - purpose_words: 'Export files to SFTP servers', - service_slug: 'file-exporting', - slot_count: 10, - title: 'Export files to SFTP servers', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'SftpStoreRobot', - priceFactor: 6.6666, - queueSlotCount: 10, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotSftpStoreInstructionsSchema = robotBase - .merge(robotUse) - .merge(sftpBase) - .extend({ - robot: z.literal('/sftp/store'), - path: z - .string() - .default('${unique_prefix}/${file.url_name}') - .describe(` -The path at which the file is to be stored. This may include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). -`), - url_template: z - .string() - .default('http://host/path') - .describe(` -The URL of the file in the result JSON. This may include any of the following supported [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). -`), - ssl_url_template: z - .string() - .default('https://{HOST}/{PATH}') - .describe(` - The SSL URL of the file in the result JSON. The following [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables) are supported. -`), - file_chmod: z - .string() - .regex(/([0-7]{3}|auto)/) - .default('auto') - .describe(` -This optional parameter controls how an uploaded file's permission bits are set. You can use any string format that the \`chmod\` command would accept, such as \`"755"\`. If you don't specify this option, the file's permission bits aren't changed at all, meaning it's up to your server's configuration (e.g. umask). - `), - }) - .strict() - -export const robotSftpStoreInstructionsWithHiddenFieldsSchema = - robotSftpStoreInstructionsSchema.extend({ - result: z.union([z.literal('debug'), robotSftpStoreInstructionsSchema.shape.result]).optional(), - allowNetwork: z - .string() - .optional() - .describe(` -Network access permission for the SFTP connection. This is used to control which networks the SFTP robot can access. -`), - }) - -export type RobotSftpStoreInstructions = z.infer -export type RobotSftpStoreInstructionsWithHiddenFields = z.infer< - typeof robotSftpStoreInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotSftpStoreInstructionsSchema = interpolateRobot( - robotSftpStoreInstructionsSchema, -) -export type InterpolatableRobotSftpStoreInstructions = InterpolatableRobotSftpStoreInstructionsInput - -export type InterpolatableRobotSftpStoreInstructionsInput = z.input< - typeof interpolatableRobotSftpStoreInstructionsSchema -> - -export const interpolatableRobotSftpStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotSftpStoreInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotSftpStoreInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotSftpStoreInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotSftpStoreInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotSftpStoreInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/speech-transcribe.ts b/packages/transloadit/src/alphalib/types/robots/speech-transcribe.ts deleted file mode 100644 index 2922cc5d..00000000 --- a/packages/transloadit/src/alphalib/types/robots/speech-transcribe.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - aiProviderSchema, - granularitySchema, - interpolateRobot, - robotBase, - robotUse, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - example_code: { - steps: { - transcribed: { - robot: '/speech/transcribe', - use: ':original', - provider: 'aws', - source_language: 'fr-FR', - format: 'text', - }, - }, - }, - example_code_description: - 'Transcribe speech in French from uploaded audio or video, and save it to a text file:', - extended_description: ` -> [!Warning] -> Transloadit aims to be deterministic, but this Robot uses third-party AI services. The providers (AWS, GCP) will evolve their models over time, giving different responses for the same input media. Avoid relying on exact responses in your tests and application. -`, - minimum_charge: 1048576, - output_factor: 0.05, - override_lvl1: 'Artificial Intelligence', - purpose_sentence: 'transcribes speech in audio or video files', - purpose_verb: 'transcribe', - purpose_word: 'transcribe speech', - purpose_words: 'Transcribe speech in audio or video files', - service_slug: 'artificial-intelligence', - slot_count: 10, - title: 'Transcribe speech in audio or video files', - typical_file_size_mb: 2.4, - typical_file_type: 'audio or video file', - name: 'SpeechTranscribeRobot', - priceFactor: 1, - queueSlotCount: 10, - minimumChargeUsdPerSpeechTranscribeMinute: { - aws: 0.024, - gcp: 0.016, - }, - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotSpeechTranscribeInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/speech/transcribe').describe(` -You can use the text that we return in your application, or you can pass the text down to other Robots to filter audio or video files that contain (or do not contain) certain content, or burn the text into images or video for example. - -Another common use case is automatically subtitling videos, or making audio searchable. -`), - provider: aiProviderSchema.describe(` -Which AI provider to leverage. - -Transloadit outsources this task and abstracts the interface so you can expect the same data structures, but different latencies and information being returned. Different cloud vendors have different areas they shine in, and we recommend to try out and see what yields the best results for your use case. -`), - granularity: granularitySchema.describe(` -Whether to return a full response (\`"full"\`), or a flat list of descriptions (\`"list"\`). -`), - format: z - .enum(['json', 'meta', 'srt', 'meta', 'text', 'webvtt']) - .default('json') - .describe(` -Output format for the transcription. - -- \`"text"\` outputs a plain text file that you can store and process. -- \`"json"\` outputs a JSON file containing timestamped words. -- \`"srt"\` and \`"webvtt"\` output subtitle files of those respective file types, which can be stored separately or used in other encoding Steps. -- \`"meta"\` does not return a file, but stores the data inside Transloadit's file object (under \`\${file.meta.transcription.text}\`) that's passed around between encoding Steps, so that you can use the values to burn the data into videos, filter on them, etc. -`), - // TODO determine the list of languages - source_language: z - .string() - .default('en-US') - .describe(` -The spoken language of the audio or video. This will also be the language of the transcribed text. - -The language should be specified in the [BCP-47](https://www.rfc-editor.org/rfc/bcp/bcp47.txt) format, such as \`"en-GB"\`, \`"de-DE"\` or \`"fr-FR"\`. Please also consult the list of supported languages for [the \`gcp\` provider](https://cloud.google.com/speech-to-text/docs/languages) and the [the \`aws\` provider](https://docs.aws.amazon.com/transcribe/latest/dg/what-is-transcribe.html). -`), - // TODO determine the list of languages - target_language: z - .string() - .default('en-US') - .describe(` - This will also be the language of the written text. - - The language should be specified in the [BCP-47](https://www.rfc-editor.org/rfc/bcp/bcp47.txt) format, such as \`"en-GB"\`, \`"de-DE"\` or \`"fr-FR"\`. Please consult the list of supported languages and voices. - `), - }) - .strict() - -export const robotSpeechTranscribeInstructionsWithHiddenFieldsSchema = - robotSpeechTranscribeInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotSpeechTranscribeInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotSpeechTranscribeInstructions = z.infer< - typeof robotSpeechTranscribeInstructionsSchema -> -export type RobotSpeechTranscribeInstructionsWithHiddenFields = z.infer< - typeof robotSpeechTranscribeInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotSpeechTranscribeInstructionsSchema = interpolateRobot( - robotSpeechTranscribeInstructionsSchema, -) -export type InterpolatableRobotSpeechTranscribeInstructions = - InterpolatableRobotSpeechTranscribeInstructionsInput - -export type InterpolatableRobotSpeechTranscribeInstructionsInput = z.input< - typeof interpolatableRobotSpeechTranscribeInstructionsSchema -> - -export const interpolatableRobotSpeechTranscribeInstructionsWithHiddenFieldsSchema = - interpolateRobot(robotSpeechTranscribeInstructionsWithHiddenFieldsSchema) -export type InterpolatableRobotSpeechTranscribeInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotSpeechTranscribeInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotSpeechTranscribeInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotSpeechTranscribeInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/supabase-import.ts b/packages/transloadit/src/alphalib/types/robots/supabase-import.ts deleted file mode 100644 index 3081d9be..00000000 --- a/packages/transloadit/src/alphalib/types/robots/supabase-import.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - files_per_page, - interpolateRobot, - page_number, - path, - recursive, - return_file_stubs, - robotBase, - robotImport, - supabaseBase, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 10, - discount_factor: 0.1, - discount_pct: 90, - example_code: { - steps: { - imported: { - robot: '/supabase/import', - credentials: 'YOUR_SUPABASE_CREDENTIALS', - path: 'path/to/files/', - recursive: true, - }, - }, - }, - example_code_description: - 'Import files from the `path/to/files` directory and its subdirectories:', - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Importing', - purpose_sentence: 'imports whole directories of files from your Supabase bucket', - purpose_verb: 'import', - purpose_word: 'Supabase', - purpose_words: 'Import files from Supabase', - requires_credentials: true, - service_slug: 'file-importing', - slot_count: 20, - title: 'Import files from Supabase', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'SupabaseImportRobot', - priceFactor: 6.6666, - queueSlotCount: 20, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: true, - stage: 'ga', -} - -export const robotSupabaseImportInstructionsSchema = robotBase - .merge(robotImport) - .merge(supabaseBase) - .extend({ - robot: z.literal('/supabase/import').describe(` -The URL to the result file will be returned in the Assembly Status JSON. -`), - path: path.describe(` -The path in your bucket to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. - -If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are direct descendants of this directory will be imported. For example: \`images/\`. - -Directories are **not** imported recursively. If you want to import files from subdirectories and sub-subdirectories, enable the \`recursive\` parameter. - -If you want to import all files from the root directory, please use \`/\` as the value here. In this case, make sure all your objects belong to a path. If you have objects in the root of your bucket that aren't prefixed with \`/\`, you'll receive an error: \`A client error (NoSuchKey) occurred when calling the GetObject operation: The specified key does not exist.\` - -You can also use an array of path strings here to import multiple paths in the same Robot's Step. -`), - recursive: recursive.describe(` -Setting this to \`true\` will enable importing files from subfolders and sub-subfolders, etc. of the given path. - -Please use the pagination parameters \`page_number\` and \`files_per_page\` wisely here. -`), - page_number: page_number.describe(` -The pagination page number. For now, in order to not break backwards compatibility in non-recursive imports, this only works when recursive is set to \`true\`. - -When doing big imports, make sure no files are added or removed from other scripts within your path, otherwise you might get weird results with the pagination. -`), - files_per_page: files_per_page.describe(` -The pagination page size. This only works when recursive is \`true\` for now, in order to not break backwards compatibility in non-recursive imports. -`), - return_file_stubs, - }) - .strict() - -export const robotSupabaseImportInstructionsWithHiddenFieldsSchema = - robotSupabaseImportInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotSupabaseImportInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotSupabaseImportInstructions = z.infer -export type RobotSupabaseImportInstructionsWithHiddenFields = z.infer< - typeof robotSupabaseImportInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotSupabaseImportInstructionsSchema = interpolateRobot( - robotSupabaseImportInstructionsSchema, -) -export type InterpolatableRobotSupabaseImportInstructions = - InterpolatableRobotSupabaseImportInstructionsInput - -export type InterpolatableRobotSupabaseImportInstructionsInput = z.input< - typeof interpolatableRobotSupabaseImportInstructionsSchema -> - -export const interpolatableRobotSupabaseImportInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotSupabaseImportInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotSupabaseImportInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotSupabaseImportInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotSupabaseImportInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotSupabaseImportInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/supabase-store.ts b/packages/transloadit/src/alphalib/types/robots/supabase-store.ts deleted file mode 100644 index 53a60bac..00000000 --- a/packages/transloadit/src/alphalib/types/robots/supabase-store.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse, supabaseBase } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 6, - discount_factor: 0.15000150001500018, - discount_pct: 84.99984999849998, - example_code: { - steps: { - exported: { - robot: '/supabase/store', - use: ':original', - credentials: 'YOUR_SUPABASE_CREDENTIALS', - path: 'my_target_folder/${unique_prefix}/${file.url_name}', - }, - }, - }, - example_code_description: 'Export uploaded files to `my_target_folder` on supabase R2:', - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Exporting', - purpose_sentence: 'exports encoding results to supabase buckets', - purpose_verb: 'export', - purpose_word: 'Supabase', - purpose_words: 'Export files to Supabase', - service_slug: 'file-exporting', - slot_count: 10, - title: 'Export files to Supabase', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'SupabaseStoreRobot', - priceFactor: 6.6666, - queueSlotCount: 10, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', - isInternal: false, -} - -export const robotSupabaseStoreInstructionsSchema = robotBase - .merge(robotUse) - .merge(supabaseBase) - .extend({ - robot: z.literal('/supabase/store'), - path: z - .string() - .default('${unique_prefix}/${file.url_name}') - .describe(` -The path at which the file is to be stored. This may include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). The path must not be a directory. -`), - headers: z - .record(z.string()) - .default({ 'Content-Type': '${file.mime}' }) - .describe(` -An object containing a list of headers to be set for this file on supabase Spaces, such as \`{ FileURL: "\${file.url_name}" }\`. This can also include any available [Assembly Variables](/docs/topics/assembly-instructions/#assembly-variables). - -Object Metadata can be specified using \`x-amz-meta-*\` headers. Note that these headers [do not support non-ASCII metadata values](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html#UserMetadata). -`), - sign_urls_for: z - .number() - .int() - .min(0) - .optional() - .describe(` -This parameter provides signed URLs in the result JSON (in the \`signed_ssl_url\` property). The number that you set this parameter to is the URL expiry time in seconds. If this parameter is not used, no URL signing is done. -`), - }) - .strict() - -export const robotSupabaseStoreInstructionsWithHiddenFieldsSchema = - robotSupabaseStoreInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotSupabaseStoreInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotSupabaseStoreInstructions = z.infer -export type RobotSupabaseStoreInstructionsWithHiddenFields = z.infer< - typeof robotSupabaseStoreInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotSupabaseStoreInstructionsSchema = interpolateRobot( - robotSupabaseStoreInstructionsSchema, -) -export type InterpolatableRobotSupabaseStoreInstructions = - InterpolatableRobotSupabaseStoreInstructionsInput - -export type InterpolatableRobotSupabaseStoreInstructionsInput = z.input< - typeof interpolatableRobotSupabaseStoreInstructionsSchema -> - -export const interpolatableRobotSupabaseStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotSupabaseStoreInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotSupabaseStoreInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotSupabaseStoreInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotSupabaseStoreInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotSupabaseStoreInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/swift-import.ts b/packages/transloadit/src/alphalib/types/robots/swift-import.ts deleted file mode 100644 index 505f7126..00000000 --- a/packages/transloadit/src/alphalib/types/robots/swift-import.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - files_per_page, - interpolateRobot, - page_number, - path, - recursive, - return_file_stubs, - robotBase, - robotImport, - swiftBase, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 10, - discount_factor: 0.1, - discount_pct: 90, - example_code: { - steps: { - imported: { - robot: '/swift/import', - credentials: 'YOUR_SWIFT_CREDENTIALS', - path: 'path/to/files/', - recursive: true, - }, - }, - }, - example_code_description: - 'Import files from the `path/to/files` directory and its subdirectories:', - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Importing', - purpose_sentence: 'imports whole directories of files from your Openstack/Swift bucket', - purpose_verb: 'import', - purpose_word: 'Openstack/Swift', - purpose_words: 'Import files from Openstack/Swift', - requires_credentials: true, - service_slug: 'file-importing', - slot_count: 20, - title: 'Import files from Openstack/Swift', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'SwiftImportRobot', - priceFactor: 6.6666, - queueSlotCount: 20, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: true, - stage: 'ga', -} - -export const robotSwiftImportInstructionsSchema = robotBase - .merge(robotImport) - .merge(swiftBase) - .extend({ - robot: z.literal('/swift/import'), - path: path.describe(` -The path in your bucket to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. - -If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are direct descendants of this directory will be imported. For example: \`images/\`. - -Directories are **not** imported recursively. If you want to import files from subdirectories and sub-subdirectories, enable the \`recursive\` parameter. - -If you want to import all files from the root directory, please use \`/\` as the value here. In this case, make sure all your objects belong to a path. If you have objects in the root of your bucket that aren't prefixed with \`/\`, you'll receive an error: \`A client error (NoSuchKey) occurred when calling the GetObject operation: The specified key does not exist.\` - -You can also use an array of path strings here to import multiple paths in the same Robot's Step. -`), - recursive: recursive.describe(` -Setting this to \`true\` will enable importing files from subdirectories and sub-subdirectories (etc.) of the given path. - -Please use the pagination parameters \`page_number\` and \`files_per_page\` wisely here. -`), - page_number: page_number.describe(` -The pagination page number. For now, in order to not break backwards compatibility in non-recursive imports, this only works when recursive is set to \`true\`. - -When doing big imports, make sure no files are added or removed from other scripts within your path, otherwise you might get weird results with the pagination. -`), - files_per_page: files_per_page.describe(` -The pagination page size. This only works when recursive is \`true\` for now, in order to not break backwards compatibility in non-recursive imports. -`), - return_file_stubs, - }) - .strict() - -export const robotSwiftImportInstructionsWithHiddenFieldsSchema = - robotSwiftImportInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotSwiftImportInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotSwiftImportInstructions = z.infer -export type RobotSwiftImportInstructionsWithHiddenFields = z.infer< - typeof robotSwiftImportInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotSwiftImportInstructionsSchema = interpolateRobot( - robotSwiftImportInstructionsSchema, -) -export type InterpolatableRobotSwiftImportInstructions = - InterpolatableRobotSwiftImportInstructionsInput - -export type InterpolatableRobotSwiftImportInstructionsInput = z.input< - typeof interpolatableRobotSwiftImportInstructionsSchema -> - -export const interpolatableRobotSwiftImportInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotSwiftImportInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotSwiftImportInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotSwiftImportInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotSwiftImportInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotSwiftImportInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/swift-store.ts b/packages/transloadit/src/alphalib/types/robots/swift-store.ts deleted file mode 100644 index de032bd3..00000000 --- a/packages/transloadit/src/alphalib/types/robots/swift-store.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse, swiftBase } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 6, - discount_factor: 0.15000150001500018, - discount_pct: 84.99984999849998, - example_code: { - steps: { - exported: { - robot: '/swift/store', - use: ':original', - credentials: 'YOUR_SWIFT_CREDENTIALS', - path: 'my_target_folder/${unique_prefix}/${file.url_name}', - }, - }, - }, - example_code_description: 'Export uploaded files to `my_target_folder` on Swift:', - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Exporting', - purpose_sentence: 'exports encoding results to OpenStack Swift buckets', - purpose_verb: 'export', - purpose_word: 'OpenStack Swift', - purpose_words: 'Export files to OpenStack/Swift', - service_slug: 'file-exporting', - slot_count: 10, - title: 'Export files to OpenStack Swift Spaces', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'SwiftStoreRobot', - priceFactor: 6.6666, - queueSlotCount: 10, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotSwiftStoreInstructionsSchema = robotBase - .merge(robotUse) - .merge(swiftBase) - .extend({ - robot: z.literal('/swift/store').describe(` -The URL to the result file in your OpenStack bucket will be returned in the Assembly Status JSON.`), - path: z - .string() - .default('${unique_prefix}/${file.url_name}') - .describe(` -The path at which the file is to be stored. This may include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). The path must not be a directory. -`), - acl: z - .enum(['private', 'public-read']) - .default('public-read') - .describe(` -The permissions used for this file. -`), - headers: z - .record(z.string()) - .default({ 'Content-Type': '${file.mime}' }) - .describe(` -An object containing a list of headers to be set for this file on swift Spaces, such as \`{ FileURL: "\${file.url_name}" }\`. This can also include any available [Assembly Variables](/docs/topics/assembly-instructions/#assembly-variables). - -Object Metadata can be specified using \`x-amz-meta-*\` headers. Note that these headers [do not support non-ASCII metadata values](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html#UserMetadata). -`), - sign_urls_for: z - .number() - .int() - .min(0) - .optional() - .describe(` -This parameter provides signed URLs in the result JSON (in the \`signed_ssl_url\` property). The number that you set this parameter to is the URL expiry time in seconds. If this parameter is not used, no URL signing is done. -`), - }) - .strict() - -export const robotSwiftStoreInstructionsWithHiddenFieldsSchema = - robotSwiftStoreInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotSwiftStoreInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotSwiftStoreInstructions = z.infer -export type RobotSwiftStoreInstructionsWithHiddenFields = z.infer< - typeof robotSwiftStoreInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotSwiftStoreInstructionsSchema = interpolateRobot( - robotSwiftStoreInstructionsSchema, -) -export type InterpolatableRobotSwiftStoreInstructions = - InterpolatableRobotSwiftStoreInstructionsInput - -export type InterpolatableRobotSwiftStoreInstructionsInput = z.input< - typeof interpolatableRobotSwiftStoreInstructionsSchema -> - -export const interpolatableRobotSwiftStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotSwiftStoreInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotSwiftStoreInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotSwiftStoreInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotSwiftStoreInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotSwiftStoreInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/text-speak.ts b/packages/transloadit/src/alphalib/types/robots/text-speak.ts deleted file mode 100644 index bf7a7710..00000000 --- a/packages/transloadit/src/alphalib/types/robots/text-speak.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - aiProviderSchema, - interpolateRobot, - robotBase, - robotUse, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - example_code: { - steps: { - synthesized: { - robot: '/text/speak', - use: ':original', - provider: 'aws', - voice: 'female-1', - target_language: 'en-US', - }, - }, - }, - example_code_description: - 'Synthesize speech from uploaded text documents, using a female voice in American English:', - extended_description: ` -> [!Warning] -> Transloadit aims to be deterministic, but this Robot uses third-party AI services. The providers (AWS, GCP) will evolve their models over time, giving different responses for the same input media. Avoid relying on exact responses in your tests and application. - -## Supported languages and voices - -{% for provider in text_speak_voices %} - -### {{provider[0] | upcase }} - - - - - - - - - - {%- for language in provider[1] %} - - - - - {%- endfor %} - -
LanguageVoices
{{language[0]}}{{ language[1] | join: ", " }}
-{% endfor %} -`, - minimum_charge: 1048576, - output_factor: 1, - override_lvl1: 'Artificial Intelligence', - purpose_sentence: 'synthesizes speech in documents', - purpose_verb: 'speak', - purpose_word: 'synthesize speech', - purpose_words: 'Synthesize speech in documents', - service_slug: 'artificial-intelligence', - slot_count: 10, - title: 'Speak text', - typical_file_size_mb: 1, - typical_file_type: 'document', - name: 'TextSpeakRobot', - priceFactor: 1, - queueSlotCount: 10, - minimumChargeUsd: 0.05, - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotTextSpeakInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/text/speak').describe(` -You can use the audio that we return in your application, or you can pass the audio down to other Robots to add a voice track to a video for example. - -Another common use case is making your product accessible to people with a reading disability. -`), - prompt: z - .string() - .nullish() - .describe(` -Which text to speak. You can also set this to \`null\` and supply an input text file. -`), - provider: aiProviderSchema.describe(` -Which AI provider to leverage. - -Transloadit outsources this task and abstracts the interface so you can expect the same data structures, but different latencies and information being returned. Different cloud vendors have different areas they shine in, and we recommend to try out and see what yields the best results for your use case. -`), - // TODO determine the list of languages - target_language: z - .string() - .default('en-US') - .describe(` -The written language of the document. This will also be the language of the spoken text. - -The language should be specified in the [BCP-47](https://www.rfc-editor.org/rfc/bcp/bcp47.txt) format, such as \`"en-GB"\`, \`"de-DE"\` or \`"fr-FR"\`. Please consult the list of supported languages and voices. -`), - voice: z - .enum(['female-1', 'female-2', 'female-3', 'female-child-1', 'male-1', 'male-child-1']) - .default('female-1') - .describe(` -The gender to be used for voice synthesis. Please consult the list of supported languages and voices. - `), - ssml: z - .boolean() - .default(false) - .describe(` -Supply [Speech Synthesis Markup Language](https://en.wikipedia.org/wiki/Speech_Synthesis_Markup_Language) instead of raw text, in order to gain more control over how your text is voiced, including rests and pronounciations. - -Please see the supported syntaxes for [AWS](https://docs.aws.amazon.com/polly/latest/dg/supportedtags.html) and [GCP](https://cloud.google.com/text-to-speech/docs/ssml). -`), - }) - .strict() - -export const robotTextSpeakInstructionsWithHiddenFieldsSchema = - robotTextSpeakInstructionsSchema.extend({ - result: z.union([z.literal('debug'), robotTextSpeakInstructionsSchema.shape.result]).optional(), - }) - -export type RobotTextSpeakInstructions = z.infer -export type RobotTextSpeakInstructionsWithHiddenFields = z.infer< - typeof robotTextSpeakInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotTextSpeakInstructionsSchema = interpolateRobot( - robotTextSpeakInstructionsSchema, -) -export type InterpolatableRobotTextSpeakInstructions = InterpolatableRobotTextSpeakInstructionsInput - -export type InterpolatableRobotTextSpeakInstructionsInput = z.input< - typeof interpolatableRobotTextSpeakInstructionsSchema -> - -export const interpolatableRobotTextSpeakInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotTextSpeakInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotTextSpeakInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotTextSpeakInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotTextSpeakInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotTextSpeakInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/text-translate.ts b/packages/transloadit/src/alphalib/types/robots/text-translate.ts deleted file mode 100644 index a4ea2b5c..00000000 --- a/packages/transloadit/src/alphalib/types/robots/text-translate.ts +++ /dev/null @@ -1,245 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - aiProviderSchema, - interpolateRobot, - robotBase, - robotUse, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - example_code: { - steps: { - translated: { - robot: '/text/translate', - use: ':original', - target_language: 'de', - provider: 'aws', - }, - }, - }, - example_code_description: 'Translate uploaded text file contents to German:', - extended_description: ` -> [!Warning] -> This Robot uses third-party AI services. They may tweak their models over time, giving different responses for the same input media. Avoid relying on exact responses in your tests and application. - -## Supported languages - -{%- for provider in text_translate_languages %} - -### {{provider[0] | upcase }} - - -{%- for language in provider[1] %} - {{ language }}{% unless forloop.last %},{% endunless %} -{%- endfor %} - -{%- endfor %} -`, - minimum_charge: 1048576, - output_factor: 1, - override_lvl1: 'Artificial Intelligence', - purpose_sentence: 'translates text in documents', - purpose_verb: 'translate', - purpose_word: 'text', - purpose_words: 'Translate text in documents', - service_slug: 'artificial-intelligence', - slot_count: 10, - title: 'Translate text', - typical_file_size_mb: 1, - typical_file_type: 'document', - name: 'TextTranslateRobot', - priceFactor: 0.00008, - queueSlotCount: 10, - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -const translatableLanguages = z - .enum([ - 'af', - 'am', - 'ar', - 'az', - 'be', - 'bg', - 'bn', - 'bs', - 'ca', - 'ceb', - 'co', - 'cs', - 'cy', - 'da', - 'de', - 'el', - 'en', - 'en-US', - 'eo', - 'es', - 'es-MX', - 'et', - 'eu', - 'fa', - 'fa-AF', - 'fi', - 'fr', - 'fr-CA', - 'fy', - 'ga', - 'gd', - 'gl', - 'gu', - 'ha', - 'haw', - 'he', - 'hi', - 'hmn', - 'hr', - 'ht', - 'hu', - 'hy', - 'id', - 'ig', - 'is', - 'it', - 'iw', - 'ja', - 'jv', - 'ka', - 'kk', - 'km', - 'kn', - 'ko', - 'ku', - 'ky', - 'la', - 'lb', - 'lo', - 'lt', - 'lv', - 'mg', - 'mi', - 'mk', - 'ml', - 'mn', - 'mr', - 'ms', - 'mt', - 'my', - 'ne', - 'nl', - 'no', - 'ny', - 'or', - 'pa', - 'pl', - 'ps', - 'pt', - 'ro', - 'ru', - 'rw', - 'sd', - 'si', - 'sk', - 'sl', - 'sm', - 'sn', - 'so', - 'sq', - 'sr', - 'st', - 'su', - 'sv', - 'sw', - 'ta', - 'te', - 'tg', - 'th', - 'tk', - 'tl', - 'tr', - 'tt', - 'ug', - 'uk', - 'ur', - 'uz', - 'vi', - 'xh', - 'yi', - 'yo', - 'zh', - 'zh-CN', - 'zh-TW', - 'zu', - ]) - .default('en') - -export const robotTextTranslateInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/text/translate').describe(` -You can use the text that we return in your application, or you can pass the text down to other Robots to add a translated subtitle track to a video for example. - -> [!Note] -> **This Robot accepts only files with a \`text/*\` MIME-type,** including plain text and Markdown. For documents in other formats, use [🤖/document/convert](/docs/robots/document-convert/) to first convert them into a compatible text format before proceeding. -`), - provider: aiProviderSchema.describe(` -Which AI provider to leverage. Valid values are \`"aws"\` (Amazon Web Services) and \`"gcp"\` (Google Cloud Platform). - -Transloadit outsources this task and abstracts the interface so you can expect the same data structures, but different latencies and information being returned. Different cloud vendors have different areas they shine in, and we recommend to try out and see what yields the best results for your use case. -`), - target_language: translatableLanguages.describe(` -The desired language to translate to. - -If the exact language can't be found, a generic variant can be fallen back to. For example, if you specify \`"en-US"\`, "en" will be used instead. Please consult the list of supported languages for each provider. -`), - source_language: translatableLanguages.describe(` -The desired language to translate from. - -By default, both providers will detect this automatically, but there are cases where specifying the source language prevents ambiguities. - -If the exact language can't be found, a generic variant can be fallen back to. For example, if you specify \`"en-US"\`, "en" will be used instead. Please consult the list of supported languages for each provider. -`), - }) - .strict() - -export const robotTextTranslateInstructionsWithHiddenFieldsSchema = - robotTextTranslateInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotTextTranslateInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotTextTranslateInstructions = z.infer -export type RobotTextTranslateInstructionsWithHiddenFields = z.infer< - typeof robotTextTranslateInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotTextTranslateInstructionsSchema = interpolateRobot( - robotTextTranslateInstructionsSchema, -) -export type InterpolatableRobotTextTranslateInstructions = - InterpolatableRobotTextTranslateInstructionsInput - -export type InterpolatableRobotTextTranslateInstructionsInput = z.input< - typeof interpolatableRobotTextTranslateInstructionsSchema -> - -export const interpolatableRobotTextTranslateInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotTextTranslateInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotTextTranslateInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotTextTranslateInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotTextTranslateInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotTextTranslateInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/tigris-import.ts b/packages/transloadit/src/alphalib/types/robots/tigris-import.ts deleted file mode 100644 index 5004c3c4..00000000 --- a/packages/transloadit/src/alphalib/types/robots/tigris-import.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - files_per_page, - interpolateRobot, - page_number, - path, - recursive, - return_file_stubs, - robotBase, - robotImport, - tigrisBase, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 10, - discount_factor: 0.1, - discount_pct: 90, - example_code: { - steps: { - imported: { - robot: '/tigris/import', - credentials: 'YOUR_TIGRIS_CREDENTIALS', - path: 'path/to/files/', - recursive: true, - }, - }, - }, - example_code_description: - 'Import files from the `path/to/files` directory and its subdirectories:', - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Importing', - purpose_sentence: 'imports whole directories of files from your Tigris bucket', - purpose_verb: 'import', - purpose_word: 'Tigris', - purpose_words: 'Import files from Tigris', - requires_credentials: true, - service_slug: 'file-importing', - slot_count: 20, - title: 'Import files from Tigris', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'TigrisImportRobot', - priceFactor: 6.6666, - queueSlotCount: 20, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: true, - stage: 'ga', -} - -export const robotTigrisImportInstructionsSchema = robotBase - .merge(robotImport) - .merge(tigrisBase) - .extend({ - robot: z.literal('/tigris/import'), - path: path.describe(` -The path in your bucket to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. - -If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are direct descendants of this directory will be imported. For example: \`images/\`. - -Directories are **not** imported recursively. If you want to import files from subdirectories and sub-subdirectories, enable the \`recursive\` parameter. - -If you want to import all files from the root directory, please use \`/\` as the value here. In this case, make sure all your objects belong to a path. If you have objects in the root of your bucket that aren't prefixed with \`/\`, you'll receive an error: \`A client error (NoSuchKey) occurred when calling the GetObject operation: The specified key does not exist.\` - -You can also use an array of path strings here to import multiple paths in the same Robot's Step. -`), - recursive: recursive.describe(` -Setting this to \`true\` will enable importing files from subdirectories and sub-subdirectories (etc.) of the given path. - -Please use the pagination parameters \`page_number\` and \`files_per_page\` wisely here. -`), - page_number: page_number.describe(` -The pagination page number. For now, in order to not break backwards compatibility in non-recursive imports, this only works when recursive is set to \`true\`. - -When doing big imports, make sure no files are added or removed from other scripts within your path, otherwise you might get weird results with the pagination. -`), - files_per_page: files_per_page.describe(` -The pagination page size. This only works when recursive is \`true\` for now, in order to not break backwards compatibility in non-recursive imports. -`), - return_file_stubs, - bucket_region: z - .string() - .optional() - .describe('The region of your Tigris bucket. This is optional as it can often be derived.'), - }) - .strict() - -export const robotTigrisImportInstructionsWithHiddenFieldsSchema = - robotTigrisImportInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotTigrisImportInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotTigrisImportInstructions = z.infer -export type RobotTigrisImportInstructionsWithHiddenFields = z.infer< - typeof robotTigrisImportInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotTigrisImportInstructionsSchema = interpolateRobot( - robotTigrisImportInstructionsSchema, -) -export type InterpolatableRobotTigrisImportInstructions = - InterpolatableRobotTigrisImportInstructionsInput - -export type InterpolatableRobotTigrisImportInstructionsInput = z.input< - typeof interpolatableRobotTigrisImportInstructionsSchema -> - -export const interpolatableRobotTigrisImportInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotTigrisImportInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotTigrisImportInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotTigrisImportInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotTigrisImportInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotTigrisImportInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/tigris-store.ts b/packages/transloadit/src/alphalib/types/robots/tigris-store.ts deleted file mode 100644 index 28f0a0f1..00000000 --- a/packages/transloadit/src/alphalib/types/robots/tigris-store.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse, tigrisBase } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 6, - discount_factor: 0.15000150001500018, - discount_pct: 84.99984999849998, - example_code: { - steps: { - exported: { - robot: '/tigris/store', - use: ':original', - credentials: 'YOUR_TIGRIS_CREDENTIALS', - path: 'my_target_folder/${unique_prefix}/${file.url_name}', - }, - }, - }, - example_code_description: 'Export uploaded files to `my_target_folder` on Tigris:', - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Exporting', - purpose_sentence: 'exports encoding results to Tigris buckets', - purpose_verb: 'export', - purpose_word: 'Tigris', - purpose_words: 'Export files to Tigris', - service_slug: 'file-exporting', - slot_count: 10, - title: 'Export files to Tigris', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'TigrisStoreRobot', - priceFactor: 6.6666, - queueSlotCount: 10, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotTigrisStoreInstructionsSchema = robotBase - .merge(robotUse) - .merge(tigrisBase) - .extend({ - robot: z.literal('/tigris/store').describe(` -The URL to the result file will be returned in the Assembly Status JSON. -`), - path: z - .string() - .default('${unique_prefix}/${file.url_name}') - .describe(` -The path at which the file is to be stored. This may include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). The path must not be a directory. -`), - acl: z - .enum(['private', 'public-read']) - .default('public-read') - .describe(` -The permissions used for this file. -`), - headers: z - .record(z.string()) - .default({ 'Content-Type': '${file.mime}' }) - .describe(` -An object containing a list of headers to be set for this file on Tigris, such as \`{ FileURL: "\${file.url_name}" }\`. This can also include any available [Assembly Variables](/docs/topics/assembly-instructions/#assembly-variables). - -Object Metadata can be specified using \`x-amz-meta-*\` headers. Note that these headers [do not support non-ASCII metadata values](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html#UserMetadata). -`), - sign_urls_for: z - .number() - .int() - .min(0) - .optional() - .describe(` -This parameter provides signed URLs in the result JSON (in the \`signed_ssl_url\` property). The number that you set this parameter to is the URL expiry time in seconds. - -If this parameter is not used, no URL signing is done. -`), - bucket_region: z - .string() - .optional() - .describe('The region of your Tigris bucket. This is optional as it can often be derived.'), - }) - .strict() - -export const robotTigrisStoreInstructionsWithHiddenFieldsSchema = - robotTigrisStoreInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotTigrisStoreInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotTigrisStoreInstructions = z.infer -export type RobotTigrisStoreInstructionsWithHiddenFields = z.infer< - typeof robotTigrisStoreInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotTigrisStoreInstructionsSchema = interpolateRobot( - robotTigrisStoreInstructionsSchema, -) -export type InterpolatableRobotTigrisStoreInstructions = - InterpolatableRobotTigrisStoreInstructionsInput - -export type InterpolatableRobotTigrisStoreInstructionsInput = z.input< - typeof interpolatableRobotTigrisStoreInstructionsSchema -> - -export const interpolatableRobotTigrisStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotTigrisStoreInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotTigrisStoreInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotTigrisStoreInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotTigrisStoreInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotTigrisStoreInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/tlcdn-deliver.ts b/packages/transloadit/src/alphalib/types/robots/tlcdn-deliver.ts deleted file mode 100644 index b327f51e..00000000 --- a/packages/transloadit/src/alphalib/types/robots/tlcdn-deliver.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { z } from 'zod' -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: false, - bytescount: 20, - discount_factor: 0.05, - discount_pct: 95, - minimum_charge: 102400, - output_factor: 1, - override_lvl1: 'Content Delivery', - purpose_sentence: 'caches and delivers files globally', - purpose_verb: 'cache & deliver', - purpose_word: 'Cache and deliver files', - purpose_words: 'Cache and deliver files globally', - service_slug: 'content-delivery', - slot_count: 0, - title: 'Cache and deliver files globally', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'TlcdnDeliverRobot', - priceFactor: 20, - queueSlotCount: 0, - minimumCharge: 102400, - downloadInputFiles: false, - preserveInputFileUrls: true, - isAllowedForUrlTransform: false, - trackOutputFileSize: false, - isInternal: true, - stage: 'ga', - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, -} - -export const robotTlcdnDeliverInstructionsSchema = robotBase - .extend({ - robot: z.literal('/tlcdn/deliver').describe(` -When you want Transloadit to tranform files on the fly, this Robot can cache and deliver the results close to your end-user, saving on latency and encoding volume. The use of this Robot is implicit when you use the tlcdn.com domain. -`), - }) - .strict() - -export const robotTlcdnDeliverInstructionsWithHiddenFieldsSchema = - robotTlcdnDeliverInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotTlcdnDeliverInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotTlcdnDeliverInstructions = z.infer -export type RobotTlcdnDeliverInstructionsWithHiddenFields = z.infer< - typeof robotTlcdnDeliverInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotTlcdnDeliverInstructionsSchema = interpolateRobot( - robotTlcdnDeliverInstructionsSchema, -) -export type InterpolatableRobotTlcdnDeliverInstructions = - InterpolatableRobotTlcdnDeliverInstructionsInput - -export type InterpolatableRobotTlcdnDeliverInstructionsInput = z.input< - typeof interpolatableRobotTlcdnDeliverInstructionsSchema -> - -export const interpolatableRobotTlcdnDeliverInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotTlcdnDeliverInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotTlcdnDeliverInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotTlcdnDeliverInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotTlcdnDeliverInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotTlcdnDeliverInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/tus-store.ts b/packages/transloadit/src/alphalib/types/robots/tus-store.ts deleted file mode 100644 index 8681b732..00000000 --- a/packages/transloadit/src/alphalib/types/robots/tus-store.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 10, - discount_factor: 0.1, - discount_pct: 90, - example_code: { - steps: { - exported: { - robot: '/tus/store', - use: ':original', - endpoint: 'https://tusd.tusdemo.net/files/', - }, - }, - }, - example_code_description: 'Export uploaded files to the Tus live demo server:', - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Exporting', - purpose_sentence: 'exports encoding results to any Tus-compatible server', - purpose_verb: 'export', - purpose_word: 'Tus servers', - purpose_words: 'Export files to Tus-compatible servers', - service_slug: 'file-exporting', - slot_count: 10, - title: 'Export files to Tus-compatible servers', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'TusStoreRobot', - priceFactor: 10, - queueSlotCount: 10, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotTusStoreInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/tus/store').describe(` -> [!Note] -> This Robot only accepts videos. - -> [!Warning] -> Vimeo's API limits the number of concurrent uploads per minute based on your Vimeo account plan. To see how many videos can be uploaded at once based on your plan, click the following [link](https://developer.vimeo.com/guidelines/rate-limiting#table-1). - -## Installation - -Since Vimeo works with OAuth, you will need to generate [Template Credentials](https://transloadit.com/c/template-credentials/) to use this Robot. - -To change the \`title\` or \`description\` per video, we recommend to [inject variables into your Template](/docs/topics/templates/). -`), - endpoint: z - .string() - .url() - .describe('The URL of the destination Tus server') - .describe(` -The URL of the Tus-compatible server, which you're uploading files to. -`), - credentials: z - .string() - .optional() - .describe(` -Create Template Credentials for this Robot in your [Transloadit account](/c/template-credentials/) and use the name of the Template Credentials as this parameter's value. For this Robot, use the HTTP template, which allows request headers to be passed along to the destination server. -`), - headers: z - .record(z.string()) - .default({}) - .describe('Headers to pass along to destination') - .describe(` -Optional extra headers outside of the Template Credentials can be passed along within this parameter. - -Although, we recommend to exclusively use Template Credentials, this may be necessary if you're looking to use dynamic credentials, which isn't a feature supported by Template Credentials. -`), - metadata: z - .record(z.string()) - .default({ filename: 'example.png', basename: 'example', extension: 'png' }) - .describe(` -Metadata to pass along to destination. Includes some file info by default. -`), - url_template: z - .string() - .optional() - .describe(` -The URL of the file in the Assembly Status JSON. The following [Assembly Variables](/docs/topics/assembly-instructions/#assembly-variables) are supported. If this is not specified, the upload URL specified by the destination server will be used instead. -`), - ssl_url_template: z - .string() - .optional() - .describe(` -The SSL URL of the file in the Assembly Status JSON. The following [Assembly Variables](/docs/topics/assembly-instructions/#assembly-variables) are supported. If this is not specified, the upload URL specified by the destination server will be used instead, as long as it starts with \`https\`. -`), - }) - .strict() - -export const robotTusStoreInstructionsWithHiddenFieldsSchema = - robotTusStoreInstructionsSchema.extend({ - result: z.union([z.literal('debug'), robotTusStoreInstructionsSchema.shape.result]).optional(), - }) - -export type RobotTusStoreInstructions = z.infer -export type RobotTusStoreInstructionsWithHiddenFields = z.infer< - typeof robotTusStoreInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotTusStoreInstructionsSchema = interpolateRobot( - robotTusStoreInstructionsSchema, -) -export type InterpolatableRobotTusStoreInstructions = InterpolatableRobotTusStoreInstructionsInput - -export type InterpolatableRobotTusStoreInstructionsInput = z.input< - typeof interpolatableRobotTusStoreInstructionsSchema -> - -export const interpolatableRobotTusStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotTusStoreInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotTusStoreInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotTusStoreInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotTusStoreInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotTusStoreInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/upload-handle.ts b/packages/transloadit/src/alphalib/types/robots/upload-handle.ts deleted file mode 100644 index b4167015..00000000 --- a/packages/transloadit/src/alphalib/types/robots/upload-handle.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: false, - bytescount: 10, - discount_factor: 0.1, - discount_pct: 90, - example_code: { - steps: { - ':original': { - robot: '/upload/handle', - }, - exported: { - robot: '/s3/store', - use: ':original', - credentials: 'YOUR_S3_CREDENTIALS', - }, - }, - }, - example_code_description: 'Handle uploads and export the uploaded files to S3:', - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'Handling Uploads', - purpose_sentence: - 'receives uploads that your users throw at you from browser or apps, or that you throw at us programmatically', - purpose_verb: 'handle', - purpose_word: 'handle uploads', - purpose_words: 'Handle uploads', - service_slug: 'handling-uploads', - slot_count: 0, - title: 'Handle uploads', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'UploadHandleRobot', - priceFactor: 10, - queueSlotCount: 0, - downloadInputFiles: false, - preserveInputFileUrls: true, - isAllowedForUrlTransform: false, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotUploadHandleInstructionsSchema = robotBase - .extend({ - robot: z.literal('/upload/handle').describe(` -Transloadit handles file uploads by default, so specifying this Robot is optional. - -It can still be a good idea to define this Robot, though. It makes your Assembly Instructions explicit, and allows you to configure exactly how uploads should be handled. For example, you can extract specific metadata from the uploaded files. - -There are **3 important constraints** when using this Robot: - -1. Don’t define a \`use\` parameter, unlike with other Robots. -2. Use it only once in a single set of Assembly Instructions. -3. Name the Step as \`:original\`. -`), - }) - .strict() - -export const robotUploadHandleInstructionsWithHiddenFieldsSchema = - robotUploadHandleInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotUploadHandleInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotUploadHandleInstructions = z.infer -export type RobotUploadHandleInstructionsWithHiddenFields = z.infer< - typeof robotUploadHandleInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotUploadHandleInstructionsSchema = interpolateRobot( - robotUploadHandleInstructionsSchema, -) -export type InterpolatableRobotUploadHandleInstructions = - InterpolatableRobotUploadHandleInstructionsInput - -export type InterpolatableRobotUploadHandleInstructionsInput = z.input< - typeof interpolatableRobotUploadHandleInstructionsSchema -> - -export const interpolatableRobotUploadHandleInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotUploadHandleInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotUploadHandleInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotUploadHandleInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotUploadHandleInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotUploadHandleInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/video-adaptive.ts b/packages/transloadit/src/alphalib/types/robots/video-adaptive.ts deleted file mode 100644 index 1afdbd66..00000000 --- a/packages/transloadit/src/alphalib/types/robots/video-adaptive.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { z } from 'zod' - -import { stackVersions } from '../stackVersions.ts' -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - interpolateRobot, - robotBase, - robotFFmpegVideo, - robotUse, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: false, - bytescount: Number.POSITIVE_INFINITY, - discount_factor: 1, - discount_pct: 0, - example_code: { - steps: { - ':original': { - robot: '/upload/handle', - }, - encoded_480p: { - robot: '/video/encode', - use: ':original', - preset: 'hls/480p', - ffmpeg_stack: stackVersions.ffmpeg.recommendedVersion, - }, - encoded_720p: { - robot: '/video/encode', - use: ':original', - preset: 'hls/720p', - ffmpeg_stack: stackVersions.ffmpeg.recommendedVersion, - }, - encoded_1080p: { - robot: '/video/encode', - use: ':original', - preset: 'hls/1080p', - ffmpeg_stack: stackVersions.ffmpeg.recommendedVersion, - }, - hls_bundled: { - robot: '/video/adaptive', - use: { - steps: ['encoded_480p', 'encoded_720p', 'encoded_1080p'], - bundle_steps: true, - }, - technique: 'hls', - playlist_name: 'my_playlist.m3u8', - }, - }, - }, - example_code_description: - 'Implementing HTTP Live Streaming: encode the uploaded video into three versions, then cut them into several segments and generate playlist files containing all the segments:', - minimum_charge: 0, - output_factor: 1.2, - override_lvl1: 'Video Encoding', - purpose_sentence: - 'encodes videos into HTTP Live Streaming (HLS) and MPEG-Dash supported formats and generates the necessary manifest and playlist files', - purpose_verb: 'convert', - purpose_word: 'make adaptive', - purpose_words: 'Convert videos to HLS and MPEG-Dash', - service_slug: 'video-encoding', - slot_count: 60, - title: 'Convert videos to HLS and MPEG-Dash', - typical_file_size_mb: 80, - typical_file_type: 'video', - name: 'VideoAdaptiveRobot', - priceFactor: 1, - queueSlotCount: 60, - isAllowedForUrlTransform: false, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotVideoAdaptiveInstructionsSchema = robotBase - .merge(robotUse) - .merge(robotFFmpegVideo) - .extend({ - robot: z.literal('/video/adaptive').describe(` -This Robot accepts all types of video files and audio files. Do not forget to use Step bundling in your \`use\` parameter to make the Robot work on several input files at once. - -This Robot is normally used in combination with [🤖/video/encode](/docs/robots/video-encode/). We have implemented video and audio encoding presets specifically for MPEG-Dash and HTTP Live Streaming support. These presets are prefixed with \`"dash/"\` and \`"hls/"\`. [View a HTTP Live Streaming demo here](/demos/video-encoding/implement-http-live-streaming/). - -### Required CORS settings for MPEG-Dash and HTTP Live Streaming - -Playing back MPEG-Dash Manifest or HLS playlist files requires a proper CORS setup on the server-side. The file-serving server should be configured to add the following header fields to responses: - -\`\`\` -Access-Control-Allow-Origin: * -Access-Control-Allow-Methods: GET -Access-Control-Allow-Headers: * -\`\`\` - -If the files are stored in an Amazon S3 Bucket, you can use the following [CORS definition](https://docs.aws.amazon.com/AmazonS3/latest/userguide/ManageCorsUsing.html) to ensure the CORS header fields are set correctly: - -\`\`\`json -[ - { - "AllowedHeaders": ["*"], - "AllowedMethods": ["GET"], - "AllowedOrigins": ["*"], - "ExposeHeaders": [] - } -] -\`\`\` - -To set up CORS for your S3 bucket: - -1. Visit -1. Click on your bucket -1. Click "Permissions" -1. Edit "Cross-origin resource sharing (CORS)" - -### Storing Segments and Playlist files - -The Robot gives its result files (segments, initialization segments, MPD manifest files and M3U8 playlist files) the right metadata property \`relative_path\`, so that you can store them easily using one of our storage Robots. - -In the \`path\` parameter of the storage Robot of your choice, use the Assembly Variable \`\${file.meta.relative_path}\` to store files in the proper paths to make the playlist files work. -`), - technique: z - .enum(['dash', 'hls']) - .default('dash') - .describe(` -Determines which streaming technique should be used. Currently supports \`"dash"\` for MPEG-Dash and \`"hls"\` for HTTP Live Streaming. -`), - playlist_name: z - .string() - .optional() - .describe(` -The filename for the generated manifest/playlist file. The default is \`"playlist.mpd"\` if your \`technique\` is \`"dash"\`, and \`"playlist.m3u8"\` if your \`technique\` is \`"hls"\`. -`), - segment_duration: z - .number() - .int() - .default(10) - .describe(` -The duration for each segment in seconds. -`), - closed_captions: z - .boolean() - .default(true) - .describe(` -Determines whether you want closed caption support when using the \`"hls"\` technique. -`), - }) - .strict() - -export const robotVideoAdaptiveInstructionsWithHiddenFieldsSchema = - robotVideoAdaptiveInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotVideoAdaptiveInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotVideoAdaptiveInstructions = z.infer -export type RobotVideoAdaptiveInstructionsWithHiddenFields = z.infer< - typeof robotVideoAdaptiveInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotVideoAdaptiveInstructionsSchema = interpolateRobot( - robotVideoAdaptiveInstructionsSchema, -) -export type InterpolatableRobotVideoAdaptiveInstructions = - InterpolatableRobotVideoAdaptiveInstructionsInput - -export type InterpolatableRobotVideoAdaptiveInstructionsInput = z.input< - typeof interpolatableRobotVideoAdaptiveInstructionsSchema -> - -export const interpolatableRobotVideoAdaptiveInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotVideoAdaptiveInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotVideoAdaptiveInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotVideoAdaptiveInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotVideoAdaptiveInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotVideoAdaptiveInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/video-concat.ts b/packages/transloadit/src/alphalib/types/robots/video-concat.ts deleted file mode 100644 index 492ce2d6..00000000 --- a/packages/transloadit/src/alphalib/types/robots/video-concat.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - interpolateRobot, - robotBase, - robotFFmpegVideo, - robotUse, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: false, - bytescount: 4, - discount_factor: 0.25, - discount_pct: 75, - example_code: { - steps: { - concatenated: { - robot: '/video/concat', - use: { - steps: [ - { - name: ':original', - fields: 'first_video_file', - as: 'video_1', - }, - { - name: ':original', - fields: 'second_video_file', - as: 'video_2', - }, - { - name: ':original', - fields: 'third_video_file', - as: 'video_3', - }, - ], - }, - }, - }, - }, - example_code_description: - 'If you have a form with 3 file input fields and want to concatenate the uploaded videos in a specific order, instruct Transloadit using the `name` attribute of each input field. Use this attribute as the value for the `fields` key in the JSON, and set `as` to `video_[[index]]`. Transloadit will concatenate the files based on the ascending index order:', - minimum_charge: 0, - output_factor: 0.6, - override_lvl1: 'Video Encoding', - purpose_sentence: 'concatenates several videos together', - purpose_verb: 'concatenate', - purpose_word: 'concatenate', - purpose_words: 'Concatenate videos', - service_slug: 'video-encoding', - slot_count: 60, - title: 'Concatenate videos', - typical_file_size_mb: 80, - typical_file_type: 'video', - uses_tools: ['ffmpeg'], - name: 'VideoConcatRobot', - priceFactor: 4, - queueSlotCount: 60, - isAllowedForUrlTransform: false, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotVideoConcatInstructionsSchema = robotBase - .merge(robotUse) - .merge(robotFFmpegVideo) - .extend({ - robot: z.literal('/video/concat').describe(` -> [!Note] -> Input videos may have differing dimensions and streams - the Robot can handle this fine. It will pre-transcode the input videos if necessary before concatenation at no additional cost. - -Itʼs possible to concatenate a virtually infinite number of video files using [🤖/video/concat](/docs/robots/video-concat/). -`), - video_fade_seconds: z - .number() - .default(1) - .describe(` -When used this adds a video fade in and out effect between each section of your concatenated video. The float value is used so if you want a video delay effect of 500 milliseconds between each video section you would select \`0.5\`, however, integer values can also be represented. - -This parameter does not add a video fade effect at the beginning or end of your video. If you want to do so, create an additional [🤖/video/encode](/docs/robots/video-encode/) Step and use our \`ffmpeg\` parameter as shown in this [demo](/demos/video-encoding/concatenate-fade-effect/). - -Please note this parameter is independent of adding audio fades between sections. -`), - audio_fade_seconds: z - .number() - .default(1) - .describe(` -When used this adds an audio fade in and out effect between each section of your concatenated video. The float value is used so if you want an audio delay effect of 500 milliseconds between each video section you would select \`0.5\`, however, integer values can also be represented. - -This parameter does not add an audio fade effect at the beginning or end of your video. If you want to do so, create an additional [🤖/video/encode](/docs/robots/video-encode/] Step and use our \`ffmpeg\` parameter as shown in this [demo](/demos/audio-encoding/ffmpeg-fade-in-and-out/). - -Please note this parameter is independent of adding video fades between sections. -`), - }) - .strict() - -export const robotVideoConcatInstructionsWithHiddenFieldsSchema = - robotVideoConcatInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotVideoConcatInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotVideoConcatInstructions = z.infer -export type RobotVideoConcatInstructionsWithHiddenFields = z.infer< - typeof robotVideoConcatInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotVideoConcatInstructionsSchema = interpolateRobot( - robotVideoConcatInstructionsSchema, -) -export type InterpolatableRobotVideoConcatInstructions = - InterpolatableRobotVideoConcatInstructionsInput - -export type InterpolatableRobotVideoConcatInstructionsInput = z.input< - typeof interpolatableRobotVideoConcatInstructionsSchema -> - -export const interpolatableRobotVideoConcatInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotVideoConcatInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotVideoConcatInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotVideoConcatInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotVideoConcatInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotVideoConcatInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/video-encode.ts b/packages/transloadit/src/alphalib/types/robots/video-encode.ts deleted file mode 100644 index e34e4f09..00000000 --- a/packages/transloadit/src/alphalib/types/robots/video-encode.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - interpolateRobot, - robotBase, - robotUse, - videoEncodeSpecificInstructionsSchema, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: false, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - example_code: { - steps: { - hevc_encoded: { - robot: '/video/encode', - use: ':original', - preset: 'hevc', - }, - }, - }, - example_code_description: - 'Transcode uploaded video to [HEVC](https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding) (H.265):', - minimum_charge: 0, - output_factor: 0.6, - override_lvl1: 'Video Encoding', - purpose_sentence: 'encodes, resizes, applies watermarks to videos and animated GIFs', - purpose_verb: 'transcode', - purpose_word: 'transcode/resize/watermark', - purpose_words: 'Transcode, resize, or watermark videos', - service_slug: 'video-encoding', - slot_count: 60, - title: 'Transcode, resize, or watermark videos', - typical_file_size_mb: 80, - typical_file_type: 'video', - uses_tools: ['ffmpeg'], - name: 'VideoEncodeRobot', - priceFactor: 1, - queueSlotCount: 60, - isAllowedForUrlTransform: false, - trackOutputFileSize: true, - isInternal: false, - stage: 'ga', - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, -} - -export const robotVideoEncodeInstructionsSchema = robotBase - .merge(robotUse) - .merge(videoEncodeSpecificInstructionsSchema) - .extend({ - robot: z.literal('/video/encode').describe(` -The /video/encode Robot is a versatile tool for video processing that handles transcoding, resizing, and watermarking. It supports various formats including modern standards like HEVC (H.265), and provides features such as presets for common devices, custom FFmpeg parameters for powerusers, watermark positioning, and more. - -## Adding text overlays with FFmpeg - -You can add text overlays to videos using FFmpeg's \`drawtext\` filter through this Robot's \`ffmpeg\` parameter. Here are two examples — one with the default font and one with a custom font family name: - -\`\`\`json -{ - "steps": { - ":original": { - "robot": "/upload/handle" - }, - "text_overlay_default": { - "use": ":original", - "robot": "/video/encode", - "preset": "empty", - "ffmpeg_stack": "{{stacks.ffmpeg.recommended_version}}", - "ffmpeg": { - "codec:a": "copy", - "vf": "drawtext=text='My text overlay':fontcolor=white:fontsize=24:box=1:boxcolor=black@0.5:boxborderw=5:x=(w-text_w)/2:y=(h-text_h)/2" - }, - "result": true - }, - "text_overlay_custom": { - "use": ":original", - "robot": "/video/encode", - "preset": "empty", - "ffmpeg_stack": "{{stacks.ffmpeg.recommended_version}}", - "ffmpeg": { - "codec:a": "copy", - "vf": "drawtext=font='Times New Roman':text='My text overlay':fontcolor=white:fontsize=24:box=1:boxcolor=black@0.5:boxborderw=5:x=(w-text_w)/2:y=(h-text_h)/2" - }, - "result": true - } - } -} -\`\`\` - -**Notes:** - -- Use the \`font\` attribute to reference a font by family name with FFmpeg's \`drawtext\` -- FFmpeg font family names typically do not contain dashes (e.g. \`Times New Roman\`), while - ImageMagick uses dashed names (e.g. \`Times-New-Roman\`). -- Preserve the source audio by setting \`"codec:a": "copy"\`. -- Position text with the \`x\` and \`y\` expressions. The example above centers the text. - -See the live demo [here](/demos/video-encoding/add-text-overlay/). -`), - font_size: z.number().optional(), - font_color: z.string().optional(), - text_background_color: z.string().optional(), - }) - .strict() - -export const robotVideoEncodeInstructionsWithHiddenFieldsSchema = - robotVideoEncodeInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotVideoEncodeInstructionsSchema.shape.result]) - .optional(), - chunked_transcoding: z.boolean().optional(), - realtime: z.boolean().optional(), - }) - -export type RobotVideoEncodeInstructions = z.infer -export type RobotVideoEncodeInstructionsWithHiddenFields = z.infer< - typeof robotVideoEncodeInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotVideoEncodeInstructionsSchema = interpolateRobot( - robotVideoEncodeInstructionsSchema, -) -export type InterpolatableRobotVideoEncodeInstructions = - InterpolatableRobotVideoEncodeInstructionsInput - -export type InterpolatableRobotVideoEncodeInstructionsInput = z.input< - typeof interpolatableRobotVideoEncodeInstructionsSchema -> - -export const interpolatableRobotVideoEncodeInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotVideoEncodeInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotVideoEncodeInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotVideoEncodeInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotVideoEncodeInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotVideoEncodeInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/video-merge.ts b/packages/transloadit/src/alphalib/types/robots/video-merge.ts deleted file mode 100644 index 2858703e..00000000 --- a/packages/transloadit/src/alphalib/types/robots/video-merge.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - color_with_alpha, - interpolateRobot, - resize_strategy, - robotBase, - robotFFmpegVideo, - robotUse, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: false, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - minimum_charge: 0, - output_factor: 0.6, - override_lvl1: 'Video Encoding', - purpose_sentence: - 'composes a new video by adding an audio track to existing still image(s) or video', - purpose_verb: 'merge', - purpose_word: 'merge', - purpose_words: 'Merge video, audio, images into one video', - service_slug: 'video-encoding', - slot_count: 60, - title: 'Merge video, audio, images into one video', - typical_file_size_mb: 80, - typical_file_type: 'video', - uses_tools: ['ffmpeg'], - name: 'VideoMergeRobot', - priceFactor: 1, - queueSlotCount: 60, - isAllowedForUrlTransform: false, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotVideoMergeInstructionsSchema = robotBase - .merge(robotUse) - .merge(robotFFmpegVideo) - .extend({ - robot: z.literal('/video/merge'), - resize_strategy: resize_strategy.describe(` -If the given width/height parameters are bigger than the input image's dimensions, then the \`resize_strategy\` determines how the image will be resized to match the provided width/height. See the [available resize strategies](/docs/topics/resize-strategies/). -`), - background: color_with_alpha.default('#00000000').describe(` -The background color of the resulting video the \`"rrggbbaa"\` format (red, green, blue, alpha) when used with the \`"pad"\` resize strategy. The default color is black. -`), - framerate: z - .union([z.number().int().min(1), z.string().regex(/^\d+(?:\/\d+)?$/)]) - .default('1/5') - .describe(` -When merging images to generate a video this is the input framerate. A value of "1/5" means each image is given 5 seconds before the next frame appears (the inverse of a framerate of "5"). Likewise for "1/10", "1/20", etc. A value of "5" means there are 5 frames per second. -`), - image_durations: z - .array(z.number()) - .default([]) - .describe(` -When merging images to generate a video this allows you to define how long (in seconds) each image will be shown inside of the video. So if you pass 3 images and define \`[2.4, 5.6, 9]\` the first image will be shown for 2.4s, the second image for 5.6s and the last one for 9s. The \`duration\` parameter will automatically be set to the sum of the image_durations, so \`17\` in our example. It can still be overwritten, though, in which case the last image will be shown until the defined duration is reached. -`), - duration: z - .number() - .default(5) - .describe(` -When merging images to generate a video or when merging audio and video this is the desired target duration in seconds. The float value can take one decimal digit. If you want all images to be displayed exactly once, then you can set the duration according to this formula: \`duration = numberOfImages / framerate\`. This also works for the inverse framerate values like \`1/5\`. - -If you set this value to \`null\` (default), then the duration of the input audio file will be used when merging images with an audio file. - -When merging audio files and video files, the duration of the longest video or audio file is used by default. -`), - audio_delay: z - .number() - .default(0) - .describe(` -When merging a video and an audio file, and when merging images and an audio file to generate a video, this is the desired delay in seconds for the audio file to start playing. Imagine you merge a video file without sound and an audio file, but you wish the audio to start playing after 5 seconds and not immediately, then this is the parameter to use. -`), - loop: z - .boolean() - .default(false) - .describe(` - Determines whether the shorter media file should be looped to match the duration of the longer one. For example, if you merge a 1-minute video with a 3-minute audio file and enable this option, the video will play three times in a row to match the audio length.`), - replace_audio: z - .boolean() - .default(false) - .describe(` -Determines whether the audio of the video should be replaced with a provided audio file. -`), - vstack: z - .boolean() - .default(false) - .describe(` -Stacks the input media vertically. All streams need to have the same pixel format and width - so consider using a [/video/encode](/docs/robots/video-encode/) Step before using this parameter to enforce this. -`), - image_url: z - .string() - .url() - .optional() - .describe(` -The URL of an image to be merged with the audio or video. When this parameter is provided, the robot will download the image from the URL and merge it with the other media. -`), - }) - .strict() - -export const robotVideoMergeInstructionsWithHiddenFieldsSchema = - robotVideoMergeInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotVideoMergeInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotVideoMergeInstructions = z.infer -export type RobotVideoMergeInstructionsWithHiddenFields = z.infer< - typeof robotVideoMergeInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotVideoMergeInstructionsSchema = interpolateRobot( - robotVideoMergeInstructionsSchema, -) -export type InterpolatableRobotVideoMergeInstructions = - InterpolatableRobotVideoMergeInstructionsInput - -export type InterpolatableRobotVideoMergeInstructionsInput = z.input< - typeof interpolatableRobotVideoMergeInstructionsSchema -> - -export const interpolatableRobotVideoMergeInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotVideoMergeInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotVideoMergeInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotVideoMergeInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotVideoMergeInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotVideoMergeInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/video-ondemand.ts b/packages/transloadit/src/alphalib/types/robots/video-ondemand.ts deleted file mode 100644 index 8f4356b8..00000000 --- a/packages/transloadit/src/alphalib/types/robots/video-ondemand.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - interpolateRobot, - robotBase, - robotUse, - videoEncodeSpecificInstructionsSchema, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - discount_factor: 1, - discount_pct: 0, - bytescount: 1, - example_code: { - steps: { - import: { - robot: '/s3/import', - path: '${fields.input}', - credentials: 'YOUR_AWS_CREDENTIALS', - return_file_stubs: true, - }, - vod: { - robot: '/video/ondemand', - use: 'import', - variants: { - '480p': { - preset: 'hls/480p', - ffmpeg_stack: '{{ stacks.ffmpeg.recommended_version }}', - }, - '720p': { - preset: 'hls/720p', - ffmpeg_stack: '{{ stacks.ffmpeg.recommended_version }}', - }, - '1080p': { - preset: 'hls/1080p', - ffmpeg_stack: '{{ stacks.ffmpeg.recommended_version }}', - }, - }, - }, - serve: { - use: 'vod', - robot: '/file/serve', - }, - }, - }, - example_code_description: - 'Enable streaming of a video stored on S3 in three variants (480p, 720p, 1080p) with on-demand encoding:', - minimum_charge: 0, - output_factor: 0.6, - override_lvl1: 'Video Encoding', - purpose_sentence: - 'generates HTTP Live Streaming (HLS) playlists and segments on-demand for adaptive and cost-efficient playback', - purpose_verb: 'stream', - purpose_word: 'stream', - purpose_words: 'Stream videos with on-demand encoding', - service_slug: 'video-encoding', - slot_count: 60, - title: 'Stream videos with on-demand encoding', - typical_file_size_mb: 300, - typical_file_type: 'video', - name: 'VideoOndemandRobot', - priceFactor: 1, - queueSlotCount: 60, - downloadInputFiles: false, - isAllowedForUrlTransform: true, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'beta', -} - -export const robotVideoOndemandInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/video/ondemand'), - variants: z - .record(videoEncodeSpecificInstructionsSchema) - .describe( - 'Defines the variants the video player can choose from. The keys are the names of the variant as they will appear in the generated playlists and URLs.', - ), - enabled_variants: z - .union([z.string(), z.array(z.string())]) - .optional() - .describe( - 'Specifies which variants, defined in the variants parameter, are enabled. Non-enabled variants will not be included in the master playlist.', - ), - segment_duration: z - .number() - .optional() - .default(6) - .describe('The duration of each segment in seconds.'), - sign_urls_for: z - .number() - .optional() - .default(0) - .describe( - 'When signing URLs is enabled, the URLs in the generated playlist files will be signed. This parameter specifies the duration (in seconds) that the signed URLs will remain valid.', - ), - asset: z - .string() - .optional() - .describe( - 'Controls which file is generated. For example, if the parameter is unset, a master playlist referencing the variants is generated.', - ), - asset_param_name: z - .string() - .optional() - .default('asset') - .describe( - 'Specifies from which URL parameter the asset parameter value is taken and which URL parameter to use when generating playlist files.', - ), - }) - .strict() - -export const robotVideoOndemandInstructionsWithHiddenFieldsSchema = - robotVideoOndemandInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotVideoOndemandInstructionsSchema.shape.result]) - .optional(), - cdn_required_bypass: z - .boolean() - .optional() - .default(false) - .describe( - 'Internal parameter that indicates whether `cdn=required` should be added to the URLs in playlists. Useful for testing with URL Transform directly and not through Smart CDN.', - ), - url_transform_format: z - .boolean() - .optional() - .default(false) - .describe( - 'Internal parameter that indicates whether the URLs in playlists should use the Smart CDN or the URL Transform format.', - ), - }) - -export type RobotVideoOndemandInstructions = z.infer -export type RobotVideoOndemandInstructionsWithHiddenFields = z.infer< - typeof robotVideoOndemandInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotVideoOndemandInstructionsSchema = interpolateRobot( - robotVideoOndemandInstructionsSchema, -) -export type InterpolatableRobotVideoOndemandInstructions = - InterpolatableRobotVideoOndemandInstructionsInput - -export type InterpolatableRobotVideoOndemandInstructionsInput = z.input< - typeof interpolatableRobotVideoOndemandInstructionsSchema -> - -export const interpolatableRobotVideoOndemandInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotVideoOndemandInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotVideoOndemandInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotVideoOndemandInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotVideoOndemandInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotVideoOndemandInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/video-subtitle.ts b/packages/transloadit/src/alphalib/types/robots/video-subtitle.ts deleted file mode 100644 index 760c5ee7..00000000 --- a/packages/transloadit/src/alphalib/types/robots/video-subtitle.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { z } from 'zod' - -import { stackVersions } from '../stackVersions.ts' -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - color_with_alpha, - color_without_alpha, - interpolateRobot, - positionSchema, - robotBase, - robotFFmpegVideo, - robotUse, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: false, - bytescount: 1, - discount_factor: 1, - discount_pct: 0, - example_code: { - steps: { - subtitled: { - robot: '/video/subtitle', - use: { - steps: [ - { - name: ':original', - fields: 'input_video', - as: 'video', - }, - { - name: ':original', - fields: 'input_srt', - as: 'subtitles', - }, - ], - }, - ffmpeg_stack: stackVersions.ffmpeg.recommendedVersion, - }, - }, - }, - example_code_description: - 'If you have two file input fields in a form — one for a video and another for an SRT or VTT subtitle, named `input_video` and `input_srt` respectively (with the HTML `name` attribute), hereʼs how to embed the subtitles into the video with Transloadit:', - minimum_charge: 0, - output_factor: 0.6, - override_lvl1: 'Video Encoding', - purpose_sentence: 'adds subtitles and closed captions to videos', - purpose_verb: 'subtitle', - purpose_word: 'subtitle', - purpose_words: 'Add subtitles to videos', - service_slug: 'video-encoding', - slot_count: 60, - title: 'Add subtitles to videos', - typical_file_size_mb: 80, - typical_file_type: 'video', - uses_tools: ['ffmpeg'], - name: 'VideoSubtitleRobot', - priceFactor: 1, - queueSlotCount: 60, - isAllowedForUrlTransform: false, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotVideoSubtitleInstructionsSchema = robotBase - .merge(robotUse) - .merge(robotFFmpegVideo) - .extend({ - robot: z.literal('/video/subtitle').describe(` -This Robot supports both SRT and VTT subtitle files. -`), - subtitles_type: z - .enum(['burned', 'external', 'burn']) - .transform((val) => (val === 'burn' ? 'burned' : val)) - .default('external') - .describe(` -Determines if subtitles are added as a separate stream to the video (value \`"external"\`) that then can be switched on and off in your video player, or if they should be burned directly into the video (value \`"burned"\` or \`"burn"\`) so that they become part of the video stream. -`), - border_style: z - .enum(['box', 'outline', 'shadow']) - .default('outline') - .describe(` -Specifies the style of the subtitle. Use the \`border_color\` parameter to specify the color of the border. -`), - border_color: color_with_alpha.default('40000000').describe(` -The color for the subtitle border. The first two hex digits specify the alpha value of the color. -`), - // TODO: Make font an enum - font: z - .string() - .default('Arial') - .describe(` -The font family to use. Also includes boldness and style of the font. - -[Here](/docs/supported-formats/fonts/) is a list of all supported fonts. -`), - font_color: color_without_alpha.default('FFFFFF').describe(` -The color of the subtitle text. The first two hex digits specify the alpha value of the color. -`), - font_size: z - .number() - .int() - .min(1) - .default(16) - .describe(` -Specifies the size of the text. -`), - position: positionSchema.default('bottom').describe(` -Specifies the position of the subtitles. -`), - language: z - .string() - .optional() - .nullable() - .describe(` -Specifies the language of the subtitles. Only used if the subtitles are external. -`), - keep_subtitles: z - .boolean() - .default(false) - .describe(` -Specifies if existing subtitles in the input file should be kept or be replaced by the new subtitle. Only used if the subtitles are external. -`), - }) - .strict() - -export const robotVideoSubtitleInstructionsWithHiddenFieldsSchema = - robotVideoSubtitleInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotVideoSubtitleInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotVideoSubtitleInstructions = z.infer -export type RobotVideoSubtitleInstructionsWithHiddenFields = z.infer< - typeof robotVideoSubtitleInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotVideoSubtitleInstructionsSchema = interpolateRobot( - robotVideoSubtitleInstructionsSchema, -) -export type InterpolatableRobotVideoSubtitleInstructions = - InterpolatableRobotVideoSubtitleInstructionsInput - -export type InterpolatableRobotVideoSubtitleInstructionsInput = z.input< - typeof interpolatableRobotVideoSubtitleInstructionsSchema -> - -export const interpolatableRobotVideoSubtitleInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotVideoSubtitleInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotVideoSubtitleInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotVideoSubtitleInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotVideoSubtitleInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotVideoSubtitleInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/video-thumbs.ts b/packages/transloadit/src/alphalib/types/robots/video-thumbs.ts deleted file mode 100644 index 77bc03b7..00000000 --- a/packages/transloadit/src/alphalib/types/robots/video-thumbs.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { z } from 'zod' - -import { stackVersions } from '../stackVersions.ts' -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - color_with_alpha, - interpolateRobot, - percentageSchema, - resize_strategy, - robotBase, - robotFFmpeg, - robotUse, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: false, - bytescount: 10, - discount_factor: 0.1, - discount_pct: 90, - example_code: { - steps: { - thumbnailed: { - robot: '/video/thumbs', - use: ':original', - count: 10, - ffmpeg_stack: stackVersions.ffmpeg.recommendedVersion, - }, - }, - }, - example_code_description: 'Extract 10 thumbnails from each uploaded video:', - minimum_charge: 0, - output_factor: 0.05, - override_lvl1: 'Video Encoding', - purpose_sentence: 'extracts any number of images from videos for use as previews', - purpose_verb: 'extract', - purpose_word: 'thumbnail', - purpose_words: 'Extract thumbnails from videos', - service_slug: 'video-encoding', - slot_count: 15, - title: 'Extract thumbnails from videos', - typical_file_size_mb: 80, - typical_file_type: 'video', - uses_tools: ['ffmpeg'], - name: 'VideoThumbsRobot', - priceFactor: 10, - queueSlotCount: 15, - isAllowedForUrlTransform: false, - trackOutputFileSize: true, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotVideoThumbsInstructionsSchema = robotBase - .merge(robotUse) - .merge(robotFFmpeg) - .extend({ - robot: z.literal('/video/thumbs').describe(` -> [!Note] -> Even though thumbnails are extracted from videos in parallel, we sort the thumbnails before adding them to the Assembly results. So the order in which they appear there reflects the order in which they appear in the video. You can also make sure by checking the thumb_index meta key. -`), - count: z - .number() - .int() - .min(1) - .max(999) - .default(8) - .describe(` -The number of thumbnails to be extracted. As some videos have incorrect durations, the actual number of thumbnails generated may be less in rare cases. The maximum number of thumbnails we currently allow is 999. - -The thumbnails are taken at regular intervals, determined by dividing the video duration by the count. For example, a count of 3 will produce thumbnails at 25%, 50% and 75% through the video. - -To extract thumbnails for specific timestamps, use the \`offsets\` parameter. -`), - offsets: z - .union([z.array(z.number()), z.array(percentageSchema)]) - .default([]) - .describe(` -An array of offsets representing seconds of the file duration, such as \`[ 2, 45, 120 ]\`. Millisecond durations of a file can also be used by using decimal place values. For example, an offset from 1250 milliseconds would be represented with \`1.25\`. Offsets can also be percentage values such as \`[ "2%", "50%", "75%" ]\`. - -This option cannot be used with the \`count\` parameter, and takes precedence if both are specified. Out-of-range offsets are silently ignored. -`), - format: z - .enum(['jpeg', 'jpg', 'png']) - .default('jpeg') - .describe(` -The format of the extracted thumbnail. Supported values are \`"jpg"\`, \`"jpeg"\` and \`"png"\`. Even if you specify the format to be \`"jpeg"\` the resulting thumbnails will have a \`"jpg"\` file extension. -`), - width: z - .number() - .int() - .min(1) - .max(1920) - .optional() - .describe(` -The width of the thumbnail, in pixels. Defaults to the original width of the video. -`), - height: z - .number() - .int() - .min(1) - .max(1080) - .optional() - .describe(` -The height of the thumbnail, in pixels. Defaults to the original height of the video. -`), - resize_strategy: resize_strategy.describe(` -One of the [available resize strategies](/docs/topics/resize-strategies/). -`), - background: color_with_alpha.default('#00000000').describe(` -The background color of the resulting thumbnails in the \`"rrggbbaa"\` format (red, green, blue, alpha) when used with the \`"pad"\` resize strategy. The default color is black. -`), - rotate: z - .union([z.literal(0), z.literal(90), z.literal(180), z.literal(270), z.literal(360)]) - .default(0) - .describe(` -Forces the video to be rotated by the specified degree integer. Currently, only multiples of 90 are supported. We automatically correct the orientation of many videos when the orientation is provided by the camera. This option is only useful for videos requiring rotation because it was not detected by the camera. -`), - input_codec: z - .string() - .optional() - .describe(` -Specifies the input codec to use when decoding the video. This is useful for videos with special codecs that require specific decoders. -`), - }) - .strict() - -export const robotVideoThumbsInstructionsWithHiddenFieldsSchema = - robotVideoThumbsInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotVideoThumbsInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotVideoThumbsInstructions = z.infer -export type RobotVideoThumbsInstructionsWithHiddenFields = z.infer< - typeof robotVideoThumbsInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotVideoThumbsInstructionsSchema = interpolateRobot( - robotVideoThumbsInstructionsSchema, -) -export type InterpolatableRobotVideoThumbsInstructions = - InterpolatableRobotVideoThumbsInstructionsInput - -export type InterpolatableRobotVideoThumbsInstructionsInput = z.input< - typeof interpolatableRobotVideoThumbsInstructionsSchema -> - -export const interpolatableRobotVideoThumbsInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotVideoThumbsInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotVideoThumbsInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotVideoThumbsInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotVideoThumbsInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotVideoThumbsInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/vimeo-import.ts b/packages/transloadit/src/alphalib/types/robots/vimeo-import.ts deleted file mode 100644 index 9a160ae7..00000000 --- a/packages/transloadit/src/alphalib/types/robots/vimeo-import.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - interpolateRobot, - path, - robotBase, - robotImport, - vimeoBase, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 10, - discount_factor: 0.1, - discount_pct: 90, - example_code: { - steps: { - imported: { - robot: '/vimeo/import', - credentials: 'YOUR_VIMEO_CREDENTIALS', - path: 'me/videos', - rendition: '720p', - page_number: 1, - files_per_page: 20, - }, - }, - }, - example_code_description: 'Import videos from your Vimeo account:', - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Importing', - purpose_sentence: 'imports videos from your Vimeo account', - purpose_verb: 'import', - purpose_word: 'Vimeo', - purpose_words: 'Import videos from Vimeo', - requires_credentials: true, - service_slug: 'file-importing', - slot_count: 20, - title: 'Import videos from Vimeo', - typical_file_size_mb: 50, - typical_file_type: 'video', - name: 'VimeoImportRobot', - priceFactor: 6.6666, - queueSlotCount: 20, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: true, - stage: 'ga', -} - -export const robotVimeoImportInstructionsSchema = robotBase - .merge(robotImport) - .merge(vimeoBase) - .extend({ - robot: z.literal('/vimeo/import'), - path: path.default('me/videos').describe(` -The Vimeo API path to import from. The most common paths are: -- \`me/videos\`: Your own videos -- \`me/likes\`: Videos you've liked -- \`me/albums/:album_id/videos\`: Videos from a specific album -- \`me/channels/:channel_id/videos\`: Videos from a specific channel -- \`me/groups/:group_id/videos\`: Videos from a specific group -- \`me/portfolios/:portfolio_id/videos\`: Videos from a specific portfolio -- \`me/watchlater\`: Videos in your watch later queue - -You can also use an array of path strings here to import multiple paths in the same Robot's Step. -`), - page_number: z - .number() - .int() - .positive() - .default(1) - .describe('The page number to import from. Vimeo API uses pagination for large result sets.'), - files_per_page: z - .number() - .int() - .positive() - .max(100) - .default(20) - .describe('The number of files to import per page. Maximum is 100 as per Vimeo API limits.'), - rendition: z - .enum(['240p', '360p', '540p', '720p', '1080p', 'source']) - .default('720p') - .describe('The quality of the video to import.'), - }) - .strict() - -export type RobotVimeoImportInstructions = z.infer -export type RobotVimeoImportInstructionsInput = z.input - -export const interpolatableRobotVimeoImportInstructionsSchema = interpolateRobot( - robotVimeoImportInstructionsSchema, -) -export type InterpolatableRobotVimeoImportInstructions = - InterpolatableRobotVimeoImportInstructionsInput - -export type InterpolatableRobotVimeoImportInstructionsInput = z.input< - typeof interpolatableRobotVimeoImportInstructionsSchema -> - -export const robotVimeoImportInstructionsWithHiddenFieldsSchema = - robotVimeoImportInstructionsSchema.extend({ - access_token: z - .string() - .optional() - .describe('Legacy authentication field. Use credentials instead.'), - return_file_stubs: z - .boolean() - .optional() - .describe( - 'When true, returns file stubs instead of downloading the actual files. Used for testing.', - ), - }) - -export const interpolatableRobotVimeoImportInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotVimeoImportInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotVimeoImportInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotVimeoImportInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotVimeoImportInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotVimeoImportInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/vimeo-store.ts b/packages/transloadit/src/alphalib/types/robots/vimeo-store.ts deleted file mode 100644 index 9d3c89c5..00000000 --- a/packages/transloadit/src/alphalib/types/robots/vimeo-store.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse, vimeoBase } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 6, - discount_factor: 0.15000150001500018, - discount_pct: 84.99984999849998, - example_code: { - steps: { - exported: { - robot: '/vimeo/store', - use: ':original', - credentials: 'YOUR_VIMEO_CREDENTIALS', - title: 'Transloadit: Video Example', - description: 'Some nice description', - }, - }, - }, - example_code_description: 'Export an uploaded video to Vimeo and set its title and description:', - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Exporting', - purpose_sentence: 'exports encoding results to vimeo', - purpose_verb: 'export', - purpose_word: 'Vimeo', - purpose_words: 'Export files to Vimeo', - service_slug: 'file-exporting', - slot_count: 10, - title: 'Export files to Vimeo', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'VimeoStoreRobot', - priceFactor: 6.6666, - queueSlotCount: 10, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotVimeoStoreInstructionsSchema = robotBase - .merge(robotUse) - .merge(vimeoBase) - .extend({ - robot: z.literal('/vimeo/store'), - title: z.string().describe(` -The title of the video to be displayed on Vimeo. -`), - description: z.string().describe(` -The description of the video to be displayed on Vimeo. -`), - acl: z - .enum(['anybody', 'contacts', 'disable', 'nobody', 'password', 'unlisted', 'users']) - .default('anybody') - .describe(` -Controls access permissions for the video. Here are the valid values: - -- \`"anybody"\` — anyone can access the video. -- \`"contacts"\` — only those who follow the owner on Vimeo can access the video. -- \`"disable"\` — the video is embeddable, but it's hidden on Vimeo and can't be played. -- \`"nobody"\` — no one except the owner can access the video. -- \`"password"\` — only those with the password can access the video. -- \`"unlisted"\` — only those with the private link can access the video. -- \`"users"\` — only Vimeo members can access the video. -`), - password: z - .string() - .optional() - .describe(` -The password to access the video if \`acl\` is \`"password"\`. -`), - showcases: z - .array(z.string()) - .default([]) - .describe(` -An array of string IDs of showcases that you want to add the video to. The IDs can be found when browsing Vimeo. For example \`https://vimeo.com/manage/showcases/[SHOWCASE_ID]/info\`. -`), - downloadable: z - .boolean() - .default(false) - .describe(` -Whether or not the video can be downloaded from the Vimeo website. - -Only set this to \`true\` if you have unlocked this feature in your Vimeo accounting by upgrading to their "Pro" plan. If you use it while on their Freemium plan, the Vimeo API will return an \`"Invalid parameter supplied"\` error. -`), - folder_id: z - .string() - .nullable() - .default(null) - .describe(` -The ID of the folder to which the video is uploaded. - -When visiting one of your folders, the URL is similar to \`https://vimeo.com/manage/folders/xxxxxxxx\`. The folder_id would be \`"xxxxxxxx"\`. -`), - folder_uri: z - .string() - .optional() - .describe(` -Deprecated. Please use \`folder_id\` instead. The URI of the folder to which the video is uploaded. -`), - }) - .strict() - -export const robotVimeoStoreInstructionsWithHiddenFieldsSchema = - robotVimeoStoreInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotVimeoStoreInstructionsSchema.shape.result]) - .optional(), - access_token: z - .string() - .optional() - .describe('Legacy authentication field. Use credentials instead.'), - }) - -export type RobotVimeoStoreInstructions = z.infer -export type RobotVimeoStoreInstructionsWithHiddenFields = z.infer< - typeof robotVimeoStoreInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotVimeoStoreInstructionsSchema = interpolateRobot( - robotVimeoStoreInstructionsSchema, -) -export type InterpolatableRobotVimeoStoreInstructions = - InterpolatableRobotVimeoStoreInstructionsInput - -export type InterpolatableRobotVimeoStoreInstructionsInput = z.input< - typeof interpolatableRobotVimeoStoreInstructionsSchema -> - -export const interpolatableRobotVimeoStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotVimeoStoreInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotVimeoStoreInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotVimeoStoreInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotVimeoStoreInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotVimeoStoreInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/wasabi-import.ts b/packages/transloadit/src/alphalib/types/robots/wasabi-import.ts deleted file mode 100644 index 5cf2a8a3..00000000 --- a/packages/transloadit/src/alphalib/types/robots/wasabi-import.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { - files_per_page, - interpolateRobot, - page_number, - path, - recursive, - return_file_stubs, - robotBase, - robotImport, - wasabiBase, -} from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 10, - discount_factor: 0.1, - discount_pct: 90, - example_code: { - steps: { - imported: { - robot: '/wasabi/import', - credentials: 'YOUR_WASABI_CREDENTIALS', - path: 'path/to/files/', - recursive: true, - }, - }, - }, - example_code_description: - 'Import files from the `path/to/files` directory and its subdirectories:', - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Importing', - purpose_sentence: 'imports whole directories of files from your wasabi bucket', - purpose_verb: 'import', - purpose_word: 'Wasabi', - purpose_words: 'Import files from Wasabi', - requires_credentials: true, - service_slug: 'file-importing', - slot_count: 20, - title: 'Import files from Wasabi', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'WasabiImportRobot', - priceFactor: 6.6666, - queueSlotCount: 20, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: true, - stage: 'ga', -} - -export const robotWasabiImportInstructionsSchema = robotBase - .merge(robotImport) - .merge(wasabiBase) - .extend({ - result: z - .boolean() - .optional() - .describe('Whether the results of this Step should be present in the Assembly Status JSON'), - robot: z.literal('/wasabi/import'), - path: path.describe(` -The path in your bucket to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`. - -If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are direct descendants of this directory will be imported. For example: \`images/\`. - -Directories are **not** imported recursively. If you want to import files from subdirectories and sub-subdirectories, enable the \`recursive\` parameter. - -If you want to import all files from the root directory, please use \`/\` as the value here. In this case, make sure all your objects belong to a path. If you have objects in the root of your bucket that aren't prefixed with \`/\`, you'll receive an error: \`A client error (NoSuchKey) occurred when calling the GetObject operation: The specified key does not exist.\` - -You can also use an array of path strings here to import multiple paths in the same Robot's Step. -`), - recursive: recursive.describe(` -Setting this to \`true\` will enable importing files from subfolders and sub-subfolders, etc. of the given path. - -Please use the pagination parameters \`page_number\` and \`files_per_page\` wisely here. -`), - page_number: page_number.describe(` -The pagination page number. For now, in order to not break backwards compatibility in non-recursive imports, this only works when recursive is set to \`true\`. - -When doing big imports, make sure no files are added or removed from other scripts within your path, otherwise you might get weird results with the pagination. -`), - files_per_page: files_per_page.describe(` -The pagination page size. This only works when recursive is \`true\` for now, in order to not break backwards compatibility in non-recursive imports. -`), - return_file_stubs, - }) - .strict() - -export const robotWasabiImportInstructionsWithHiddenFieldsSchema = - robotWasabiImportInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotWasabiImportInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotWasabiImportInstructions = z.infer -export type RobotWasabiImportInstructionsWithHiddenFields = z.infer< - typeof robotWasabiImportInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotWasabiImportInstructionsSchema = interpolateRobot( - robotWasabiImportInstructionsSchema, -) -export type InterpolatableRobotWasabiImportInstructions = - InterpolatableRobotWasabiImportInstructionsInput - -export type InterpolatableRobotWasabiImportInstructionsInput = z.input< - typeof interpolatableRobotWasabiImportInstructionsSchema -> - -export const interpolatableRobotWasabiImportInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotWasabiImportInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotWasabiImportInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotWasabiImportInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotWasabiImportInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotWasabiImportInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/wasabi-store.ts b/packages/transloadit/src/alphalib/types/robots/wasabi-store.ts deleted file mode 100644 index e714cee3..00000000 --- a/packages/transloadit/src/alphalib/types/robots/wasabi-store.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse, wasabiBase } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 6, - discount_factor: 0.15000150001500018, - discount_pct: 84.99984999849998, - example_code: { - steps: { - exported: { - robot: '/wasabi/store', - use: ':original', - credentials: 'YOUR_WASABI_CREDENTIALS', - path: 'my_target_folder/${unique_prefix}/${file.url_name}', - }, - }, - }, - example_code_description: 'Export uploaded files to `my_target_folder` on Wasabi:', - has_small_icon: true, - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Exporting', - purpose_sentence: 'exports encoding results to Wasabi buckets', - purpose_verb: 'export', - purpose_word: 'Wasabi', - purpose_words: 'Export files to Wasabi', - service_slug: 'file-exporting', - slot_count: 10, - title: 'Export files to Wasabi', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'WasabiStoreRobot', - priceFactor: 6.6666, - queueSlotCount: 10, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotWasabiStoreInstructionsSchema = robotBase - .merge(robotUse) - .merge(wasabiBase) - .extend({ - robot: z.literal('/wasabi/store').describe(` -The URL to the result file will be returned in the Assembly Status JSON. -`), - path: z - .string() - .default('${unique_prefix}/${file.url_name}') - .describe(` -The path at which the file is to be stored. This may include any available [Assembly variables](/docs/topics/assembly-instructions/#assembly-variables). The path must not be a directory. -`), - acl: z - .enum(['private', 'public-read']) - .default('public-read') - .describe(` -The permissions used for this file. -`), - headers: z - .record(z.string()) - .default({ 'Content-Type': '${file.mime}' }) - .describe(` -An object containing a list of headers to be set for this file on Wasabi Spaces, such as \`{ FileURL: "\${file.url_name}" }\`. This can also include any available [Assembly Variables](/docs/topics/assembly-instructions/#assembly-variables). - -Object Metadata can be specified using \`x-amz-meta-*\` headers. Note that these headers [do not support non-ASCII metadata values](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html#UserMetadata). -`), - sign_urls_for: z - .number() - .int() - .min(0) - .optional() - .describe(` -This parameter provides signed URLs in the result JSON (in the \`signed_ssl_url\` property). The number that you set this parameter to is the URL expiry time in seconds. If this parameter is not used, no URL signing is done. -`), - }) - .strict() - -export const robotWasabiStoreInstructionsWithHiddenFieldsSchema = - robotWasabiStoreInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotWasabiStoreInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotWasabiStoreInstructions = z.infer -export type RobotWasabiStoreInstructionsWithHiddenFields = z.infer< - typeof robotWasabiStoreInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotWasabiStoreInstructionsSchema = interpolateRobot( - robotWasabiStoreInstructionsSchema, -) -export type InterpolatableRobotWasabiStoreInstructions = - InterpolatableRobotWasabiStoreInstructionsInput - -export type InterpolatableRobotWasabiStoreInstructionsInput = z.input< - typeof interpolatableRobotWasabiStoreInstructionsSchema -> - -export const interpolatableRobotWasabiStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotWasabiStoreInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotWasabiStoreInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotWasabiStoreInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotWasabiStoreInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotWasabiStoreInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/robots/youtube-store.ts b/packages/transloadit/src/alphalib/types/robots/youtube-store.ts deleted file mode 100644 index 83f0e040..00000000 --- a/packages/transloadit/src/alphalib/types/robots/youtube-store.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { z } from 'zod' - -import type { RobotMetaInput } from './_instructions-primitives.ts' -import { interpolateRobot, robotBase, robotUse } from './_instructions-primitives.ts' - -export const meta: RobotMetaInput = { - allowed_for_url_transform: true, - bytescount: 6, - discount_factor: 0.15000150001500018, - discount_pct: 84.99984999849998, - example_code: { - steps: { - exported: { - robot: '/youtube/store', - use: ':original', - credentials: 'YOUR_YOUTUBE_CREDENTIALS', - title: 'Transloadit: Video Example', - description: 'Some nice description', - category: 'science & technology', - keywords: 'transloadit, robots, botty', - visibility: 'private', - }, - }, - }, - example_code_description: 'Export an uploaded video to YouTube and set some basic parameters:', - minimum_charge: 0, - output_factor: 1, - override_lvl1: 'File Exporting', - purpose_sentence: 'exports encoding results to YouTube', - purpose_verb: 'export', - purpose_word: 'YouTube', - purpose_words: 'Export files to YouTube', - service_slug: 'file-exporting', - slot_count: 10, - title: 'Export files to YouTube', - typical_file_size_mb: 1.2, - typical_file_type: 'file', - name: 'YoutubeStoreRobot', - priceFactor: 6.6666, - queueSlotCount: 10, - isAllowedForUrlTransform: true, - trackOutputFileSize: false, - isInternal: false, - removeJobResultFilesFromDiskRightAfterStoringOnS3: false, - stage: 'ga', -} - -export const robotYoutubeStoreInstructionsSchema = robotBase - .merge(robotUse) - .extend({ - robot: z.literal('/youtube/store').describe(` -> [!Note] -> This Robot only accepts videos. - -## Installation - -Since YouTube works with OAuth, you will need to generate [Template Credentials](/c/template-credentials/) to use this Robot. - -To change the \`title\`, \`description\`, \`category\`, or \`keywords\` per video, we recommend to [inject variables into your Template](/docs/topics/templates/). - -## Adding a thumbnail image to your video - -You can add a custom thumbnail to your video on YouTube by using our \`"as"\` syntax for the \`"use"\` parameter to supply both a video and an image to the step: - -\`\`\`json -"exported": { - "use": [ - { "name": "video_encode_step", "as": "video" }, - { "name": "image_resize_step", "as": "image" }, - ], - ... -}, -\`\`\` - -If you encounter an error such as "The authenticated user doesnʼt have permissions to upload and set custom video thumbnails", you should go to your YouTube account and try adding a custom thumbnail to one of your existing videos. Youʼll be prompted to add your phone number. Once youʼve added it, the error should go away. -`), - credentials: z.string().describe(` -The authentication Template credentials used for your YouTube account. You can generate them on the [Template Credentials page](/c/template-credentials/). Simply add the name of your YouTube channel, and you will be redirected to a Google verification page. Accept the presented permissions and you will be good to go. -`), - title: z - .string() - .max(80) - .describe(` -The title of the video to be displayed on YouTube. - -Note that since the YouTube API requires titles to be within 80 characters, longer titles may be truncated. -`), - description: z.string().describe(` -The description of the video to be displayed on YouTube. This can be up to 5000 characters, including \`\\n\` for new-lines. -`), - category: z - .preprocess( - (val) => (typeof val === 'string' ? val.toLowerCase() : val), - z.enum([ - 'autos & vehicles', - 'comedy', - 'education', - 'entertainment', - 'film & animation', - 'gaming', - 'howto & style', - 'music', - 'news & politics', - 'people & blogs', - 'pets & animals', - 'science & technology', - 'sports', - 'travel & events', - ]), - ) - .describe(` -The category to which this video will be assigned. -`), - keywords: z.string().describe(` -Tags used to describe the video, separated by commas. These tags will also be displayed on YouTube. -`), - visibility: z.enum(['public', 'private', 'unlisted']).describe(` -Defines the visibility of the uploaded video. -`), - }) - .strict() - -export const robotYoutubeStoreInstructionsWithHiddenFieldsSchema = - robotYoutubeStoreInstructionsSchema.extend({ - result: z - .union([z.literal('debug'), robotYoutubeStoreInstructionsSchema.shape.result]) - .optional(), - }) - -export type RobotYoutubeStoreInstructions = z.infer -export type RobotYoutubeStoreInstructionsWithHiddenFields = z.infer< - typeof robotYoutubeStoreInstructionsWithHiddenFieldsSchema -> - -export const interpolatableRobotYoutubeStoreInstructionsSchema = interpolateRobot( - robotYoutubeStoreInstructionsSchema, -) -export type InterpolatableRobotYoutubeStoreInstructions = - InterpolatableRobotYoutubeStoreInstructionsInput - -export type InterpolatableRobotYoutubeStoreInstructionsInput = z.input< - typeof interpolatableRobotYoutubeStoreInstructionsSchema -> - -export const interpolatableRobotYoutubeStoreInstructionsWithHiddenFieldsSchema = interpolateRobot( - robotYoutubeStoreInstructionsWithHiddenFieldsSchema, -) -export type InterpolatableRobotYoutubeStoreInstructionsWithHiddenFields = z.infer< - typeof interpolatableRobotYoutubeStoreInstructionsWithHiddenFieldsSchema -> -export type InterpolatableRobotYoutubeStoreInstructionsWithHiddenFieldsInput = z.input< - typeof interpolatableRobotYoutubeStoreInstructionsWithHiddenFieldsSchema -> diff --git a/packages/transloadit/src/alphalib/types/stackVersions.ts b/packages/transloadit/src/alphalib/types/stackVersions.ts deleted file mode 100644 index 79f88c3e..00000000 --- a/packages/transloadit/src/alphalib/types/stackVersions.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const stackVersions = { - ffmpeg: { - recommendedVersion: 'v6' as const, - test: /^v?[567](\.\d+)?(\.\d+)?$/, - suggestedValues: ['v5', 'v6', 'v7'] as const, - }, - imagemagick: { - recommendedVersion: 'v3' as const, - test: /^v?[23](\.\d+)?(\.\d+)?$/, - suggestedValues: ['v2', 'v3'] as const, - }, -} diff --git a/packages/transloadit/src/alphalib/types/template.ts b/packages/transloadit/src/alphalib/types/template.ts deleted file mode 100644 index d237b809..00000000 --- a/packages/transloadit/src/alphalib/types/template.ts +++ /dev/null @@ -1,277 +0,0 @@ -import { z } from 'zod' - -import { robotsSchema, robotsWithHiddenBotsAndFieldsSchema } from './robots/_index.ts' -import type { RobotUse } from './robots/_instructions-primitives.ts' - -export const stepSchema = z - .object({ - // This is a hack to get nicer robot hover messages in editors. - robot: z - .string() - .describe('Identifier of the [robot](https://transloadit.com/docs/robots/) to execute'), - }) - .and(robotsSchema) -export const stepsSchema = z.record(stepSchema).describe('Contains Assembly Instructions.') -export type Step = z.infer -export type StepInput = z.input -export type StepInputWithUse = StepInput & RobotUse -export type Steps = z.infer -export type StepsInput = z.input -export const optionalStepsSchema = stepsSchema.optional() - -export const stepSchemaWithHiddenFields = z - .object({ - // This is a hack to get nicer robot hover messages in editors. - robot: z - .string() - .describe('Identifier of the [robot](https://transloadit.com/docs/robots/) to execute'), - }) - .and(robotsWithHiddenBotsAndFieldsSchema) -export const stepsSchemaWithHiddenFields = z - .record(stepSchemaWithHiddenFields) - .describe('Contains Assembly Instructions.') -export type StepWithHiddenFields = z.infer -export type StepWithHiddenFieldsInput = z.input -export type StepsWithHiddenFields = z.infer -export type StepsWithHiddenFieldsInput = z.input -const optionalStepsWithHiddenFieldsSchema = stepsSchemaWithHiddenFields.optional() - -export const fieldsSchema = z - .record(z.any()) - .optional() - .describe( - 'An object of string keyed values (name -> value) that can be used as Assembly Variables, just like additional form fields can. You can use anything that is JSON stringifyable as a value', - ) - -export const notifyUrlSchema = z - .string() - .optional() - .nullable() - .describe( - 'Transloadit can send a Pingback to your server when the Assembly is completed. We’ll send the Assembly status in a form url-encoded JSON string inside of a transloadit field in a multipart POST request to the URL supplied here.', - ) - -export const templateIdSchema = z - .string() - .optional() - .describe( - 'The ID of the Template that contains your Assembly Instructions. If you set `allow_steps_override` to `false` in your Template, then `steps` and `template_id` will be mutually exclusive — you may supply only one of these parameters.', - ) - -export const assemblyAuthInstructionsSchema = z - .object({ - key: z.string().describe('Transloadit API key used to authenticate requests'), - secret: z.string().optional().describe('Transloadit API secret used to sign requests'), - expires: z - .string() - .optional() - .describe('ISO 8601 expiration timestamp for signature authentication'), - max_size: z.number().optional().describe('Maximum allowed upload size in bytes'), - nonce: z.string().optional().describe('Unique, random nonce for this request'), - referer: z - .string() - .optional() - .describe('Regular expression matched against the HTTP Referer to restrict upload origin'), - }) - .describe(`Contains at least your Transloadit Auth Key in the \`key\` property. - -If you enable Signature Authentication, you must also set an expiry date for the request in the expires property: - -\`\`\`jsonc -{ - "key": "23c96d084c744219a2ce156772ec3211", - "expires": "2009-08-28T01:02:03.000Z" -} -\`\`\` - -We strongly recommend including the \`nonce\` property — a randomly generated, unique value per request that prevents duplicate processing upon retries, can aid in debugging, and avoids attack vectors such as signature key reuse: - -\`\`\`jsonc -{ - // … - "nonce": "04ac6cb6-df43-41fb-a7fd-e5dd711a64e1" -} -\`\`\` - -The \`referer\` property is a regular expression to match against the HTTP referer of this upload, such as \`"example\\.org"\`. Specify this key to make sure that uploads only come from your domain. - -Uploads without a referer will always pass (as they are turned off for some browsers) making this useful in just a handful of use cases. For details about regular expressions, see [Mozilla's RegExp documentation](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RegExp). - -The \`max_size\` property can be used to set a maximum size that an upload can have in bytes, such as \`1048576\` (1 MB). Specify this to prevent users from uploading excessively large files. - -This can be set as part of the Assembly request or as part of the Template. - -The file size is checked as soon as the upload is started and if it exceeds the maximum size, the entire upload process is canceled and the Assembly will error out, even if it contains files that do not exceed the \`max_size\` limitation. - -If you want to just ignore the files that exceed a certain size, but process all others, then please use [🤖/file/filter](https://transloadit.com/docs/robots/file-filter/). -`) - -const assemblyInstructionsSharedShape = { - allow_steps_override: z - .boolean() - .optional() - .describe( - 'Set this to false to disallow Overruling Templates at Runtime. If you set this to false then template_id and steps will be mutually exclusive and you may only supply one of those parameters. Recommended when deploying Transloadit in untrusted environments. This makes sense to set as part of a Template, rather than on the Assembly itself when creating it.', - ), - notify_url: notifyUrlSchema, - fields: fieldsSchema, - quiet: z - .boolean() - .optional() - .describe( - 'Set this to true to reduce the response from an Assembly POST request to only the necessary fields. This prevents any potentially confidential information being leaked to the end user who is making the Assembly request. A successful Assembly will only include the ok and assembly_id fields. An erroneous Assembly will only include the error, http_code, message and assembly_id fields. The full Assembly Status will then still be sent to the notify_url if one was specified.', - ), - // This is done to avoid heavy inference cost - steps: optionalStepsSchema as typeof optionalStepsSchema, - template_id: templateIdSchema, -} as const - -export const assemblyInstructionsSchema = z.object({ - auth: assemblyAuthInstructionsSchema.optional(), - ...assemblyInstructionsSharedShape, -}) - -export const assemblyInstructionsSchemaWithRequiredAuth = z.object({ - auth: assemblyAuthInstructionsSchema, - ...assemblyInstructionsSharedShape, -}) - -export type AssemblyInstructions = z.infer -export type AssemblyInstructionsInput = z.input -export type AssemblyAuthInstructionsInput = z.input -export type AssemblyAuthInstructionsPartialInput = Partial< - z.input -> - -export const templateParamsSchema = z - .object({ - auth: assemblyAuthInstructionsSchema, - name: z - .string() - .min(5) - .max(40) - .regex(/^[a-z-]+$/) - .describe( - 'Name of this Template. Must be between 5-40 symbols (inclusive), lowercase, can only contain dashes and latin letters.', - ), - template: z - .string() - .describe(`All the [Assembly Instructions](/docs/topics/assembly-instructions/) and [Template options](/docs/topics/templates/#template-options) as a JSON encoded string. - -Example value: - -\`\`\`json -"{\\"allow_steps_override\\": false, \\"steps\\": { ... }}" -\`\`\` -`), - require_signature_auth: z - .union([z.literal(0), z.literal(1)]) - .default(0) - .describe( - 'Use `1` to deny requests that do not include a signature. With [Signature Authentication](/docs/api/authentication/) you can ensure no one else is sending requests on your behalf.', - ), - }) - .strict() - -export const templateGetParamsSchema = z - .object({ - auth: assemblyAuthInstructionsSchema, - }) - .strict() - -export const templateListParamsSchema = z - .object({ - auth: assemblyAuthInstructionsSchema, - page: z - .number() - .int() - .default(1) - .describe('Specifies the current page, within the current pagination'), - pagesize: z - .number() - .int() - .min(1) - .max(5000) - .default(50) - .describe( - 'Specifies how many Templates to be received per API request, which is useful for pagination.', - ), - sort: z - .enum(['id', 'name', 'created', 'modified']) - .default('created') - .describe('The field to sort by.'), - order: z - .enum(['asc', 'desc']) - .default('desc') - .describe( - 'The sort direction. Can be `"desc"` for descending (default) or `"asc"` for ascending.', - ), - fromdate: z - .string() - .describe( - 'Specifies the minimum Assembly UTC creation date/time. Only Templates after this time will be retrieved. Use the format `Y-m-d H:i:s`.', - ), - todate: z - .string() - .default('NOW()') - .describe( - 'Specifies the maximum Assembly UTC creation date/time. Only Templates before this time will be retrieved. Use the format `Y-m-d H:i:s`.', - ), - keywords: z - .array(z.string()) - .default([]) - .describe( - 'Specifies keywords to be matched in the Assembly Status. The Assembly fields checked include the `id`, `redirect_url`, `fields`, and `notify_url`, as well as error messages and files used.', - ), - }) - .strict() - -// These are used in system tests, but not exposed to the public right now -// Meaning we do not want to document them, do not want to offer auto complete on them -// if customers use them, they will have to surpress a typescript error -// however when they do, our runtime schema validation will not blow up on it -// because we are using this version of the schema in our actual API -// so that tests can also use them: -export const assemblyInstructionsWithHiddenSchema = assemblyInstructionsSchema.extend({ - steps: optionalStepsWithHiddenFieldsSchema as typeof optionalStepsWithHiddenFieldsSchema, - imagemagick_stack: z.string().optional(), - exiftool_stack: z.string().optional(), - mplayer_stack: z.string().optional(), - mediainfo_stack: z.string().optional(), - ffmpeg_stack: z.string().optional(), - usage_tags: z.string().optional(), - response_headers: z - .object({ - cors: z - .object({ - 'Access-Control-Allow-Methods': z.string(), - 'Access-Control-Allow-Origin': z.string(), - 'Access-Control-Allow-Headers': z.string(), - 'Access-Control-Expose-Headers': z.string(), - 'Access-Control-Allow-Credentials': z.boolean(), - 'Access-Control-Max-Age': z.number(), - 'Access-Control-Allow-Private-Network': z.boolean(), - 'Access-Control-Allow-Public-Network': z.boolean(), - }) - .optional(), - }) - .optional(), - randomize_watermarks: z.boolean().optional(), - await: z - .union([ - z.boolean(), - z.literal('notification'), - z.literal('persisting'), - z.literal('transcoding'), - ]) - .optional(), - blocking: z.boolean().optional(), - reparse_template: z.union([z.literal(1), z.boolean()]).optional(), - ignore_upload_meta_data_errors: z.boolean().optional(), - emit_execution_progress: z.boolean().optional(), -}) - -export type AssemblyInstructionsWithHidden = z.infer -export type AssemblyInstructionsWithHiddenInput = z.input< - typeof assemblyInstructionsWithHiddenSchema -> diff --git a/packages/transloadit/src/alphalib/types/templateCredential.ts b/packages/transloadit/src/alphalib/types/templateCredential.ts deleted file mode 100644 index b43a7f87..00000000 --- a/packages/transloadit/src/alphalib/types/templateCredential.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { z } from 'zod' - -import { assemblyAuthInstructionsSchema } from './template.ts' - -export const retrieveTemplateCredentialsParamsSchema = z - .object({ - auth: assemblyAuthInstructionsSchema, - }) - .strict() - -export const templateCredentialsSchema = z - .object({ - auth: assemblyAuthInstructionsSchema, - name: z - .string() - .min(4) - .regex(/^[a-zA-Z-]+$/) - .describe( - 'Name of the Template Credentials. Must be longer than 3 characters, can only contain dashes and latin letters.', - ), - type: z - .enum([ - 'ai', - 'azure', - 'backblaze', - 'cloudflare', - 'companion', - 'digitalocean', - 'dropbox', - 'ftp', - 'google', - 'http', - 'minio', - 'rackspace', - 's3', - 'sftp', - 'supabase', - 'swift', - 'tigris', - 'vimeo', - 'wasabi', - 'youtube', - ]) - .describe('The service to create credentials for.'), - content: z - .object({}) - .describe(`Key and value pairs which fill in the details of the Template Credentials. For example, for an S3 bucket, this would be a valid content object to send: - -\`\`\`jsonc -{ - "content": { - "key": "xyxy", - "secret": "xyxyxyxy", - "bucket" : "mybucket.example.com", - "bucket_region": "us-east-1" - } -} -\`\`\` -`), - }) - .strict() diff --git a/packages/transloadit/src/alphalib/zodParseWithContext.ts b/packages/transloadit/src/alphalib/zodParseWithContext.ts deleted file mode 100644 index 3bd83c86..00000000 --- a/packages/transloadit/src/alphalib/zodParseWithContext.ts +++ /dev/null @@ -1,306 +0,0 @@ -import type { z } from 'zod' - -export type ZodIssueWithContext = z.ZodIssue & { - parentObj: unknown - humanReadable: string -} - -function getByPath(obj: unknown, path: string): unknown { - if (!path) return obj - const parts = path.split('.') - let current = obj - for (const part of parts) { - if (current == null || typeof current !== 'object') return undefined - current = (current as Record)[part] - } - return current -} - -export type ZodParseWithContextResult = { - errors: ZodIssueWithContext[] - humanReadable: string -} & ( - | { - success: true - safe: z.infer - } - | { - success: false - safe?: never - } -) - -export function zodParseWithContext( - schema: T, - obj: unknown, -): ZodParseWithContextResult { - const zodRes = schema.safeParse(obj) - if (!zodRes.success) { - // Check for empty object input causing a general union failure - if ( - typeof obj === 'object' && - obj !== null && - Object.keys(obj).length === 0 && - zodRes.error.errors.length > 0 - ) { - // console.log('[zodParseWithContext] Empty object detected, Zod errors:', JSON.stringify(zodRes.error.errors, null, 2)); - - const firstError = zodRes.error.errors[0] - if ( - zodRes.error.errors.length === 1 && - firstError && - firstError.code === 'invalid_union' && - firstError.path.length === 0 && - Array.isArray((firstError as z.ZodInvalidUnionIssue).unionErrors) && - (firstError as z.ZodInvalidUnionIssue).unionErrors.length > 0 - ) { - const humanReadable = - 'Validation failed: Input object is empty or missing key fields required to determine its type, ' + - 'and does not match any variant of the expected schema. Please provide a valid object.' - return { - success: false, - // For this specific summarized error, we might not need to map all detailed ZodIssueWithContext - // or we can provide a simplified single error entry reflecting this summary. - // For now, let's return the original errors but with the new top-level humanReadable. - errors: zodRes.error.errors.map((e) => ({ - ...e, - parentObj: obj, - humanReadable: e.message, - })), - humanReadable, - } - } - } - - const zodIssuesWithContext: ZodIssueWithContext[] = [] - const badPaths = new Map() - - for (const zodIssue of zodRes.error.errors) { - const lastPath = zodIssue.path - let parentObj: unknown = {} - - if (lastPath) { - const strPath = lastPath.slice(0, -1).join('.') - parentObj = getByPath(obj, strPath) ?? {} - } - - const path = zodIssue.path - .map((p) => (typeof p === 'string' ? p.replaceAll('.', '\\.') : p)) - .join('.') - if (!badPaths.has(path)) { - badPaths.set(path, []) - } - - const messages: string[] = [] - - // --- Handle specific high-priority codes BEFORE union/switch --- - if (zodIssue.code === 'unrecognized_keys') { - const maxKeysToShow = 3 - const { keys } = zodIssue - const truncatedKeys = keys.slice(0, maxKeysToShow) - const ellipsis = keys.length > maxKeysToShow ? '...' : '' - let message = `has unrecognized keys: ${truncatedKeys.map((k) => `\`${k}\``).join(', ')}${ellipsis}` - // Add hint for root-level unrecognized keys, likely from union .strict() failures - if (zodIssue.path.length === 0) { - message += - ' (Hint: No union variant matched. Check for extra keys or type mismatches in variants.)' - } - messages.push(message) - } - // --- End high-priority handling --- - - // Handle union type validation errors (only if not handled above) - else if ('unionErrors' in zodIssue && zodIssue.unionErrors) { - // --- Moved initialization out of the loop --- - const collectedLiterals: Record = {} - const collectedMessages: Record> = {} - - // Process nested issues within the union - for (const unionError of zodIssue.unionErrors) { - for (const issue of unionError.issues) { - // console.log('---- Zod Union Issue ----\\n', JSON.stringify(issue, null, 2)) - const nestedPath = issue.path.join('.') - - // Ensure paths exist in collection maps - if (!collectedLiterals[nestedPath]) collectedLiterals[nestedPath] = [] - if (!collectedMessages[nestedPath]) collectedMessages[nestedPath] = new Set() - - if (issue.code === 'custom' && issue.message.includes('interpolation string')) { - continue - } - if (issue.code === 'invalid_literal') { - const { expected } = issue - if ( - expected !== undefined && - expected !== null && - (typeof expected === 'string' || - typeof expected === 'number' || - typeof expected === 'boolean') - ) { - collectedLiterals[nestedPath].push(expected) - } - // Still add the raw message for fallback - collectedMessages[nestedPath].add(issue.message) - } - // Keep existing enum handling if needed, but literal should cover most cases - else if (issue.code === 'invalid_enum_value') { - const { options } = issue - if (options && options.length > 0) { - collectedLiterals[nestedPath].push(...options.map(String)) // Assuming options are compatible - } - collectedMessages[nestedPath].add(issue.message) - } - // Keep existing unrecognized keys handling - else if (issue.code === 'unrecognized_keys') { - const maxKeysToShow = 3 - const { keys } = issue - const truncatedKeys = keys.slice(0, maxKeysToShow) - const ellipsis = keys.length > maxKeysToShow ? '...' : '' - collectedMessages[nestedPath].add( - `has unrecognized keys: ${truncatedKeys.map((k) => `\`${k}\``).join(', ')}${ellipsis}`, - ) - } - // <-- Add handling for invalid_type here --> - else if (issue.code === 'invalid_type') { - const received = issue.received === 'undefined' ? 'missing' : issue.received - const actualValue = getByPath(parentObj, nestedPath) - const actualValueStr = - typeof actualValue === 'object' && actualValue !== null - ? JSON.stringify(actualValue) - : String(actualValue) - - let expectedOutput = String(issue.expected) - const MAX_EXPECTED_TO_SHOW = 3 - if (typeof issue.expected === 'string' && issue.expected.includes(' | ')) { - const expectedValues = issue.expected.split(' | ') - if (expectedValues.length > MAX_EXPECTED_TO_SHOW) { - const shownValues = expectedValues.slice(0, MAX_EXPECTED_TO_SHOW).join(' | ') - const remainingCount = expectedValues.length - MAX_EXPECTED_TO_SHOW - expectedOutput = `${shownValues} | .. or ${remainingCount} others ..` - } - } - - collectedMessages[nestedPath].add( - `got invalid type: ${received} (value: \`${actualValueStr}\`, expected: ${expectedOutput})`, - ) - } - // <-- End added handling --> - else { - collectedMessages[nestedPath].add(issue.message) // Handle other nested codes - } - } - } - - // --- Moved processing logic here --- - // Now, add messages to badPaths based on collected info AFTER processing ALL union errors - for (const nestedPath in collectedMessages) { - if (!badPaths.has(nestedPath)) { - badPaths.set(nestedPath, []) - } - - const targetMessages = badPaths.get(nestedPath) - if (!targetMessages) { - continue - } - - // Prioritize more specific messages (like invalid type with details) - const invalidTypeMessages = Array.from(collectedMessages[nestedPath]).filter((m) => - m.startsWith('got invalid type:'), - ) - const unrecognizedKeyMessages = Array.from(collectedMessages[nestedPath]).filter((m) => - m.startsWith('has unrecognized keys:'), - ) - const literalMessages = collectedLiterals[nestedPath] ?? [] - - if (invalidTypeMessages.length > 0) { - targetMessages.push(...invalidTypeMessages) - } else if (unrecognizedKeyMessages.length > 0) { - targetMessages.push(...unrecognizedKeyMessages) - } else if (literalMessages.length > 0) { - const uniqueLiterals = [...new Set(literalMessages)] - targetMessages.push(`should be one of: \`${uniqueLiterals.join('`, `')}\``) - } else { - // Fallback to joining the collected raw messages for this path - targetMessages.push(...collectedMessages[nestedPath]) - } - } - } - // Handle other specific error codes (only if not handled above) - else { - // Handle specific error codes for better messages - let received: string - let type: string - let bigType: string - - switch (zodIssue.code) { - case 'invalid_type': { - received = zodIssue.received === 'undefined' ? 'missing' : zodIssue.received - const actualValue = getByPath(obj, path) - const actualValueStr = - typeof actualValue === 'object' && actualValue !== null - ? JSON.stringify(actualValue) - : String(actualValue) // Use String() for null/primitives - - // Simple message not relying on zodIssue.expected - messages.push(`got invalid type: ${received} (value: \`${actualValueStr}\`)`) - break - } - case 'invalid_string': - if (zodIssue.validation === 'email') { - messages.push('should be a valid email address') - } else if (zodIssue.validation === 'url') { - messages.push('should be a valid URL') - } else { - messages.push(zodIssue.message) - } - break - case 'too_small': - type = zodIssue.type === 'string' ? 'characters' : 'items' - messages.push(`should have at least ${zodIssue.minimum} ${type}`) - break - case 'too_big': - bigType = zodIssue.type === 'string' ? 'characters' : 'items' - messages.push(`should have at most ${zodIssue.maximum} ${bigType}`) - break - case 'custom': - messages.push(zodIssue.message) - break - default: - messages.push(zodIssue.message) - } - } - - // Ensure messages collected directly in the `messages` array (e.g., from the switch) - // are added to the correct path in badPaths. - if (messages.length > 0) { - badPaths.get(path)?.push(...messages) - } - - const field = path || 'Input' - // Ensure humanReadable for the individual ZodIssueWithContext is still generated correctly - // even if messages array is empty because handled via badPaths/nested issues. - const issueSpecificMessages = badPaths.get(path) ?? messages - const humanReadable = `Path \`${field}\` ${issueSpecificMessages.join(', ')}` - - zodIssuesWithContext.push({ - ...zodIssue, - parentObj, - humanReadable, - }) - } - - // Improved formatting for the top-level humanReadable string - const errorList = Array.from(badPaths.entries()) - .map(([path, messages]) => { - const field = path || 'Input' - return ` - \`${field}\`: ${messages.join(', ')}` // Format as list item - }) - .join('\n') - - const humanReadable = `Validation failed for the following fields:\n${errorList}` // Add header - - return { success: false, errors: zodIssuesWithContext, humanReadable } - } - - return { success: true, safe: zodRes.data, errors: [], humanReadable: '' } -} diff --git a/packages/transloadit/src/apiTypes.ts b/packages/transloadit/src/apiTypes.ts deleted file mode 100644 index 28d474c8..00000000 --- a/packages/transloadit/src/apiTypes.ts +++ /dev/null @@ -1,154 +0,0 @@ -import type { AssemblyInstructions, AssemblyInstructionsInput } from './alphalib/types/template.ts' - -export { - type AssemblyIndexItem, - assemblyIndexItemSchema, - assemblyStatusSchema, -} from './alphalib/types/assemblyStatus.ts' -export type { AssemblyInstructions, AssemblyInstructionsInput } from './alphalib/types/template.ts' -export { assemblyInstructionsSchema } from './alphalib/types/template.ts' - -export interface OptionalAuthParams { - auth?: { key?: string; expires?: string } -} - -// todo make zod schemas for these types in the backend for these too (in alphalib?) -// currently the types are not entirely correct, and probably lacking some props - -export interface BaseResponse { - // todo are these always there? maybe sometimes missing or null - ok: string // todo should we type the different possible `ok` responses? - message: string -} - -export interface PaginationList { - items: T[] -} - -export interface PaginationListWithCount extends PaginationList { - count: number -} - -// `auth` is not required in the JS API because it can be specified in the constructor, -// and it will then be auto-added before the request -export type CreateAssemblyParams = Omit & OptionalAuthParams - -export type ListAssembliesParams = OptionalAuthParams & { - page?: number - pagesize?: number - type?: 'all' | 'uploading' | 'executing' | 'canceled' | 'completed' | 'failed' | 'request_aborted' - fromdate?: string - todate?: string - keywords?: string[] -} - -export type ReplayAssemblyParams = Pick< - CreateAssemblyParams, - 'auth' | 'template_id' | 'notify_url' | 'fields' | 'steps' -> & { - reparse_template?: number -} - -export interface ReplayAssemblyResponse extends BaseResponse { - success: boolean - assembly_id: string - assembly_url: string - assembly_ssl_url: string - notify_url?: string -} - -export type ReplayAssemblyNotificationParams = OptionalAuthParams & { - notify_url?: string - wait?: boolean -} - -export interface ReplayAssemblyNotificationResponse { - ok: string - success: boolean - notification_id: string -} - -export type TemplateContent = Pick< - CreateAssemblyParams, - 'allow_steps_override' | 'steps' | 'auth' | 'notify_url' -> - -export type ResponseTemplateContent = Pick< - AssemblyInstructions, - 'allow_steps_override' | 'steps' | 'auth' | 'notify_url' -> - -export type CreateTemplateParams = OptionalAuthParams & { - name: string - template: TemplateContent - require_signature_auth?: number -} - -export type EditTemplateParams = OptionalAuthParams & { - name?: string - template?: TemplateContent - require_signature_auth?: number -} - -export type ListTemplatesParams = OptionalAuthParams & { - page?: number - pagesize?: number - sort?: 'id' | 'name' | 'created' | 'modified' - order?: 'desc' | 'asc' - fromdate?: string - todate?: string - keywords?: string[] -} - -interface TemplateResponseBase { - id: string - name: string - content: ResponseTemplateContent - require_signature_auth: number -} - -export interface ListedTemplate extends TemplateResponseBase { - encryption_version: number - last_used?: string - created: string - modified: string -} - -export interface TemplateResponse extends TemplateResponseBase, BaseResponse {} - -// todo type this according to api2 valid values for better dx? -export type TemplateCredentialContent = Record - -export type CreateTemplateCredentialParams = OptionalAuthParams & { - name: string - type: string - content: TemplateCredentialContent -} - -export type ListTemplateCredentialsParams = OptionalAuthParams & { - page?: number - sort?: string - order: 'asc' | 'desc' -} - -// todo -export interface TemplateCredential { - id: string - name: string - type: string - content: TemplateCredentialContent - account_id?: string - created?: string - modified?: string - stringified?: string -} - -export interface TemplateCredentialResponse extends BaseResponse { - credential: TemplateCredential -} - -export interface TemplateCredentialsResponse extends BaseResponse { - credentials: TemplateCredential[] -} - -export type BillResponse = unknown // todo diff --git a/packages/transloadit/src/cli.ts b/packages/transloadit/src/cli.ts deleted file mode 100644 index bdcd0b93..00000000 --- a/packages/transloadit/src/cli.ts +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env node - -import { realpathSync } from 'node:fs' -import path from 'node:path' -import process from 'node:process' -import { fileURLToPath } from 'node:url' -import 'dotenv/config' -import { createCli } from './cli/commands/index.ts' - -const currentFile = realpathSync(fileURLToPath(import.meta.url)) - -function resolveInvokedPath(invoked?: string): string | null { - if (invoked == null) return null - try { - return realpathSync(invoked) - } catch { - return path.resolve(invoked) - } -} - -export function shouldRunCli(invoked?: string): boolean { - const resolved = resolveInvokedPath(invoked) - if (resolved == null) return false - return resolved === currentFile -} - -export async function main(args = process.argv.slice(2)): Promise { - const cli = createCli() - const exitCode = await cli.run(args) - if (exitCode !== 0) { - process.exitCode = exitCode - } -} - -export function runCliWhenExecuted(): void { - if (!shouldRunCli(process.argv[1])) return - - void main().catch((error) => { - console.error((error as Error).message) - process.exitCode = 1 - }) -} - -runCliWhenExecuted() diff --git a/packages/transloadit/src/cli/OutputCtl.ts b/packages/transloadit/src/cli/OutputCtl.ts deleted file mode 100644 index be6fc9a2..00000000 --- a/packages/transloadit/src/cli/OutputCtl.ts +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Log levels following syslog severity (https://en.wikipedia.org/wiki/Syslog#Severity_level) - * Lower numbers = more severe, higher numbers = more verbose - */ -const LOG_LEVEL = { - ERR: 3, // Error conditions - WARN: 4, // Warning conditions - NOTICE: 5, // Normal but significant (default) - INFO: 6, // Informational - DEBUG: 7, // Debug-level messages - TRACE: 8, // Most verbose/detailed -} as const - -export type LogLevelName = keyof typeof LOG_LEVEL -export type LogLevelValue = (typeof LOG_LEVEL)[LogLevelName] - -export const LOG_LEVEL_DEFAULT: LogLevelValue = LOG_LEVEL.NOTICE - -/** Valid log level names for CLI parsing */ -export const LOG_LEVEL_NAMES = Object.keys(LOG_LEVEL).map((k) => - k.toLowerCase(), -) as Lowercase[] - -/** Valid numeric log level values */ -const LOG_LEVEL_VALUES = new Set(Object.values(LOG_LEVEL)) - -/** Parse a log level string (name or number) to its numeric value */ -export function parseLogLevel(level: string): LogLevelValue { - // Try parsing as number first - const num = Number(level) - if (!Number.isNaN(num)) { - if (LOG_LEVEL_VALUES.has(num as LogLevelValue)) { - return num as LogLevelValue - } - throw new Error( - `Invalid log level: ${level}. Valid values: ${[...LOG_LEVEL_VALUES].join(', ')} or ${LOG_LEVEL_NAMES.join(', ')}`, - ) - } - - // Try as level name - const upper = level.toUpperCase() as LogLevelName - if (upper in LOG_LEVEL) { - return LOG_LEVEL[upper] - } - throw new Error( - `Invalid log level: ${level}. Valid levels: ${LOG_LEVEL_NAMES.join(', ')} or ${[...LOG_LEVEL_VALUES].join(', ')}`, - ) -} - -export interface OutputCtlOptions { - logLevel?: LogLevelValue - jsonMode?: boolean -} - -/** Interface for output controllers (used to allow test mocks) */ -export interface IOutputCtl { - error(msg: unknown): void - warn(msg: unknown): void - notice(msg: unknown): void - info(msg: unknown): void - debug(msg: unknown): void - trace(msg: unknown): void - print(simple: unknown, json: unknown): void -} - -export default class OutputCtl implements IOutputCtl { - private json: boolean - private logLevel: LogLevelValue - - constructor({ logLevel = LOG_LEVEL_DEFAULT, jsonMode = false }: OutputCtlOptions = {}) { - this.json = jsonMode - this.logLevel = logLevel - - process.stdout.on('error', (err: NodeJS.ErrnoException) => { - if (err.code === 'EPIPE') { - process.exitCode = 0 - } - }) - process.stderr.on('error', (err: NodeJS.ErrnoException) => { - if (err.code === 'EPIPE') { - process.exitCode = 0 - } - }) - } - - error(msg: unknown): void { - if (this.logLevel >= LOG_LEVEL.ERR) console.error('err ', msg) - } - - warn(msg: unknown): void { - if (this.logLevel >= LOG_LEVEL.WARN) console.error('warn ', msg) - } - - notice(msg: unknown): void { - if (this.logLevel >= LOG_LEVEL.NOTICE) console.error('notice ', msg) - } - - info(msg: unknown): void { - if (this.logLevel >= LOG_LEVEL.INFO) console.error('info ', msg) - } - - debug(msg: unknown): void { - if (this.logLevel >= LOG_LEVEL.DEBUG) console.error('debug ', msg) - } - - trace(msg: unknown): void { - if (this.logLevel >= LOG_LEVEL.TRACE) console.error('trace ', msg) - } - - print(simple: unknown, json: unknown): void { - if (this.json) console.log(JSON.stringify(json)) - else if (typeof simple === 'string') console.log(simple) - else console.dir(simple, { depth: null }) - } -} diff --git a/packages/transloadit/src/cli/commands/BaseCommand.ts b/packages/transloadit/src/cli/commands/BaseCommand.ts deleted file mode 100644 index e652ec25..00000000 --- a/packages/transloadit/src/cli/commands/BaseCommand.ts +++ /dev/null @@ -1,71 +0,0 @@ -import 'dotenv/config' -import process from 'node:process' -import { Command, Option } from 'clipanion' -import { Transloadit as TransloaditClient } from '../../Transloadit.ts' -import { getEnvCredentials } from '../helpers.ts' -import type { IOutputCtl } from '../OutputCtl.ts' -import OutputCtl, { LOG_LEVEL_DEFAULT, LOG_LEVEL_NAMES, parseLogLevel } from '../OutputCtl.ts' - -abstract class BaseCommand extends Command { - logLevelOption = Option.String('-l,--log-level', { - description: `Log level: ${LOG_LEVEL_NAMES.join(', ')} or 3-8 (default: notice)`, - }) - - json = Option.Boolean('-j,--json', false, { - description: 'Output in JSON format', - }) - - endpoint = Option.String('--endpoint', { - description: - 'API endpoint URL (default: https://api2.transloadit.com, or TRANSLOADIT_ENDPOINT env var)', - }) - - protected output!: IOutputCtl - protected client!: TransloaditClient - - protected setupOutput(): void { - const logLevel = this.logLevelOption ? parseLogLevel(this.logLevelOption) : LOG_LEVEL_DEFAULT - this.output = new OutputCtl({ - logLevel, - jsonMode: this.json, - }) - } - - protected setupClient(): boolean { - const creds = getEnvCredentials() - if (!creds) { - this.output.error( - 'Please provide API authentication in the environment variables TRANSLOADIT_KEY and TRANSLOADIT_SECRET', - ) - return false - } - - const endpoint = this.endpoint || process.env.TRANSLOADIT_ENDPOINT - - this.client = new TransloaditClient({ ...creds, ...(endpoint && { endpoint }) }) - return true - } - - abstract override execute(): Promise -} - -export abstract class AuthenticatedCommand extends BaseCommand { - override async execute(): Promise { - this.setupOutput() - if (!this.setupClient()) { - return 1 - } - return await this.run() - } - - protected abstract run(): Promise -} - -export abstract class UnauthenticatedCommand extends BaseCommand { - override async execute(): Promise { - this.setupOutput() - return await this.run() - } - - protected abstract run(): Promise -} diff --git a/packages/transloadit/src/cli/commands/assemblies.ts b/packages/transloadit/src/cli/commands/assemblies.ts deleted file mode 100644 index d29ff660..00000000 --- a/packages/transloadit/src/cli/commands/assemblies.ts +++ /dev/null @@ -1,1373 +0,0 @@ -import EventEmitter from 'node:events' -import fs from 'node:fs' -import fsp from 'node:fs/promises' -import path from 'node:path' -import process from 'node:process' -import type { Readable, Writable } from 'node:stream' -import { pipeline } from 'node:stream/promises' -import { setTimeout as delay } from 'node:timers/promises' -import tty from 'node:tty' -import { promisify } from 'node:util' -import { Command, Option } from 'clipanion' -import got from 'got' -import PQueue from 'p-queue' -import * as t from 'typanion' -import { z } from 'zod' -import { tryCatch } from '../../alphalib/tryCatch.ts' -import type { Steps, StepsInput } from '../../alphalib/types/template.ts' -import { stepsSchema } from '../../alphalib/types/template.ts' -import type { CreateAssemblyParams, ReplayAssemblyParams } from '../../apiTypes.ts' -import type { CreateAssemblyOptions, Transloadit } from '../../Transloadit.ts' -import { createReadStream, formatAPIError, streamToBuffer } from '../helpers.ts' -import type { IOutputCtl } from '../OutputCtl.ts' -import { ensureError, isErrnoException } from '../types.ts' -import { AuthenticatedCommand } from './BaseCommand.ts' - -// --- From assemblies.ts: Schemas and interfaces --- -export interface AssemblyListOptions { - before?: string - after?: string - fields?: string[] - keywords?: string[] - pagesize?: number -} - -export interface AssemblyGetOptions { - assemblies: string[] -} - -interface AssemblyDeleteOptions { - assemblies: string[] -} - -export interface AssemblyReplayOptions { - fields?: Record - reparse?: boolean - steps?: string - notify_url?: string - assemblies: string[] -} - -const AssemblySchema = z.object({ - id: z.string(), -}) - -// --- Business logic functions (from assemblies.ts) --- - -export function list( - output: IOutputCtl, - client: Transloadit, - { before, after, fields, keywords }: AssemblyListOptions, -): Promise { - const assemblies = client.streamAssemblies({ - fromdate: after, - todate: before, - keywords, - }) - - assemblies.on('readable', () => { - const assembly: unknown = assemblies.read() - if (assembly == null) return - - const parsed = AssemblySchema.safeParse(assembly) - if (!parsed.success) return - - if (fields == null) { - output.print(parsed.data.id, assembly) - } else { - const assemblyRecord = assembly as Record - output.print(fields.map((field) => assemblyRecord[field]).join(' '), assembly) - } - }) - - return new Promise((resolve) => { - assemblies.on('end', resolve) - assemblies.on('error', (err: unknown) => { - output.error(formatAPIError(err)) - resolve() - }) - }) -} - -export async function get( - output: IOutputCtl, - client: Transloadit, - { assemblies }: AssemblyGetOptions, -): Promise { - for (const assembly of assemblies) { - await delay(1000) - const [err, result] = await tryCatch(client.getAssembly(assembly)) - if (err) { - output.error(formatAPIError(err)) - throw ensureError(err) - } - output.print(result, result) - } -} - -async function deleteAssemblies( - output: IOutputCtl, - client: Transloadit, - { assemblies }: AssemblyDeleteOptions, -): Promise { - const promises = assemblies.map(async (assembly) => { - const [err] = await tryCatch(client.cancelAssembly(assembly)) - if (err) { - output.error(formatAPIError(err)) - } - }) - await Promise.all(promises) -} - -// Export with `delete` alias for tests (can't use `delete` as function name) -export { deleteAssemblies as delete } - -export async function replay( - output: IOutputCtl, - client: Transloadit, - { fields, reparse, steps, notify_url, assemblies }: AssemblyReplayOptions, -): Promise { - if (steps) { - try { - const buf = await streamToBuffer(createReadStream(steps)) - const parsed: unknown = JSON.parse(buf.toString()) - const validated = stepsSchema.safeParse(parsed) - if (!validated.success) { - throw new Error(`Invalid steps format: ${validated.error.message}`) - } - await apiCall(validated.data) - } catch (err) { - const error = ensureError(err) - output.error(error.message) - } - } else { - await apiCall() - } - - async function apiCall(stepsOverride?: Steps): Promise { - const promises = assemblies.map(async (assembly) => { - const [err] = await tryCatch( - client.replayAssembly(assembly, { - reparse_template: reparse ? 1 : 0, - fields, - notify_url, - // Steps (validated) is assignable to StepsInput at runtime; cast for TS - steps: stepsOverride as ReplayAssemblyParams['steps'], - }), - ) - if (err) { - output.error(formatAPIError(err)) - } - }) - await Promise.all(promises) - } -} - -// --- From assemblies-create.ts: Helper classes and functions --- -interface NodeWatcher { - on(event: 'error', listener: (err: Error) => void): void - on(event: 'close', listener: () => void): void - on(event: 'change', listener: (evt: string, filename: string) => void): void - on(event: string, listener: (...args: unknown[]) => void): void - close(): void -} - -type NodeWatchFn = (path: string, options?: { recursive?: boolean }) => NodeWatcher - -let nodeWatch: NodeWatchFn | undefined - -async function getNodeWatch(): Promise { - if (!nodeWatch) { - const mod = (await import('node-watch')) as unknown as { default: NodeWatchFn } - nodeWatch = mod.default - } - return nodeWatch -} - -// workaround for determining mime-type of stdin -const stdinWithPath = process.stdin as unknown as { path: string } -stdinWithPath.path = '/dev/stdin' - -interface OutStream extends Writable { - path?: string - mtime?: Date -} - -interface Job { - in: Readable | null - out: OutStream | null -} - -type OutstreamProvider = (inpath: string | null, indir?: string) => Promise - -interface StreamRegistry { - [key: string]: OutStream | undefined -} - -interface JobEmitterOptions { - recursive?: boolean - outstreamProvider: OutstreamProvider - streamRegistry: StreamRegistry - watch?: boolean - reprocessStale?: boolean -} - -interface ReaddirJobEmitterOptions { - dir: string - streamRegistry: StreamRegistry - recursive?: boolean - outstreamProvider: OutstreamProvider - topdir?: string -} - -interface SingleJobEmitterOptions { - file: string - streamRegistry: StreamRegistry - outstreamProvider: OutstreamProvider -} - -interface WatchJobEmitterOptions { - file: string - streamRegistry: StreamRegistry - recursive?: boolean - outstreamProvider: OutstreamProvider -} - -interface StatLike { - isDirectory(): boolean -} - -const fstatAsync = promisify(fs.fstat) - -async function myStat( - stdioStream: NodeJS.ReadStream | NodeJS.WriteStream, - filepath: string, -): Promise { - if (filepath === '-') { - const stream = stdioStream as NodeJS.ReadStream & { fd: number } - return await fstatAsync(stream.fd) - } - return await fsp.stat(filepath) -} - -function dirProvider(output: string): OutstreamProvider { - return async (inpath, indir = process.cwd()) => { - if (inpath == null || inpath === '-') { - throw new Error('You must provide an input to output to a directory') - } - - let relpath = path.relative(indir, inpath) - relpath = relpath.replace(/^(\.\.\/)+/, '') - const outpath = path.join(output, relpath) - const outdir = path.dirname(outpath) - - await fsp.mkdir(outdir, { recursive: true }) - const [, stats] = await tryCatch(fsp.stat(outpath)) - const mtime = stats?.mtime ?? new Date(0) - const outstream = fs.createWriteStream(outpath) as OutStream - // Attach a no-op error handler to prevent unhandled errors if stream is destroyed - // before being consumed (e.g., due to output collision detection) - outstream.on('error', () => {}) - outstream.mtime = mtime - return outstream - } -} - -function fileProvider(output: string): OutstreamProvider { - const dirExistsP = fsp.mkdir(path.dirname(output), { recursive: true }) - return async (_inpath) => { - await dirExistsP - if (output === '-') return process.stdout as OutStream - - const [, stats] = await tryCatch(fsp.stat(output)) - const mtime = stats?.mtime ?? new Date(0) - const outstream = fs.createWriteStream(output) as OutStream - // Attach a no-op error handler to prevent unhandled errors if stream is destroyed - // before being consumed (e.g., due to output collision detection) - outstream.on('error', () => {}) - outstream.mtime = mtime - return outstream - } -} - -function nullProvider(): OutstreamProvider { - return async (_inpath) => null -} - -class MyEventEmitter extends EventEmitter { - protected hasEnded: boolean - - constructor() { - super() - this.hasEnded = false - } - - override emit(event: string | symbol, ...args: unknown[]): boolean { - if (this.hasEnded) return false - if (event === 'end' || event === 'error') { - this.hasEnded = true - return super.emit(event, ...args) - } - return super.emit(event, ...args) - } -} - -class ReaddirJobEmitter extends MyEventEmitter { - constructor({ - dir, - streamRegistry, - recursive, - outstreamProvider, - topdir = dir, - }: ReaddirJobEmitterOptions) { - super() - - process.nextTick(() => { - this.processDirectory({ dir, streamRegistry, recursive, outstreamProvider, topdir }).catch( - (err) => { - this.emit('error', err) - }, - ) - }) - } - - private async processDirectory({ - dir, - streamRegistry, - recursive, - outstreamProvider, - topdir, - }: ReaddirJobEmitterOptions & { topdir: string }): Promise { - const files = await fsp.readdir(dir) - - const pendingOperations: Promise[] = [] - - for (const filename of files) { - const file = path.normalize(path.join(dir, filename)) - pendingOperations.push( - this.processFile({ file, streamRegistry, recursive, outstreamProvider, topdir }), - ) - } - - await Promise.all(pendingOperations) - this.emit('end') - } - - private async processFile({ - file, - streamRegistry, - recursive = false, - outstreamProvider, - topdir, - }: { - file: string - streamRegistry: StreamRegistry - recursive?: boolean - outstreamProvider: OutstreamProvider - topdir: string - }): Promise { - const stats = await fsp.stat(file) - - if (stats.isDirectory()) { - if (recursive) { - await new Promise((resolve, reject) => { - const subdirEmitter = new ReaddirJobEmitter({ - dir: file, - streamRegistry, - recursive, - outstreamProvider, - topdir, - }) - subdirEmitter.on('job', (job: Job) => this.emit('job', job)) - subdirEmitter.on('error', (error: Error) => reject(error)) - subdirEmitter.on('end', () => resolve()) - }) - } - } else { - const existing = streamRegistry[file] - if (existing) existing.end() - const outstream = await outstreamProvider(file, topdir) - streamRegistry[file] = outstream ?? undefined - const instream = fs.createReadStream(file) - // Attach a no-op error handler to prevent unhandled errors if stream is destroyed - // before being consumed (e.g., due to output collision detection) - instream.on('error', () => {}) - this.emit('job', { in: instream, out: outstream }) - } - } -} - -class SingleJobEmitter extends MyEventEmitter { - constructor({ file, streamRegistry, outstreamProvider }: SingleJobEmitterOptions) { - super() - - const normalizedFile = path.normalize(file) - const existing = streamRegistry[normalizedFile] - if (existing) existing.end() - outstreamProvider(normalizedFile).then((outstream) => { - streamRegistry[normalizedFile] = outstream ?? undefined - - let instream: Readable | null - if (normalizedFile === '-') { - if (tty.isatty(process.stdin.fd)) { - instream = null - } else { - instream = process.stdin - } - } else { - instream = fs.createReadStream(normalizedFile) - // Attach a no-op error handler to prevent unhandled errors if stream is destroyed - // before being consumed (e.g., due to output collision detection) - instream.on('error', () => {}) - } - - process.nextTick(() => { - this.emit('job', { in: instream, out: outstream }) - this.emit('end') - }) - }) - } -} - -class InputlessJobEmitter extends MyEventEmitter { - constructor({ - outstreamProvider, - }: { streamRegistry: StreamRegistry; outstreamProvider: OutstreamProvider }) { - super() - - process.nextTick(() => { - outstreamProvider(null).then((outstream) => { - try { - this.emit('job', { in: null, out: outstream }) - } catch (err) { - this.emit('error', err) - } - - this.emit('end') - }) - }) - } -} - -class NullJobEmitter extends MyEventEmitter { - constructor() { - super() - process.nextTick(() => this.emit('end')) - } -} - -class WatchJobEmitter extends MyEventEmitter { - private watcher: NodeWatcher | null = null - - constructor({ file, streamRegistry, recursive, outstreamProvider }: WatchJobEmitterOptions) { - super() - - this.init({ file, streamRegistry, recursive, outstreamProvider }).catch((err) => { - this.emit('error', err) - }) - - // Clean up watcher on process exit signals - const cleanup = () => this.close() - process.once('SIGINT', cleanup) - process.once('SIGTERM', cleanup) - } - - /** Close the file watcher and release resources */ - close(): void { - if (this.watcher) { - this.watcher.close() - this.watcher = null - } - } - - private async init({ - file, - streamRegistry, - recursive, - outstreamProvider, - }: WatchJobEmitterOptions): Promise { - const stats = await fsp.stat(file) - const topdir = stats.isDirectory() ? file : undefined - - const watchFn = await getNodeWatch() - this.watcher = watchFn(file, { recursive }) - - this.watcher.on('error', (err: Error) => { - this.close() - this.emit('error', err) - }) - this.watcher.on('close', () => this.emit('end')) - this.watcher.on('change', (_evt: string, filename: string) => { - const normalizedFile = path.normalize(filename) - this.handleChange(normalizedFile, topdir, streamRegistry, outstreamProvider).catch((err) => { - this.emit('error', err) - }) - }) - } - - private async handleChange( - normalizedFile: string, - topdir: string | undefined, - streamRegistry: StreamRegistry, - outstreamProvider: OutstreamProvider, - ): Promise { - const stats = await fsp.stat(normalizedFile) - if (stats.isDirectory()) return - - const existing = streamRegistry[normalizedFile] - if (existing) existing.end() - - const outstream = await outstreamProvider(normalizedFile, topdir) - streamRegistry[normalizedFile] = outstream ?? undefined - - const instream = fs.createReadStream(normalizedFile) - // Attach a no-op error handler to prevent unhandled errors if stream is destroyed - // before being consumed (e.g., due to output collision detection) - instream.on('error', () => {}) - this.emit('job', { in: instream, out: outstream }) - } -} - -class MergedJobEmitter extends MyEventEmitter { - constructor(...jobEmitters: MyEventEmitter[]) { - super() - - let ncomplete = 0 - - for (const jobEmitter of jobEmitters) { - jobEmitter.on('error', (err: Error) => this.emit('error', err)) - jobEmitter.on('job', (job: Job) => this.emit('job', job)) - jobEmitter.on('end', () => { - if (++ncomplete === jobEmitters.length) this.emit('end') - }) - } - - if (jobEmitters.length === 0) { - this.emit('end') - } - } -} - -class ConcattedJobEmitter extends MyEventEmitter { - constructor(emitterFn: () => MyEventEmitter, ...emitterFns: (() => MyEventEmitter)[]) { - super() - - const emitter = emitterFn() - - emitter.on('error', (err: Error) => this.emit('error', err)) - emitter.on('job', (job: Job) => this.emit('job', job)) - - if (emitterFns.length === 0) { - emitter.on('end', () => this.emit('end')) - } else { - emitter.on('end', () => { - const firstFn = emitterFns[0] - if (!firstFn) { - this.emit('end') - return - } - const restEmitter = new ConcattedJobEmitter(firstFn, ...emitterFns.slice(1)) - restEmitter.on('error', (err: Error) => this.emit('error', err)) - restEmitter.on('job', (job: Job) => this.emit('job', job)) - restEmitter.on('end', () => this.emit('end')) - }) - } - } -} - -function detectConflicts(jobEmitter: EventEmitter): MyEventEmitter { - const emitter = new MyEventEmitter() - const outfileAssociations: Record = {} - - jobEmitter.on('end', () => emitter.emit('end')) - jobEmitter.on('error', (err: Error) => emitter.emit('error', err)) - jobEmitter.on('job', (job: Job) => { - if (job.in == null || job.out == null) { - emitter.emit('job', job) - return - } - const inPath = (job.in as fs.ReadStream).path as string - const outPath = job.out.path as string - if (Object.hasOwn(outfileAssociations, outPath) && outfileAssociations[outPath] !== inPath) { - emitter.emit( - 'error', - new Error(`Output collision between '${inPath}' and '${outfileAssociations[outPath]}'`), - ) - } else { - outfileAssociations[outPath] = inPath - emitter.emit('job', job) - } - }) - - return emitter -} - -function dismissStaleJobs(jobEmitter: EventEmitter): MyEventEmitter { - const emitter = new MyEventEmitter() - const pendingChecks: Promise[] = [] - - jobEmitter.on('end', () => Promise.all(pendingChecks).then(() => emitter.emit('end'))) - jobEmitter.on('error', (err: Error) => emitter.emit('error', err)) - jobEmitter.on('job', (job: Job) => { - if (job.in == null || job.out == null) { - emitter.emit('job', job) - return - } - - const inPath = (job.in as fs.ReadStream).path as string - const checkPromise = fsp - .stat(inPath) - .then((stats) => { - const inM = stats.mtime - const outM = job.out?.mtime ?? new Date(0) - - if (outM <= inM) emitter.emit('job', job) - }) - .catch(() => { - emitter.emit('job', job) - }) - pendingChecks.push(checkPromise) - }) - - return emitter -} - -function makeJobEmitter( - inputs: string[], - { - recursive, - outstreamProvider, - streamRegistry, - watch: watchOption, - reprocessStale, - }: JobEmitterOptions, -): MyEventEmitter { - const emitter = new EventEmitter() - - const emitterFns: (() => MyEventEmitter)[] = [] - const watcherFns: (() => MyEventEmitter)[] = [] - - async function processInputs(): Promise { - for (const input of inputs) { - if (input === '-') { - emitterFns.push( - () => new SingleJobEmitter({ file: input, outstreamProvider, streamRegistry }), - ) - watcherFns.push(() => new NullJobEmitter()) - } else { - const stats = await fsp.stat(input) - if (stats.isDirectory()) { - emitterFns.push( - () => - new ReaddirJobEmitter({ dir: input, recursive, outstreamProvider, streamRegistry }), - ) - watcherFns.push( - () => - new WatchJobEmitter({ file: input, recursive, outstreamProvider, streamRegistry }), - ) - } else { - emitterFns.push( - () => new SingleJobEmitter({ file: input, outstreamProvider, streamRegistry }), - ) - watcherFns.push( - () => - new WatchJobEmitter({ file: input, recursive, outstreamProvider, streamRegistry }), - ) - } - } - } - - if (inputs.length === 0) { - emitterFns.push(() => new InputlessJobEmitter({ outstreamProvider, streamRegistry })) - } - - startEmitting() - } - - function startEmitting(): void { - let source: MyEventEmitter = new MergedJobEmitter(...emitterFns.map((f) => f())) - - if (watchOption) { - source = new ConcattedJobEmitter( - () => source, - () => new MergedJobEmitter(...watcherFns.map((f) => f())), - ) - } - - source.on('job', (job: Job) => emitter.emit('job', job)) - source.on('error', (err: Error) => emitter.emit('error', err)) - source.on('end', () => emitter.emit('end')) - } - - processInputs().catch((err) => { - emitter.emit('error', err) - }) - - const stalefilter = reprocessStale ? (x: EventEmitter) => x as MyEventEmitter : dismissStaleJobs - return stalefilter(detectConflicts(emitter)) -} - -export interface AssembliesCreateOptions { - steps?: string - template?: string - fields?: Record - watch?: boolean - recursive?: boolean - inputs: string[] - output?: string | null - del?: boolean - reprocessStale?: boolean - singleAssembly?: boolean - concurrency?: number -} - -const DEFAULT_CONCURRENCY = 5 - -// --- Main assembly create function --- -export async function create( - outputctl: IOutputCtl, - client: Transloadit, - { - steps, - template, - fields, - watch: watchOption, - recursive, - inputs, - output, - del, - reprocessStale, - singleAssembly, - concurrency = DEFAULT_CONCURRENCY, - }: AssembliesCreateOptions, -): Promise<{ results: unknown[]; hasFailures: boolean }> { - // Quick fix for https://github.com/transloadit/transloadify/issues/13 - // Only default to stdout when output is undefined (not provided), not when explicitly null - let resolvedOutput = output - if (resolvedOutput === undefined && !process.stdout.isTTY) resolvedOutput = '-' - - // Read steps file async before entering the Promise constructor - // We use StepsInput (the input type) rather than Steps (the transformed output type) - // to avoid zod adding default values that the API may reject - let stepsData: StepsInput | undefined - if (steps) { - const stepsContent = await fsp.readFile(steps, 'utf8') - const parsed: unknown = JSON.parse(stepsContent) - // Basic structural validation: must be an object with step names as keys - if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed)) { - throw new Error('Invalid steps format: expected an object with step names as keys') - } - // Validate each step has a robot field - for (const [stepName, step] of Object.entries(parsed)) { - if (step == null || typeof step !== 'object' || Array.isArray(step)) { - throw new Error(`Invalid steps format: step '${stepName}' must be an object`) - } - if (!('robot' in step) || typeof (step as Record).robot !== 'string') { - throw new Error( - `Invalid steps format: step '${stepName}' must have a 'robot' string property`, - ) - } - } - stepsData = parsed as StepsInput - } - - // Determine output stat async before entering the Promise constructor - let outstat: StatLike | undefined - if (resolvedOutput != null) { - const [err, stat] = await tryCatch(myStat(process.stdout, resolvedOutput)) - if (err && (!isErrnoException(err) || err.code !== 'ENOENT')) throw err - outstat = stat ?? { isDirectory: () => false } - - if (!outstat.isDirectory() && inputs.length !== 0) { - const firstInput = inputs[0] - if (firstInput) { - const firstInputStat = await myStat(process.stdin, firstInput) - if (inputs.length > 1 || firstInputStat.isDirectory()) { - const msg = 'Output must be a directory when specifying multiple inputs' - outputctl.error(msg) - throw new Error(msg) - } - } - } - } - - return new Promise((resolve, reject) => { - const params: CreateAssemblyParams = ( - stepsData ? { steps: stepsData as CreateAssemblyParams['steps'] } : { template_id: template } - ) as CreateAssemblyParams - if (fields) { - params.fields = fields - } - - const outstreamProvider: OutstreamProvider = - resolvedOutput == null - ? nullProvider() - : outstat?.isDirectory() - ? dirProvider(resolvedOutput) - : fileProvider(resolvedOutput) - const streamRegistry: StreamRegistry = {} - - const emitter = makeJobEmitter(inputs, { - recursive, - watch: watchOption, - outstreamProvider, - streamRegistry, - reprocessStale, - }) - - // Use p-queue for concurrency management - const queue = new PQueue({ concurrency }) - const results: unknown[] = [] - let hasFailures = false - // AbortController to cancel all in-flight createAssembly calls when an error occurs - const abortController = new AbortController() - - // Helper to process a single assembly job - async function processAssemblyJob( - inPath: string | null, - outPath: string | null, - outMtime: Date | undefined, - ): Promise { - outputctl.debug(`PROCESSING JOB ${inPath ?? 'null'} ${outPath ?? 'null'}`) - - // Create fresh streams for this job - const inStream = inPath ? fs.createReadStream(inPath) : null - inStream?.on('error', () => {}) - const outStream = outPath ? (fs.createWriteStream(outPath) as OutStream) : null - outStream?.on('error', () => {}) - if (outStream) outStream.mtime = outMtime - - let superceded = false - if (outStream != null) { - outStream.on('finish', () => { - superceded = true - }) - } - - const createOptions: CreateAssemblyOptions = { - params, - signal: abortController.signal, - } - if (inStream != null) { - createOptions.uploads = { in: inStream } - } - - const result = await client.createAssembly(createOptions) - if (superceded) return undefined - - const assemblyId = result.assembly_id - if (!assemblyId) throw new Error('No assembly_id in result') - - const assembly = await client.awaitAssemblyCompletion(assemblyId, { - signal: abortController.signal, - onPoll: () => { - if (superceded) return false - return true - }, - onAssemblyProgress: (status) => { - outputctl.debug(`Assembly status: ${status.ok}`) - }, - }) - - if (superceded) return undefined - - if (assembly.error || (assembly.ok && assembly.ok !== 'ASSEMBLY_COMPLETED')) { - const msg = `Assembly failed: ${assembly.error || assembly.message} (Status: ${assembly.ok})` - outputctl.error(msg) - throw new Error(msg) - } - - if (!assembly.results) throw new Error('No results in assembly') - const resultsKeys = Object.keys(assembly.results) - const firstKey = resultsKeys[0] - if (!firstKey) throw new Error('No results in assembly') - const firstResult = assembly.results[firstKey] - if (!firstResult || !firstResult[0]) throw new Error('No results in assembly') - const resulturl = firstResult[0].url - - if (outStream != null && resulturl && !superceded) { - outputctl.debug('DOWNLOADING') - const [dlErr] = await tryCatch( - pipeline(got.stream(resulturl, { signal: abortController.signal }), outStream), - ) - if (dlErr) { - if (dlErr.name !== 'AbortError') { - outputctl.error(dlErr.message) - throw dlErr - } - } - } - - outputctl.debug(`COMPLETED ${inPath ?? 'null'} ${outPath ?? 'null'}`) - - if (del && inPath) { - await fsp.unlink(inPath) - } - return assembly - } - - if (singleAssembly) { - // Single-assembly mode: collect file paths, then create one assembly with all inputs - // We close streams immediately to avoid exhausting file descriptors with many files - const collectedPaths: string[] = [] - - emitter.on('job', (job: Job) => { - if (job.in != null) { - const inPath = (job.in as fs.ReadStream).path as string - outputctl.debug(`COLLECTING JOB ${inPath}`) - collectedPaths.push(inPath) - // Close the stream immediately to avoid file descriptor exhaustion - ;(job.in as fs.ReadStream).destroy() - outputctl.debug(`STREAM CLOSED ${inPath}`) - } - }) - - emitter.on('error', (err: Error) => { - abortController.abort() - queue.clear() - outputctl.error(err) - reject(err) - }) - - emitter.on('end', async () => { - if (collectedPaths.length === 0) { - resolve({ results: [], hasFailures: false }) - return - } - - // Build uploads object, creating fresh streams for each file - const uploads: Record = {} - const inputPaths: string[] = [] - for (const inPath of collectedPaths) { - const basename = path.basename(inPath) - let key = basename - let counter = 1 - while (key in uploads) { - key = `${path.parse(basename).name}_${counter}${path.parse(basename).ext}` - counter++ - } - uploads[key] = fs.createReadStream(inPath) - inputPaths.push(inPath) - } - - outputctl.debug(`Creating single assembly with ${Object.keys(uploads).length} files`) - - try { - const assembly = await queue.add(async () => { - const createOptions: CreateAssemblyOptions = { - params, - signal: abortController.signal, - } - if (Object.keys(uploads).length > 0) { - createOptions.uploads = uploads - } - - const result = await client.createAssembly(createOptions) - const assemblyId = result.assembly_id - if (!assemblyId) throw new Error('No assembly_id in result') - - const asm = await client.awaitAssemblyCompletion(assemblyId, { - signal: abortController.signal, - onAssemblyProgress: (status) => { - outputctl.debug(`Assembly status: ${status.ok}`) - }, - }) - - if (asm.error || (asm.ok && asm.ok !== 'ASSEMBLY_COMPLETED')) { - const msg = `Assembly failed: ${asm.error || asm.message} (Status: ${asm.ok})` - outputctl.error(msg) - throw new Error(msg) - } - - // Download all results - if (asm.results && resolvedOutput != null) { - for (const [stepName, stepResults] of Object.entries(asm.results)) { - for (const stepResult of stepResults) { - const resultUrl = stepResult.url - if (!resultUrl) continue - - let outPath: string - if (outstat?.isDirectory()) { - outPath = path.join(resolvedOutput, stepResult.name || `${stepName}_result`) - } else { - outPath = resolvedOutput - } - - outputctl.debug(`DOWNLOADING ${stepResult.name} to ${outPath}`) - const [dlErr] = await tryCatch( - pipeline( - got.stream(resultUrl, { signal: abortController.signal }), - fs.createWriteStream(outPath), - ), - ) - if (dlErr) { - if (dlErr.name === 'AbortError') continue - outputctl.error(dlErr.message) - throw dlErr - } - } - } - } - - // Delete input files if requested - if (del) { - for (const inPath of inputPaths) { - await fsp.unlink(inPath) - } - } - return asm - }) - results.push(assembly) - } catch (err) { - hasFailures = true - outputctl.error(err as Error) - } - - resolve({ results, hasFailures }) - }) - } else { - // Default mode: one assembly per file with p-queue concurrency limiting - emitter.on('job', (job: Job) => { - const inPath = job.in - ? (((job.in as fs.ReadStream).path as string | undefined) ?? null) - : null - const outPath = job.out?.path ?? null - const outMtime = job.out?.mtime - outputctl.debug(`GOT JOB ${inPath ?? 'null'} ${outPath ?? 'null'}`) - - // Close the original streams immediately - we'll create fresh ones when processing - if (job.in != null) { - ;(job.in as fs.ReadStream).destroy() - } - if (job.out != null) { - job.out.destroy() - } - - // Add job to queue - p-queue handles concurrency automatically - queue - .add(async () => { - const result = await processAssemblyJob(inPath, outPath, outMtime) - if (result !== undefined) { - results.push(result) - } - }) - .catch((err: unknown) => { - hasFailures = true - outputctl.error(err as Error) - }) - }) - - emitter.on('error', (err: Error) => { - abortController.abort() - queue.clear() - outputctl.error(err) - reject(err) - }) - - emitter.on('end', async () => { - // Wait for all queued jobs to complete - await queue.onIdle() - resolve({ results, hasFailures }) - }) - } - }) -} - -// --- Command classes --- -export class AssembliesCreateCommand extends AuthenticatedCommand { - static override paths = [ - ['assemblies', 'create'], - ['assembly', 'create'], - ['a', 'create'], - ['a', 'c'], - ] - - static override usage = Command.Usage({ - category: 'Assemblies', - description: 'Create assemblies to process media', - details: ` - Create assemblies to process media files using Transloadit. - You must specify either --steps or --template. - `, - examples: [ - [ - 'Process a file with steps', - 'transloadit assemblies create --steps steps.json -i input.jpg -o output.jpg', - ], - [ - 'Process with a template', - 'transloadit assemblies create --template TEMPLATE_ID -i input.jpg -o output/', - ], - [ - 'Watch for changes', - 'transloadit assemblies create --steps steps.json -i input/ -o output/ --watch', - ], - ], - }) - - steps = Option.String('--steps,-s', { - description: 'Specify assembly instructions with a JSON file', - }) - - template = Option.String('--template,-t', { - description: 'Specify a template to use for these assemblies', - }) - - inputs = Option.Array('--input,-i', { - description: 'Provide an input file or a directory', - }) - - outputPath = Option.String('--output,-o', { - description: 'Specify an output file or directory', - }) - - fields = Option.Array('--field,-f', { - description: 'Set a template field (KEY=VAL)', - }) - - watch = Option.Boolean('--watch,-w', false, { - description: 'Watch inputs for changes', - }) - - recursive = Option.Boolean('--recursive,-r', false, { - description: 'Enumerate input directories recursively', - }) - - deleteAfterProcessing = Option.Boolean('--delete-after-processing,-d', false, { - description: 'Delete input files after they are processed', - }) - - reprocessStale = Option.Boolean('--reprocess-stale', false, { - description: 'Process inputs even if output is newer', - }) - - singleAssembly = Option.Boolean('--single-assembly', false, { - description: 'Pass all input files to a single assembly instead of one assembly per file', - }) - - concurrency = Option.String('--concurrency,-c', { - description: 'Maximum number of concurrent assemblies (default: 5)', - validator: t.isNumber(), - }) - - protected async run(): Promise { - if (!this.steps && !this.template) { - this.output.error('assemblies create requires exactly one of either --steps or --template') - return 1 - } - if (this.steps && this.template) { - this.output.error('assemblies create requires exactly one of either --steps or --template') - return 1 - } - - const inputList = this.inputs ?? [] - if (inputList.length === 0 && this.watch) { - this.output.error('assemblies create --watch requires at least one input') - return 1 - } - - // Default to stdin if no inputs and not a TTY - if (inputList.length === 0 && !process.stdin.isTTY) { - inputList.push('-') - } - - const fieldsMap: Record = {} - for (const field of this.fields ?? []) { - const eqIndex = field.indexOf('=') - if (eqIndex === -1) { - this.output.error(`invalid argument for --field: '${field}'`) - return 1 - } - const key = field.slice(0, eqIndex) - const value = field.slice(eqIndex + 1) - fieldsMap[key] = value - } - - if (this.singleAssembly && this.watch) { - this.output.error('--single-assembly cannot be used with --watch') - return 1 - } - - const { hasFailures } = await create(this.output, this.client, { - steps: this.steps, - template: this.template, - fields: fieldsMap, - watch: this.watch, - recursive: this.recursive, - inputs: inputList, - output: this.outputPath ?? null, - del: this.deleteAfterProcessing, - reprocessStale: this.reprocessStale, - singleAssembly: this.singleAssembly, - concurrency: this.concurrency, - }) - return hasFailures ? 1 : undefined - } -} - -export class AssembliesListCommand extends AuthenticatedCommand { - static override paths = [ - ['assemblies', 'list'], - ['assembly', 'list'], - ['a', 'list'], - ['a', 'l'], - ] - - static override usage = Command.Usage({ - category: 'Assemblies', - description: 'List assemblies matching given criteria', - examples: [ - ['List recent assemblies', 'transloadit assemblies list'], - ['List assemblies after a date', 'transloadit assemblies list --after 2024-01-01'], - ], - }) - - before = Option.String('--before,-b', { - description: 'Return only assemblies created before specified date', - }) - - after = Option.String('--after,-a', { - description: 'Return only assemblies created after specified date', - }) - - keywords = Option.String('--keywords', { - description: 'Comma-separated list of keywords to match assemblies', - }) - - fields = Option.String('--fields', { - description: 'Comma-separated list of fields to return for each assembly', - }) - - protected async run(): Promise { - const keywordList = this.keywords ? this.keywords.split(',') : undefined - const fieldList = this.fields ? this.fields.split(',') : undefined - - await list(this.output, this.client, { - before: this.before, - after: this.after, - keywords: keywordList, - fields: fieldList, - }) - return undefined - } -} - -export class AssembliesGetCommand extends AuthenticatedCommand { - static override paths = [ - ['assemblies', 'get'], - ['assembly', 'get'], - ['a', 'get'], - ['a', 'g'], - ] - - static override usage = Command.Usage({ - category: 'Assemblies', - description: 'Fetch assembly statuses', - examples: [['Get assembly status', 'transloadit assemblies get ASSEMBLY_ID']], - }) - - assemblyIds = Option.Rest({ required: 1 }) - - protected async run(): Promise { - await get(this.output, this.client, { - assemblies: this.assemblyIds, - }) - return undefined - } -} - -export class AssembliesDeleteCommand extends AuthenticatedCommand { - static override paths = [ - ['assemblies', 'delete'], - ['assembly', 'delete'], - ['a', 'delete'], - ['a', 'd'], - ['assemblies', 'cancel'], - ['assembly', 'cancel'], - ] - - static override usage = Command.Usage({ - category: 'Assemblies', - description: 'Cancel assemblies', - examples: [['Cancel an assembly', 'transloadit assemblies delete ASSEMBLY_ID']], - }) - - assemblyIds = Option.Rest({ required: 1 }) - - protected async run(): Promise { - await deleteAssemblies(this.output, this.client, { - assemblies: this.assemblyIds, - }) - return undefined - } -} - -export class AssembliesReplayCommand extends AuthenticatedCommand { - static override paths = [ - ['assemblies', 'replay'], - ['assembly', 'replay'], - ['a', 'replay'], - ['a', 'r'], - ] - - static override usage = Command.Usage({ - category: 'Assemblies', - description: 'Replay assemblies', - details: ` - Replay one or more assemblies. By default, replays use the original assembly instructions. - Use --steps to override the instructions, or --reparse-template to use the latest template version. - `, - examples: [ - ['Replay an assembly with original steps', 'transloadit assemblies replay ASSEMBLY_ID'], - [ - 'Replay with different steps', - 'transloadit assemblies replay --steps new-steps.json ASSEMBLY_ID', - ], - [ - 'Replay with updated template', - 'transloadit assemblies replay --reparse-template ASSEMBLY_ID', - ], - ], - }) - - fields = Option.Array('--field,-f', { - description: 'Set a template field (KEY=VAL)', - }) - - steps = Option.String('--steps,-s', { - description: 'Optional JSON file to override assembly instructions', - }) - - notifyUrl = Option.String('--notify-url', { - description: 'Specify a new URL for assembly notifications', - }) - - reparseTemplate = Option.Boolean('--reparse-template', false, { - description: 'Use the most up-to-date version of the template', - }) - - assemblyIds = Option.Rest({ required: 1 }) - - protected async run(): Promise { - const fieldsMap: Record = {} - for (const field of this.fields ?? []) { - const eqIndex = field.indexOf('=') - if (eqIndex === -1) { - this.output.error(`invalid argument for --field: '${field}'`) - return 1 - } - const key = field.slice(0, eqIndex) - const value = field.slice(eqIndex + 1) - fieldsMap[key] = value - } - - await replay(this.output, this.client, { - fields: fieldsMap, - reparse: this.reparseTemplate, - steps: this.steps, - notify_url: this.notifyUrl, - assemblies: this.assemblyIds, - }) - return undefined - } -} diff --git a/packages/transloadit/src/cli/commands/auth.ts b/packages/transloadit/src/cli/commands/auth.ts deleted file mode 100644 index 9394367e..00000000 --- a/packages/transloadit/src/cli/commands/auth.ts +++ /dev/null @@ -1,354 +0,0 @@ -import process from 'node:process' -import { Command, Option } from 'clipanion' -import type { ZodIssue } from 'zod' -import { z } from 'zod' -import { - assemblyAuthInstructionsSchema, - assemblyInstructionsSchema, -} from '../../alphalib/types/template.ts' -import type { OptionalAuthParams } from '../../apiTypes.ts' -import { Transloadit } from '../../Transloadit.ts' -import { getEnvCredentials } from '../helpers.ts' -import { UnauthenticatedCommand } from './BaseCommand.ts' - -type UrlParamPrimitive = string | number | boolean -type UrlParamArray = UrlParamPrimitive[] -type NormalizedUrlParams = Record - -const smartCdnParamsSchema = z - .object({ - workspace: z.string().min(1, 'workspace is required'), - template: z.string().min(1, 'template is required'), - input: z.union([z.string(), z.number(), z.boolean()]), - url_params: z.record(z.unknown()).optional(), - expire_at_ms: z.union([z.number(), z.string()]).optional(), - }) - .passthrough() - -const cliSignatureParamsSchema = assemblyInstructionsSchema - .extend({ auth: assemblyAuthInstructionsSchema.partial().optional() }) - .partial() - .passthrough() - -type CliSignatureParams = z.infer - -function formatIssues(issues: ZodIssue[]): string { - return issues - .map((issue) => { - const path = issue.path.join('.') || '(root)' - return `${path}: ${issue.message}` - }) - .join('; ') -} - -function normalizeUrlParam(value: unknown): UrlParamPrimitive | UrlParamArray | undefined { - if (value == null) return undefined - if (Array.isArray(value)) { - const normalized = value.filter( - (item): item is UrlParamPrimitive => - typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean', - ) - return normalized.length > 0 ? normalized : undefined - } - if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { - return value - } - return undefined -} - -function normalizeUrlParams(params?: Record): NormalizedUrlParams | undefined { - if (params == null) return undefined - let normalized: NormalizedUrlParams | undefined - for (const [key, value] of Object.entries(params)) { - const normalizedValue = normalizeUrlParam(value) - if (normalizedValue === undefined) continue - if (normalized == null) normalized = {} - normalized[key] = normalizedValue - } - return normalized -} - -async function readStdin(): Promise { - if (process.stdin.isTTY) return '' - - process.stdin.setEncoding('utf8') - let data = '' - - for await (const chunk of process.stdin) { - data += chunk - } - - return data -} - -const getCredentials = getEnvCredentials - -// Result type for signature operations -type SigResult = { ok: true; output: string } | { ok: false; error: string } - -// Core logic for signature generation -function generateSignature( - input: string, - credentials: { authKey: string; authSecret: string }, - algorithm?: string, -): SigResult { - const { authKey, authSecret } = credentials - let params: CliSignatureParams - - if (input === '') { - params = { auth: { key: authKey } } - } else { - let parsed: unknown - try { - parsed = JSON.parse(input) - } catch (error) { - return { ok: false, error: `Failed to parse JSON from stdin: ${(error as Error).message}` } - } - - if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed)) { - return { ok: false, error: 'Invalid params provided via stdin. Expected a JSON object.' } - } - - const parsedResult = cliSignatureParamsSchema.safeParse(parsed) - if (!parsedResult.success) { - return { ok: false, error: `Invalid params: ${formatIssues(parsedResult.error.issues)}` } - } - - const parsedParams = parsedResult.data - const existingAuth = parsedParams.auth ?? {} - - params = { - ...parsedParams, - auth: { - ...existingAuth, - key: authKey, - }, - } - } - - const client = new Transloadit({ authKey, authSecret }) - try { - const signature = client.calcSignature(params as OptionalAuthParams, algorithm) - return { ok: true, output: JSON.stringify(signature) } - } catch (error) { - return { ok: false, error: `Failed to generate signature: ${(error as Error).message}` } - } -} - -// Core logic for Smart CDN URL generation -function generateSmartCdnUrl( - input: string, - credentials: { authKey: string; authSecret: string }, -): SigResult { - const { authKey, authSecret } = credentials - - if (input === '') { - return { - ok: false, - error: - 'Missing params provided via stdin. Expected a JSON object with workspace, template, input, and optional Smart CDN parameters.', - } - } - - let parsed: unknown - try { - parsed = JSON.parse(input) - } catch (error) { - return { ok: false, error: `Failed to parse JSON from stdin: ${(error as Error).message}` } - } - - if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed)) { - return { ok: false, error: 'Invalid params provided via stdin. Expected a JSON object.' } - } - - const parsedResult = smartCdnParamsSchema.safeParse(parsed) - if (!parsedResult.success) { - return { ok: false, error: `Invalid params: ${formatIssues(parsedResult.error.issues)}` } - } - - const { workspace, template, input: inputFieldRaw, url_params, expire_at_ms } = parsedResult.data - const urlParams = normalizeUrlParams(url_params) - - let expiresAt: number | undefined - if (typeof expire_at_ms === 'string') { - const parsedNumber = Number.parseInt(expire_at_ms, 10) - if (Number.isNaN(parsedNumber)) { - return { ok: false, error: 'Invalid params: expire_at_ms must be a number.' } - } - expiresAt = parsedNumber - } else { - expiresAt = expire_at_ms - } - - const inputField = typeof inputFieldRaw === 'string' ? inputFieldRaw : String(inputFieldRaw) - - const client = new Transloadit({ authKey, authSecret }) - try { - const signedUrl = client.getSignedSmartCDNUrl({ - workspace, - template, - input: inputField, - urlParams, - expiresAt, - }) - return { ok: true, output: signedUrl } - } catch (error) { - return { ok: false, error: `Failed to generate Smart CDN URL: ${(error as Error).message}` } - } -} - -// Testable helper functions exported for unit tests -export interface RunSigOptions { - providedInput?: string - algorithm?: string -} - -export interface RunSmartSigOptions { - providedInput?: string -} - -export async function runSig(options: RunSigOptions = {}): Promise { - const credentials = getCredentials() - if (credentials == null) { - console.error( - 'Missing credentials. Please set TRANSLOADIT_KEY and TRANSLOADIT_SECRET environment variables.', - ) - process.exitCode = 1 - return - } - - const rawInput = options.providedInput ?? (await readStdin()) - const result = generateSignature(rawInput.trim(), credentials, options.algorithm) - - if (result.ok) { - process.stdout.write(`${result.output}\n`) - } else { - console.error(result.error) - process.exitCode = 1 - } -} - -export async function runSmartSig(options: RunSmartSigOptions = {}): Promise { - const credentials = getCredentials() - if (credentials == null) { - console.error( - 'Missing credentials. Please set TRANSLOADIT_KEY and TRANSLOADIT_SECRET environment variables.', - ) - process.exitCode = 1 - return - } - - const rawInput = options.providedInput ?? (await readStdin()) - const result = generateSmartCdnUrl(rawInput.trim(), credentials) - - if (result.ok) { - process.stdout.write(`${result.output}\n`) - } else { - console.error(result.error) - process.exitCode = 1 - } -} - -/** - * Generate a signature for assembly params - */ -export class SignatureCommand extends UnauthenticatedCommand { - static override paths = [ - ['auth', 'signature'], - ['auth', 'sig'], - ['signature'], - ['sig'], // BC alias - ] - - static override usage = Command.Usage({ - category: 'Auth', - description: 'Generate a signature for assembly params', - details: ` - Read params JSON from stdin and output signed payload JSON. - If no input is provided, generates a signature with default params. - `, - examples: [ - ['Generate signature', 'echo \'{"steps":{}}\' | transloadit signature'], - ['With algorithm', 'echo \'{"steps":{}}\' | transloadit signature --algorithm sha384'], - ['Using alias', 'echo \'{"steps":{}}\' | transloadit sig'], - ], - }) - - algorithm = Option.String('--algorithm,-a', { - description: 'Signature algorithm to use (sha1, sha256, sha384, sha512)', - }) - - protected async run(): Promise { - const credentials = getCredentials() - if (credentials == null) { - this.output.error( - 'Missing credentials. Please set TRANSLOADIT_KEY and TRANSLOADIT_SECRET environment variables.', - ) - return 1 - } - - const rawInput = await readStdin() - const result = generateSignature(rawInput.trim(), credentials, this.algorithm) - - if (result.ok) { - process.stdout.write(`${result.output}\n`) - return undefined - } - - this.output.error(result.error) - return 1 - } -} - -/** - * Generate a signed Smart CDN URL - */ -export class SmartCdnSignatureCommand extends UnauthenticatedCommand { - static override paths = [ - ['auth', 'smart-cdn'], - ['auth', 'smart_cdn'], - ['smart-cdn'], - ['smart_sig'], // BC alias - ] - - static override usage = Command.Usage({ - category: 'Auth', - description: 'Generate a signed Smart CDN URL', - details: ` - Read Smart CDN params JSON from stdin and output a signed URL. - Required fields: workspace, template, input - Optional fields: expire_at_ms, url_params - `, - examples: [ - [ - 'Generate Smart CDN URL', - 'echo \'{"workspace":"w","template":"t","input":"i"}\' | transloadit smart-cdn', - ], - [ - 'Using alias', - 'echo \'{"workspace":"w","template":"t","input":"i"}\' | transloadit smart_sig', - ], - ], - }) - - protected async run(): Promise { - const credentials = getCredentials() - if (credentials == null) { - this.output.error( - 'Missing credentials. Please set TRANSLOADIT_KEY and TRANSLOADIT_SECRET environment variables.', - ) - return 1 - } - - const rawInput = await readStdin() - const result = generateSmartCdnUrl(rawInput.trim(), credentials) - - if (result.ok) { - process.stdout.write(`${result.output}\n`) - return undefined - } - - this.output.error(result.error) - return 1 - } -} diff --git a/packages/transloadit/src/cli/commands/bills.ts b/packages/transloadit/src/cli/commands/bills.ts deleted file mode 100644 index 03d0a998..00000000 --- a/packages/transloadit/src/cli/commands/bills.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { Command, Option } from 'clipanion' -import { z } from 'zod' -import { tryCatch } from '../../alphalib/tryCatch.ts' -import type { Transloadit } from '../../Transloadit.ts' -import { formatAPIError } from '../helpers.ts' -import type { IOutputCtl } from '../OutputCtl.ts' -import { AuthenticatedCommand } from './BaseCommand.ts' - -// --- Types and business logic --- - -export interface BillsGetOptions { - months: string[] -} - -const BillResponseSchema = z.object({ - total: z.number(), -}) - -export async function get( - output: IOutputCtl, - client: Transloadit, - { months }: BillsGetOptions, -): Promise { - const requests = months.map((month) => client.getBill(month)) - - const [err, results] = await tryCatch(Promise.all(requests)) - if (err) { - output.error(formatAPIError(err)) - return - } - - for (const result of results) { - const parsed = BillResponseSchema.safeParse(result) - if (parsed.success) { - output.print(`$${parsed.data.total}`, result) - } else { - output.print('Unable to parse bill response', result) - } - } -} - -// --- Command class --- - -export class BillsGetCommand extends AuthenticatedCommand { - static override paths = [ - ['bills', 'get'], - ['bill', 'get'], - ['b', 'get'], - ['b', 'g'], - ] - - static override usage = Command.Usage({ - category: 'Bills', - description: 'Fetch billing information', - details: ` - Fetch billing information for the specified months. - Months should be specified in YYYY-MM format. - If no month is specified, returns the current month. - `, - examples: [ - ['Get current month billing', 'transloadit bills get'], - ['Get specific month', 'transloadit bills get 2024-01'], - ['Get multiple months', 'transloadit bills get 2024-01 2024-02'], - ], - }) - - months = Option.Rest() - - protected async run(): Promise { - const monthList: string[] = [] - - for (const month of this.months) { - if (!/^\d{4}-\d{1,2}$/.test(month)) { - this.output.error(`invalid date format '${month}' (YYYY-MM)`) - return 1 - } - monthList.push(month) - } - - // Default to current month if none specified - if (monthList.length === 0) { - const d = new Date() - monthList.push(`${d.getUTCFullYear()}-${d.getUTCMonth() + 1}`) - } - - await get(this.output, this.client, { - months: monthList, - }) - return undefined - } -} diff --git a/packages/transloadit/src/cli/commands/index.ts b/packages/transloadit/src/cli/commands/index.ts deleted file mode 100644 index 5837d5a9..00000000 --- a/packages/transloadit/src/cli/commands/index.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Builtins, Cli } from 'clipanion' - -import packageJson from '../../../package.json' with { type: 'json' } - -import { - AssembliesCreateCommand, - AssembliesDeleteCommand, - AssembliesGetCommand, - AssembliesListCommand, - AssembliesReplayCommand, -} from './assemblies.ts' - -import { SignatureCommand, SmartCdnSignatureCommand } from './auth.ts' - -import { BillsGetCommand } from './bills.ts' - -import { NotificationsReplayCommand } from './notifications.ts' - -import { - TemplatesCreateCommand, - TemplatesDeleteCommand, - TemplatesGetCommand, - TemplatesListCommand, - TemplatesModifyCommand, - TemplatesSyncCommand, -} from './templates.ts' - -export function createCli(): Cli { - const cli = new Cli({ - binaryLabel: 'Transloadit CLI', - binaryName: 'transloadit', - binaryVersion: packageJson.version, - }) - - // Built-in commands - cli.register(Builtins.HelpCommand) - cli.register(Builtins.VersionCommand) - - // Auth commands (signature generation) - cli.register(SignatureCommand) - cli.register(SmartCdnSignatureCommand) - - // Assemblies commands - cli.register(AssembliesCreateCommand) - cli.register(AssembliesListCommand) - cli.register(AssembliesGetCommand) - cli.register(AssembliesDeleteCommand) - cli.register(AssembliesReplayCommand) - - // Templates commands - cli.register(TemplatesCreateCommand) - cli.register(TemplatesGetCommand) - cli.register(TemplatesModifyCommand) - cli.register(TemplatesDeleteCommand) - cli.register(TemplatesListCommand) - cli.register(TemplatesSyncCommand) - - // Bills commands - cli.register(BillsGetCommand) - - // Notifications commands - cli.register(NotificationsReplayCommand) - - return cli -} diff --git a/packages/transloadit/src/cli/commands/notifications.ts b/packages/transloadit/src/cli/commands/notifications.ts deleted file mode 100644 index e65ba452..00000000 --- a/packages/transloadit/src/cli/commands/notifications.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Command, Option } from 'clipanion' -import { tryCatch } from '../../alphalib/tryCatch.ts' -import type { Transloadit } from '../../Transloadit.ts' -import type { IOutputCtl } from '../OutputCtl.ts' -import { ensureError } from '../types.ts' -import { AuthenticatedCommand } from './BaseCommand.ts' - -// --- Types and business logic --- - -interface NotificationsReplayOptions { - notify_url?: string - assemblies: string[] -} - -async function replay( - output: IOutputCtl, - client: Transloadit, - { notify_url, assemblies }: NotificationsReplayOptions, -): Promise { - const promises = assemblies.map((id) => client.replayAssemblyNotification(id, { notify_url })) - const [err] = await tryCatch(Promise.all(promises)) - if (err) { - output.error(ensureError(err).message) - } -} - -// --- Command class --- - -export class NotificationsReplayCommand extends AuthenticatedCommand { - static override paths = [ - ['assembly-notifications', 'replay'], - ['notifications', 'replay'], - ['notification', 'replay'], - ['n', 'replay'], - ['n', 'r'], - ] - - static override usage = Command.Usage({ - category: 'Notifications', - description: 'Replay notifications for assemblies', - examples: [ - ['Replay notifications', 'transloadit assembly-notifications replay ASSEMBLY_ID'], - [ - 'Replay to a new URL', - 'transloadit assembly-notifications replay --notify-url https://example.com/notify ASSEMBLY_ID', - ], - ], - }) - - notifyUrl = Option.String('--notify-url', { - description: 'Specify a new URL to send the notifications to', - }) - - assemblyIds = Option.Rest({ required: 1 }) - - protected async run(): Promise { - await replay(this.output, this.client, { - notify_url: this.notifyUrl, - assemblies: this.assemblyIds, - }) - return undefined - } -} diff --git a/packages/transloadit/src/cli/commands/templates.ts b/packages/transloadit/src/cli/commands/templates.ts deleted file mode 100644 index 03405d54..00000000 --- a/packages/transloadit/src/cli/commands/templates.ts +++ /dev/null @@ -1,556 +0,0 @@ -import fsp from 'node:fs/promises' -import path from 'node:path' -import { promisify } from 'node:util' -import { Command, Option } from 'clipanion' -import rreaddir from 'recursive-readdir' -import { z } from 'zod' -import { tryCatch } from '../../alphalib/tryCatch.ts' -import type { Steps } from '../../alphalib/types/template.ts' -import { stepsSchema } from '../../alphalib/types/template.ts' -import type { TemplateContent } from '../../apiTypes.ts' -import type { Transloadit } from '../../Transloadit.ts' -import { createReadStream, formatAPIError, streamToBuffer } from '../helpers.ts' -import type { IOutputCtl } from '../OutputCtl.ts' -import ModifiedLookup from '../template-last-modified.ts' -import type { TemplateFile } from '../types.ts' -import { ensureError, isTransloaditAPIError, TemplateFileDataSchema } from '../types.ts' -import { AuthenticatedCommand } from './BaseCommand.ts' - -const rreaddirAsync = promisify(rreaddir) - -export interface TemplateCreateOptions { - name: string - file: string -} - -export interface TemplateGetOptions { - templates: string[] -} - -export interface TemplateModifyOptions { - template: string - name?: string - file: string -} - -interface TemplateDeleteOptions { - templates: string[] -} - -interface TemplateListOptions { - before?: string - after?: string - order?: 'asc' | 'desc' - sort?: string - fields?: string[] -} - -export interface TemplateSyncOptions { - files: string[] - recursive?: boolean -} - -export async function create( - output: IOutputCtl, - client: Transloadit, - { name, file }: TemplateCreateOptions, -): Promise { - try { - const buf = await streamToBuffer(createReadStream(file)) - - const parsed: unknown = JSON.parse(buf.toString()) - const validated = stepsSchema.safeParse(parsed) - if (!validated.success) { - throw new Error(`Invalid template steps format: ${validated.error.message}`) - } - - const result = await client.createTemplate({ - name, - // Steps (validated) is assignable to StepsInput at runtime; cast for TS - template: { steps: validated.data } as TemplateContent, - }) - output.print(result.id, result) - return result - } catch (err) { - const error = ensureError(err) - output.error(error.message) - throw err - } -} - -export async function get( - output: IOutputCtl, - client: Transloadit, - { templates }: TemplateGetOptions, -): Promise { - const requests = templates.map((template) => client.getTemplate(template)) - - const [err, results] = await tryCatch(Promise.all(requests)) - if (err) { - output.error(formatAPIError(err)) - throw err - } - - for (const result of results) { - output.print(result, result) - } -} - -export async function modify( - output: IOutputCtl, - client: Transloadit, - { template, name, file }: TemplateModifyOptions, -): Promise { - try { - const buf = await streamToBuffer(createReadStream(file)) - - let steps: Steps | null = null - let newName = name - - if (buf.length > 0) { - const parsed: unknown = JSON.parse(buf.toString()) - const validated = stepsSchema.safeParse(parsed) - if (!validated.success) { - throw new Error(`Invalid template steps format: ${validated.error.message}`) - } - steps = validated.data - } - - if (!name || buf.length === 0) { - const tpl = await client.getTemplate(template) - if (!name) newName = tpl.name - if (buf.length === 0 && tpl.content.steps) { - steps = tpl.content.steps - } - } - - if (steps === null) { - throw new Error('No steps to update template with') - } - - await client.editTemplate(template, { - name: newName, - // Steps (validated) is assignable to StepsInput at runtime; cast for TS - template: { steps } as TemplateContent, - }) - } catch (err) { - output.error(formatAPIError(err)) - throw err - } -} - -async function deleteTemplates( - output: IOutputCtl, - client: Transloadit, - { templates }: TemplateDeleteOptions, -): Promise { - await Promise.all( - templates.map(async (template) => { - const [err] = await tryCatch(client.deleteTemplate(template)) - if (err) { - output.error(formatAPIError(err)) - throw err - } - }), - ) -} - -// Export with `delete` alias for external consumers -export { deleteTemplates as delete } - -const TemplateIdSchema = z.object({ - id: z.string(), -}) - -function list( - output: IOutputCtl, - client: Transloadit, - { before, after, order, sort, fields }: TemplateListOptions, -): void { - const stream = client.streamTemplates({ - todate: before, - fromdate: after, - order, - sort: sort as 'id' | 'name' | 'created' | 'modified' | undefined, - }) - - stream.on('readable', () => { - const template: unknown = stream.read() - if (template == null) return - - const parsed = TemplateIdSchema.safeParse(template) - if (!parsed.success) return - - if (fields == null) { - output.print(parsed.data.id, template) - } else { - const templateRecord = template as Record - output.print(fields.map((field) => templateRecord[field]).join(' '), template) - } - }) - - stream.on('error', (err: unknown) => { - output.error(formatAPIError(err)) - }) -} - -export async function sync( - output: IOutputCtl, - client: Transloadit, - { files, recursive }: TemplateSyncOptions, -): Promise { - // Promise [String] -- all files in the directory tree - const relevantFilesNested = await Promise.all( - files.map(async (file) => { - const stats = await fsp.stat(file) - if (!stats.isDirectory()) return [file] - - let children: string[] - if (recursive) { - children = (await rreaddirAsync(file)) as string[] - } else { - const list = await fsp.readdir(file) - children = list.map((child) => path.join(file, child)) - } - - if (recursive) return children - - // Filter directories if not recursive - const filtered = await Promise.all( - children.map(async (child) => { - const childStats = await fsp.stat(child) - return childStats.isDirectory() ? null : child - }), - ) - return filtered.filter((f): f is string => f !== null) - }), - ) - const relevantFiles = relevantFilesNested.flat() - - // Promise [{ file: String, data: JSON }] -- all templates - const maybeFiles = await Promise.all(relevantFiles.map(templateFileOrNull)) - const templates = maybeFiles.filter((maybeFile): maybeFile is TemplateFile => maybeFile !== null) - - async function templateFileOrNull(file: string): Promise { - if (path.extname(file) !== '.json') return null - - try { - const data = await fsp.readFile(file, 'utf8') - const parsed: unknown = JSON.parse(data) - const validated = TemplateFileDataSchema.safeParse(parsed) - if (!validated.success) return null - return 'transloadit_template_id' in validated.data ? { file, data: validated.data } : null - } catch (e) { - if (e instanceof SyntaxError) return null - throw e - } - } - - const modified = new ModifiedLookup(client) - - const [err] = await tryCatch( - Promise.all( - templates.map(async (template) => { - if (!('steps' in template.data)) { - if (!template.data.transloadit_template_id) { - throw new Error(`Template file has no id and no steps: ${template.file}`) - } - return download(template) - } - - if (!template.data.transloadit_template_id) return upload(template) - - const stats = await fsp.stat(template.file) - const fileModified = stats.mtime - - let templateModified: Date - const templateId = template.data.transloadit_template_id - try { - await client.getTemplate(templateId) - templateModified = await new Promise((resolve, reject) => - modified.byId(templateId, (err, res) => { - if (err) { - reject(err) - } else if (res) { - resolve(res) - } else { - reject(new Error('No date returned')) - } - }), - ) - } catch (err) { - if (isTransloaditAPIError(err)) { - if (err.code === 'SERVER_404' || (err.response && err.response.statusCode === 404)) { - throw new Error(`Template file references nonexistent template: ${template.file}`) - } - } - throw err - } - - if (fileModified > templateModified) return upload(template) - return download(template) - }), - ), - ) - if (err) { - output.error(err) - throw err - } - - async function upload(template: TemplateFile): Promise { - const params = { - name: path.basename(template.file, '.json'), - template: { steps: template.data.steps } as TemplateContent, - } - - if (!template.data.transloadit_template_id) { - const result = await client.createTemplate(params) - template.data.transloadit_template_id = result.id - await fsp.writeFile(template.file, JSON.stringify(template.data)) - return - } - - await client.editTemplate(template.data.transloadit_template_id, params) - } - - async function download(template: TemplateFile): Promise { - const templateId = template.data.transloadit_template_id - if (!templateId) { - throw new Error('Cannot download template without id') - } - - const result = await client.getTemplate(templateId) - - // Use empty object if template has no steps (undefined would be stripped by JSON.stringify) - template.data.steps = result.content.steps ?? {} - const file = path.join(path.dirname(template.file), `${result.name}.json`) - - await fsp.writeFile(template.file, JSON.stringify(template.data)) - - if (file !== template.file) { - await fsp.rename(template.file, file) - } - } -} -export class TemplatesCreateCommand extends AuthenticatedCommand { - static override paths = [ - ['templates', 'create'], - ['template', 'create'], - ['t', 'create'], - ['t', 'c'], - ] - - static override usage = Command.Usage({ - category: 'Templates', - description: 'Create a new template', - details: ` - Create a new template with the given name. - If FILE is not specified, reads from STDIN. - `, - examples: [ - ['Create template from file', 'transloadit templates create my-template steps.json'], - ['Create template from stdin', 'cat steps.json | transloadit templates create my-template'], - ], - }) - - name = Option.String({ required: true }) - file = Option.String({ required: false }) - - protected async run(): Promise { - await create(this.output, this.client, { - name: this.name, - file: this.file ?? '-', - }) - return undefined - } -} - -export class TemplatesGetCommand extends AuthenticatedCommand { - static override paths = [ - ['templates', 'get'], - ['template', 'get'], - ['t', 'get'], - ['t', 'g'], - ] - - static override usage = Command.Usage({ - category: 'Templates', - description: 'Retrieve the template content as JSON', - examples: [['Get a template', 'transloadit templates get TEMPLATE_ID']], - }) - - templateIds = Option.Rest({ required: 1 }) - - protected async run(): Promise { - await get(this.output, this.client, { - templates: this.templateIds, - }) - return undefined - } -} - -export class TemplatesModifyCommand extends AuthenticatedCommand { - static override paths = [ - ['templates', 'modify'], - ['template', 'modify'], - ['t', 'modify'], - ['t', 'm'], - ['templates', 'edit'], - ['template', 'edit'], - ] - - static override usage = Command.Usage({ - category: 'Templates', - description: 'Change the JSON content of a template', - details: ` - Modify an existing template. - If FILE is not specified, reads from STDIN. - `, - examples: [ - ['Modify template from file', 'transloadit templates modify TEMPLATE_ID steps.json'], - ['Rename a template', 'transloadit templates modify --name new-name TEMPLATE_ID'], - ], - }) - - newName = Option.String('--name,-n', { - description: 'A new name for the template', - }) - - templateId = Option.String({ required: true }) - file = Option.String({ required: false }) - - protected async run(): Promise { - await modify(this.output, this.client, { - template: this.templateId, - name: this.newName, - file: this.file ?? '-', - }) - return undefined - } -} - -export class TemplatesDeleteCommand extends AuthenticatedCommand { - static override paths = [ - ['templates', 'delete'], - ['template', 'delete'], - ['t', 'delete'], - ['t', 'd'], - ] - - static override usage = Command.Usage({ - category: 'Templates', - description: 'Delete templates', - examples: [['Delete a template', 'transloadit templates delete TEMPLATE_ID']], - }) - - templateIds = Option.Rest({ required: 1 }) - - protected async run(): Promise { - await deleteTemplates(this.output, this.client, { - templates: this.templateIds, - }) - return undefined - } -} - -export class TemplatesListCommand extends AuthenticatedCommand { - static override paths = [ - ['templates', 'list'], - ['template', 'list'], - ['t', 'list'], - ['t', 'l'], - ] - - static override usage = Command.Usage({ - category: 'Templates', - description: 'List templates matching given criteria', - examples: [ - ['List all templates', 'transloadit templates list'], - ['List templates sorted by name', 'transloadit templates list --sort name'], - ], - }) - - after = Option.String('--after,-a', { - description: 'Return only templates created after specified date', - }) - - before = Option.String('--before,-b', { - description: 'Return only templates created before specified date', - }) - - sort = Option.String('--sort', { - description: 'Field to sort by (id, name, created, or modified)', - }) - - order = Option.String('--order', { - description: 'Sort ascending or descending (asc or desc)', - }) - - fields = Option.String('--fields', { - description: 'Comma-separated list of fields to return for each template', - }) - - protected async run(): Promise { - if (this.sort && !['id', 'name', 'created', 'modified'].includes(this.sort)) { - this.output.error('invalid argument for --sort') - return 1 - } - - if (this.order && !['asc', 'desc'].includes(this.order)) { - this.output.error('invalid argument for --order') - return 1 - } - - const fieldList = this.fields ? this.fields.split(',') : undefined - - await list(this.output, this.client, { - after: this.after, - before: this.before, - sort: this.sort, - order: this.order as 'asc' | 'desc' | undefined, - fields: fieldList, - }) - return undefined - } -} - -export class TemplatesSyncCommand extends AuthenticatedCommand { - static override paths = [ - ['templates', 'sync'], - ['template', 'sync'], - ['t', 'sync'], - ['t', 's'], - ] - - static override usage = Command.Usage({ - category: 'Templates', - description: 'Synchronize local template files with the Transloadit API', - details: ` - Template files must be named *.json and have the key "transloadit_template_id" - and optionally "steps". If "transloadit_template_id" is an empty string, then - a new template will be created using the instructions in "steps". If "steps" is - missing then it will be filled in by the instructions of the template specified - by "transloadit_template_id". If both keys are present then the local template - file and the remote template will be synchronized to whichever was more recently - modified. - `, - examples: [ - ['Sync templates in a directory', 'transloadit templates sync templates/'], - ['Sync recursively', 'transloadit templates sync --recursive templates/'], - ], - }) - - recursive = Option.Boolean('--recursive,-r', false, { - description: 'Look for template files in directories recursively', - }) - - files = Option.Rest() - - protected async run(): Promise { - await sync(this.output, this.client, { - recursive: this.recursive, - files: this.files, - }) - return undefined - } -} diff --git a/packages/transloadit/src/cli/helpers.ts b/packages/transloadit/src/cli/helpers.ts deleted file mode 100644 index a2029faa..00000000 --- a/packages/transloadit/src/cli/helpers.ts +++ /dev/null @@ -1,50 +0,0 @@ -import fs from 'node:fs' -import type { Readable } from 'node:stream' -import { isAPIError } from './types.ts' - -export function getEnvCredentials(): { authKey: string; authSecret: string } | null { - const authKey = process.env.TRANSLOADIT_KEY ?? process.env.TRANSLOADIT_AUTH_KEY - const authSecret = process.env.TRANSLOADIT_SECRET ?? process.env.TRANSLOADIT_AUTH_SECRET - - if (!authKey || !authSecret) return null - - return { authKey, authSecret } -} - -export function createReadStream(file: string): Readable { - if (file === '-') return process.stdin - return fs.createReadStream(file) -} - -export async function streamToBuffer(stream: Readable): Promise { - const chunks: Buffer[] = [] - for await (const chunk of stream) { - chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)) - } - return Buffer.concat(chunks) -} - -export function formatAPIError(err: unknown): string { - if (isAPIError(err)) { - return `${err.error}: ${err.message}` - } - if (err instanceof Error) { - return err.message - } - return String(err) -} - -// Re-export APIError type for CLI consumers relying on deep imports. -/** @public */ -export type { APIError } from './types.ts' - -export function zip(listA: A[], listB: B[]): [A, B][] -export function zip(...lists: T[][]): T[][] -export function zip(...lists: T[][]): T[][] { - const length = Math.max(...lists.map((list) => list.length)) - const result: T[][] = new Array(length) - for (let i = 0; i < result.length; i++) { - result[i] = lists.map((list) => list[i] as T) - } - return result -} diff --git a/packages/transloadit/src/cli/template-last-modified.ts b/packages/transloadit/src/cli/template-last-modified.ts deleted file mode 100644 index eae0718a..00000000 --- a/packages/transloadit/src/cli/template-last-modified.ts +++ /dev/null @@ -1,156 +0,0 @@ -import type { Transloadit } from '../Transloadit.ts' -import { ensureError } from './types.ts' - -interface TemplateItem { - id: string - modified: string -} - -type FetchCallback = (err: Error | null, result?: T) => void -type PageFetcher = (page: number, pagesize: number, cb: FetchCallback) => void - -class MemoizedPagination { - private pagesize: number - private fetch: PageFetcher - private cache: (T | undefined)[] - - constructor(pagesize: number, fetch: PageFetcher) { - this.pagesize = pagesize - this.fetch = fetch - this.cache = [] - } - - get(i: number, cb: FetchCallback): void { - const cached = this.cache[i] - if (cached !== undefined) { - process.nextTick(() => cb(null, cached)) - return - } - - const page = Math.floor(i / this.pagesize) + 1 - const start = (page - 1) * this.pagesize - - this.fetch(page, this.pagesize, (err, result) => { - if (err) { - cb(err) - return - } - if (!result) { - cb(new Error('No result returned from fetch')) - return - } - for (let j = 0; j < this.pagesize; j++) { - this.cache[start + j] = result[j] - } - cb(null, this.cache[i]) - }) - } -} - -export default class ModifiedLookup { - private byOrdinal: MemoizedPagination - - constructor(client: Transloadit, pagesize = 50) { - this.byOrdinal = new MemoizedPagination(pagesize, (page, pagesize, cb) => { - const params = { - sort: 'id' as const, - order: 'asc' as const, - fields: ['id', 'modified'] as ('id' | 'modified')[], - page, - pagesize, - } - - client - .listTemplates(params) - .then((result) => { - const items: TemplateItem[] = new Array(pagesize) - // Fill with sentinel value larger than any hex ID - items.fill({ id: 'gggggggggggggggggggggggggggggggg', modified: '' }) - for (let i = 0; i < result.items.length; i++) { - const item = result.items[i] - if (item) { - items[i] = { id: item.id, modified: item.modified } - } - } - cb(null, items) - }) - .catch((err: unknown) => { - cb(ensureError(err)) - }) - }) - } - - private idByOrd(ord: number, cb: FetchCallback): void { - this.byOrdinal.get(ord, (err, result) => { - if (err) { - cb(err) - return - } - if (!result) { - cb(new Error('No result found')) - return - } - cb(null, result.id) - }) - } - - byId(id: string, cb: FetchCallback): void { - const findUpperBound = (bound: number): void => { - this.idByOrd(bound, (err, idAtBound) => { - if (err) { - cb(err) - return - } - if (idAtBound === id) { - complete(bound) - return - } - if (idAtBound && idAtBound > id) { - refine(Math.floor(bound / 2), bound) - return - } - findUpperBound(bound * 2) - }) - } - - const refine = (lower: number, upper: number): void => { - if (lower >= upper - 1) { - cb(new Error(`Template ID ${id} not found in ModifiedLookup`)) - return - } - - const middle = Math.floor((lower + upper) / 2) - this.idByOrd(middle, (err, idAtMiddle) => { - if (err) { - cb(err) - return - } - if (idAtMiddle === id) { - complete(middle) - return - } - if (idAtMiddle && idAtMiddle < id) { - refine(middle, upper) - return - } - refine(lower, middle) - }) - } - - const complete = (ord: number): void => { - this.byOrdinal.get(ord, (err, result) => { - if (err) { - cb(err) - return - } - if (!result) { - cb(new Error('No result found')) - return - } - cb(null, new Date(result.modified)) - }) - } - - findUpperBound(1) - } -} diff --git a/packages/transloadit/src/cli/types.ts b/packages/transloadit/src/cli/types.ts deleted file mode 100644 index b0a59177..00000000 --- a/packages/transloadit/src/cli/types.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { z } from 'zod' -import type { Steps } from '../alphalib/types/template.ts' -import { optionalStepsSchema } from '../alphalib/types/template.ts' - -// Zod schemas for runtime validation -const APIErrorSchema = z.object({ - error: z.string(), - message: z.string(), -}) -export type APIError = z.infer - -const TransloaditAPIErrorSchema = z.object({ - error: z.string().optional(), - message: z.string(), - code: z.string().optional(), - transloaditErrorCode: z.string().optional(), - response: z - .object({ - body: z - .object({ - error: z.string().optional(), - }) - .optional(), - statusCode: z.number().optional(), - }) - .optional(), -}) -export type TransloaditAPIError = z.infer - -// Template file data - explicit type to avoid TS inference limits -export interface TemplateFileData { - transloadit_template_id?: string - steps?: Steps - [key: string]: unknown // passthrough -} - -export const TemplateFileDataSchema: z.ZodType = z - .object({ - transloadit_template_id: z.string().optional(), - steps: optionalStepsSchema, - }) - .passthrough() as z.ZodType - -export interface TemplateFile { - file: string - data: TemplateFileData -} - -// Helper to ensure error is Error type -export function ensureError(value: unknown): Error { - if (value instanceof Error) { - return value - } - return new Error(`Non-error was thrown: ${String(value)}`) -} - -// Type guard for APIError -export function isAPIError(value: unknown): value is APIError { - return APIErrorSchema.safeParse(value).success -} - -// Type guard for TransloaditAPIError -export function isTransloaditAPIError(value: unknown): value is TransloaditAPIError { - return TransloaditAPIErrorSchema.safeParse(value).success -} - -// Type guard for NodeJS.ErrnoException -export function isErrnoException(value: unknown): value is NodeJS.ErrnoException { - return value instanceof Error && 'code' in value -} diff --git a/packages/transloadit/src/tus.ts b/packages/transloadit/src/tus.ts deleted file mode 100644 index 5f4a1b3c..00000000 --- a/packages/transloadit/src/tus.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { stat } from 'node:fs/promises' -import { basename } from 'node:path' -import type { Readable } from 'node:stream' -import debug from 'debug' -import pMap from 'p-map' -import type { OnSuccessPayload, UploadOptions } from 'tus-js-client' -import { Upload } from 'tus-js-client' -import type { AssemblyStatus } from './alphalib/types/assemblyStatus.ts' -import type { UploadProgress } from './Transloadit.ts' - -const log = debug('transloadit') - -export interface Stream { - path?: string - stream: Readable -} - -interface SendTusRequestOptions { - streamsMap: Record - assembly: AssemblyStatus - requestedChunkSize: number - uploadConcurrency: number - onProgress: (options: UploadProgress) => void - signal?: AbortSignal -} - -export async function sendTusRequest({ - streamsMap, - assembly, - requestedChunkSize, - uploadConcurrency, - onProgress, - signal, -}: SendTusRequestOptions) { - const streamLabels = Object.keys(streamsMap) - - let totalBytes = 0 - let lastEmittedProgress = 0 - - const sizes: Record = {} - - const haveUnknownLengthStreams = streamLabels.some((label) => !streamsMap[label]?.path) - - // Initialize size data - await pMap( - streamLabels, - async (label) => { - // Check if aborted before each operation - if (signal?.aborted) throw new Error('Upload aborted') - - const streamInfo = streamsMap[label] - if (!streamInfo) { - throw new Error(`Stream info not found for label: ${label}`) - } - const { path } = streamInfo - - if (path) { - const { size } = await stat(path) - sizes[label] = size - totalBytes += size - } - }, - { concurrency: 5, signal }, - ) - - const uploadProgresses: Record = {} - - async function uploadSingleStream(label: string) { - uploadProgresses[label] = 0 - - const streamInfo = streamsMap[label] - if (!streamInfo) { - throw new Error(`Stream info not found for label: ${label}`) - } - const { stream, path } = streamInfo - const size = sizes[label] - - let chunkSize = requestedChunkSize - let uploadLengthDeferred: boolean - const isStreamLengthKnown = !!path - if (!isStreamLengthKnown) { - // tus-js-client requires these options to be set for unknown size streams - // https://github.com/tus/tus-js-client/blob/master/docs/api.md#uploadlengthdeferred - uploadLengthDeferred = true - if (chunkSize === Number.POSITIVE_INFINITY) chunkSize = 50e6 - } - - const onTusProgress = (bytesUploaded: number): void => { - uploadProgresses[label] = bytesUploaded - - // get all uploaded bytes for all files - let uploadedBytes = 0 - for (const l of streamLabels) { - uploadedBytes += uploadProgresses[l] ?? 0 - } - - // don't send redundant progress - if (lastEmittedProgress < uploadedBytes) { - lastEmittedProgress = uploadedBytes - // If we have any unknown length streams, we cannot trust totalBytes - // totalBytes should then be undefined to mimic behavior of form uploads. - onProgress({ - uploadedBytes, - totalBytes: haveUnknownLengthStreams ? undefined : totalBytes, - }) - } - } - - const filename = path ? basename(path) : label - - await new Promise((resolvePromise, rejectPromise) => { - if (!assembly.assembly_ssl_url) { - rejectPromise(new Error('assembly_ssl_url is not present in the assembly status')) - return - } - - // Check if already aborted before starting - if (signal?.aborted) { - rejectPromise(new Error('Upload aborted')) - return - } - - // Wrap resolve/reject to clean up abort listener - let abortHandler: (() => void) | undefined - const resolve = (payload: OnSuccessPayload) => { - if (abortHandler) signal?.removeEventListener('abort', abortHandler) - resolvePromise(payload) - } - const reject = (err: unknown) => { - if (abortHandler) signal?.removeEventListener('abort', abortHandler) - rejectPromise(err) - } - - const tusOptions: UploadOptions = { - endpoint: assembly.tus_url, - metadata: { - assembly_url: assembly.assembly_ssl_url, - fieldname: label, - filename, - }, - onError: reject, - onProgress: onTusProgress, - onSuccess: resolve, - } - // tus-js-client doesn't like undefined/null - if (size != null) tusOptions.uploadSize = size - if (chunkSize) tusOptions.chunkSize = chunkSize - if (uploadLengthDeferred) tusOptions.uploadLengthDeferred = uploadLengthDeferred - - const tusUpload = new Upload(stream, tusOptions) - - // Handle abort signal - if (signal) { - abortHandler = () => { - tusUpload.abort() - reject(new Error('Upload aborted')) - } - signal.addEventListener('abort', abortHandler, { once: true }) - } - - tusUpload.start() - }) - - log(label, 'upload done') - } - - await pMap(streamLabels, uploadSingleStream, { concurrency: uploadConcurrency, signal }) -} diff --git a/scripts/pack-transloadit.ts b/scripts/pack-transloadit.ts index a166f0eb..a7d6567a 100644 --- a/scripts/pack-transloadit.ts +++ b/scripts/pack-transloadit.ts @@ -15,8 +15,8 @@ const runPack = async () => { cwd: repoRoot, }) - await execFileAsync('npm', ['pack', '--ignore-scripts', '--prefix', legacyPackage], { - cwd: repoRoot, + await execFileAsync('npm', ['pack', '--ignore-scripts'], { + cwd: legacyPackage, }) const entries = await readdir(legacyPackage) From 69963f9f8e1131b810d6e9625aee77fb9049a5de Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Fri, 16 Jan 2026 19:26:43 +0100 Subject: [PATCH 21/43] docs: note legacy transloadit packaging --- CONTRIBUTING.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 48e28d71..95eac7d9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -79,6 +79,14 @@ Coverage reports are: View the coverage report locally by opening `coverage/index.html` in your browser. +## Packaging the legacy `transloadit` package + +The `packages/transloadit` folder is a generated legacy wrapper. The `src` directory and top-level `README.md`, `CHANGELOG.md`, and `LICENSE` are produced during packing by `scripts/prepare-transloadit.js` and are not tracked in git. If you need to validate the legacy package contents, run: + +```sh +yarn pack +``` + ## Releasing Only maintainers can make releases. Releases to [npm](https://www.npmjs.com) are automated using GitHub actions. To make a release, perform the following steps: From df764e46fd0e1b2af90b00159f2d953468bf0cb0 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Fri, 16 Jan 2026 19:37:47 +0100 Subject: [PATCH 22/43] fix: stabilize e2e tooling paths --- .github/workflows/ci.yml | 2 +- packages/node/test/e2e/cli/test-utils.ts | 2 +- packages/transloadit/package.json | 14 ++------------ scripts/prepare-transloadit.js | 22 +++++++++++++++++++++- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index edf7b8e9..50f6eac2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -111,7 +111,7 @@ jobs: TRANSLOADIT_KEY: ${{ secrets.TRANSLOADIT_KEY }} TRANSLOADIT_SECRET: ${{ secrets.TRANSLOADIT_SECRET }} NODE_OPTIONS: --trace-deprecation --trace-warnings - CLOUDFLARED_PATH: ../cloudflared-linux-amd64 + CLOUDFLARED_PATH: ${{ github.workspace }}/cloudflared-linux-amd64 DEBUG: 'transloadit:*' - name: Generate the badge from the json-summary diff --git a/packages/node/test/e2e/cli/test-utils.ts b/packages/node/test/e2e/cli/test-utils.ts index 799cd2e5..448dad13 100644 --- a/packages/node/test/e2e/cli/test-utils.ts +++ b/packages/node/test/e2e/cli/test-utils.ts @@ -60,7 +60,7 @@ export function runCli( args: string, env: Record = {}, ): Promise<{ stdout: string; stderr: string }> { - return execAsync(`npx tsx ${cliPath} ${args}`, { + return execAsync(`${process.execPath} --experimental-strip-types ${cliPath} ${args}`, { env: { ...process.env, ...env }, }) } diff --git a/packages/transloadit/package.json b/packages/transloadit/package.json index f54b16ae..3d356b1b 100644 --- a/packages/transloadit/package.json +++ b/packages/transloadit/package.json @@ -3,14 +3,7 @@ "version": "4.1.3", "description": "Node.js SDK for Transloadit", "type": "module", - "keywords": [ - "transloadit", - "encoding", - "transcoding", - "video", - "audio", - "mp3" - ], + "keywords": ["transloadit", "encoding", "transcoding", "video", "audio", "mp3"], "author": "Tim Koschuetzki ", "packageManager": "yarn@4.12.0", "engines": { @@ -87,9 +80,6 @@ ".": "./dist/Transloadit.js", "./package.json": "./package.json" }, - "files": [ - "dist", - "src" - ], + "files": ["dist", "src"], "bin": "./dist/cli.js" } diff --git a/scripts/prepare-transloadit.js b/scripts/prepare-transloadit.js index 309bfe2c..de9c9744 100644 --- a/scripts/prepare-transloadit.js +++ b/scripts/prepare-transloadit.js @@ -27,6 +27,25 @@ const writeJson = async (filePath, data) => { await writeFile(filePath, json) } +const formatPackageJson = (data) => { + let json = JSON.stringify(data, null, 2) + const inlineArray = (key) => { + const pattern = new RegExp(`"${key}": \\[\\n([\\s\\S]*?)\\n\\s*\\]`, 'm') + return json.replace(pattern, (_match, inner) => { + const values = inner + .split('\n') + .map((line) => line.trim()) + .filter(Boolean) + .map((line) => line.replace(/,$/, '')) + return `"${key}": [${values.join(', ')}]` + }) + } + + json = inlineArray('keywords') + json = inlineArray('files') + return `${json}\n` +} + const writeLegacyPackageJson = async () => { const nodePackageJson = await readJson(resolve(nodePackage, 'package.json')) const scripts = { ...(nodePackageJson.scripts ?? {}) } @@ -37,7 +56,8 @@ const writeLegacyPackageJson = async () => { scripts, } - await writeJson(resolve(legacyPackage, 'package.json'), legacyPackageJson) + const formatted = formatPackageJson(legacyPackageJson) + await writeFile(resolve(legacyPackage, 'package.json'), formatted) } const writeLegacyChangelog = async () => { From c63cfff53efd2bc566c479126a6340b0a2da338e Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Fri, 16 Jan 2026 19:50:38 +0100 Subject: [PATCH 23/43] chore: expand yarn check scope --- .changeset/config.json | 8 +- biome.json | 4 +- docs/fingerprint/transloadit-after.json | 5 +- docs/fingerprint/transloadit-baseline.json | 5 +- package.json | 16 +- packages/node/package.json | 2 +- packages/types/package.json | 5 +- packages/types/scripts/emit-types.test.ts | 4 +- packages/types/scripts/emit-types.ts | 12 +- packages/zod/package.json | 6 +- packages/zod/scripts/sync-v3.ts | 2 +- packages/zod/scripts/sync-v4.ts | 345 +++++++++--------- packages/zod/test/fixtures/assembly-status.ts | 16 +- packages/zod/test/runtime-parity.test.ts | 12 +- packages/zod/test/type-equality-v3.ts | 11 +- packages/zod/test/type-equality-v4.ts | 6 +- packages/zod/tsconfig.test.json | 6 +- scripts/fingerprint-pack.js | 2 +- scripts/prepare-transloadit.js | 7 +- yarn.lock | 93 ++++- 20 files changed, 323 insertions(+), 244 deletions(-) diff --git a/.changeset/config.json b/.changeset/config.json index b4ac3b60..efa45198 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -7,13 +7,7 @@ } ], "commit": false, - "fixed": [ - [ - "@transloadit/node", - "transloadit", - "@transloadit/types" - ] - ], + "fixed": [["@transloadit/node", "transloadit", "@transloadit/types"]], "linked": [], "access": "public", "baseBranch": "main", diff --git a/biome.json b/biome.json index c1fea328..1d4ad946 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.2.4/schema.json", + "$schema": "https://biomejs.dev/schemas/2.3.11/schema.json", "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true }, "files": { "ignoreUnknown": false, @@ -7,7 +7,7 @@ "**", "!package.json", "!coverage", - "!**/coverage/**", + "!**/coverage", "!dist", "!fixture", "!.vscode", diff --git a/docs/fingerprint/transloadit-after.json b/docs/fingerprint/transloadit-after.json index 7ce4a3ef..8e10805b 100644 --- a/docs/fingerprint/transloadit-after.json +++ b/docs/fingerprint/transloadit-after.json @@ -13,10 +13,7 @@ ".": "./dist/Transloadit.js", "./package.json": "./package.json" }, - "files": [ - "dist", - "src" - ] + "files": ["dist", "src"] }, "files": [ { diff --git a/docs/fingerprint/transloadit-baseline.json b/docs/fingerprint/transloadit-baseline.json index 7ce4a3ef..8e10805b 100644 --- a/docs/fingerprint/transloadit-baseline.json +++ b/docs/fingerprint/transloadit-baseline.json @@ -13,10 +13,7 @@ ".": "./dist/Transloadit.js", "./package.json": "./package.json" }, - "files": [ - "dist", - "src" - ] + "files": ["dist", "src"] }, "files": [ { diff --git a/package.json b/package.json index ff81168a..d46ce49a 100644 --- a/package.json +++ b/package.json @@ -7,16 +7,16 @@ "packages/*" ], "scripts": { - "check": "yarn workspace @transloadit/node check && yarn workspace @transloadit/types check && yarn workspace @transloadit/zod check", - "lint:js": "yarn workspace @transloadit/node lint:js", - "lint:ts": "yarn workspace @transloadit/node lint:ts", - "lint": "yarn workspace @transloadit/node lint", - "fix": "yarn workspace @transloadit/node fix", - "fix:js": "yarn workspace @transloadit/node fix:js", - "fix:js:unsafe": "yarn workspace @transloadit/node fix:js:unsafe", + "check": "yarn lint:ts && yarn fix:js && yarn test:unit", + "lint:js": "biome check .", + "lint:ts": "yarn workspace @transloadit/node lint:ts && yarn workspace @transloadit/types lint:ts && yarn workspace @transloadit/zod lint:ts", + "lint": "yarn lint:js", + "fix": "yarn fix:js", + "fix:js": "biome check --write .", + "fix:js:unsafe": "biome check --write . --unsafe", "knip": "yarn workspace @transloadit/node knip", "pack": "node --experimental-strip-types scripts/pack-transloadit.ts", - "test:unit": "yarn workspace @transloadit/node test:unit", + "test:unit": "yarn workspace @transloadit/node test:unit && yarn workspace @transloadit/types test:unit && yarn workspace @transloadit/zod test:unit", "test:e2e": "yarn workspace @transloadit/node test:e2e", "test": "yarn workspace @transloadit/node test" }, diff --git a/packages/node/package.json b/packages/node/package.json index 62a23a75..62dda798 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -30,7 +30,7 @@ "zod": "3.25.76" }, "devDependencies": { - "@biomejs/biome": "^2.2.4", + "@biomejs/biome": "^2.3.11", "@types/debug": "^4.1.12", "@types/minimist": "^1.2.5", "@types/node": "^24.10.3", diff --git a/packages/types/package.json b/packages/types/package.json index d91cb6c3..092cd0bb 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -4,9 +4,7 @@ "description": "Transloadit type definitions", "type": "module", "license": "MIT", - "files": [ - "dist" - ], + "files": ["dist"], "types": "./dist/index.d.ts", "exports": { ".": { @@ -27,6 +25,7 @@ }, "scripts": { "generate": "node --experimental-strip-types scripts/emit-types.ts", + "lint:ts": "yarn generate && tsc --build tsconfig.build.json", "test:unit": "node --experimental-strip-types scripts/emit-types.test.ts", "build": "yarn generate && tsc --build tsconfig.build.json", "check": "yarn generate && yarn test:unit && tsc --build tsconfig.build.json" diff --git a/packages/types/scripts/emit-types.test.ts b/packages/types/scripts/emit-types.test.ts index cc0990cd..70af677c 100644 --- a/packages/types/scripts/emit-types.test.ts +++ b/packages/types/scripts/emit-types.test.ts @@ -20,8 +20,8 @@ const cases = [ expected: 'path\\\\name', }, { - input: 'it\'s fine', - expected: 'it\\\'s fine', + input: "it's fine", + expected: "it\\'s fine", }, ] diff --git a/packages/types/scripts/emit-types.ts b/packages/types/scripts/emit-types.ts index 27a0c97c..6e753049 100644 --- a/packages/types/scripts/emit-types.ts +++ b/packages/types/scripts/emit-types.ts @@ -77,8 +77,10 @@ const isSymbolFromPath = (symbol: ts.Symbol | undefined, token: string): boolean return symbol.declarations.some((decl) => decl.getSourceFile().fileName.includes(token)) } -const isZodSymbol = (symbol: ts.Symbol | undefined): boolean => isSymbolFromPath(symbol, zodPathToken) -const isLibSymbol = (symbol: ts.Symbol | undefined): boolean => isSymbolFromPath(symbol, libPathToken) +const isZodSymbol = (symbol: ts.Symbol | undefined): boolean => + isSymbolFromPath(symbol, zodPathToken) +const isLibSymbol = (symbol: ts.Symbol | undefined): boolean => + isSymbolFromPath(symbol, libPathToken) const isZodText = (value: string): boolean => value.includes('Zod') || value.includes('objectOutputType') || value.includes('objectInputType') @@ -91,11 +93,7 @@ const shouldUseTypeToString = (type: ts.Type, checker: ts.TypeChecker): boolean return !isZodText(text) } -const hasZodType = ( - type: ts.Type, - checker: ts.TypeChecker, - seen: Set, -): boolean => { +const hasZodType = (type: ts.Type, checker: ts.TypeChecker, seen: Set): boolean => { if (seen.has(type)) return false seen.add(type) diff --git a/packages/zod/package.json b/packages/zod/package.json index 5dafb843..8b47c925 100644 --- a/packages/zod/package.json +++ b/packages/zod/package.json @@ -4,9 +4,7 @@ "description": "Transloadit Zod schemas", "type": "module", "license": "MIT", - "files": [ - "dist" - ], + "files": ["dist"], "exports": { ".": { "types": "./dist/v3/index.d.ts", @@ -33,6 +31,8 @@ "sync:v3": "node scripts/sync-v3.ts", "sync:v4": "node scripts/sync-v4.ts", "sync": "yarn sync:v3 && yarn sync:v4", + "lint:ts": "yarn sync && tsc --build tsconfig.build.json", + "test:unit": "node test/exports-sync.test.ts && node test/runtime-parity.test.ts", "build": "yarn sync && tsc --build tsconfig.build.json", "check": "yarn sync && tsc --build tsconfig.build.json && tsc --noEmit --project tsconfig.test.json && node test/exports-sync.test.ts && node test/runtime-parity.test.ts" }, diff --git a/packages/zod/scripts/sync-v3.ts b/packages/zod/scripts/sync-v3.ts index 04897ce3..022d7fda 100644 --- a/packages/zod/scripts/sync-v3.ts +++ b/packages/zod/scripts/sync-v3.ts @@ -1,4 +1,4 @@ -import { cp, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises' +import { cp, mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises' import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' diff --git a/packages/zod/scripts/sync-v4.ts b/packages/zod/scripts/sync-v4.ts index 2430e70a..9ba23ae9 100644 --- a/packages/zod/scripts/sync-v4.ts +++ b/packages/zod/scripts/sync-v4.ts @@ -1,4 +1,4 @@ -import { cp, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises' +import { cp, mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises' import { dirname, resolve, sep } from 'node:path' import { fileURLToPath } from 'node:url' @@ -119,163 +119,164 @@ const patchInterpolatableHelpers = (contents: string): string => { return contents } - const replacement = `type InterpolatableTuple = Schemas extends readonly [\n` + - ` infer Head extends z.core.SomeType,\n` + - ` ...infer Rest extends z.core.SomeType[],\n` + - `]\n` + - ` ? [InterpolatableSchema, ...InterpolatableTuple]\n` + - ` : Schemas\n\n` + - `type InterpolatableSchema = Schema extends z.ZodString\n` + - ` ? Schema\n` + - ` : Schema extends\n` + - ` | z.ZodBoolean\n` + - ` | z.ZodEnum\n` + - ` | z.ZodLiteral\n` + - ` | z.ZodNumber\n` + - ` | z.ZodPipe\n` + - ` ? z.ZodUnion<[z.ZodString, Schema]>\n` + - ` : Schema extends z.ZodArray\n` + - ` ? z.ZodUnion<[z.ZodString, z.ZodArray>]>\n` + - ` : Schema extends z.ZodDefault\n` + - ` ? z.ZodDefault>\n` + - ` : Schema extends z.ZodNullable\n` + - ` ? z.ZodNullable>\n` + - ` : Schema extends z.ZodOptional\n` + - ` ? z.ZodOptional>\n` + - ` : Schema extends z.ZodRecord\n` + - ` ? z.ZodRecord>\n` + - ` : Schema extends z.ZodTuple\n` + - ` ? z.ZodUnion<[\n` + - ` z.ZodString,\n` + - ` z.ZodTuple<\n` + - ` InterpolatableTuple,\n` + - ` Rest extends z.core.SomeType ? InterpolatableSchema : null\n` + - ` >,\n` + - ` ]>\n` + - ` : Schema extends z.ZodObject\n` + - ` ? z.ZodUnion<[\n` + - ` z.ZodString,\n` + - ` z.ZodObject<{ [Key in keyof T]: InterpolatableSchema }, Config>,\n` + - ` ]>\n` + - ` : Schema extends z.ZodDiscriminatedUnion\n` + - ` ? Schema\n` + - ` : Schema extends z.ZodUnion\n` + - ` ? z.ZodUnion<[z.ZodString, ...InterpolatableTuple]>\n` + - ` : Schema\n\n` + - `const applyArrayChecks = (schema: z.ZodArray, checks: unknown): z.ZodArray => {\n` + - ` if (!Array.isArray(checks)) return schema\n` + - ` let next = schema\n` + - ` for (const check of checks) {\n` + - ` const def = (check as { _zod?: { def?: { check?: string; minimum?: number; maximum?: number; length?: number } } })?._zod?.def\n` + - ` if (!def) continue\n` + + const replacement = + 'type InterpolatableTuple = Schemas extends readonly [\n' + + ' infer Head extends z.core.SomeType,\n' + + ' ...infer Rest extends z.core.SomeType[],\n' + + ']\n' + + ' ? [InterpolatableSchema, ...InterpolatableTuple]\n' + + ' : Schemas\n\n' + + 'type InterpolatableSchema = Schema extends z.ZodString\n' + + ' ? Schema\n' + + ' : Schema extends\n' + + ' | z.ZodBoolean\n' + + ' | z.ZodEnum\n' + + ' | z.ZodLiteral\n' + + ' | z.ZodNumber\n' + + ' | z.ZodPipe\n' + + ' ? z.ZodUnion<[z.ZodString, Schema]>\n' + + ' : Schema extends z.ZodArray\n' + + ' ? z.ZodUnion<[z.ZodString, z.ZodArray>]>\n' + + ' : Schema extends z.ZodDefault\n' + + ' ? z.ZodDefault>\n' + + ' : Schema extends z.ZodNullable\n' + + ' ? z.ZodNullable>\n' + + ' : Schema extends z.ZodOptional\n' + + ' ? z.ZodOptional>\n' + + ' : Schema extends z.ZodRecord\n' + + ' ? z.ZodRecord>\n' + + ' : Schema extends z.ZodTuple\n' + + ' ? z.ZodUnion<[\n' + + ' z.ZodString,\n' + + ' z.ZodTuple<\n' + + ' InterpolatableTuple,\n' + + ' Rest extends z.core.SomeType ? InterpolatableSchema : null\n' + + ' >,\n' + + ' ]>\n' + + ' : Schema extends z.ZodObject\n' + + ' ? z.ZodUnion<[\n' + + ' z.ZodString,\n' + + ' z.ZodObject<{ [Key in keyof T]: InterpolatableSchema }, Config>,\n' + + ' ]>\n' + + ' : Schema extends z.ZodDiscriminatedUnion\n' + + ' ? Schema\n' + + ' : Schema extends z.ZodUnion\n' + + ' ? z.ZodUnion<[z.ZodString, ...InterpolatableTuple]>\n' + + ' : Schema\n\n' + + 'const applyArrayChecks = (schema: z.ZodArray, checks: unknown): z.ZodArray => {\n' + + ' if (!Array.isArray(checks)) return schema\n' + + ' let next = schema\n' + + ' for (const check of checks) {\n' + + ' const def = (check as { _zod?: { def?: { check?: string; minimum?: number; maximum?: number; length?: number } } })?._zod?.def\n' + + ' if (!def) continue\n' + ` if (def.check === 'min_length' && typeof def.minimum === 'number') {\n` + - ` next = next.min(def.minimum)\n` + - ` }\n` + + ' next = next.min(def.minimum)\n' + + ' }\n' + ` if (def.check === 'max_length' && typeof def.maximum === 'number') {\n` + - ` next = next.max(def.maximum)\n` + - ` }\n` + + ' next = next.max(def.maximum)\n' + + ' }\n' + ` if (def.check === 'length_equals' && typeof def.length === 'number') {\n` + - ` next = next.length(def.length)\n` + - ` }\n` + - ` }\n` + - ` return next\n` + - `}\n\n` + - `export function interpolateRecursive(\n` + - ` schema: Schema,\n` + - `): InterpolatableSchema {\n` + - ` const def = (schema as z.core.SomeType)._zod.def as unknown\n\n` + - ` switch ((def as { type?: string }).type) {\n` + + ' next = next.length(def.length)\n' + + ' }\n' + + ' }\n' + + ' return next\n' + + '}\n\n' + + 'export function interpolateRecursive(\n' + + ' schema: Schema,\n' + + '): InterpolatableSchema {\n' + + ' const def = (schema as z.core.SomeType)._zod.def as unknown\n\n' + + ' switch ((def as { type?: string }).type) {\n' + ` case 'boolean':\n` + - ` return z.union([\n` + - ` interpolationSchemaFull,\n` + - ` z\n` + - ` .union([schema, booleanStringSchema])\n` + - ` .transform((value) => value === true || value === false),\n` + - ` ]) as unknown as InterpolatableSchema\n` + + ' return z.union([\n' + + ' interpolationSchemaFull,\n' + + ' z\n' + + ' .union([schema, booleanStringSchema])\n' + + ' .transform((value) => value === true || value === false),\n' + + ' ]) as unknown as InterpolatableSchema\n' + ` case 'array': {\n` + - ` const arrayDef = def as { element: z.ZodTypeAny; checks?: unknown }\n` + - ` let replacement = z.array(interpolateRecursive(arrayDef.element))\n` + - ` replacement = applyArrayChecks(replacement, arrayDef.checks)\n` + - ` return z.union([interpolationSchemaFull, replacement]) as unknown as InterpolatableSchema\n` + - ` }\n` + + ' const arrayDef = def as { element: z.ZodTypeAny; checks?: unknown }\n' + + ' let replacement = z.array(interpolateRecursive(arrayDef.element))\n' + + ' replacement = applyArrayChecks(replacement, arrayDef.checks)\n' + + ' return z.union([interpolationSchemaFull, replacement]) as unknown as InterpolatableSchema\n' + + ' }\n' + ` case 'default': {\n` + - ` const defaultDef = def as { innerType: z.ZodTypeAny; defaultValue: unknown }\n` + - ` const replacement = interpolateRecursive(defaultDef.innerType).default(defaultDef.defaultValue as never)\n` + - ` const description = (schema as { description?: string }).description\n` + - ` return (description ? replacement.describe(description) : replacement) as unknown as InterpolatableSchema\n` + - ` }\n` + + ' const defaultDef = def as { innerType: z.ZodTypeAny; defaultValue: unknown }\n' + + ' const replacement = interpolateRecursive(defaultDef.innerType).default(defaultDef.defaultValue as never)\n' + + ' const description = (schema as { description?: string }).description\n' + + ' return (description ? replacement.describe(description) : replacement) as unknown as InterpolatableSchema\n' + + ' }\n' + ` case 'enum':\n` + ` case 'literal':\n` + - ` return z.union([interpolationSchemaFull, schema]) as unknown as InterpolatableSchema\n` + + ' return z.union([interpolationSchemaFull, schema]) as unknown as InterpolatableSchema\n' + ` case 'number':\n` + - ` return z.union([\n` + - ` z\n` + - ` .string()\n` + - ` .regex(/^\\d+(\\.\\d+)?$/)\n` + - ` .transform((value) => Number(value)),\n` + - ` interpolationSchemaFull,\n` + - ` schema,\n` + - ` ]) as unknown as InterpolatableSchema\n` + + ' return z.union([\n' + + ' z\n' + + ' .string()\n' + + ' .regex(/^\\d+(\\.\\d+)?$/)\n' + + ' .transform((value) => Number(value)),\n' + + ' interpolationSchemaFull,\n' + + ' schema,\n' + + ' ]) as unknown as InterpolatableSchema\n' + ` case 'nullable': {\n` + - ` const nullableDef = def as { innerType: z.ZodTypeAny }\n` + - ` const replacement = interpolateRecursive(nullableDef.innerType).nullable()\n` + - ` const description = (schema as { description?: string }).description\n` + - ` return (description ? replacement.describe(description) : replacement) as unknown as InterpolatableSchema\n` + - ` }\n` + + ' const nullableDef = def as { innerType: z.ZodTypeAny }\n' + + ' const replacement = interpolateRecursive(nullableDef.innerType).nullable()\n' + + ' const description = (schema as { description?: string }).description\n' + + ' return (description ? replacement.describe(description) : replacement) as unknown as InterpolatableSchema\n' + + ' }\n' + ` case 'object': {\n` + - ` const objectDef = def as { shape: Record | (() => Record); catchall?: z.ZodTypeAny }\n` + + ' const objectDef = def as { shape: Record | (() => Record); catchall?: z.ZodTypeAny }\n' + ` const shape = typeof objectDef.shape === 'function' ? objectDef.shape() : objectDef.shape\n` + - ` let replacement = z.object(\n` + - ` Object.fromEntries(\n` + - ` Object.entries(shape).map(([key, nested]) => [\n` + - ` key,\n` + - ` interpolateRecursive(nested as z.ZodTypeAny),\n` + - ` ]),\n` + - ` ),\n` + - ` )\n` + - ` if (objectDef.catchall) {\n` + - ` const catchallType = objectDef.catchall._zod.def.type\n` + + ' let replacement = z.object(\n' + + ' Object.fromEntries(\n' + + ' Object.entries(shape).map(([key, nested]) => [\n' + + ' key,\n' + + ' interpolateRecursive(nested as z.ZodTypeAny),\n' + + ' ]),\n' + + ' ),\n' + + ' )\n' + + ' if (objectDef.catchall) {\n' + + ' const catchallType = objectDef.catchall._zod.def.type\n' + ` if (catchallType === 'never') {\n` + - ` replacement = replacement.strict()\n` + - ` } else {\n` + - ` replacement = replacement.catchall(objectDef.catchall)\n` + - ` }\n` + - ` }\n` + - ` return z.union([interpolationSchemaFull, replacement]) as unknown as InterpolatableSchema\n` + - ` }\n` + + ' replacement = replacement.strict()\n' + + ' } else {\n' + + ' replacement = replacement.catchall(objectDef.catchall)\n' + + ' }\n' + + ' }\n' + + ' return z.union([interpolationSchemaFull, replacement]) as unknown as InterpolatableSchema\n' + + ' }\n' + ` case 'optional': {\n` + - ` const optionalDef = def as { innerType: z.ZodTypeAny }\n` + - ` return interpolateRecursive(optionalDef.innerType).optional() as unknown as InterpolatableSchema\n` + - ` }\n` + + ' const optionalDef = def as { innerType: z.ZodTypeAny }\n' + + ' return interpolateRecursive(optionalDef.innerType).optional() as unknown as InterpolatableSchema\n' + + ' }\n' + ` case 'record': {\n` + - ` const recordDef = def as { keyType?: z.ZodTypeAny; valueType: z.ZodTypeAny }\n` + - ` const keyType = (recordDef.keyType ?? z.string()) as z.core.$ZodRecordKey\n` + - ` return z.record(keyType, interpolateRecursive(recordDef.valueType)) as unknown as InterpolatableSchema\n` + - ` }\n` + + ' const recordDef = def as { keyType?: z.ZodTypeAny; valueType: z.ZodTypeAny }\n' + + ' const keyType = (recordDef.keyType ?? z.string()) as z.core.$ZodRecordKey\n' + + ' return z.record(keyType, interpolateRecursive(recordDef.valueType)) as unknown as InterpolatableSchema\n' + + ' }\n' + ` case 'string':\n` + - ` return z.union([interpolationSchemaPartial, schema]) as unknown as InterpolatableSchema\n` + + ' return z.union([interpolationSchemaPartial, schema]) as unknown as InterpolatableSchema\n' + ` case 'tuple': {\n` + - ` const tupleDef = def as { items: z.ZodTypeAny[]; rest?: z.ZodTypeAny }\n` + - ` const items = tupleDef.items.map(interpolateRecursive)\n` + - ` const tuple = items.length === 0 ? z.tuple([]) : z.tuple(items as [z.ZodTypeAny, ...z.ZodTypeAny[]])\n` + - ` return z.union([\n` + - ` interpolationSchemaFull,\n` + - ` tupleDef.rest ? tuple.rest(tupleDef.rest) : tuple,\n` + - ` ]) as unknown as InterpolatableSchema\n` + - ` }\n` + + ' const tupleDef = def as { items: z.ZodTypeAny[]; rest?: z.ZodTypeAny }\n' + + ' const items = tupleDef.items.map(interpolateRecursive)\n' + + ' const tuple = items.length === 0 ? z.tuple([]) : z.tuple(items as [z.ZodTypeAny, ...z.ZodTypeAny[]])\n' + + ' return z.union([\n' + + ' interpolationSchemaFull,\n' + + ' tupleDef.rest ? tuple.rest(tupleDef.rest) : tuple,\n' + + ' ]) as unknown as InterpolatableSchema\n' + + ' }\n' + ` case 'union': {\n` + - ` const unionDef = def as { options: z.ZodTypeAny[]; discriminator?: string }\n` + - ` if (unionDef.discriminator) {\n` + - ` return schema as unknown as InterpolatableSchema\n` + - ` }\n` + - ` return z.union([interpolationSchemaFull, ...unionDef.options.map(interpolateRecursive)]) as unknown as InterpolatableSchema\n` + - ` }\n` + + ' const unionDef = def as { options: z.ZodTypeAny[]; discriminator?: string }\n' + + ' if (unionDef.discriminator) {\n' + + ' return schema as unknown as InterpolatableSchema\n' + + ' }\n' + + ' return z.union([interpolationSchemaFull, ...unionDef.options.map(interpolateRecursive)]) as unknown as InterpolatableSchema\n' + + ' }\n' + ` case 'pipe':\n` + - ` return z.union([interpolationSchemaFull, schema]) as unknown as InterpolatableSchema\n` + - ` default:\n` + - ` return schema as unknown as InterpolatableSchema\n` + - ` }\n` + - `}\n\n` + ' return z.union([interpolationSchemaFull, schema]) as unknown as InterpolatableSchema\n' + + ' default:\n' + + ' return schema as unknown as InterpolatableSchema\n' + + ' }\n' + + '}\n\n' return `${contents.slice(0, start)}${replacement}${contents.slice(end)}` } @@ -288,47 +289,45 @@ const patchInterpolatableRobot = (contents: string): string => { return contents } - const replacement = `type InterpolatableRobot =\n` + - ` Schema extends z.ZodObject\n` + - ` ? z.ZodObject<\n` + - ` {\n` + - ` [Key in keyof T]: Key extends (typeof uninterpolatableKeys)[number]\n` + - ` ? T[Key]\n` + - ` : InterpolatableSchema\n` + - ` },\n` + - ` Config\n` + - ` >\n` + - ` : never\n\n` + - `export function interpolateRobot(\n` + - ` schema: Schema,\n` + - `): InterpolatableRobot {\n` + - ` const def = (schema as z.core.SomeType)._zod.def as unknown\n` + + const replacement = + 'type InterpolatableRobot =\n' + + ' Schema extends z.ZodObject\n' + + ' ? z.ZodObject<\n' + + ' {\n' + + ' [Key in keyof T]: Key extends (typeof uninterpolatableKeys)[number]\n' + + ' ? T[Key]\n' + + ' : InterpolatableSchema\n' + + ' },\n' + + ' Config\n' + + ' >\n' + + ' : never\n\n' + + 'export function interpolateRobot(\n' + + ' schema: Schema,\n' + + '): InterpolatableRobot {\n' + + ' const def = (schema as z.core.SomeType)._zod.def as unknown\n' + ` const shape = typeof (def as { shape: Record | (() => Record) }).shape === 'function'\n` + - ` ? (def as { shape: () => Record }).shape()\n` + - ` : (def as { shape: Record }).shape\n` + - ` return z\n` + - ` .object(\n` + - ` Object.fromEntries(\n` + - ` Object.entries(shape).map(([key, nested]) => [\n` + - ` key,\n` + - ` (uninterpolatableKeys as readonly string[]).includes(key)\n` + - ` ? nested\n` + - ` : interpolateRecursive(nested as z.ZodTypeAny),\n` + - ` ]),\n` + - ` ),\n` + - ` )\n` + - ` .strict() as InterpolatableRobot\n` + - `}\n\n` + ' ? (def as { shape: () => Record }).shape()\n' + + ' : (def as { shape: Record }).shape\n' + + ' return z\n' + + ' .object(\n' + + ' Object.fromEntries(\n' + + ' Object.entries(shape).map(([key, nested]) => [\n' + + ' key,\n' + + ' (uninterpolatableKeys as readonly string[]).includes(key)\n' + + ' ? nested\n' + + ' : interpolateRecursive(nested as z.ZodTypeAny),\n' + + ' ]),\n' + + ' ),\n' + + ' )\n' + + ' .strict() as InterpolatableRobot\n' + + '}\n\n' return `${contents.slice(0, start)}${replacement}${contents.slice(end)}` } const patchAiChatSchema = (contents: string): string => contents - .replace( - 'const jsonValueSchema: z.ZodType =', - 'const jsonValueSchema: z.ZodType =', - ) + .replace('const jsonValueSchema: z.ZodType =', 'const jsonValueSchema: z.ZodType =') .replace('result: z.unknown(),', 'result: z.unknown().optional(),') const patchFile = (filePath: string, contents: string): string => { diff --git a/packages/zod/test/fixtures/assembly-status.ts b/packages/zod/test/fixtures/assembly-status.ts index 19631225..b527da40 100644 --- a/packages/zod/test/fixtures/assembly-status.ts +++ b/packages/zod/test/fixtures/assembly-status.ts @@ -49,7 +49,12 @@ const assemblyStatusOk = { running_jobs: [], bytes_usage: 1048639, executing_jobs: [], - started_jobs: [':original:::original', 'converted:::original', 'exported:::original', 'exported::converted'], + started_jobs: [ + ':original:::original', + 'converted:::original', + 'exported:::original', + 'exported::converted', + ], parent_assembly_status: null, params: '{"steps":{":original":{"robot":"/upload/handle"},"converted":{"use":":original","robot":"/document/convert","result":true,"format":"vtt"},"exported":{"use":["converted",":original"],"robot":"/s3/store","credentials":"demo_s3_credentials","url_prefix":"https://demos.transloadit.com/"}}, "auth":{"key":"****","expires":"2023-12-21T16:36:26.972Z"}}', @@ -66,7 +71,8 @@ const assemblyStatusOk = { size: 302, offset: 302, finished: true, - upload_url: 'https://api2-buchen.transloadit.com/resumable/files/ab9753a6f59d0ef51b881a56004d9f14', + upload_url: + 'https://api2-buchen.transloadit.com/resumable/files/ab9753a6f59d0ef51b881a56004d9f14', }, ], uploads: [ @@ -87,7 +93,8 @@ const assemblyStatusOk = { original_md5hash: '23664a1e4a8cad08d4ca6294d3d9bee3', from_batch_import: false, is_tus_file: true, - tus_upload_url: 'https://api2-buchen.transloadit.com/resumable/files/ab9753a6f59d0ef51b881a56004d9f14', + tus_upload_url: + 'https://api2-buchen.transloadit.com/resumable/files/ab9753a6f59d0ef51b881a56004d9f14', url: 'https://demos.transloadit.com/7e/5ce0bb323044e7a9deeb7d6f6e312f/subtitle.srt', ssl_url: 'https://demos.transloadit.com/7e/5ce0bb323044e7a9deeb7d6f6e312f/subtitle.srt', meta: {}, @@ -160,7 +167,8 @@ const assemblyStatusOk = { const assemblyStatusUploading = { ok: 'ASSEMBLY_UPLOADING', assembly_id: 'b841ea401e1a11e7b37d7bda1b503cdd', - assembly_ssl_url: 'https://api2-freja.transloadit.com/assemblies/b841ea401e1a11e7b37d7bda1b503cdd', + assembly_ssl_url: + 'https://api2-freja.transloadit.com/assemblies/b841ea401e1a11e7b37d7bda1b503cdd', websocket_url: 'https://api2-freja.transloadit.com/ws20277', tus_url: 'https://api2-freja.transloadit.com/resumable/files/', expected_tus_uploads: 2, diff --git a/packages/zod/test/runtime-parity.test.ts b/packages/zod/test/runtime-parity.test.ts index dae96c49..fdf3ea25 100644 --- a/packages/zod/test/runtime-parity.test.ts +++ b/packages/zod/test/runtime-parity.test.ts @@ -1,13 +1,19 @@ import assert from 'node:assert/strict' import { assemblyStatusSchema as v3AssemblyStatus } from '../src/v3/assemblyStatus.ts' +import { + robotBase as v3RobotBase, + robotFFmpeg as v3RobotFFmpeg, +} from '../src/v3/robots/_instructions-primitives.ts' import { assemblyInstructionsSchema as v3AssemblyInstructions } from '../src/v3/template.ts' import { assemblyStatusSchema as v4AssemblyStatus } from '../src/v4/assemblyStatus.ts' +import { + robotBase as v4RobotBase, + robotFFmpeg as v4RobotFFmpeg, +} from '../src/v4/robots/_instructions-primitives.ts' import { assemblyInstructionsSchema as v4AssemblyInstructions } from '../src/v4/template.ts' -import { robotBase as v3RobotBase, robotFFmpeg as v3RobotFFmpeg } from '../src/v3/robots/_instructions-primitives.ts' -import { robotBase as v4RobotBase, robotFFmpeg as v4RobotFFmpeg } from '../src/v4/robots/_instructions-primitives.ts' -import { assemblyStatusFixtures } from './fixtures/assembly-status.ts' import { assemblyInstructionFixtures } from './fixtures/assembly-instructions.ts' +import { assemblyStatusFixtures } from './fixtures/assembly-status.ts' const schemas = [ { diff --git a/packages/zod/test/type-equality-v3.ts b/packages/zod/test/type-equality-v3.ts index 10cee065..ea041099 100644 --- a/packages/zod/test/type-equality-v3.ts +++ b/packages/zod/test/type-equality-v3.ts @@ -1,12 +1,11 @@ -import type { z } from 'zod/v3' import type { AssemblyStatus } from '@transloadit/types/assemblyStatus' import type { AssemblyInstructions } from '@transloadit/types/template' -import { assemblyStatusSchema } from '../src/v3/assemblyStatus.js' -import { assemblyInstructionsSchema } from '../src/v3/template.js' +import type { z } from 'zod/v3' +import type { assemblyStatusSchema } from '../src/v3/assemblyStatus.js' +import type { assemblyInstructionsSchema } from '../src/v3/template.js' -type Equal = (() => T extends A ? 1 : 2) extends () => T extends B ? 1 : 2 - ? true - : false +type Equal = + (() => T extends A ? 1 : 2) extends () => T extends B ? 1 : 2 ? true : false type Assert = T diff --git a/packages/zod/test/type-equality-v4.ts b/packages/zod/test/type-equality-v4.ts index cdd12997..d6c6e5f5 100644 --- a/packages/zod/test/type-equality-v4.ts +++ b/packages/zod/test/type-equality-v4.ts @@ -1,8 +1,8 @@ -import type { z } from 'zod/v4' import type { AssemblyStatus } from '@transloadit/types/assemblyStatus' import type { AssemblyInstructions } from '@transloadit/types/template' -import { assemblyStatusSchema } from '../src/v4/assemblyStatus.js' -import { assemblyInstructionsSchema } from '../src/v4/template.js' +import type { z } from 'zod/v4' +import type { assemblyStatusSchema } from '../src/v4/assemblyStatus.js' +import type { assemblyInstructionsSchema } from '../src/v4/template.js' type Equal = [A] extends [B] ? ([B] extends [A] ? true : false) : false diff --git a/packages/zod/tsconfig.test.json b/packages/zod/tsconfig.test.json index 3b4898fb..cd96fcb3 100644 --- a/packages/zod/tsconfig.test.json +++ b/packages/zod/tsconfig.test.json @@ -8,9 +8,5 @@ "@transloadit/types/*": ["../types/src/generated/*.ts"] } }, - "include": [ - "test/type-equality-v3.ts", - "test/type-equality-v4.ts", - "test/runtime-parity.test.ts" - ] + "include": ["test/type-equality-v3.ts", "test/type-equality-v4.ts", "test/runtime-parity.test.ts"] } diff --git a/scripts/fingerprint-pack.js b/scripts/fingerprint-pack.js index ee82bd3a..84d09e8f 100644 --- a/scripts/fingerprint-pack.js +++ b/scripts/fingerprint-pack.js @@ -1,5 +1,5 @@ -import { createHash } from 'node:crypto' import { execFile, spawn } from 'node:child_process' +import { createHash } from 'node:crypto' import { createReadStream } from 'node:fs' import { mkdir, rm, stat, writeFile } from 'node:fs/promises' import { resolve } from 'node:path' diff --git a/scripts/prepare-transloadit.js b/scripts/prepare-transloadit.js index de9c9744..4503d5bd 100644 --- a/scripts/prepare-transloadit.js +++ b/scripts/prepare-transloadit.js @@ -1,7 +1,7 @@ +import { execFile } from 'node:child_process' import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises' import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' -import { execFile } from 'node:child_process' import { promisify } from 'node:util' const execFileAsync = promisify(execFile) @@ -22,11 +22,6 @@ const readJson = async (filePath) => { return JSON.parse(raw) } -const writeJson = async (filePath, data) => { - const json = `${JSON.stringify(data, null, 2)}\n` - await writeFile(filePath, json) -} - const formatPackageJson = (data) => { let json = JSON.stringify(data, null, 2) const inlineArray = (key) => { diff --git a/yarn.lock b/yarn.lock index 5603f056..93aa3d4a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -781,6 +781,41 @@ __metadata: languageName: node linkType: hard +"@biomejs/biome@npm:^2.3.11": + version: 2.3.11 + resolution: "@biomejs/biome@npm:2.3.11" + dependencies: + "@biomejs/cli-darwin-arm64": "npm:2.3.11" + "@biomejs/cli-darwin-x64": "npm:2.3.11" + "@biomejs/cli-linux-arm64": "npm:2.3.11" + "@biomejs/cli-linux-arm64-musl": "npm:2.3.11" + "@biomejs/cli-linux-x64": "npm:2.3.11" + "@biomejs/cli-linux-x64-musl": "npm:2.3.11" + "@biomejs/cli-win32-arm64": "npm:2.3.11" + "@biomejs/cli-win32-x64": "npm:2.3.11" + dependenciesMeta: + "@biomejs/cli-darwin-arm64": + optional: true + "@biomejs/cli-darwin-x64": + optional: true + "@biomejs/cli-linux-arm64": + optional: true + "@biomejs/cli-linux-arm64-musl": + optional: true + "@biomejs/cli-linux-x64": + optional: true + "@biomejs/cli-linux-x64-musl": + optional: true + "@biomejs/cli-win32-arm64": + optional: true + "@biomejs/cli-win32-x64": + optional: true + bin: + biome: bin/biome + checksum: 10c0/b9764070c3d1583466a8861d37dc480c18103f7bb52115db0f265a38e6343d69792c9beea094e0b3db0905cb365b9a82ad2a0f3f05b7f04873a8f9b444263140 + languageName: node + linkType: hard + "@biomejs/cli-darwin-arm64@npm:2.2.4": version: 2.2.4 resolution: "@biomejs/cli-darwin-arm64@npm:2.2.4" @@ -788,6 +823,13 @@ __metadata: languageName: node linkType: hard +"@biomejs/cli-darwin-arm64@npm:2.3.11": + version: 2.3.11 + resolution: "@biomejs/cli-darwin-arm64@npm:2.3.11" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@biomejs/cli-darwin-x64@npm:2.2.4": version: 2.2.4 resolution: "@biomejs/cli-darwin-x64@npm:2.2.4" @@ -795,6 +837,13 @@ __metadata: languageName: node linkType: hard +"@biomejs/cli-darwin-x64@npm:2.3.11": + version: 2.3.11 + resolution: "@biomejs/cli-darwin-x64@npm:2.3.11" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@biomejs/cli-linux-arm64-musl@npm:2.2.4": version: 2.2.4 resolution: "@biomejs/cli-linux-arm64-musl@npm:2.2.4" @@ -802,6 +851,13 @@ __metadata: languageName: node linkType: hard +"@biomejs/cli-linux-arm64-musl@npm:2.3.11": + version: 2.3.11 + resolution: "@biomejs/cli-linux-arm64-musl@npm:2.3.11" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "@biomejs/cli-linux-arm64@npm:2.2.4": version: 2.2.4 resolution: "@biomejs/cli-linux-arm64@npm:2.2.4" @@ -809,6 +865,13 @@ __metadata: languageName: node linkType: hard +"@biomejs/cli-linux-arm64@npm:2.3.11": + version: 2.3.11 + resolution: "@biomejs/cli-linux-arm64@npm:2.3.11" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@biomejs/cli-linux-x64-musl@npm:2.2.4": version: 2.2.4 resolution: "@biomejs/cli-linux-x64-musl@npm:2.2.4" @@ -816,6 +879,13 @@ __metadata: languageName: node linkType: hard +"@biomejs/cli-linux-x64-musl@npm:2.3.11": + version: 2.3.11 + resolution: "@biomejs/cli-linux-x64-musl@npm:2.3.11" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "@biomejs/cli-linux-x64@npm:2.2.4": version: 2.2.4 resolution: "@biomejs/cli-linux-x64@npm:2.2.4" @@ -823,6 +893,13 @@ __metadata: languageName: node linkType: hard +"@biomejs/cli-linux-x64@npm:2.3.11": + version: 2.3.11 + resolution: "@biomejs/cli-linux-x64@npm:2.3.11" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@biomejs/cli-win32-arm64@npm:2.2.4": version: 2.2.4 resolution: "@biomejs/cli-win32-arm64@npm:2.2.4" @@ -830,6 +907,13 @@ __metadata: languageName: node linkType: hard +"@biomejs/cli-win32-arm64@npm:2.3.11": + version: 2.3.11 + resolution: "@biomejs/cli-win32-arm64@npm:2.3.11" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@biomejs/cli-win32-x64@npm:2.2.4": version: 2.2.4 resolution: "@biomejs/cli-win32-x64@npm:2.2.4" @@ -837,6 +921,13 @@ __metadata: languageName: node linkType: hard +"@biomejs/cli-win32-x64@npm:2.3.11": + version: 2.3.11 + resolution: "@biomejs/cli-win32-x64@npm:2.3.11" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@changesets/apply-release-plan@npm:^7.0.14": version: 7.0.14 resolution: "@changesets/apply-release-plan@npm:7.0.14" @@ -2623,7 +2714,7 @@ __metadata: dependencies: "@aws-sdk/client-s3": "npm:^3.891.0" "@aws-sdk/s3-request-presigner": "npm:^3.891.0" - "@biomejs/biome": "npm:^2.2.4" + "@biomejs/biome": "npm:^2.3.11" "@transloadit/sev-logger": "npm:^0.0.15" "@types/debug": "npm:^4.1.12" "@types/minimist": "npm:^1.2.5" From d90a343f6c5256454ec6263c08f3c6a677d1f279 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Fri, 16 Jan 2026 20:16:59 +0100 Subject: [PATCH 24/43] fix: harden zod scripts and tooling --- CONTRIBUTING.md | 2 +- docs/todo.md | 6 ++-- .../node/src/InconsistentResponseError.ts | 2 +- packages/node/src/Transloadit.ts | 2 +- packages/transloadit/package.json | 2 +- packages/zod/package.json | 8 ++--- packages/zod/scripts/sync-v3.ts | 2 +- packages/zod/test/scripts-config.test.ts | 26 ++++++++++++++++ ...ingerprint-pack.js => fingerprint-pack.ts} | 30 +++++++++++++------ scripts/pack-transloadit.ts | 10 +++++-- ...-transloadit.js => prepare-transloadit.ts} | 20 +++++++------ 11 files changed, 77 insertions(+), 33 deletions(-) create mode 100644 packages/zod/test/scripts-config.test.ts rename scripts/{fingerprint-pack.js => fingerprint-pack.ts} (85%) rename scripts/{prepare-transloadit.js => prepare-transloadit.ts} (76%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 95eac7d9..22c36fcb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -81,7 +81,7 @@ View the coverage report locally by opening `coverage/index.html` in your browse ## Packaging the legacy `transloadit` package -The `packages/transloadit` folder is a generated legacy wrapper. The `src` directory and top-level `README.md`, `CHANGELOG.md`, and `LICENSE` are produced during packing by `scripts/prepare-transloadit.js` and are not tracked in git. If you need to validate the legacy package contents, run: +The `packages/transloadit` folder is a generated legacy wrapper. The `src` directory and top-level `README.md`, `CHANGELOG.md`, and `LICENSE` are produced during packing by `scripts/prepare-transloadit.ts` and are not tracked in git. If you need to validate the legacy package contents, run: ```sh yarn pack diff --git a/docs/todo.md b/docs/todo.md index dc41f133..bae7b40c 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -10,13 +10,13 @@ - [x] Assert byte-for-byte identity ## Fingerprint script -- [x] Add `scripts/fingerprint-pack.js` +- [x] Add `scripts/fingerprint-pack.ts` - [x] Use `npm pack --json` to get tarball name - [x] Hash the tarball SHA256 - [x] Hash each file via `tar -tf` + `tar -xOf` - [x] Output JSON artifact for comparison -- [x] Support `node scripts/fingerprint-pack.js .` -- [x] Support `node scripts/fingerprint-pack.js packages/transloadit` +- [x] Support `node --experimental-strip-types scripts/fingerprint-pack.ts .` +- [x] Support `node --experimental-strip-types scripts/fingerprint-pack.ts packages/transloadit` ## Versioning and releases - [x] Add Changesets at workspace root diff --git a/packages/node/src/InconsistentResponseError.ts b/packages/node/src/InconsistentResponseError.ts index 455fe499..fbc5fe95 100644 --- a/packages/node/src/InconsistentResponseError.ts +++ b/packages/node/src/InconsistentResponseError.ts @@ -1,3 +1,3 @@ -export default class InconsistentResponseError extends Error { +export class InconsistentResponseError extends Error { override name = 'InconsistentResponseError' } diff --git a/packages/node/src/Transloadit.ts b/packages/node/src/Transloadit.ts index a093675e..157858bd 100644 --- a/packages/node/src/Transloadit.ts +++ b/packages/node/src/Transloadit.ts @@ -42,7 +42,7 @@ import type { TemplateCredentialsResponse, TemplateResponse, } from './apiTypes.ts' -import InconsistentResponseError from './InconsistentResponseError.ts' +import { InconsistentResponseError } from './InconsistentResponseError.ts' import PaginationStream from './PaginationStream.ts' import PollingTimeoutError from './PollingTimeoutError.ts' import type { Stream } from './tus.ts' diff --git a/packages/transloadit/package.json b/packages/transloadit/package.json index 3d356b1b..c6f0ab2c 100644 --- a/packages/transloadit/package.json +++ b/packages/transloadit/package.json @@ -69,7 +69,7 @@ "lint:deps": "knip --dependencies --no-progress", "fix:deps": "knip --dependencies --no-progress --fix", "knip": "knip --no-config-hints --no-progress", - "prepack": "node ../../scripts/prepare-transloadit.js", + "prepack": "node --experimental-strip-types ../../scripts/prepare-transloadit.ts", "test:unit": "vitest run --coverage ./test/unit", "test:e2e": "vitest run ./test/e2e", "test": "vitest run --coverage" diff --git a/packages/zod/package.json b/packages/zod/package.json index 8b47c925..fff06d7a 100644 --- a/packages/zod/package.json +++ b/packages/zod/package.json @@ -28,13 +28,13 @@ } }, "scripts": { - "sync:v3": "node scripts/sync-v3.ts", - "sync:v4": "node scripts/sync-v4.ts", + "sync:v3": "node --experimental-strip-types scripts/sync-v3.ts", + "sync:v4": "node --experimental-strip-types scripts/sync-v4.ts", "sync": "yarn sync:v3 && yarn sync:v4", "lint:ts": "yarn sync && tsc --build tsconfig.build.json", - "test:unit": "node test/exports-sync.test.ts && node test/runtime-parity.test.ts", + "test:unit": "node --experimental-strip-types test/scripts-config.test.ts && node --experimental-strip-types test/exports-sync.test.ts && node --experimental-strip-types test/runtime-parity.test.ts", "build": "yarn sync && tsc --build tsconfig.build.json", - "check": "yarn sync && tsc --build tsconfig.build.json && tsc --noEmit --project tsconfig.test.json && node test/exports-sync.test.ts && node test/runtime-parity.test.ts" + "check": "yarn sync && tsc --build tsconfig.build.json && tsc --noEmit --project tsconfig.test.json && yarn test:unit" }, "dependencies": { "zod": "^4.0.0" diff --git a/packages/zod/scripts/sync-v3.ts b/packages/zod/scripts/sync-v3.ts index 022d7fda..f0a862b8 100644 --- a/packages/zod/scripts/sync-v3.ts +++ b/packages/zod/scripts/sync-v3.ts @@ -24,7 +24,7 @@ const indexContents = [ '', ].join('\n') -const collectFiles = async (dir, acc = []) => { +const collectFiles = async (dir: string, acc: string[] = []): Promise => { const entries = await readdir(dir, { withFileTypes: true }) for (const entry of entries) { const full = resolve(dir, entry.name) diff --git a/packages/zod/test/scripts-config.test.ts b/packages/zod/test/scripts-config.test.ts new file mode 100644 index 00000000..a01c9299 --- /dev/null +++ b/packages/zod/test/scripts-config.test.ts @@ -0,0 +1,26 @@ +import assert from 'node:assert/strict' +import { readFile } from 'node:fs/promises' +import { dirname, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const filePath = fileURLToPath(import.meta.url) +const zodRoot = resolve(dirname(filePath), '..') +const packageJsonPath = resolve(zodRoot, 'package.json') +const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8')) as { + scripts?: Record +} + +const scripts = packageJson.scripts ?? {} +const requiredFlag = '--experimental-strip-types' +const requiredScripts = ['sync:v3', 'sync:v4', 'test:unit'] + +for (const scriptName of requiredScripts) { + const script = scripts[scriptName] + assert.ok(script, `Missing ${scriptName} script in package.json`) + assert.ok( + script.includes(requiredFlag), + `${scriptName} should include ${requiredFlag} so Node can run TypeScript`, + ) +} + +console.log('zod scripts config: ok') diff --git a/scripts/fingerprint-pack.js b/scripts/fingerprint-pack.ts similarity index 85% rename from scripts/fingerprint-pack.js rename to scripts/fingerprint-pack.ts index 84d09e8f..47a5526e 100644 --- a/scripts/fingerprint-pack.js +++ b/scripts/fingerprint-pack.ts @@ -7,8 +7,8 @@ import { promisify } from 'node:util' const execFileAsync = promisify(execFile) -const usage = () => { - console.log(`Usage: node scripts/fingerprint-pack.js [path] [options] +const usage = (): void => { + console.log(`Usage: node --experimental-strip-types scripts/fingerprint-pack.ts [path] [options] Options: -o, --out Write JSON output to a file @@ -18,7 +18,14 @@ Options: `) } -const parseArgs = () => { +interface ParsedArgs { + target: string + out: string | null + keep: boolean + ignoreScripts: boolean +} + +const parseArgs = (): ParsedArgs => { const args = process.argv.slice(2) let target = '.' let out = null @@ -53,7 +60,7 @@ const parseArgs = () => { return { target, out, keep, ignoreScripts } } -const hashFile = async (filePath) => +const hashFile = async (filePath: string): Promise => new Promise((resolvePromise, reject) => { const hash = createHash('sha256') const stream = createReadStream(filePath) @@ -62,7 +69,10 @@ const hashFile = async (filePath) => stream.on('end', () => resolvePromise(hash.digest('hex'))) }) -const hashTarEntry = async (tarballPath, entry) => +const hashTarEntry = async ( + tarballPath: string, + entry: string, +): Promise<{ sha256: string; sizeBytes: number }> => new Promise((resolvePromise, reject) => { const hash = createHash('sha256') let sizeBytes = 0 @@ -87,12 +97,14 @@ const hashTarEntry = async (tarballPath, entry) => }) }) -const readTarEntry = async (tarballPath, entry) => { - const { stdout } = await execFileAsync('tar', ['-xOf', tarballPath, entry]) +const readTarEntry = async (tarballPath: string, entry: string): Promise => { + const { stdout } = await execFileAsync('tar', ['-xOf', tarballPath, entry], { + encoding: 'utf8', + }) return stdout } -const main = async () => { +const main = async (): Promise => { const { target, out, keep, ignoreScripts } = parseArgs() const cwd = resolve(process.cwd(), target) @@ -100,7 +112,7 @@ const main = async () => { if (ignoreScripts) { packArgs.push('--ignore-scripts') } - const { stdout } = await execFileAsync('npm', packArgs, { cwd }) + const { stdout } = await execFileAsync('npm', packArgs, { cwd, encoding: 'utf8' }) const packed = JSON.parse(stdout.trim()) const info = Array.isArray(packed) ? packed[0] : packed if (!info?.filename) { diff --git a/scripts/pack-transloadit.ts b/scripts/pack-transloadit.ts index a7d6567a..bce9a654 100644 --- a/scripts/pack-transloadit.ts +++ b/scripts/pack-transloadit.ts @@ -11,9 +11,13 @@ const repoRoot = resolve(dirname(filePath), '..') const legacyPackage = resolve(repoRoot, 'packages/transloadit') const runPack = async () => { - await execFileAsync('node', [resolve(repoRoot, 'scripts/prepare-transloadit.js')], { - cwd: repoRoot, - }) + await execFileAsync( + 'node', + ['--experimental-strip-types', resolve(repoRoot, 'scripts/prepare-transloadit.ts')], + { + cwd: repoRoot, + }, + ) await execFileAsync('npm', ['pack', '--ignore-scripts'], { cwd: legacyPackage, diff --git a/scripts/prepare-transloadit.js b/scripts/prepare-transloadit.ts similarity index 76% rename from scripts/prepare-transloadit.js rename to scripts/prepare-transloadit.ts index 4503d5bd..5e2294ad 100644 --- a/scripts/prepare-transloadit.js +++ b/scripts/prepare-transloadit.ts @@ -11,20 +11,20 @@ const repoRoot = resolve(dirname(filePath), '..') const nodePackage = resolve(repoRoot, 'packages/node') const legacyPackage = resolve(repoRoot, 'packages/transloadit') -const copyDir = async (from, to) => { +const copyDir = async (from: string, to: string): Promise => { await rm(to, { recursive: true, force: true }) await mkdir(to, { recursive: true }) await cp(from, to, { recursive: true }) } -const readJson = async (filePath) => { +const readJson = async (filePath: string): Promise => { const raw = await readFile(filePath, 'utf8') return JSON.parse(raw) } -const formatPackageJson = (data) => { +const formatPackageJson = (data: Record): string => { let json = JSON.stringify(data, null, 2) - const inlineArray = (key) => { + const inlineArray = (key: string): string => { const pattern = new RegExp(`"${key}": \\[\\n([\\s\\S]*?)\\n\\s*\\]`, 'm') return json.replace(pattern, (_match, inner) => { const values = inner @@ -41,10 +41,12 @@ const formatPackageJson = (data) => { return `${json}\n` } -const writeLegacyPackageJson = async () => { - const nodePackageJson = await readJson(resolve(nodePackage, 'package.json')) +type PackageJson = Record & { scripts?: Record } + +const writeLegacyPackageJson = async (): Promise => { + const nodePackageJson = await readJson(resolve(nodePackage, 'package.json')) const scripts = { ...(nodePackageJson.scripts ?? {}) } - scripts.prepack = 'node ../../scripts/prepare-transloadit.js' + scripts.prepack = 'node --experimental-strip-types ../../scripts/prepare-transloadit.ts' const legacyPackageJson = { ...nodePackageJson, name: 'transloadit', @@ -55,13 +57,13 @@ const writeLegacyPackageJson = async () => { await writeFile(resolve(legacyPackage, 'package.json'), formatted) } -const writeLegacyChangelog = async () => { +const writeLegacyChangelog = async (): Promise => { const changelog = await readFile(resolve(nodePackage, 'CHANGELOG.md'), 'utf8') const updated = changelog.replace(/^# .+$/m, '# transloadit') await writeFile(resolve(legacyPackage, 'CHANGELOG.md'), updated) } -const main = async () => { +const main = async (): Promise => { await execFileAsync('yarn', ['workspace', '@transloadit/node', 'prepack'], { cwd: repoRoot, }) From 8badb8309a6ab05ef0e8d3eb44828ec9706703b2 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Fri, 16 Jan 2026 23:46:18 +0100 Subject: [PATCH 25/43] fix: normalize type export paths --- packages/transloadit/package.json | 2 +- packages/types/scripts/emit-types.test.ts | 19 ++++++++++++++++++- packages/types/scripts/emit-types.ts | 7 ++++++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/transloadit/package.json b/packages/transloadit/package.json index c6f0ab2c..08f56bdb 100644 --- a/packages/transloadit/package.json +++ b/packages/transloadit/package.json @@ -30,7 +30,7 @@ "zod": "3.25.76" }, "devDependencies": { - "@biomejs/biome": "^2.2.4", + "@biomejs/biome": "^2.3.11", "@types/debug": "^4.1.12", "@types/minimist": "^1.2.5", "@types/node": "^24.10.3", diff --git a/packages/types/scripts/emit-types.test.ts b/packages/types/scripts/emit-types.test.ts index 70af677c..0d439afe 100644 --- a/packages/types/scripts/emit-types.test.ts +++ b/packages/types/scripts/emit-types.test.ts @@ -1,6 +1,6 @@ import assert from 'node:assert/strict' -import { escapeStringLiteral } from './emit-types.ts' +import { escapeStringLiteral, normalizeExportPath } from './emit-types.ts' const cases = [ { @@ -30,3 +30,20 @@ for (const { input, expected } of cases) { } console.log('emit-types escapeStringLiteral: ok') + +const exportCases = [ + { + input: 'robots\\image-resize.ts', + expected: 'robots/image-resize', + }, + { + input: 'template.ts', + expected: 'template', + }, +] + +for (const { input, expected } of exportCases) { + assert.equal(normalizeExportPath(input), expected) +} + +console.log('emit-types normalizeExportPath: ok') diff --git a/packages/types/scripts/emit-types.ts b/packages/types/scripts/emit-types.ts index 6e753049..1d719baf 100644 --- a/packages/types/scripts/emit-types.ts +++ b/packages/types/scripts/emit-types.ts @@ -386,6 +386,11 @@ const generateFile = (sourceFile: ts.SourceFile, checker: ts.TypeChecker): strin return `${lines.join('\n')}\n` } +export const normalizeExportPath = (relPath: string): string => { + const trimmed = relPath.endsWith('.ts') ? relPath.slice(0, -3) : relPath + return trimmed.replace(/\\/g, '/') +} + const main = async () => { const schemaStats = await stat(schemaRoot).catch(() => null) if (!schemaStats?.isDirectory()) { @@ -414,7 +419,7 @@ const main = async () => { const content = generateFile(sourceFile, checker) await writeFile(outFile, content, 'utf8') - const exportPath = rel.endsWith('.ts') ? rel.slice(0, -3) : rel + const exportPath = normalizeExportPath(rel) if (exportPath.endsWith('/index')) { continue } From b3b9b6a3e5c153a372ca610516d071d93463a73c Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Sat, 17 Jan 2026 08:12:33 +0100 Subject: [PATCH 26/43] fix: guard zod sync patching --- .changeset/config.json | 2 +- .github/workflows/ci.yml | 3 ++ packages/zod/package.json | 2 +- packages/zod/scripts/sync-v4.ts | 48 +++++++++++++++++-- .../zod/test/patch-ai-chat-schema.test.ts | 37 ++++++++++++++ 5 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 packages/zod/test/patch-ai-chat-schema.test.ts diff --git a/.changeset/config.json b/.changeset/config.json index efa45198..9c900b1d 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -7,7 +7,7 @@ } ], "commit": false, - "fixed": [["@transloadit/node", "transloadit", "@transloadit/types"]], + "fixed": [["@transloadit/node", "transloadit", "@transloadit/types", "@transloadit/zod"]], "linked": [], "access": "public", "baseBranch": "main", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 50f6eac2..29e2751d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,6 +76,9 @@ jobs: node-version: ${{ matrix.node }} - run: corepack yarn - run: corepack yarn test:unit + if: matrix.node == 24 + - run: corepack yarn workspace @transloadit/node test:unit + if: matrix.node != 24 - name: Upload coverage reports artifact if: matrix.node == 24 uses: actions/upload-artifact@v4 diff --git a/packages/zod/package.json b/packages/zod/package.json index fff06d7a..7e55e2b6 100644 --- a/packages/zod/package.json +++ b/packages/zod/package.json @@ -32,7 +32,7 @@ "sync:v4": "node --experimental-strip-types scripts/sync-v4.ts", "sync": "yarn sync:v3 && yarn sync:v4", "lint:ts": "yarn sync && tsc --build tsconfig.build.json", - "test:unit": "node --experimental-strip-types test/scripts-config.test.ts && node --experimental-strip-types test/exports-sync.test.ts && node --experimental-strip-types test/runtime-parity.test.ts", + "test:unit": "node --experimental-strip-types test/scripts-config.test.ts && node --experimental-strip-types test/patch-ai-chat-schema.test.ts && node --experimental-strip-types test/exports-sync.test.ts && node --experimental-strip-types test/runtime-parity.test.ts", "build": "yarn sync && tsc --build tsconfig.build.json", "check": "yarn sync && tsc --build tsconfig.build.json && tsc --noEmit --project tsconfig.test.json && yarn test:unit" }, diff --git a/packages/zod/scripts/sync-v4.ts b/packages/zod/scripts/sync-v4.ts index 9ba23ae9..d85e9955 100644 --- a/packages/zod/scripts/sync-v4.ts +++ b/packages/zod/scripts/sync-v4.ts @@ -1,3 +1,4 @@ +import { realpathSync } from 'node:fs' import { cp, mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises' import { dirname, resolve, sep } from 'node:path' import { fileURLToPath } from 'node:url' @@ -325,10 +326,34 @@ const patchInterpolatableRobot = (contents: string): string => { return `${contents.slice(0, start)}${replacement}${contents.slice(end)}` } -const patchAiChatSchema = (contents: string): string => - contents - .replace('const jsonValueSchema: z.ZodType =', 'const jsonValueSchema: z.ZodType =') - .replace('result: z.unknown(),', 'result: z.unknown().optional(),') +export const patchAiChatSchema = (contents: string): string => { + const jsonValueToken = 'const jsonValueSchema: z.ZodType =' + const jsonValuePatched = 'const jsonValueSchema: z.ZodType =' + const resultToken = 'result: z.unknown(),' + const resultPatched = 'result: z.unknown().optional(),' + + let next = contents + if (next.includes(jsonValueToken)) { + next = next.replace(jsonValueToken, jsonValuePatched) + } + if (next.includes(resultToken)) { + next = next.replace(resultToken, resultPatched) + } + + const hasJsonValue = next.includes(jsonValuePatched) + const hasResult = next.includes(resultPatched) + if (!hasJsonValue || !hasResult) { + const missing = [ + !hasJsonValue ? 'jsonValueSchema' : null, + !hasResult ? 'result optional' : null, + ] + .filter(Boolean) + .join(', ') + throw new Error(`ai-chat schema patch failed (${missing})`) + } + + return next +} const patchFile = (filePath: string, contents: string): string => { let next = contents @@ -360,4 +385,17 @@ const main = async () => { } } -await main() +const shouldRun = (): boolean => { + if (!process.argv[1]) return false + try { + const current = realpathSync(fileURLToPath(import.meta.url)) + const invoked = realpathSync(process.argv[1]) + return current === invoked + } catch { + return false + } +} + +if (shouldRun()) { + await main() +} diff --git a/packages/zod/test/patch-ai-chat-schema.test.ts b/packages/zod/test/patch-ai-chat-schema.test.ts new file mode 100644 index 00000000..ceb2b7e7 --- /dev/null +++ b/packages/zod/test/patch-ai-chat-schema.test.ts @@ -0,0 +1,37 @@ +import assert from 'node:assert/strict' +import { patchAiChatSchema } from '../scripts/sync-v4.ts' + +const source = ` +const jsonValueSchema: z.ZodType = + z.union([z.string()]) + +const responseSchema = z.object({ + result: z.unknown(), +}) +` + +const patched = patchAiChatSchema(source) +assert.ok( + patched.includes('const jsonValueSchema: z.ZodType ='), + 'should widen jsonValueSchema to ZodType', +) +assert.ok(patched.includes('result: z.unknown().optional(),'), 'should make result optional') + +const alreadyPatched = ` +const jsonValueSchema: z.ZodType = + z.union([z.string()]) + +const responseSchema = z.object({ + result: z.unknown().optional(), +}) +` + +assert.equal( + patchAiChatSchema(alreadyPatched), + alreadyPatched, + 'should be a no-op when already patched', +) + +assert.throws(() => patchAiChatSchema('const jsonValueSchema: z.ZodType = z.string()'), /ai-chat/i) + +console.log('ai-chat schema patching: ok') From 0ff6c283be06084e48435ffde67eed243cdad7d8 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Sat, 17 Jan 2026 08:24:54 +0100 Subject: [PATCH 27/43] docs(ci): note Node 24 requirement for root tests --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29e2751d..2c5dc9e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,6 +75,8 @@ jobs: with: node-version: ${{ matrix.node }} - run: corepack yarn + # Root unit tests execute TypeScript scripts via Node's strip-types support. + # Keep those on Node 24+; Node 20/22 only run @transloadit/node unit tests. - run: corepack yarn test:unit if: matrix.node == 24 - run: corepack yarn workspace @transloadit/node test:unit From c4bfdb1111bb9a20f7724a39f10c34b9159e4050 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Sat, 17 Jan 2026 11:06:47 +0100 Subject: [PATCH 28/43] chore: update tooling requirements and parity checks --- .github/workflows/ci.yml | 9 +- CONTRIBUTING.md | 15 +- docs/fingerprint/transloadit-after.json | 13 +- .../transloadit-baseline.package.json | 87 ++++++++ docs/monorepo-architecture.md | 14 +- docs/todo.md | 4 +- package.json | 3 +- packages/node/README.md | 3 + packages/node/package.json | 5 +- .../node/src/InconsistentResponseError.ts | 2 +- packages/node/src/Transloadit.ts | 2 +- packages/node/src/alphalib/types/template.ts | 4 +- packages/node/test/e2e/cli/test-utils.ts | 2 +- packages/transloadit/package.json | 20 +- packages/types/README.md | 7 + packages/types/package.json | 11 +- packages/zod/README.md | 7 + packages/zod/package.json | 13 +- packages/zod/test/scripts-config.test.ts | 9 +- scripts/fingerprint-pack.ts | 2 +- scripts/pack-transloadit.ts | 2 +- scripts/prepare-transloadit.ts | 28 +-- scripts/verify-fingerprint.ts | 204 ++++++++++++++++++ 23 files changed, 408 insertions(+), 58 deletions(-) create mode 100644 docs/fingerprint/transloadit-baseline.package.json create mode 100644 packages/types/README.md create mode 100644 packages/zod/README.md create mode 100644 scripts/verify-fingerprint.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c5dc9e8..e0485e52 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -196,11 +196,12 @@ jobs: - uses: softprops/action-gh-release@v1 with: draft: true + - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 registry-url: https://registry.npmjs.org - - uses: actions/download-artifact@v4 - with: { name: package } - - run: npm install -g npm@^11.5.1 - - run: npm publish *.tgz --provenance + - run: corepack yarn + - run: corepack yarn changeset publish + env: + NPM_CONFIG_PROVENANCE: "true" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 22c36fcb..3cae4e96 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,6 +20,10 @@ To check for updates, run: yarn upgrade-interactive ``` +## Tooling requirements + +Local tooling (the TypeScript scripts in `scripts/` and package tests) requires Node 22.18+ so `node file.ts` works without flags. The published packages still support Node 20+ at runtime. + ## Linting This project is linted using Biome. You can lint the project by running: @@ -89,7 +93,7 @@ yarn pack ## Releasing -Only maintainers can make releases. Releases to [npm](https://www.npmjs.com) are automated using GitHub actions. To make a release, perform the following steps: +Only maintainers can make releases. Releases to [npm](https://www.npmjs.com) are automated using GitHub actions and Changesets (including the legacy `transloadit` package). To make a release, perform the following steps: 1. Create a changeset: - `yarn changeset` @@ -101,5 +105,12 @@ Only maintainers can make releases. Releases to [npm](https://www.npmjs.com) are 4. Publish (maintainers only; GitHub Actions handles the release): - `yarn changeset publish` 5. When successful add [release notes](https://github.com/transloadit/node-sdk/releases). -6. If this was a pre-release, remember to reset the [npm `latest` tag](https://www.npmjs.com/package/transloadit?activeTab=versions) to the previous version (replace `x.y.z` with previous version): +6. Scoped packages publish with the `experimental` dist-tag by default. If you need to promote a scoped package to `latest`, update the tag manually. +7. If this was a pre-release, remember to reset the [npm `latest` tag](https://www.npmjs.com/package/transloadit?activeTab=versions) to the previous version (replace `x.y.z` with previous version): - `npm dist-tag add transloadit@X.Y.Z latest` + +### Release FAQ + +- **Lockstep versions:** Changesets use a fixed group, so version bumps and releases are always in lock‑step across `transloadit`, `@transloadit/node`, `@transloadit/types`, and `@transloadit/zod`. +- **Legacy parity:** `transloadit` is generated from `@transloadit/node` artifacts via `scripts/prepare-transloadit.ts`, then verified with `yarn parity:transloadit`. Only `package.json` metadata drift is allowed; any other drift fails. +- **Experimental packages:** Scoped packages (`@transloadit/node`, `@transloadit/types`, `@transloadit/zod`) publish with the `experimental` dist-tag. The unscoped `transloadit` package remains stable. diff --git a/docs/fingerprint/transloadit-after.json b/docs/fingerprint/transloadit-after.json index 8e10805b..14fb42b5 100644 --- a/docs/fingerprint/transloadit-after.json +++ b/docs/fingerprint/transloadit-after.json @@ -2,8 +2,8 @@ "packageDir": "/home/kvz/code/node-sdk/packages/transloadit", "tarball": { "filename": "transloadit-4.1.2.tgz", - "sizeBytes": 1110479, - "sha256": "1f9d45a0d0055c488da28eac563ad79073cedfab16ccf7bd4e5ea9bb50cf655a" + "sizeBytes": 1110470, + "sha256": "61a25c361b18372e0e590cf3195c617ae88966e53b3217acc88e1680493cad9f" }, "packageJson": { "name": "transloadit", @@ -13,7 +13,10 @@ ".": "./dist/Transloadit.js", "./package.json": "./package.json" }, - "files": ["dist", "src"] + "files": [ + "dist", + "src" + ] }, "files": [ { @@ -613,8 +616,8 @@ }, { "path": "package.json", - "sizeBytes": 2648, - "sha256": "a2fd83a1adc245ade4d1a376c3d8c92ba286e73412afb89fa5d8ef31212f560d" + "sizeBytes": 2601, + "sha256": "18cd60ab2fb8dd2a146181d9b0965e40e42a4ee1ffa9100f812bff85bd88a185" }, { "path": "dist/alphalib/types/robots/_index.d.ts.map", diff --git a/docs/fingerprint/transloadit-baseline.package.json b/docs/fingerprint/transloadit-baseline.package.json new file mode 100644 index 00000000..d441de58 --- /dev/null +++ b/docs/fingerprint/transloadit-baseline.package.json @@ -0,0 +1,87 @@ +{ + "name": "transloadit", + "version": "4.1.2", + "description": "Node.js SDK for Transloadit", + "type": "module", + "keywords": [ + "transloadit", + "encoding", + "transcoding", + "video", + "audio", + "mp3" + ], + "author": "Tim Koschuetzki ", + "packageManager": "yarn@4.12.0", + "engines": { + "node": ">= 20" + }, + "dependencies": { + "@aws-sdk/client-s3": "^3.891.0", + "@aws-sdk/s3-request-presigner": "^3.891.0", + "@transloadit/sev-logger": "^0.0.15", + "clipanion": "^4.0.0-rc.4", + "debug": "^4.4.3", + "dotenv": "^17.2.3", + "form-data": "^4.0.4", + "got": "14.4.9", + "into-stream": "^9.0.0", + "is-stream": "^4.0.1", + "node-watch": "^0.7.4", + "p-map": "^7.0.3", + "p-queue": "^9.0.1", + "recursive-readdir": "^2.2.3", + "tus-js-client": "^4.3.1", + "type-fest": "^4.41.0", + "zod": "3.25.76" + }, + "devDependencies": { + "@biomejs/biome": "^2.2.4", + "@types/debug": "^4.1.12", + "@types/recursive-readdir": "^2.2.4", + "@types/temp": "^0.9.4", + "@vitest/coverage-v8": "^3.2.4", + "badge-maker": "^5.0.2", + "execa": "9.6.0", + "image-size": "^2.0.2", + "nock": "^14.0.10", + "npm-run-all": "^4.1.5", + "p-retry": "^7.0.0", + "rimraf": "^6.1.2", + "temp": "^0.9.4", + "tsx": "4.20.5", + "typescript": "5.9.2", + "vitest": "^3.2.4" + }, + "repository": { + "type": "git", + "url": "git://github.com/transloadit/node-sdk.git" + }, + "directories": { + "src": "./src" + }, + "scripts": { + "check": "yarn lint:ts && yarn fix && yarn test:unit", + "fix:js": "biome check --write .", + "lint:ts": "tsc --build", + "fix:js:unsafe": "biome check --write . --unsafe", + "lint:js": "biome check .", + "lint": "npm-run-all --parallel 'lint:js'", + "fix": "npm-run-all --serial 'fix:js'", + "prepack": "rm -f tsconfig.tsbuildinfo tsconfig.build.tsbuildinfo && tsc --build tsconfig.build.json", + "test:unit": "vitest run --coverage ./test/unit", + "test:e2e": "vitest run ./test/e2e", + "test": "vitest run --coverage" + }, + "license": "MIT", + "main": "./dist/Transloadit.js", + "exports": { + ".": "./dist/Transloadit.js", + "./package.json": "./package.json" + }, + "files": [ + "dist", + "src" + ], + "bin": "./dist/cli.js" +} \ No newline at end of file diff --git a/docs/monorepo-architecture.md b/docs/monorepo-architecture.md index 444d529d..bb47f840 100644 --- a/docs/monorepo-architecture.md +++ b/docs/monorepo-architecture.md @@ -38,6 +38,7 @@ node-sdk/ - Publishes the legacy package name. - Built from the same runtime artifacts as `@transloadit/node`. - Compatibility is verified via tarball fingerprinting. +- Allowed drift is limited to `package.json` metadata and is reported explicitly. ### @transloadit/types - Pure type package; no Zod runtime dependency. @@ -84,7 +85,7 @@ The `@transloadit/zod` `check` script runs `sync`, `tsc`, and runtime parity. ## Node & TypeScript execution -All internal scripts are TypeScript and run via Node 24’s erasable syntax support (no `--experimental-strip-types` in the zod package). Other packages may still use the flag where needed. +All internal scripts are TypeScript and rely on Node’s built-in type stripping. Tooling expects Node 22.18+ (or newer) so scripts can run directly via `node script.ts` without flags. ## Publishing strategy @@ -92,6 +93,17 @@ All internal scripts are TypeScript and run via Node 24’s erasable syntax supp - `transloadit` is generated from the same artifacts and fingerprinted. - `@transloadit/types` and `@transloadit/zod` are versioned in lock‑step (changesets). +### Parity verification + +The `transloadit` tarball is compared against a recorded baseline: + +- `scripts/fingerprint-pack.ts` creates a tarball fingerprint. +- `scripts/verify-fingerprint.ts` compares fingerprints and reports drift. +- `docs/fingerprint/transloadit-baseline.json` is the baseline fingerprint. +- `docs/fingerprint/transloadit-baseline.package.json` is used to diff `package.json`. + +Only `package.json` metadata drift is currently allowed; any other file difference fails the check. + ## Future extensions - Add JSON Schema export for v4 (`@transloadit/jsonschema` or subpath) once v4 tooling is stable. diff --git a/docs/todo.md b/docs/todo.md index bae7b40c..1092ee7c 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -15,8 +15,8 @@ - [x] Hash the tarball SHA256 - [x] Hash each file via `tar -tf` + `tar -xOf` - [x] Output JSON artifact for comparison -- [x] Support `node --experimental-strip-types scripts/fingerprint-pack.ts .` -- [x] Support `node --experimental-strip-types scripts/fingerprint-pack.ts packages/transloadit` +- [x] Support `node scripts/fingerprint-pack.ts .` +- [x] Support `node scripts/fingerprint-pack.ts packages/transloadit` ## Versioning and releases - [x] Add Changesets at workspace root diff --git a/package.json b/package.json index d46ce49a..b47ca987 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "fix:js": "biome check --write .", "fix:js:unsafe": "biome check --write . --unsafe", "knip": "yarn workspace @transloadit/node knip", - "pack": "node --experimental-strip-types scripts/pack-transloadit.ts", + "pack": "node scripts/pack-transloadit.ts", + "parity:transloadit": "node scripts/fingerprint-pack.ts packages/transloadit --ignore-scripts --out /tmp/transloadit-after.json && node scripts/verify-fingerprint.ts --current /tmp/transloadit-after.json --baseline docs/fingerprint/transloadit-baseline.json --allow package.json --baseline-package-json docs/fingerprint/transloadit-baseline.package.json --current-package-json packages/transloadit/package.json", "test:unit": "yarn workspace @transloadit/node test:unit && yarn workspace @transloadit/types test:unit && yarn workspace @transloadit/zod test:unit", "test:e2e": "yarn workspace @transloadit/node test:e2e", "test": "yarn workspace @transloadit/node test" diff --git a/packages/node/README.md b/packages/node/README.md index c041b465..8a4d41be 100644 --- a/packages/node/README.md +++ b/packages/node/README.md @@ -1,6 +1,9 @@ [![Build Status](https://github.com/transloadit/node-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/transloadit/node-sdk/actions/workflows/ci.yml) [![Coverage](https://codecov.io/gh/transloadit/node-sdk/branch/main/graph/badge.svg)](https://codecov.io/gh/transloadit/node-sdk) +> **Stability: Experimental.** This scoped package is new and may change without notice. +> For the stable SDK, use the unscoped `transloadit` package. + diff --git a/packages/node/package.json b/packages/node/package.json index 62dda798..5877370f 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -1,6 +1,6 @@ { "name": "@transloadit/node", - "version": "4.1.3", + "version": "4.1.2", "description": "Node.js SDK for Transloadit", "type": "module", "keywords": ["transloadit", "encoding", "transcoding", "video", "audio", "mp3"], @@ -29,6 +29,9 @@ "type-fest": "^4.41.0", "zod": "3.25.76" }, + "publishConfig": { + "tag": "experimental" + }, "devDependencies": { "@biomejs/biome": "^2.3.11", "@types/debug": "^4.1.12", diff --git a/packages/node/src/InconsistentResponseError.ts b/packages/node/src/InconsistentResponseError.ts index fbc5fe95..455fe499 100644 --- a/packages/node/src/InconsistentResponseError.ts +++ b/packages/node/src/InconsistentResponseError.ts @@ -1,3 +1,3 @@ -export class InconsistentResponseError extends Error { +export default class InconsistentResponseError extends Error { override name = 'InconsistentResponseError' } diff --git a/packages/node/src/Transloadit.ts b/packages/node/src/Transloadit.ts index 157858bd..a093675e 100644 --- a/packages/node/src/Transloadit.ts +++ b/packages/node/src/Transloadit.ts @@ -42,7 +42,7 @@ import type { TemplateCredentialsResponse, TemplateResponse, } from './apiTypes.ts' -import { InconsistentResponseError } from './InconsistentResponseError.ts' +import InconsistentResponseError from './InconsistentResponseError.ts' import PaginationStream from './PaginationStream.ts' import PollingTimeoutError from './PollingTimeoutError.ts' import type { Stream } from './tus.ts' diff --git a/packages/node/src/alphalib/types/template.ts b/packages/node/src/alphalib/types/template.ts index 793e22a1..d237b809 100644 --- a/packages/node/src/alphalib/types/template.ts +++ b/packages/node/src/alphalib/types/template.ts @@ -37,7 +37,7 @@ export type StepsWithHiddenFieldsInput = z.input value) that can be used as Assembly Variables, just like additional form fields can. You can use anything that is JSON stringifyable as a value', @@ -121,7 +121,7 @@ const assemblyInstructionsSharedShape = { .describe( 'Set this to true to reduce the response from an Assembly POST request to only the necessary fields. This prevents any potentially confidential information being leaked to the end user who is making the Assembly request. A successful Assembly will only include the ok and assembly_id fields. An erroneous Assembly will only include the error, http_code, message and assembly_id fields. The full Assembly Status will then still be sent to the notify_url if one was specified.', ), - // Keep the inline cast; helper wrappers trigger TS7056 (type serialization limit). + // This is done to avoid heavy inference cost steps: optionalStepsSchema as typeof optionalStepsSchema, template_id: templateIdSchema, } as const diff --git a/packages/node/test/e2e/cli/test-utils.ts b/packages/node/test/e2e/cli/test-utils.ts index 448dad13..a8a69f19 100644 --- a/packages/node/test/e2e/cli/test-utils.ts +++ b/packages/node/test/e2e/cli/test-utils.ts @@ -60,7 +60,7 @@ export function runCli( args: string, env: Record = {}, ): Promise<{ stdout: string; stderr: string }> { - return execAsync(`${process.execPath} --experimental-strip-types ${cliPath} ${args}`, { + return execAsync(`${process.execPath} ${cliPath} ${args}`, { env: { ...process.env, ...env }, }) } diff --git a/packages/transloadit/package.json b/packages/transloadit/package.json index 08f56bdb..94c80c27 100644 --- a/packages/transloadit/package.json +++ b/packages/transloadit/package.json @@ -1,9 +1,16 @@ { "name": "transloadit", - "version": "4.1.3", + "version": "4.1.2", "description": "Node.js SDK for Transloadit", "type": "module", - "keywords": ["transloadit", "encoding", "transcoding", "video", "audio", "mp3"], + "keywords": [ + "transloadit", + "encoding", + "transcoding", + "video", + "audio", + "mp3" + ], "author": "Tim Koschuetzki ", "packageManager": "yarn@4.12.0", "engines": { @@ -30,7 +37,7 @@ "zod": "3.25.76" }, "devDependencies": { - "@biomejs/biome": "^2.3.11", + "@biomejs/biome": "^2.2.4", "@types/debug": "^4.1.12", "@types/minimist": "^1.2.5", "@types/node": "^24.10.3", @@ -69,7 +76,7 @@ "lint:deps": "knip --dependencies --no-progress", "fix:deps": "knip --dependencies --no-progress --fix", "knip": "knip --no-config-hints --no-progress", - "prepack": "node --experimental-strip-types ../../scripts/prepare-transloadit.ts", + "prepack": "node ../../scripts/prepare-transloadit.ts", "test:unit": "vitest run --coverage ./test/unit", "test:e2e": "vitest run ./test/e2e", "test": "vitest run --coverage" @@ -80,6 +87,9 @@ ".": "./dist/Transloadit.js", "./package.json": "./package.json" }, - "files": ["dist", "src"], + "files": [ + "dist", + "src" + ], "bin": "./dist/cli.js" } diff --git a/packages/types/README.md b/packages/types/README.md new file mode 100644 index 00000000..40262ef4 --- /dev/null +++ b/packages/types/README.md @@ -0,0 +1,7 @@ +# @transloadit/types (experimental) + +**Stability: Experimental.** This package is new and may change without notice. + +This package provides type-only exports generated from Transloadit schemas. + +For the stable SDK, use the unscoped `transloadit` package. diff --git a/packages/types/package.json b/packages/types/package.json index 092cd0bb..fd419a9c 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,10 +1,10 @@ { "name": "@transloadit/types", - "version": "4.1.3", + "version": "4.1.2", "description": "Transloadit type definitions", "type": "module", "license": "MIT", - "files": ["dist"], + "files": ["dist", "README.md"], "types": "./dist/index.d.ts", "exports": { ".": { @@ -24,14 +24,17 @@ } }, "scripts": { - "generate": "node --experimental-strip-types scripts/emit-types.ts", + "generate": "node scripts/emit-types.ts", "lint:ts": "yarn generate && tsc --build tsconfig.build.json", - "test:unit": "node --experimental-strip-types scripts/emit-types.test.ts", + "test:unit": "node scripts/emit-types.test.ts", "build": "yarn generate && tsc --build tsconfig.build.json", "check": "yarn generate && yarn test:unit && tsc --build tsconfig.build.json" }, "devDependencies": { "typescript": "5.9.3", "zod": "3.25.76" + }, + "publishConfig": { + "tag": "experimental" } } diff --git a/packages/zod/README.md b/packages/zod/README.md new file mode 100644 index 00000000..be540e46 --- /dev/null +++ b/packages/zod/README.md @@ -0,0 +1,7 @@ +# @transloadit/zod (experimental) + +**Stability: Experimental.** This package is new and may change without notice. + +Exports Zod schemas derived from Transloadit types. + +For the stable SDK, use the unscoped `transloadit` package. diff --git a/packages/zod/package.json b/packages/zod/package.json index 7e55e2b6..8785c0ff 100644 --- a/packages/zod/package.json +++ b/packages/zod/package.json @@ -1,10 +1,10 @@ { "name": "@transloadit/zod", - "version": "4.1.3", + "version": "4.1.2", "description": "Transloadit Zod schemas", "type": "module", "license": "MIT", - "files": ["dist"], + "files": ["dist", "README.md"], "exports": { ".": { "types": "./dist/v3/index.d.ts", @@ -28,11 +28,11 @@ } }, "scripts": { - "sync:v3": "node --experimental-strip-types scripts/sync-v3.ts", - "sync:v4": "node --experimental-strip-types scripts/sync-v4.ts", + "sync:v3": "node scripts/sync-v3.ts", + "sync:v4": "node scripts/sync-v4.ts", "sync": "yarn sync:v3 && yarn sync:v4", "lint:ts": "yarn sync && tsc --build tsconfig.build.json", - "test:unit": "node --experimental-strip-types test/scripts-config.test.ts && node --experimental-strip-types test/patch-ai-chat-schema.test.ts && node --experimental-strip-types test/exports-sync.test.ts && node --experimental-strip-types test/runtime-parity.test.ts", + "test:unit": "node test/scripts-config.test.ts && node test/patch-ai-chat-schema.test.ts && node test/exports-sync.test.ts && node test/runtime-parity.test.ts", "build": "yarn sync && tsc --build tsconfig.build.json", "check": "yarn sync && tsc --build tsconfig.build.json && tsc --noEmit --project tsconfig.test.json && yarn test:unit" }, @@ -41,5 +41,8 @@ }, "devDependencies": { "typescript": "5.9.3" + }, + "publishConfig": { + "tag": "experimental" } } diff --git a/packages/zod/test/scripts-config.test.ts b/packages/zod/test/scripts-config.test.ts index a01c9299..97e19d12 100644 --- a/packages/zod/test/scripts-config.test.ts +++ b/packages/zod/test/scripts-config.test.ts @@ -11,15 +11,18 @@ const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8')) as { } const scripts = packageJson.scripts ?? {} -const requiredFlag = '--experimental-strip-types' const requiredScripts = ['sync:v3', 'sync:v4', 'test:unit'] for (const scriptName of requiredScripts) { const script = scripts[scriptName] assert.ok(script, `Missing ${scriptName} script in package.json`) assert.ok( - script.includes(requiredFlag), - `${scriptName} should include ${requiredFlag} so Node can run TypeScript`, + script.includes('node '), + `${scriptName} should invoke node directly for TypeScript scripts`, + ) + assert.ok( + !script.includes('--experimental-strip-types'), + `${scriptName} should not use --experimental-strip-types (Node 22.18+ runs .ts directly)`, ) } diff --git a/scripts/fingerprint-pack.ts b/scripts/fingerprint-pack.ts index 47a5526e..06278ded 100644 --- a/scripts/fingerprint-pack.ts +++ b/scripts/fingerprint-pack.ts @@ -8,7 +8,7 @@ import { promisify } from 'node:util' const execFileAsync = promisify(execFile) const usage = (): void => { - console.log(`Usage: node --experimental-strip-types scripts/fingerprint-pack.ts [path] [options] + console.log(`Usage: node scripts/fingerprint-pack.ts [path] [options] Options: -o, --out Write JSON output to a file diff --git a/scripts/pack-transloadit.ts b/scripts/pack-transloadit.ts index bce9a654..ddc4a3a1 100644 --- a/scripts/pack-transloadit.ts +++ b/scripts/pack-transloadit.ts @@ -13,7 +13,7 @@ const legacyPackage = resolve(repoRoot, 'packages/transloadit') const runPack = async () => { await execFileAsync( 'node', - ['--experimental-strip-types', resolve(repoRoot, 'scripts/prepare-transloadit.ts')], + [resolve(repoRoot, 'scripts/prepare-transloadit.ts')], { cwd: repoRoot, }, diff --git a/scripts/prepare-transloadit.ts b/scripts/prepare-transloadit.ts index 5e2294ad..ad1b916d 100644 --- a/scripts/prepare-transloadit.ts +++ b/scripts/prepare-transloadit.ts @@ -23,34 +23,26 @@ const readJson = async (filePath: string): Promise => { } const formatPackageJson = (data: Record): string => { - let json = JSON.stringify(data, null, 2) - const inlineArray = (key: string): string => { - const pattern = new RegExp(`"${key}": \\[\\n([\\s\\S]*?)\\n\\s*\\]`, 'm') - return json.replace(pattern, (_match, inner) => { - const values = inner - .split('\n') - .map((line) => line.trim()) - .filter(Boolean) - .map((line) => line.replace(/,$/, '')) - return `"${key}": [${values.join(', ')}]` - }) - } - - json = inlineArray('keywords') - json = inlineArray('files') - return `${json}\n` + return `${JSON.stringify(data, null, 2)}\n` } type PackageJson = Record & { scripts?: Record } const writeLegacyPackageJson = async (): Promise => { const nodePackageJson = await readJson(resolve(nodePackage, 'package.json')) + const legacyExisting = await readJson(resolve(legacyPackage, 'package.json')).catch( + () => null, + ) const scripts = { ...(nodePackageJson.scripts ?? {}) } - scripts.prepack = 'node --experimental-strip-types ../../scripts/prepare-transloadit.ts' - const legacyPackageJson = { + scripts.prepack = 'node ../../scripts/prepare-transloadit.ts' + const legacyPackageJson: PackageJson = { ...nodePackageJson, name: 'transloadit', scripts, + devDependencies: legacyExisting?.devDependencies ?? nodePackageJson.devDependencies, + } + if ('publishConfig' in legacyPackageJson) { + delete legacyPackageJson.publishConfig } const formatted = formatPackageJson(legacyPackageJson) diff --git a/scripts/verify-fingerprint.ts b/scripts/verify-fingerprint.ts new file mode 100644 index 00000000..7c51dd5e --- /dev/null +++ b/scripts/verify-fingerprint.ts @@ -0,0 +1,204 @@ +import { spawnSync } from 'node:child_process' +import { readFile } from 'node:fs/promises' +import { resolve } from 'node:path' + +interface FingerprintEntry { + path: string + sizeBytes: number + sha256: string +} + +interface Fingerprint { + files: FingerprintEntry[] +} + +interface ParsedArgs { + baseline: string + current: string + allow: Set + baselinePackageJson: string | null + currentPackageJson: string | null +} + +const usage = (): void => { + console.log(`Usage: node scripts/verify-fingerprint.ts [options] + +Options: + --baseline Baseline fingerprint JSON (default: docs/fingerprint/transloadit-baseline.json) + --current Current fingerprint JSON (required) + --allow Allow drift for this file path (repeatable) + --baseline-package-json Baseline package.json (for diff output) + --current-package-json Current package.json (for diff output) + -h, --help Show help +`) +} + +const parseArgs = (): ParsedArgs => { + const args = process.argv.slice(2) + let baseline = 'docs/fingerprint/transloadit-baseline.json' + let current = '' + const allow = new Set() + let baselinePackageJson: string | null = null + let currentPackageJson: string | null = null + + for (let i = 0; i < args.length; i += 1) { + const arg = args[i] + if (arg === '-h' || arg === '--help') { + usage() + process.exit(0) + } + if (arg === '--baseline') { + baseline = args[i + 1] + i += 1 + continue + } + if (arg === '--current') { + current = args[i + 1] + i += 1 + continue + } + if (arg === '--allow') { + allow.add(args[i + 1]) + i += 1 + continue + } + if (arg === '--baseline-package-json') { + baselinePackageJson = args[i + 1] + i += 1 + continue + } + if (arg === '--current-package-json') { + currentPackageJson = args[i + 1] + i += 1 + continue + } + if (arg.startsWith('-')) { + throw new Error(`Unknown option: ${arg}`) + } + throw new Error(`Unexpected argument: ${arg}`) + } + + if (!current) { + usage() + throw new Error('Missing required --current') + } + + return { baseline, current, allow, baselinePackageJson, currentPackageJson } +} + +const readFingerprint = async (filePath: string): Promise => { + const raw = await readFile(resolve(filePath), 'utf8') + return JSON.parse(raw) as Fingerprint +} + +const indexByPath = (entries: FingerprintEntry[]): Map => { + const map = new Map() + for (const entry of entries) { + map.set(entry.path, entry) + } + return map +} + +const printDiff = (baselinePath: string, currentPath: string): void => { + const result = spawnSync('diff', ['-u', baselinePath, currentPath], { encoding: 'utf8' }) + if (result.error) { + console.log(` (diff unavailable: ${result.error.message})`) + return + } + if (result.status === 0) { + console.log(' (no content diff)') + return + } + if (result.status !== 1) { + console.log(` (diff failed with status ${result.status ?? 'unknown'})`) + return + } + const output = result.stdout.trimEnd() + if (!output) { + console.log(' (no diff output)') + return + } + const lines = output.split('\n') + const limit = 200 + const truncated = lines.length > limit + const slice = truncated ? lines.slice(0, limit) : lines + for (const line of slice) { + console.log(` ${line}`) + } + if (truncated) { + console.log(` ...diff truncated (${lines.length - limit} more lines)`) + } +} + +const main = async (): Promise => { + const { baseline, current, allow, baselinePackageJson, currentPackageJson } = parseArgs() + const baselineData = await readFingerprint(baseline) + const currentData = await readFingerprint(current) + const baselineMap = indexByPath(baselineData.files) + const currentMap = indexByPath(currentData.files) + + const missing = Array.from(baselineMap.keys()).filter((key) => !currentMap.has(key)) + const extra = Array.from(currentMap.keys()).filter((key) => !baselineMap.has(key)) + + const changed: string[] = [] + for (const [path, entry] of baselineMap.entries()) { + const currentEntry = currentMap.get(path) + if (!currentEntry) { + continue + } + if (entry.sha256 !== currentEntry.sha256 || entry.sizeBytes !== currentEntry.sizeBytes) { + changed.push(path) + } + } + + const errors: string[] = [] + const notices: string[] = [] + + if (missing.length > 0) { + errors.push(`Missing files (${missing.length}): ${missing.join(', ')}`) + } + if (extra.length > 0) { + errors.push(`Extra files (${extra.length}): ${extra.join(', ')}`) + } + + for (const path of changed) { + const baselineEntry = baselineMap.get(path) + const currentEntry = currentMap.get(path) + if (!baselineEntry || !currentEntry) { + continue + } + const details = `${path} (${baselineEntry.sha256.slice(0, 12)}:${baselineEntry.sizeBytes} -> ${currentEntry.sha256.slice(0, 12)}:${currentEntry.sizeBytes})` + if (allow.has(path)) { + notices.push(details) + continue + } + errors.push(`Unexpected drift: ${details}`) + } + + if (notices.length > 0) { + console.log(`NOTICE: allowed drift in ${notices.length} file(s)`) + for (const item of notices) { + console.log(`- ${item}`) + if ( + item.startsWith('package.json') && + baselinePackageJson && + currentPackageJson && + allow.has('package.json') + ) { + printDiff(resolve(baselinePackageJson), resolve(currentPackageJson)) + } + } + } + + if (errors.length > 0) { + console.error(`ERROR: parity check failed with ${errors.length} issue(s)`) + for (const item of errors) { + console.error(`- ${item}`) + } + process.exit(1) + } + + console.log('OK: parity check passed') +} + +await main() From 102fd5c80096b0fe00fff0b75cd2d23384b5a8f4 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Sat, 17 Jan 2026 11:10:38 +0100 Subject: [PATCH 29/43] chore: apply yarn check formatting --- docs/fingerprint/transloadit-after.json | 5 +---- .../transloadit-baseline.package.json | 16 +++------------- packages/transloadit/package.json | 14 ++------------ scripts/pack-transloadit.ts | 10 +++------- 4 files changed, 9 insertions(+), 36 deletions(-) diff --git a/docs/fingerprint/transloadit-after.json b/docs/fingerprint/transloadit-after.json index 14fb42b5..cca93ce0 100644 --- a/docs/fingerprint/transloadit-after.json +++ b/docs/fingerprint/transloadit-after.json @@ -13,10 +13,7 @@ ".": "./dist/Transloadit.js", "./package.json": "./package.json" }, - "files": [ - "dist", - "src" - ] + "files": ["dist", "src"] }, "files": [ { diff --git a/docs/fingerprint/transloadit-baseline.package.json b/docs/fingerprint/transloadit-baseline.package.json index d441de58..2ac9446a 100644 --- a/docs/fingerprint/transloadit-baseline.package.json +++ b/docs/fingerprint/transloadit-baseline.package.json @@ -3,14 +3,7 @@ "version": "4.1.2", "description": "Node.js SDK for Transloadit", "type": "module", - "keywords": [ - "transloadit", - "encoding", - "transcoding", - "video", - "audio", - "mp3" - ], + "keywords": ["transloadit", "encoding", "transcoding", "video", "audio", "mp3"], "author": "Tim Koschuetzki ", "packageManager": "yarn@4.12.0", "engines": { @@ -79,9 +72,6 @@ ".": "./dist/Transloadit.js", "./package.json": "./package.json" }, - "files": [ - "dist", - "src" - ], + "files": ["dist", "src"], "bin": "./dist/cli.js" -} \ No newline at end of file +} diff --git a/packages/transloadit/package.json b/packages/transloadit/package.json index 94c80c27..904ec81e 100644 --- a/packages/transloadit/package.json +++ b/packages/transloadit/package.json @@ -3,14 +3,7 @@ "version": "4.1.2", "description": "Node.js SDK for Transloadit", "type": "module", - "keywords": [ - "transloadit", - "encoding", - "transcoding", - "video", - "audio", - "mp3" - ], + "keywords": ["transloadit", "encoding", "transcoding", "video", "audio", "mp3"], "author": "Tim Koschuetzki ", "packageManager": "yarn@4.12.0", "engines": { @@ -87,9 +80,6 @@ ".": "./dist/Transloadit.js", "./package.json": "./package.json" }, - "files": [ - "dist", - "src" - ], + "files": ["dist", "src"], "bin": "./dist/cli.js" } diff --git a/scripts/pack-transloadit.ts b/scripts/pack-transloadit.ts index ddc4a3a1..9b46cba9 100644 --- a/scripts/pack-transloadit.ts +++ b/scripts/pack-transloadit.ts @@ -11,13 +11,9 @@ const repoRoot = resolve(dirname(filePath), '..') const legacyPackage = resolve(repoRoot, 'packages/transloadit') const runPack = async () => { - await execFileAsync( - 'node', - [resolve(repoRoot, 'scripts/prepare-transloadit.ts')], - { - cwd: repoRoot, - }, - ) + await execFileAsync('node', [resolve(repoRoot, 'scripts/prepare-transloadit.ts')], { + cwd: repoRoot, + }) await execFileAsync('npm', ['pack', '--ignore-scripts'], { cwd: legacyPackage, From b760f8e6f931009f66e4adfc76773716cc509b9f Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Sat, 17 Jan 2026 11:18:24 +0100 Subject: [PATCH 30/43] fix: restore biome and sync before zod tests --- package.json | 1 + packages/zod/package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index b47ca987..68ea42c0 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "test": "yarn workspace @transloadit/node test" }, "devDependencies": { + "@biomejs/biome": "^2.3.11", "@changesets/cli": "^2.29.7" } } diff --git a/packages/zod/package.json b/packages/zod/package.json index 8785c0ff..db082c3d 100644 --- a/packages/zod/package.json +++ b/packages/zod/package.json @@ -32,7 +32,7 @@ "sync:v4": "node scripts/sync-v4.ts", "sync": "yarn sync:v3 && yarn sync:v4", "lint:ts": "yarn sync && tsc --build tsconfig.build.json", - "test:unit": "node test/scripts-config.test.ts && node test/patch-ai-chat-schema.test.ts && node test/exports-sync.test.ts && node test/runtime-parity.test.ts", + "test:unit": "yarn sync && node test/scripts-config.test.ts && node test/patch-ai-chat-schema.test.ts && node test/exports-sync.test.ts && node test/runtime-parity.test.ts", "build": "yarn sync && tsc --build tsconfig.build.json", "check": "yarn sync && tsc --build tsconfig.build.json && tsc --noEmit --project tsconfig.test.json && yarn test:unit" }, From eb644e103422f884f3a4a91035d8de81dd23d7ad Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Sat, 17 Jan 2026 11:22:30 +0100 Subject: [PATCH 31/43] chore: update yarn.lock --- yarn.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/yarn.lock b/yarn.lock index 93aa3d4a..26f5064c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6971,6 +6971,7 @@ __metadata: version: 0.0.0-use.local resolution: "transloadit-node-sdk@workspace:." dependencies: + "@biomejs/biome": "npm:^2.3.11" "@changesets/cli": "npm:^2.29.7" languageName: unknown linkType: soft From 83db9f99d9b9912810495e485458867bcbd164b1 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Sat, 17 Jan 2026 11:35:10 +0100 Subject: [PATCH 32/43] Fix Biome lint after yarn install --- biome.json | 4 ++++ packages/node/package.json | 14 ++++++++++++-- packages/transloadit/package.json | 14 ++++++++++++-- packages/types/package.json | 5 ++++- packages/zod/package.json | 5 ++++- 5 files changed, 36 insertions(+), 6 deletions(-) diff --git a/biome.json b/biome.json index 1d4ad946..04c6f1f3 100644 --- a/biome.json +++ b/biome.json @@ -96,6 +96,10 @@ "actions": { "source": { "organizeImports": "on" } } }, "overrides": [ + { + "includes": ["**/package.json"], + "formatter": { "expand": "always" } + }, { "includes": ["*.html"], "javascript": { "formatter": { "quoteStyle": "double" } } diff --git a/packages/node/package.json b/packages/node/package.json index 5877370f..d9dbb67b 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -3,7 +3,14 @@ "version": "4.1.2", "description": "Node.js SDK for Transloadit", "type": "module", - "keywords": ["transloadit", "encoding", "transcoding", "video", "audio", "mp3"], + "keywords": [ + "transloadit", + "encoding", + "transcoding", + "video", + "audio", + "mp3" + ], "author": "Tim Koschuetzki ", "packageManager": "yarn@4.12.0", "engines": { @@ -83,6 +90,9 @@ ".": "./dist/Transloadit.js", "./package.json": "./package.json" }, - "files": ["dist", "src"], + "files": [ + "dist", + "src" + ], "bin": "./dist/cli.js" } diff --git a/packages/transloadit/package.json b/packages/transloadit/package.json index 904ec81e..94c80c27 100644 --- a/packages/transloadit/package.json +++ b/packages/transloadit/package.json @@ -3,7 +3,14 @@ "version": "4.1.2", "description": "Node.js SDK for Transloadit", "type": "module", - "keywords": ["transloadit", "encoding", "transcoding", "video", "audio", "mp3"], + "keywords": [ + "transloadit", + "encoding", + "transcoding", + "video", + "audio", + "mp3" + ], "author": "Tim Koschuetzki ", "packageManager": "yarn@4.12.0", "engines": { @@ -80,6 +87,9 @@ ".": "./dist/Transloadit.js", "./package.json": "./package.json" }, - "files": ["dist", "src"], + "files": [ + "dist", + "src" + ], "bin": "./dist/cli.js" } diff --git a/packages/types/package.json b/packages/types/package.json index fd419a9c..4bf6af41 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -4,7 +4,10 @@ "description": "Transloadit type definitions", "type": "module", "license": "MIT", - "files": ["dist", "README.md"], + "files": [ + "dist", + "README.md" + ], "types": "./dist/index.d.ts", "exports": { ".": { diff --git a/packages/zod/package.json b/packages/zod/package.json index db082c3d..4e68bc93 100644 --- a/packages/zod/package.json +++ b/packages/zod/package.json @@ -4,7 +4,10 @@ "description": "Transloadit Zod schemas", "type": "module", "license": "MIT", - "files": ["dist", "README.md"], + "files": [ + "dist", + "README.md" + ], "exports": { ".": { "types": "./dist/v3/index.d.ts", From 94c008b94d115278d8520fd9830140fe67f58c04 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Sat, 17 Jan 2026 11:53:19 +0100 Subject: [PATCH 33/43] Consolidate toolchain devDependencies --- package.json | 9 ++- packages/node/package.json | 9 +-- packages/transloadit/package.json | 9 +-- packages/types/package.json | 1 - packages/zod/package.json | 3 - yarn.lock | 114 ++---------------------------- 6 files changed, 17 insertions(+), 128 deletions(-) diff --git a/package.json b/package.json index 68ea42c0..e8656ddc 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,13 @@ }, "devDependencies": { "@biomejs/biome": "^2.3.11", - "@changesets/cli": "^2.29.7" + "@changesets/cli": "^2.29.7", + "@types/node": "^24.10.3", + "@vitest/coverage-v8": "^3.2.4", + "knip": "^5.73.3", + "npm-run-all": "^4.1.5", + "tsx": "4.21.0", + "typescript": "5.9.3", + "vitest": "^3.2.4" } } diff --git a/packages/node/package.json b/packages/node/package.json index d9dbb67b..0b64ec8a 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -40,26 +40,19 @@ "tag": "experimental" }, "devDependencies": { - "@biomejs/biome": "^2.3.11", "@types/debug": "^4.1.12", "@types/minimist": "^1.2.5", "@types/node": "^24.10.3", "@types/recursive-readdir": "^2.2.4", "@types/temp": "^0.9.4", - "@vitest/coverage-v8": "^3.2.4", "badge-maker": "^5.0.2", "execa": "9.6.0", "image-size": "^2.0.2", - "knip": "^5.73.3", "minimatch": "^10.1.1", "nock": "^14.0.10", - "npm-run-all": "^4.1.5", "p-retry": "^7.0.0", "rimraf": "^6.1.2", - "temp": "^0.9.4", - "tsx": "4.21.0", - "typescript": "5.9.3", - "vitest": "^3.2.4" + "temp": "^0.9.4" }, "repository": { "type": "git", diff --git a/packages/transloadit/package.json b/packages/transloadit/package.json index 94c80c27..bfe53acc 100644 --- a/packages/transloadit/package.json +++ b/packages/transloadit/package.json @@ -37,26 +37,19 @@ "zod": "3.25.76" }, "devDependencies": { - "@biomejs/biome": "^2.2.4", "@types/debug": "^4.1.12", "@types/minimist": "^1.2.5", "@types/node": "^24.10.3", "@types/recursive-readdir": "^2.2.4", "@types/temp": "^0.9.4", - "@vitest/coverage-v8": "^3.2.4", "badge-maker": "^5.0.2", "execa": "9.6.0", "image-size": "^2.0.2", - "knip": "^5.73.3", "minimatch": "^10.1.1", "nock": "^14.0.10", - "npm-run-all": "^4.1.5", "p-retry": "^7.0.0", "rimraf": "^6.1.2", - "temp": "^0.9.4", - "tsx": "4.21.0", - "typescript": "5.9.3", - "vitest": "^3.2.4" + "temp": "^0.9.4" }, "repository": { "type": "git", diff --git a/packages/types/package.json b/packages/types/package.json index 4bf6af41..b90e8b03 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -34,7 +34,6 @@ "check": "yarn generate && yarn test:unit && tsc --build tsconfig.build.json" }, "devDependencies": { - "typescript": "5.9.3", "zod": "3.25.76" }, "publishConfig": { diff --git a/packages/zod/package.json b/packages/zod/package.json index 4e68bc93..462436e7 100644 --- a/packages/zod/package.json +++ b/packages/zod/package.json @@ -42,9 +42,6 @@ "dependencies": { "zod": "^4.0.0" }, - "devDependencies": { - "typescript": "5.9.3" - }, "publishConfig": { "tag": "experimental" } diff --git a/yarn.lock b/yarn.lock index 26f5064c..1244ef40 100644 --- a/yarn.lock +++ b/yarn.lock @@ -746,41 +746,6 @@ __metadata: languageName: node linkType: hard -"@biomejs/biome@npm:^2.2.4": - version: 2.2.4 - resolution: "@biomejs/biome@npm:2.2.4" - dependencies: - "@biomejs/cli-darwin-arm64": "npm:2.2.4" - "@biomejs/cli-darwin-x64": "npm:2.2.4" - "@biomejs/cli-linux-arm64": "npm:2.2.4" - "@biomejs/cli-linux-arm64-musl": "npm:2.2.4" - "@biomejs/cli-linux-x64": "npm:2.2.4" - "@biomejs/cli-linux-x64-musl": "npm:2.2.4" - "@biomejs/cli-win32-arm64": "npm:2.2.4" - "@biomejs/cli-win32-x64": "npm:2.2.4" - dependenciesMeta: - "@biomejs/cli-darwin-arm64": - optional: true - "@biomejs/cli-darwin-x64": - optional: true - "@biomejs/cli-linux-arm64": - optional: true - "@biomejs/cli-linux-arm64-musl": - optional: true - "@biomejs/cli-linux-x64": - optional: true - "@biomejs/cli-linux-x64-musl": - optional: true - "@biomejs/cli-win32-arm64": - optional: true - "@biomejs/cli-win32-x64": - optional: true - bin: - biome: bin/biome - checksum: 10c0/7229fcc743db48f3cfd7417fb3f33d1cd9e7dfe42a12ed3c1046cd3110718237bb71ea3fe5c8b0de9bd3df2b918d0be58027602aa3720b64904b63d9cedf53e3 - languageName: node - linkType: hard - "@biomejs/biome@npm:^2.3.11": version: 2.3.11 resolution: "@biomejs/biome@npm:2.3.11" @@ -816,13 +781,6 @@ __metadata: languageName: node linkType: hard -"@biomejs/cli-darwin-arm64@npm:2.2.4": - version: 2.2.4 - resolution: "@biomejs/cli-darwin-arm64@npm:2.2.4" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - "@biomejs/cli-darwin-arm64@npm:2.3.11": version: 2.3.11 resolution: "@biomejs/cli-darwin-arm64@npm:2.3.11" @@ -830,13 +788,6 @@ __metadata: languageName: node linkType: hard -"@biomejs/cli-darwin-x64@npm:2.2.4": - version: 2.2.4 - resolution: "@biomejs/cli-darwin-x64@npm:2.2.4" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - "@biomejs/cli-darwin-x64@npm:2.3.11": version: 2.3.11 resolution: "@biomejs/cli-darwin-x64@npm:2.3.11" @@ -844,13 +795,6 @@ __metadata: languageName: node linkType: hard -"@biomejs/cli-linux-arm64-musl@npm:2.2.4": - version: 2.2.4 - resolution: "@biomejs/cli-linux-arm64-musl@npm:2.2.4" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - "@biomejs/cli-linux-arm64-musl@npm:2.3.11": version: 2.3.11 resolution: "@biomejs/cli-linux-arm64-musl@npm:2.3.11" @@ -858,13 +802,6 @@ __metadata: languageName: node linkType: hard -"@biomejs/cli-linux-arm64@npm:2.2.4": - version: 2.2.4 - resolution: "@biomejs/cli-linux-arm64@npm:2.2.4" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - "@biomejs/cli-linux-arm64@npm:2.3.11": version: 2.3.11 resolution: "@biomejs/cli-linux-arm64@npm:2.3.11" @@ -872,13 +809,6 @@ __metadata: languageName: node linkType: hard -"@biomejs/cli-linux-x64-musl@npm:2.2.4": - version: 2.2.4 - resolution: "@biomejs/cli-linux-x64-musl@npm:2.2.4" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - "@biomejs/cli-linux-x64-musl@npm:2.3.11": version: 2.3.11 resolution: "@biomejs/cli-linux-x64-musl@npm:2.3.11" @@ -886,13 +816,6 @@ __metadata: languageName: node linkType: hard -"@biomejs/cli-linux-x64@npm:2.2.4": - version: 2.2.4 - resolution: "@biomejs/cli-linux-x64@npm:2.2.4" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - "@biomejs/cli-linux-x64@npm:2.3.11": version: 2.3.11 resolution: "@biomejs/cli-linux-x64@npm:2.3.11" @@ -900,13 +823,6 @@ __metadata: languageName: node linkType: hard -"@biomejs/cli-win32-arm64@npm:2.2.4": - version: 2.2.4 - resolution: "@biomejs/cli-win32-arm64@npm:2.2.4" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - "@biomejs/cli-win32-arm64@npm:2.3.11": version: 2.3.11 resolution: "@biomejs/cli-win32-arm64@npm:2.3.11" @@ -914,13 +830,6 @@ __metadata: languageName: node linkType: hard -"@biomejs/cli-win32-x64@npm:2.2.4": - version: 2.2.4 - resolution: "@biomejs/cli-win32-x64@npm:2.2.4" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@biomejs/cli-win32-x64@npm:2.3.11": version: 2.3.11 resolution: "@biomejs/cli-win32-x64@npm:2.3.11" @@ -2714,14 +2623,12 @@ __metadata: dependencies: "@aws-sdk/client-s3": "npm:^3.891.0" "@aws-sdk/s3-request-presigner": "npm:^3.891.0" - "@biomejs/biome": "npm:^2.3.11" "@transloadit/sev-logger": "npm:^0.0.15" "@types/debug": "npm:^4.1.12" "@types/minimist": "npm:^1.2.5" "@types/node": "npm:^24.10.3" "@types/recursive-readdir": "npm:^2.2.4" "@types/temp": "npm:^0.9.4" - "@vitest/coverage-v8": "npm:^3.2.4" badge-maker: "npm:^5.0.2" clipanion: "npm:^4.0.0-rc.4" debug: "npm:^4.4.3" @@ -2732,23 +2639,18 @@ __metadata: image-size: "npm:^2.0.2" into-stream: "npm:^9.0.0" is-stream: "npm:^4.0.1" - knip: "npm:^5.73.3" minimatch: "npm:^10.1.1" nock: "npm:^14.0.10" node-watch: "npm:^0.7.4" - npm-run-all: "npm:^4.1.5" p-map: "npm:^7.0.3" p-queue: "npm:^9.0.1" p-retry: "npm:^7.0.0" recursive-readdir: "npm:^2.2.3" rimraf: "npm:^6.1.2" temp: "npm:^0.9.4" - tsx: "npm:4.21.0" tus-js-client: "npm:^4.3.1" typanion: "npm:^3.14.0" type-fest: "npm:^4.41.0" - typescript: "npm:5.9.3" - vitest: "npm:^3.2.4" zod: "npm:3.25.76" bin: node: ./dist/cli.js @@ -2766,7 +2668,6 @@ __metadata: version: 0.0.0-use.local resolution: "@transloadit/types@workspace:packages/types" dependencies: - typescript: "npm:5.9.3" zod: "npm:3.25.76" languageName: unknown linkType: soft @@ -2775,7 +2676,6 @@ __metadata: version: 0.0.0-use.local resolution: "@transloadit/zod@workspace:packages/zod" dependencies: - typescript: "npm:5.9.3" zod: "npm:^4.0.0" languageName: unknown linkType: soft @@ -6973,6 +6873,13 @@ __metadata: dependencies: "@biomejs/biome": "npm:^2.3.11" "@changesets/cli": "npm:^2.29.7" + "@types/node": "npm:^24.10.3" + "@vitest/coverage-v8": "npm:^3.2.4" + knip: "npm:^5.73.3" + npm-run-all: "npm:^4.1.5" + tsx: "npm:4.21.0" + typescript: "npm:5.9.3" + vitest: "npm:^3.2.4" languageName: unknown linkType: soft @@ -6982,14 +6889,12 @@ __metadata: dependencies: "@aws-sdk/client-s3": "npm:^3.891.0" "@aws-sdk/s3-request-presigner": "npm:^3.891.0" - "@biomejs/biome": "npm:^2.2.4" "@transloadit/sev-logger": "npm:^0.0.15" "@types/debug": "npm:^4.1.12" "@types/minimist": "npm:^1.2.5" "@types/node": "npm:^24.10.3" "@types/recursive-readdir": "npm:^2.2.4" "@types/temp": "npm:^0.9.4" - "@vitest/coverage-v8": "npm:^3.2.4" badge-maker: "npm:^5.0.2" clipanion: "npm:^4.0.0-rc.4" debug: "npm:^4.4.3" @@ -7000,23 +6905,18 @@ __metadata: image-size: "npm:^2.0.2" into-stream: "npm:^9.0.0" is-stream: "npm:^4.0.1" - knip: "npm:^5.73.3" minimatch: "npm:^10.1.1" nock: "npm:^14.0.10" node-watch: "npm:^0.7.4" - npm-run-all: "npm:^4.1.5" p-map: "npm:^7.0.3" p-queue: "npm:^9.0.1" p-retry: "npm:^7.0.0" recursive-readdir: "npm:^2.2.3" rimraf: "npm:^6.1.2" temp: "npm:^0.9.4" - tsx: "npm:4.21.0" tus-js-client: "npm:^4.3.1" typanion: "npm:^3.14.0" type-fest: "npm:^4.41.0" - typescript: "npm:5.9.3" - vitest: "npm:^3.2.4" zod: "npm:3.25.76" bin: transloadit: ./dist/cli.js From e059ebd124c25555671dd23d613a90e43dcc3e47 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Sat, 17 Jan 2026 12:02:28 +0100 Subject: [PATCH 34/43] Run knip from yarn check --- .ai/AGENTS.md | 6 +++--- package.json | 2 +- packages/node/knip.ts | 8 +++++++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.ai/AGENTS.md b/.ai/AGENTS.md index 8702c618..8eac1091 100644 --- a/.ai/AGENTS.md +++ b/.ai/AGENTS.md @@ -4,9 +4,9 @@ This document serves as a quick reference for agentic coding assistants working ## Check your work -After making changes, always run `corepack yarn check`, this lints with typescript, formats code, -and runs quick unit tests. Remember that this formats code and may make changes, that you are to -commit if you were working on those files. +After making changes, always run `corepack yarn check`, which runs Knip (with fixes/removals), +lints TypeScript, formats code, and runs quick unit tests. Remember that Knip and formatting may +make changes that you must review and commit when you touched the affected files. ## When running in GitHub Actions diff --git a/package.json b/package.json index e8656ddc..1f5cfc14 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "packages/*" ], "scripts": { - "check": "yarn lint:ts && yarn fix:js && yarn test:unit", + "check": "yarn workspace @transloadit/node run knip --fix --allow-remove-files && yarn lint:ts && yarn fix:js && yarn test:unit", "lint:js": "biome check .", "lint:ts": "yarn workspace @transloadit/node lint:ts && yarn workspace @transloadit/types lint:ts && yarn workspace @transloadit/zod lint:ts", "lint": "yarn lint:js", diff --git a/packages/node/knip.ts b/packages/node/knip.ts index df913853..3c552b0d 100644 --- a/packages/node/knip.ts +++ b/packages/node/knip.ts @@ -25,9 +25,15 @@ const config: KnipConfig = { '@types/minimist', 'minimatch', 'tsx', - // Tooling invoked via package.json scripts; knip's binary detection is disabled. + // Tooling lives at the repo root in this monorepo; knip runs in this workspace. '@biomejs/biome', + '@vitest/coverage-v8', + 'knip', 'npm-run-all', + 'tsx', + 'typescript', + 'vitest', + 'vitest/config', ], ignoreExportsUsedInFile: { type: true, From 0d4dbcb753f8befa95c7d364013357485cabf852 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Sat, 17 Jan 2026 13:01:00 +0100 Subject: [PATCH 35/43] Run knip across workspaces --- knip.ts | 80 +++++++++++++++++++++++++++++++ package.json | 4 +- packages/node/knip.ts | 53 -------------------- packages/node/package.json | 3 +- packages/transloadit/package.json | 13 +---- packages/zod/package.json | 4 ++ yarn.lock | 12 ++--- 7 files changed, 92 insertions(+), 77 deletions(-) create mode 100644 knip.ts delete mode 100644 packages/node/knip.ts diff --git a/knip.ts b/knip.ts new file mode 100644 index 00000000..f2aa7dbd --- /dev/null +++ b/knip.ts @@ -0,0 +1,80 @@ +import type { KnipConfig } from 'knip' + +const sharedIgnore = ['dist/**', 'coverage/**', 'static-build/**', 'node_modules/**'] +const alphalibIgnore = 'src/alphalib/**' + +const config: KnipConfig = { + rules: { + // Binary resolution is unreliable with Yarn PnP; avoid false positives. + binaries: 'off', + exports: 'warn', + types: 'warn', + nsExports: 'warn', + nsTypes: 'warn', + duplicates: 'warn', + }, + ignoreWorkspaces: ['.'], + ignoreExportsUsedInFile: { + type: true, + interface: true, + }, + workspaces: { + 'packages/node': { + entry: ['src/Transloadit.ts', 'src/cli.ts', 'test/**/*.{ts,tsx,js,jsx}', 'vitest.config.ts'], + project: ['{src,test}/**/*.{ts,tsx,js,jsx}'], + ignore: [...sharedIgnore, alphalibIgnore], + ignoreDependencies: [ + // Used in src/alphalib/** which is excluded from knip + '@aws-sdk/client-s3', + '@aws-sdk/s3-request-presigner', + '@transloadit/sev-logger', + 'type-fest', + 'zod', + // Repo-specific ignores + '@types/minimist', + 'minimatch', + // Tooling lives at the repo root in this monorepo. + 'vitest', + 'vitest/config', + ], + }, + 'packages/transloadit': { + entry: [ + 'src/Transloadit.ts', + 'src/cli.ts', + 'src/cli/commands/**/*.ts', + 'src/cli/types.ts', + 'src/tus.ts', + ], + project: ['src/**/*.{ts,tsx,js,jsx}'], + ignore: [...sharedIgnore, alphalibIgnore], + ignoreDependencies: [ + // Used in src/alphalib/** which is excluded from knip + '@aws-sdk/client-s3', + '@aws-sdk/s3-request-presigner', + '@transloadit/sev-logger', + 'type-fest', + 'zod', + // Repo-specific ignores + '@types/minimist', + 'minimatch', + ], + }, + 'packages/types': { + entry: ['src/index.ts', 'scripts/emit-types.ts', 'scripts/emit-types.test.ts'], + project: ['{src,scripts}/**/*.ts'], + ignore: ['dist/**', 'node_modules/**'], + ignoreDependencies: [ + // Zod is required for type inspection but not imported directly. + 'zod', + ], + }, + 'packages/zod': { + entry: ['src/**/*.{ts,tsx,js,jsx}', 'scripts/**/*.ts', 'test/**/*.ts'], + project: ['{src,scripts,test}/**/*.ts'], + ignore: ['dist/**', 'node_modules/**'], + }, + }, +} + +export default config diff --git a/package.json b/package.json index 1f5cfc14..d7078c3c 100644 --- a/package.json +++ b/package.json @@ -7,14 +7,14 @@ "packages/*" ], "scripts": { - "check": "yarn workspace @transloadit/node run knip --fix --allow-remove-files && yarn lint:ts && yarn fix:js && yarn test:unit", + "check": "yarn run --binaries-only knip --fix --allow-remove-files --no-config-hints && yarn lint:ts && yarn fix:js && yarn test:unit", "lint:js": "biome check .", "lint:ts": "yarn workspace @transloadit/node lint:ts && yarn workspace @transloadit/types lint:ts && yarn workspace @transloadit/zod lint:ts", "lint": "yarn lint:js", "fix": "yarn fix:js", "fix:js": "biome check --write .", "fix:js:unsafe": "biome check --write . --unsafe", - "knip": "yarn workspace @transloadit/node knip", + "knip": "yarn run --binaries-only knip --no-config-hints --no-progress", "pack": "node scripts/pack-transloadit.ts", "parity:transloadit": "node scripts/fingerprint-pack.ts packages/transloadit --ignore-scripts --out /tmp/transloadit-after.json && node scripts/verify-fingerprint.ts --current /tmp/transloadit-after.json --baseline docs/fingerprint/transloadit-baseline.json --allow package.json --baseline-package-json docs/fingerprint/transloadit-baseline.package.json --current-package-json packages/transloadit/package.json", "test:unit": "yarn workspace @transloadit/node test:unit && yarn workspace @transloadit/types test:unit && yarn workspace @transloadit/zod test:unit", diff --git a/packages/node/knip.ts b/packages/node/knip.ts deleted file mode 100644 index 3c552b0d..00000000 --- a/packages/node/knip.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { KnipConfig } from 'knip' - -// Note: `yarn check` runs knip with --fix --allow-remove-files. This is safe because -// lint:ts and tests run immediately after - they'll fail if knip removes something needed. -const config: KnipConfig = { - entry: ['src/Transloadit.ts', 'src/cli.ts', 'test/**/*.{ts,tsx,js,jsx}', 'vitest.config.ts'], - project: ['{src,test}/**/*.{ts,tsx,js,jsx}'], - ignore: [ - 'dist/**', - 'coverage/**', - 'static-build/**', - 'node_modules/**', - // alphalib is a shared utility library. Exclude it so knip does not remove - // files that may only be used in other repos. - 'src/alphalib/**', - ], - ignoreDependencies: [ - // Used in src/alphalib/** which is excluded from knip - '@aws-sdk/client-s3', - '@aws-sdk/s3-request-presigner', - '@transloadit/sev-logger', - 'type-fest', - 'zod', - // Repo-specific ignores - '@types/minimist', - 'minimatch', - 'tsx', - // Tooling lives at the repo root in this monorepo; knip runs in this workspace. - '@biomejs/biome', - '@vitest/coverage-v8', - 'knip', - 'npm-run-all', - 'tsx', - 'typescript', - 'vitest', - 'vitest/config', - ], - ignoreExportsUsedInFile: { - type: true, - interface: true, - }, - rules: { - // Binary resolution is unreliable with Yarn PnP; avoid false positives. - binaries: 'off', - exports: 'warn', - types: 'warn', - nsExports: 'warn', - nsTypes: 'warn', - duplicates: 'warn', - }, -} - -export default config diff --git a/packages/node/package.json b/packages/node/package.json index 0b64ec8a..31664941 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -62,7 +62,7 @@ "src": "./src" }, "scripts": { - "check": "yarn exec knip --fix --allow-remove-files --no-config-hints && yarn lint:ts && yarn fix && yarn test:unit", + "check": "yarn lint:ts && yarn fix && yarn test:unit", "fix:js": "biome check --write .", "lint:ts": "tsc --build", "fix:js:unsafe": "biome check --write . --unsafe", @@ -71,7 +71,6 @@ "fix": "npm-run-all --serial 'fix:js'", "lint:deps": "knip --dependencies --no-progress", "fix:deps": "knip --dependencies --no-progress --fix", - "knip": "knip --no-config-hints --no-progress", "prepack": "rm -f tsconfig.tsbuildinfo tsconfig.build.tsbuildinfo && tsc --build tsconfig.build.json", "test:unit": "vitest run --coverage ./test/unit", "test:e2e": "vitest run ./test/e2e", diff --git a/packages/transloadit/package.json b/packages/transloadit/package.json index bfe53acc..2edfd7e3 100644 --- a/packages/transloadit/package.json +++ b/packages/transloadit/package.json @@ -41,15 +41,7 @@ "@types/minimist": "^1.2.5", "@types/node": "^24.10.3", "@types/recursive-readdir": "^2.2.4", - "@types/temp": "^0.9.4", - "badge-maker": "^5.0.2", - "execa": "9.6.0", - "image-size": "^2.0.2", - "minimatch": "^10.1.1", - "nock": "^14.0.10", - "p-retry": "^7.0.0", - "rimraf": "^6.1.2", - "temp": "^0.9.4" + "minimatch": "^10.1.1" }, "repository": { "type": "git", @@ -59,7 +51,7 @@ "src": "./src" }, "scripts": { - "check": "yarn exec knip --fix --allow-remove-files --no-config-hints && yarn lint:ts && yarn fix && yarn test:unit", + "check": "yarn lint:ts && yarn fix && yarn test:unit", "fix:js": "biome check --write .", "lint:ts": "tsc --build", "fix:js:unsafe": "biome check --write . --unsafe", @@ -68,7 +60,6 @@ "fix": "npm-run-all --serial 'fix:js'", "lint:deps": "knip --dependencies --no-progress", "fix:deps": "knip --dependencies --no-progress --fix", - "knip": "knip --no-config-hints --no-progress", "prepack": "node ../../scripts/prepare-transloadit.ts", "test:unit": "vitest run --coverage ./test/unit", "test:e2e": "vitest run ./test/e2e", diff --git a/packages/zod/package.json b/packages/zod/package.json index 462436e7..6846df09 100644 --- a/packages/zod/package.json +++ b/packages/zod/package.json @@ -40,8 +40,12 @@ "check": "yarn sync && tsc --build tsconfig.build.json && tsc --noEmit --project tsconfig.test.json && yarn test:unit" }, "dependencies": { + "type-fest": "^4.41.0", "zod": "^4.0.0" }, + "devDependencies": { + "@transloadit/types": "workspace:*" + }, "publishConfig": { "tag": "experimental" } diff --git a/yarn.lock b/yarn.lock index 1244ef40..118110ba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2664,7 +2664,7 @@ __metadata: languageName: node linkType: hard -"@transloadit/types@workspace:packages/types": +"@transloadit/types@workspace:*, @transloadit/types@workspace:packages/types": version: 0.0.0-use.local resolution: "@transloadit/types@workspace:packages/types" dependencies: @@ -2676,6 +2676,8 @@ __metadata: version: 0.0.0-use.local resolution: "@transloadit/zod@workspace:packages/zod" dependencies: + "@transloadit/types": "workspace:*" + type-fest: "npm:^4.41.0" zod: "npm:^4.0.0" languageName: unknown linkType: soft @@ -6894,26 +6896,18 @@ __metadata: "@types/minimist": "npm:^1.2.5" "@types/node": "npm:^24.10.3" "@types/recursive-readdir": "npm:^2.2.4" - "@types/temp": "npm:^0.9.4" - badge-maker: "npm:^5.0.2" clipanion: "npm:^4.0.0-rc.4" debug: "npm:^4.4.3" dotenv: "npm:^17.2.3" - execa: "npm:9.6.0" form-data: "npm:^4.0.4" got: "npm:14.4.9" - image-size: "npm:^2.0.2" into-stream: "npm:^9.0.0" is-stream: "npm:^4.0.1" minimatch: "npm:^10.1.1" - nock: "npm:^14.0.10" node-watch: "npm:^0.7.4" p-map: "npm:^7.0.3" p-queue: "npm:^9.0.1" - p-retry: "npm:^7.0.0" recursive-readdir: "npm:^2.2.3" - rimraf: "npm:^6.1.2" - temp: "npm:^0.9.4" tus-js-client: "npm:^4.3.1" typanion: "npm:^3.14.0" type-fest: "npm:^4.41.0" From bb0723515c0e0b840d52fdb4ca6af712e816e157 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Sat, 17 Jan 2026 13:16:01 +0100 Subject: [PATCH 36/43] Add root knip dep scripts --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index d7078c3c..b7f1703d 100644 --- a/package.json +++ b/package.json @@ -7,13 +7,15 @@ "packages/*" ], "scripts": { - "check": "yarn run --binaries-only knip --fix --allow-remove-files --no-config-hints && yarn lint:ts && yarn fix:js && yarn test:unit", + "check": "yarn run --binaries-only knip --fix --allow-remove-files --no-config-hints && yarn lint:ts && yarn fix:js && yarn lint:js && yarn test:unit", "lint:js": "biome check .", "lint:ts": "yarn workspace @transloadit/node lint:ts && yarn workspace @transloadit/types lint:ts && yarn workspace @transloadit/zod lint:ts", "lint": "yarn lint:js", "fix": "yarn fix:js", "fix:js": "biome check --write .", "fix:js:unsafe": "biome check --write . --unsafe", + "lint:deps": "yarn run --binaries-only knip --dependencies --no-progress --no-config-hints", + "fix:deps": "yarn run --binaries-only knip --dependencies --no-progress --fix --no-config-hints", "knip": "yarn run --binaries-only knip --no-config-hints --no-progress", "pack": "node scripts/pack-transloadit.ts", "parity:transloadit": "node scripts/fingerprint-pack.ts packages/transloadit --ignore-scripts --out /tmp/transloadit-after.json && node scripts/verify-fingerprint.ts --current /tmp/transloadit-after.json --baseline docs/fingerprint/transloadit-baseline.json --allow package.json --baseline-package-json docs/fingerprint/transloadit-baseline.package.json --current-package-json packages/transloadit/package.json", From b8feec609612c681a65e0474a9512394f665f302 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Sat, 17 Jan 2026 13:27:59 +0100 Subject: [PATCH 37/43] Run knip deps check in CI --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e0485e52..d2bce1f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: - run: corepack yarn lint:js knip: - name: Knip + name: Knip (deps) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -47,7 +47,7 @@ jobs: with: node-version: 22 - run: corepack yarn - - run: corepack yarn knip + - run: corepack yarn lint:deps typescript: name: Lint (TypeScript) From 839192e45c17c821bf38e37d1b95cb46d51136f8 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Sat, 17 Jan 2026 14:34:38 +0100 Subject: [PATCH 38/43] Add CI checks for parity and zod types --- .github/workflows/ci.yml | 37 +++++++++++++++++++++++++++++++ package.json | 7 ++++-- packages/node/package.json | 4 ++-- packages/transloadit/package.json | 2 +- packages/types/package.json | 6 ++--- packages/zod/package.json | 7 +++--- 6 files changed, 52 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d2bce1f0..d3392f29 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,6 +49,39 @@ jobs: - run: corepack yarn - run: corepack yarn lint:deps + knip-full: + name: Knip (full) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - run: corepack yarn + - run: corepack yarn knip + + parity: + name: Legacy parity + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - run: corepack yarn + - run: corepack yarn parity:transloadit + + zod-types: + name: Zod type checks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - run: corepack yarn + - run: corepack yarn workspace @transloadit/zod test:types + typescript: name: Lint (TypeScript) runs-on: ubuntu-latest @@ -186,8 +219,12 @@ jobs: needs: - pack - biome + - knip + - knip-full + - parity - typescript - unit + - zod-types if: startsWith(github.ref, 'refs/tags/') permissions: id-token: write diff --git a/package.json b/package.json index b7f1703d..891c7957 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "scripts": { "check": "yarn run --binaries-only knip --fix --allow-remove-files --no-config-hints && yarn lint:ts && yarn fix:js && yarn lint:js && yarn test:unit", "lint:js": "biome check .", - "lint:ts": "yarn workspace @transloadit/node lint:ts && yarn workspace @transloadit/types lint:ts && yarn workspace @transloadit/zod lint:ts", + "lint:ts": "yarn tsc:node && yarn tsc:types && yarn tsc:zod", "lint": "yarn lint:js", "fix": "yarn fix:js", "fix:js": "biome check --write .", @@ -21,7 +21,10 @@ "parity:transloadit": "node scripts/fingerprint-pack.ts packages/transloadit --ignore-scripts --out /tmp/transloadit-after.json && node scripts/verify-fingerprint.ts --current /tmp/transloadit-after.json --baseline docs/fingerprint/transloadit-baseline.json --allow package.json --baseline-package-json docs/fingerprint/transloadit-baseline.package.json --current-package-json packages/transloadit/package.json", "test:unit": "yarn workspace @transloadit/node test:unit && yarn workspace @transloadit/types test:unit && yarn workspace @transloadit/zod test:unit", "test:e2e": "yarn workspace @transloadit/node test:e2e", - "test": "yarn workspace @transloadit/node test" + "test": "yarn workspace @transloadit/node test", + "tsc:node": "node ./node_modules/typescript/bin/tsc -b packages/node/tsconfig.build.json", + "tsc:types": "node ./node_modules/typescript/bin/tsc -b packages/types/tsconfig.build.json", + "tsc:zod": "node ./node_modules/typescript/bin/tsc -b packages/zod/tsconfig.build.json" }, "devDependencies": { "@biomejs/biome": "^2.3.11", diff --git a/packages/node/package.json b/packages/node/package.json index 31664941..490bf5cd 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -64,14 +64,14 @@ "scripts": { "check": "yarn lint:ts && yarn fix && yarn test:unit", "fix:js": "biome check --write .", - "lint:ts": "tsc --build", + "lint:ts": "yarn --cwd ../.. tsc:node", "fix:js:unsafe": "biome check --write . --unsafe", "lint:js": "biome check .", "lint": "npm-run-all --parallel 'lint:js'", "fix": "npm-run-all --serial 'fix:js'", "lint:deps": "knip --dependencies --no-progress", "fix:deps": "knip --dependencies --no-progress --fix", - "prepack": "rm -f tsconfig.tsbuildinfo tsconfig.build.tsbuildinfo && tsc --build tsconfig.build.json", + "prepack": "rm -f tsconfig.tsbuildinfo tsconfig.build.tsbuildinfo && yarn --cwd ../.. tsc:node", "test:unit": "vitest run --coverage ./test/unit", "test:e2e": "vitest run ./test/e2e", "test": "vitest run --coverage" diff --git a/packages/transloadit/package.json b/packages/transloadit/package.json index 2edfd7e3..fc7ef32f 100644 --- a/packages/transloadit/package.json +++ b/packages/transloadit/package.json @@ -53,7 +53,7 @@ "scripts": { "check": "yarn lint:ts && yarn fix && yarn test:unit", "fix:js": "biome check --write .", - "lint:ts": "tsc --build", + "lint:ts": "yarn --cwd ../.. tsc:node", "fix:js:unsafe": "biome check --write . --unsafe", "lint:js": "biome check .", "lint": "npm-run-all --parallel 'lint:js'", diff --git a/packages/types/package.json b/packages/types/package.json index b90e8b03..e81cfe8d 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -28,10 +28,10 @@ }, "scripts": { "generate": "node scripts/emit-types.ts", - "lint:ts": "yarn generate && tsc --build tsconfig.build.json", + "lint:ts": "yarn generate && ../../node_modules/.bin/tsc --build tsconfig.build.json", "test:unit": "node scripts/emit-types.test.ts", - "build": "yarn generate && tsc --build tsconfig.build.json", - "check": "yarn generate && yarn test:unit && tsc --build tsconfig.build.json" + "build": "yarn generate && ../../node_modules/.bin/tsc --build tsconfig.build.json", + "check": "yarn generate && yarn test:unit && ../../node_modules/.bin/tsc --build tsconfig.build.json" }, "devDependencies": { "zod": "3.25.76" diff --git a/packages/zod/package.json b/packages/zod/package.json index 6846df09..ab9f21a7 100644 --- a/packages/zod/package.json +++ b/packages/zod/package.json @@ -34,10 +34,11 @@ "sync:v3": "node scripts/sync-v3.ts", "sync:v4": "node scripts/sync-v4.ts", "sync": "yarn sync:v3 && yarn sync:v4", - "lint:ts": "yarn sync && tsc --build tsconfig.build.json", + "lint:ts": "yarn sync && ../../node_modules/.bin/tsc --build tsconfig.build.json", + "test:types": "yarn sync && ../../node_modules/.bin/tsc --noEmit --project tsconfig.test.json", "test:unit": "yarn sync && node test/scripts-config.test.ts && node test/patch-ai-chat-schema.test.ts && node test/exports-sync.test.ts && node test/runtime-parity.test.ts", - "build": "yarn sync && tsc --build tsconfig.build.json", - "check": "yarn sync && tsc --build tsconfig.build.json && tsc --noEmit --project tsconfig.test.json && yarn test:unit" + "build": "yarn sync && ../../node_modules/.bin/tsc --build tsconfig.build.json", + "check": "yarn sync && ../../node_modules/.bin/tsc --build tsconfig.build.json && ../../node_modules/.bin/tsc --noEmit --project tsconfig.test.json && yarn test:unit" }, "dependencies": { "type-fest": "^4.41.0", From afd002af753d9d886072cf50869992210915018f Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Sat, 17 Jan 2026 15:10:27 +0100 Subject: [PATCH 39/43] Refine verify scripts and CI wiring --- .github/workflows/ci.yml | 68 ++++++---------------------------------- knip.ts | 20 ++++++++++++ package.json | 11 ++++--- 3 files changed, 37 insertions(+), 62 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d3392f29..33d51080 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,71 +27,27 @@ jobs: name: package path: '*.tgz' - biome: - name: Lint (Biome) + verify: + name: Verify (fast) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 22 - - run: corepack yarn - - run: corepack yarn lint:js - - knip: - name: Knip (deps) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 22 - - run: corepack yarn - - run: corepack yarn lint:deps - - knip-full: - name: Knip (full) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 22 - - run: corepack yarn - - run: corepack yarn knip - - parity: - name: Legacy parity - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 22 - - run: corepack yarn - - run: corepack yarn parity:transloadit - - zod-types: - name: Zod type checks - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 22 + node-version: 24 - run: corepack yarn - - run: corepack yarn workspace @transloadit/zod test:types + - run: corepack yarn verify - typescript: - name: Lint (TypeScript) + verify-full: + name: Verify (full) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 24 - run: corepack yarn - - run: corepack yarn lint:ts + - run: corepack yarn verify:full unit: name: Unit tests (Node ${{ matrix.node }}) @@ -218,13 +174,9 @@ jobs: runs-on: ubuntu-latest needs: - pack - - biome - - knip - - knip-full - - parity - - typescript + - verify + - verify-full - unit - - zod-types if: startsWith(github.ref, 'refs/tags/') permissions: id-token: write diff --git a/knip.ts b/knip.ts index f2aa7dbd..c51cbfd5 100644 --- a/knip.ts +++ b/knip.ts @@ -14,6 +14,7 @@ const config: KnipConfig = { duplicates: 'warn', }, ignoreWorkspaces: ['.'], + ignoreBinaries: ['biome', 'knip', 'npm-run-all', 'tsc', 'vitest'], ignoreExportsUsedInFile: { type: true, interface: true, @@ -53,10 +54,25 @@ const config: KnipConfig = { '@aws-sdk/client-s3', '@aws-sdk/s3-request-presigner', '@transloadit/sev-logger', + 'clipanion', + 'debug', + 'dotenv', + 'form-data', + 'got', + 'into-stream', + 'is-stream', + 'node-watch', + 'p-map', + 'p-queue', + 'recursive-readdir', + 'tus-js-client', + 'typanion', 'type-fest', 'zod', // Repo-specific ignores + '@types/debug', '@types/minimist', + '@types/recursive-readdir', 'minimatch', ], }, @@ -73,6 +89,10 @@ const config: KnipConfig = { entry: ['src/**/*.{ts,tsx,js,jsx}', 'scripts/**/*.ts', 'test/**/*.ts'], project: ['{src,scripts,test}/**/*.ts'], ignore: ['dist/**', 'node_modules/**'], + ignoreDependencies: [ + // Generated code uses this after sync, but sources don't import it directly. + 'type-fest', + ], }, }, } diff --git a/package.json b/package.json index 891c7957..db1f2408 100644 --- a/package.json +++ b/package.json @@ -7,19 +7,22 @@ "packages/*" ], "scripts": { - "check": "yarn run --binaries-only knip --fix --allow-remove-files --no-config-hints && yarn lint:ts && yarn fix:js && yarn lint:js && yarn test:unit", + "check": "yarn fix:deps && yarn fix:js && yarn lint:ts && yarn test:unit", + "verify": "yarn lint:deps && yarn lint:js && yarn lint:ts && yarn test:unit", + "verify:full": "yarn verify && yarn knip && yarn parity:transloadit && yarn test:types", "lint:js": "biome check .", "lint:ts": "yarn tsc:node && yarn tsc:types && yarn tsc:zod", "lint": "yarn lint:js", "fix": "yarn fix:js", "fix:js": "biome check --write .", "fix:js:unsafe": "biome check --write . --unsafe", - "lint:deps": "yarn run --binaries-only knip --dependencies --no-progress --no-config-hints", - "fix:deps": "yarn run --binaries-only knip --dependencies --no-progress --fix --no-config-hints", - "knip": "yarn run --binaries-only knip --no-config-hints --no-progress", + "lint:deps": "yarn run --binaries-only knip --include dependencies,unlisted,unresolved,catalog --no-progress --no-config-hints", + "fix:deps": "yarn run --binaries-only knip --include dependencies,unlisted,unresolved,catalog --no-progress --fix --no-config-hints", + "knip": "yarn run --binaries-only knip --exclude binaries --no-config-hints --no-progress", "pack": "node scripts/pack-transloadit.ts", "parity:transloadit": "node scripts/fingerprint-pack.ts packages/transloadit --ignore-scripts --out /tmp/transloadit-after.json && node scripts/verify-fingerprint.ts --current /tmp/transloadit-after.json --baseline docs/fingerprint/transloadit-baseline.json --allow package.json --baseline-package-json docs/fingerprint/transloadit-baseline.package.json --current-package-json packages/transloadit/package.json", "test:unit": "yarn workspace @transloadit/node test:unit && yarn workspace @transloadit/types test:unit && yarn workspace @transloadit/zod test:unit", + "test:types": "yarn workspace @transloadit/zod test:types", "test:e2e": "yarn workspace @transloadit/node test:e2e", "test": "yarn workspace @transloadit/node test", "tsc:node": "node ./node_modules/typescript/bin/tsc -b packages/node/tsconfig.build.json", From d1945b63297cf4ddce341c3678ec75f5e29529fe Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Sat, 17 Jan 2026 15:16:11 +0100 Subject: [PATCH 40/43] Fix CI types build and CLI bin naming --- package.json | 4 ++-- packages/node/package.json | 10 ++++++---- packages/transloadit/package.json | 6 +++--- yarn.lock | 2 +- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index db1f2408..f8215a3d 100644 --- a/package.json +++ b/package.json @@ -20,13 +20,13 @@ "fix:deps": "yarn run --binaries-only knip --include dependencies,unlisted,unresolved,catalog --no-progress --fix --no-config-hints", "knip": "yarn run --binaries-only knip --exclude binaries --no-config-hints --no-progress", "pack": "node scripts/pack-transloadit.ts", - "parity:transloadit": "node scripts/fingerprint-pack.ts packages/transloadit --ignore-scripts --out /tmp/transloadit-after.json && node scripts/verify-fingerprint.ts --current /tmp/transloadit-after.json --baseline docs/fingerprint/transloadit-baseline.json --allow package.json --baseline-package-json docs/fingerprint/transloadit-baseline.package.json --current-package-json packages/transloadit/package.json", + "parity:transloadit": "node scripts/prepare-transloadit.ts && node scripts/fingerprint-pack.ts packages/transloadit --ignore-scripts --out /tmp/transloadit-after.json && node scripts/verify-fingerprint.ts --current /tmp/transloadit-after.json --baseline docs/fingerprint/transloadit-baseline.json --allow package.json --baseline-package-json docs/fingerprint/transloadit-baseline.package.json --current-package-json packages/transloadit/package.json", "test:unit": "yarn workspace @transloadit/node test:unit && yarn workspace @transloadit/types test:unit && yarn workspace @transloadit/zod test:unit", "test:types": "yarn workspace @transloadit/zod test:types", "test:e2e": "yarn workspace @transloadit/node test:e2e", "test": "yarn workspace @transloadit/node test", "tsc:node": "node ./node_modules/typescript/bin/tsc -b packages/node/tsconfig.build.json", - "tsc:types": "node ./node_modules/typescript/bin/tsc -b packages/types/tsconfig.build.json", + "tsc:types": "yarn workspace @transloadit/types generate && node ./node_modules/typescript/bin/tsc -b packages/types/tsconfig.build.json", "tsc:zod": "node ./node_modules/typescript/bin/tsc -b packages/zod/tsconfig.build.json" }, "devDependencies": { diff --git a/packages/node/package.json b/packages/node/package.json index 490bf5cd..a78ecfbd 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -72,9 +72,9 @@ "lint:deps": "knip --dependencies --no-progress", "fix:deps": "knip --dependencies --no-progress --fix", "prepack": "rm -f tsconfig.tsbuildinfo tsconfig.build.tsbuildinfo && yarn --cwd ../.. tsc:node", - "test:unit": "vitest run --coverage ./test/unit", - "test:e2e": "vitest run ./test/e2e", - "test": "vitest run --coverage" + "test:unit": "../../node_modules/.bin/vitest run --coverage ./test/unit", + "test:e2e": "../../node_modules/.bin/vitest run ./test/e2e", + "test": "../../node_modules/.bin/vitest run --coverage" }, "license": "MIT", "main": "./dist/Transloadit.js", @@ -86,5 +86,7 @@ "dist", "src" ], - "bin": "./dist/cli.js" + "bin": { + "transloadit": "./dist/cli.js" + } } diff --git a/packages/transloadit/package.json b/packages/transloadit/package.json index fc7ef32f..f0ccf2f6 100644 --- a/packages/transloadit/package.json +++ b/packages/transloadit/package.json @@ -61,9 +61,9 @@ "lint:deps": "knip --dependencies --no-progress", "fix:deps": "knip --dependencies --no-progress --fix", "prepack": "node ../../scripts/prepare-transloadit.ts", - "test:unit": "vitest run --coverage ./test/unit", - "test:e2e": "vitest run ./test/e2e", - "test": "vitest run --coverage" + "test:unit": "../../node_modules/.bin/vitest run --coverage ./test/unit", + "test:e2e": "../../node_modules/.bin/vitest run ./test/e2e", + "test": "../../node_modules/.bin/vitest run --coverage" }, "license": "MIT", "main": "./dist/Transloadit.js", diff --git a/yarn.lock b/yarn.lock index 118110ba..e3edcb0d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2653,7 +2653,7 @@ __metadata: type-fest: "npm:^4.41.0" zod: "npm:3.25.76" bin: - node: ./dist/cli.js + transloadit: ./dist/cli.js languageName: unknown linkType: soft From d05e81e142963df9f5f73e27e7d9b1fb39ee85a8 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Sat, 17 Jan 2026 15:18:39 +0100 Subject: [PATCH 41/43] Sync zod sources before typecheck --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f8215a3d..43f244a8 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "test": "yarn workspace @transloadit/node test", "tsc:node": "node ./node_modules/typescript/bin/tsc -b packages/node/tsconfig.build.json", "tsc:types": "yarn workspace @transloadit/types generate && node ./node_modules/typescript/bin/tsc -b packages/types/tsconfig.build.json", - "tsc:zod": "node ./node_modules/typescript/bin/tsc -b packages/zod/tsconfig.build.json" + "tsc:zod": "yarn workspace @transloadit/zod sync && node ./node_modules/typescript/bin/tsc -b packages/zod/tsconfig.build.json" }, "devDependencies": { "@biomejs/biome": "^2.3.11", From 968528bf79a83f2065c118dc2a3cf0bf643b88e4 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Sat, 17 Jan 2026 15:51:40 +0100 Subject: [PATCH 42/43] Release patch versions --- packages/node/CHANGELOG.md | 6 ++++++ packages/node/package.json | 2 +- packages/transloadit/package.json | 2 +- packages/types/CHANGELOG.md | 6 ++++++ packages/types/package.json | 2 +- packages/zod/CHANGELOG.md | 6 ++++++ packages/zod/package.json | 2 +- 7 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/node/CHANGELOG.md b/packages/node/CHANGELOG.md index 39cd42df..1f1a1eb2 100644 --- a/packages/node/CHANGELOG.md +++ b/packages/node/CHANGELOG.md @@ -4,4 +4,10 @@ ### Patch Changes +- Publish initial patch releases for the split packages and legacy wrapper. + +## 4.1.3 + +### Patch Changes + - f989dc1: chore: align workspace packages for upcoming monorepo releases diff --git a/packages/node/package.json b/packages/node/package.json index a78ecfbd..aa82979a 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -1,6 +1,6 @@ { "name": "@transloadit/node", - "version": "4.1.2", + "version": "4.1.3", "description": "Node.js SDK for Transloadit", "type": "module", "keywords": [ diff --git a/packages/transloadit/package.json b/packages/transloadit/package.json index f0ccf2f6..c6b8c68c 100644 --- a/packages/transloadit/package.json +++ b/packages/transloadit/package.json @@ -1,6 +1,6 @@ { "name": "transloadit", - "version": "4.1.2", + "version": "4.1.3", "description": "Node.js SDK for Transloadit", "type": "module", "keywords": [ diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index b4308fde..81eb724b 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -4,4 +4,10 @@ ### Patch Changes +- Publish initial patch releases for the split packages and legacy wrapper. + +## 4.1.3 + +### Patch Changes + - f989dc1: chore: align workspace packages for upcoming monorepo releases diff --git a/packages/types/package.json b/packages/types/package.json index e81cfe8d..406b0d47 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@transloadit/types", - "version": "4.1.2", + "version": "4.1.3", "description": "Transloadit type definitions", "type": "module", "license": "MIT", diff --git a/packages/zod/CHANGELOG.md b/packages/zod/CHANGELOG.md index bfcdfe2a..2c6f53a9 100644 --- a/packages/zod/CHANGELOG.md +++ b/packages/zod/CHANGELOG.md @@ -4,4 +4,10 @@ ### Patch Changes +- Publish initial patch releases for the split packages and legacy wrapper. + +## 4.1.3 + +### Patch Changes + - f989dc1: chore: align workspace packages for upcoming monorepo releases diff --git a/packages/zod/package.json b/packages/zod/package.json index ab9f21a7..555e3321 100644 --- a/packages/zod/package.json +++ b/packages/zod/package.json @@ -1,6 +1,6 @@ { "name": "@transloadit/zod", - "version": "4.1.2", + "version": "4.1.3", "description": "Transloadit Zod schemas", "type": "module", "license": "MIT", From 98e88a1e538ffe409b5a47c62c4d96cf5e70cfe6 Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Sat, 17 Jan 2026 17:26:36 +0100 Subject: [PATCH 43/43] Align transloadit bin entry --- packages/transloadit/package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/transloadit/package.json b/packages/transloadit/package.json index c6b8c68c..0dfb43d7 100644 --- a/packages/transloadit/package.json +++ b/packages/transloadit/package.json @@ -75,5 +75,7 @@ "dist", "src" ], - "bin": "./dist/cli.js" + "bin": { + "transloadit": "./dist/cli.js" + } }