diff --git a/docs/04_upgrading/upgrading_to_v3.mdx b/docs/04_upgrading/upgrading_to_v3.mdx index dcaaa0d72..ea1398070 100644 --- a/docs/04_upgrading/upgrading_to_v3.mdx +++ b/docs/04_upgrading/upgrading_to_v3.mdx @@ -391,13 +391,23 @@ Two options that carried a `@deprecated` marker throughout v2 have been removed. `ActorClient.start()`, `call()`, `validateInput()` and `RunClient.metamorph()` took their `input` as `unknown`, so any value compiled, including ones the client cannot send. -The input is now typed `ActorInput`, an alias for `object`, so it's an object or an array that the client serializes into the request body. Any other value stops compiling, including a value typed `unknown`, which has to be narrowed or cast first. To run an Actor without input, omit the argument or pass `undefined`. +The input is now typed `ActorInput`, which accepts a plain object, an array of plain objects, or raw bytes as a `Uint8Array` (such as a `Buffer`) or an `ArrayBuffer`. The client serializes objects and arrays into the request body and sends bytes as they are. Any other value stops compiling, including a `Date`, `Map`, `Set` or function, and a value typed `unknown`, which has to be narrowed or cast first. To run an Actor without input, omit the argument or pass `undefined`. ```diff - await client.actor('my-actor').call(null, { memory: 1024 }); // v2 + await client.actor('my-actor').call(undefined, { memory: 1024 }); // v3 ``` +An input declared as an `interface` or a class stops compiling as well, since `ActorInput` needs an index signature and neither gets one implicitly. Declare the input type as a `type` alias, or spread the value into a new object: + +```diff +- interface MyInput { url: string } // v2 ++ type MyInput = { url: string }; // v3 + +- await client.actor('my-actor').call(input); // v2, with an interface-typed `input` ++ await client.actor('my-actor').call({ ...input }); // v3 +``` + A raw `string` body stops compiling too, with or without a `contentType`. Pass the input as an object and let the client serialize it: ```diff diff --git a/docs/public-api/apify-client.api.md b/docs/public-api/apify-client.api.md index bf4c82a5f..32ffbaf7b 100644 --- a/docs/public-api/apify-client.api.md +++ b/docs/public-api/apify-client.api.md @@ -201,7 +201,7 @@ export interface ActorExampleRunInput extends GeneratedExampleRunInput { } // @public -export type ActorInput = object; +export type ActorInput = Dictionary | readonly Dictionary[] | Uint8Array | ArrayBuffer; // @public export interface ActorLastRunOptions { diff --git a/src/resource_clients/actor.ts b/src/resource_clients/actor.ts index e67f7877b..f35129047 100644 --- a/src/resource_clients/actor.ts +++ b/src/resource_clients/actor.ts @@ -8,6 +8,7 @@ import { ResourceClient } from '../base/resource_client.js'; import type { ApifyRequestConfig } from '../http_clients/index.js'; import type { Actor, ActorRun } from '../models.js'; import type { TimeoutOptions } from '../timeouts.js'; +import type { Dictionary } from '../utils.js'; import * as schemas from '../schemas.js'; import { timeoutOptionsSchema, timeoutOptionsShape } from '../timeouts.js'; import { anyObjectSchema, parseArgument, parseResponse, stringifyWebhooksToBase64 } from '../utils.js'; @@ -179,7 +180,8 @@ export class ActorClient extends ResourceClient { * asynchronously and this method returns immediately without waiting for completion. * Use the {@link call} method if you want to wait for the Actor to finish. * - * @param input - Input for the Actor, serialized to JSON. Omit it to run the Actor without input. + * @param input - Input for the Actor: an object or array serialized to JSON, or raw bytes sent as they are. + * Omit it to run the Actor without input. * @param options - Run configuration options * @param options.build - Tag or number of the build to run (e.g., `'beta'` or `'1.2.345'`). If not provided, uses the default build. * @param options.memory - Memory in megabytes allocated for the run. If not provided, uses the Actor's default memory setting. @@ -262,7 +264,8 @@ export class ActorClient extends ResourceClient { * by polling the run status. It optionally streams logs to the console or a custom Log instance. * By default, it waits indefinitely unless the `waitSecs` option is provided. * - * @param input - Input for the Actor, serialized to JSON. Omit it to run the Actor without input. + * @param input - Input for the Actor: an object or array serialized to JSON, or raw bytes sent as they are. + * Omit it to run the Actor without input. * @param options - Run configuration options (extends all options from {@link start}) * @param options.waitSecs - Maximum time to wait for the run to finish, in seconds. If omitted, waits indefinitely. * @param options.log - Log instance for streaming run logs. Use `'default'` for console output, `null` to disable logging, or provide a custom Log instance. @@ -321,7 +324,8 @@ export class ActorClient extends ResourceClient { * invalid, the API responds with an error that is thrown as an `ApifyApiError` describing the * validation problem. * - * @param input - Input to validate against the Actor's input schema, serialized to JSON. + * @param input - Input to validate against the Actor's input schema: an object or array serialized to JSON, or + * raw bytes sent as they are. * @param options - Validation options * @param options.build - Tag or number of the build whose input schema the input is validated against * (e.g., `'latest'` or `'1.2.345'`). If not provided, uses the default build. @@ -588,10 +592,12 @@ export type ActorUpdateOptions = Partial< * Input for an Actor run, as taken by {@link ActorClient.start}, {@link ActorClient.call}, * {@link ActorClient.validateInput} and {@link RunClient.metamorph}. * - * An object or an array. Declared as `object` rather than an index-signature type such as - * `Dictionary`, which would reject a caller's own `interface`. + * A plain object or an array of plain objects, serialized into the request body, or raw bytes + * (a `Uint8Array` such as a `Buffer`, or an `ArrayBuffer`) sent as they are, typically with a + * `contentType`. An input typed as an `interface` or a class needs a spread (`{ ...input }`), or for + * an interface a `type` alias, to compile. */ -export type ActorInput = object; +export type ActorInput = Dictionary | readonly Dictionary[] | Uint8Array | ArrayBuffer; export interface ActorStartOptions extends TimeoutOptions { /** diff --git a/src/resource_clients/run.ts b/src/resource_clients/run.ts index 349d70374..2413eb1d3 100644 --- a/src/resource_clients/run.ts +++ b/src/resource_clients/run.ts @@ -167,7 +167,8 @@ export class RunClient extends ResourceClient { * This is useful for chaining Actor executions or implementing complex workflows. * * @param targetActorId - ID or username/name of the target Actor - * @param input - Input for the target Actor, serialized to JSON. Omit it to metamorph without input. + * @param input - Input for the target Actor: an object or array serialized to JSON, or raw bytes sent as they + * are. Omit it to metamorph without input. * @param options - Metamorph options * @param options.build - Tag or number of the target Actor's build to run. Default is the target Actor's default build. * @param options.timeoutSecs - Timeout for the API request. Default is `'medium'`. diff --git a/test/actors.test.ts b/test/actors.test.ts index 7b251808a..107603362 100644 --- a/test/actors.test.ts +++ b/test/actors.test.ts @@ -3,7 +3,7 @@ import type { AddressInfo } from 'node:net'; import { setTimeout } from 'node:timers/promises'; import c from 'ansi-colors'; -import type { ActorCollectionCreateOptions, ActorCollectionListOptions, ActorVersion } from 'apify-client'; +import type { ActorCollectionCreateOptions, ActorCollectionListOptions, ActorInput, ActorVersion } from 'apify-client'; import { ActorListSortBy, ActorSourceType, @@ -14,7 +14,7 @@ import { } from 'apify-client'; import express from 'express'; import type { Page } from 'puppeteer'; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, expectTypeOf, test, vi } from 'vitest'; import { META_ORIGINS, WEBHOOK_EVENT_TYPES } from '@apify/consts'; import { LEVELS, Log } from '@apify/log'; @@ -239,6 +239,29 @@ describe('Actor methods', () => { }); }); + test('ActorInput accepts plain objects, arrays of them and raw bytes, but no other objects', () => { + interface InterfaceInput { + url: string; + } + type AliasInput = { url: string }; + + expectTypeOf<{ url: string }>().toExtend(); + expectTypeOf().toExtend(); + expectTypeOf().toExtend(); + expectTypeOf().toExtend(); + expectTypeOf().toExtend(); + expectTypeOf().toExtend(); + expectTypeOf().toExtend(); + + expectTypeOf().not.toExtend(); + expectTypeOf().not.toExtend(); + expectTypeOf>().not.toExtend(); + expectTypeOf>().not.toExtend(); + expectTypeOf<() => void>().not.toExtend(); + expectTypeOf().not.toExtend(); + expectTypeOf().not.toExtend(); + }); + test('start() works with functions in input', async () => { const actorId = 'some-id'; const input = {