Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion docs/04_upgrading/upgrading_to_v3.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -391,13 +391,23 @@ Two options that carried a `@deprecated` marker throughout v2 have been removed.

<ApiLink to="class/ActorClient#start">`ActorClient.start()`</ApiLink>, <ApiLink to="class/ActorClient#call">`call()`</ApiLink>, <ApiLink to="class/ActorClient#validateInput">`validateInput()`</ApiLink> and <ApiLink to="class/RunClient#metamorph">`RunClient.metamorph()`</ApiLink> 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
Expand Down
2 changes: 1 addition & 1 deletion docs/public-api/apify-client.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
18 changes: 12 additions & 6 deletions src/resource_clients/actor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
/**
Expand Down
3 changes: 2 additions & 1 deletion src/resource_clients/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'`.
Expand Down
27 changes: 25 additions & 2 deletions test/actors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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';
Expand Down Expand Up @@ -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<ActorInput>();
expectTypeOf<AliasInput>().toExtend<ActorInput>();
expectTypeOf<AliasInput[]>().toExtend<ActorInput>();
expectTypeOf<readonly AliasInput[]>().toExtend<ActorInput>();
expectTypeOf<Buffer>().toExtend<ActorInput>();
expectTypeOf<Uint8Array>().toExtend<ActorInput>();
expectTypeOf<ArrayBuffer>().toExtend<ActorInput>();

expectTypeOf<InterfaceInput>().not.toExtend<ActorInput>();
expectTypeOf<Date>().not.toExtend<ActorInput>();
expectTypeOf<Map<string, unknown>>().not.toExtend<ActorInput>();
expectTypeOf<Set<unknown>>().not.toExtend<ActorInput>();
expectTypeOf<() => void>().not.toExtend<ActorInput>();
expectTypeOf<string>().not.toExtend<ActorInput>();
expectTypeOf<string[]>().not.toExtend<ActorInput>();
});

test('start() works with functions in input', async () => {
const actorId = 'some-id';
const input = {
Expand Down
Loading