# @hyperscale0/sdk

TypeScript client for running a Hyperscale Product: customers, payments, schedules and the sandbox clock.

```sh
npm i @hyperscale0/sdk
```

The SDK runs a Product that already exists. Create the Product with `hyperscale product create` or in the Build portal, and copy its `prd_…` id. `hyperscale key create --product <id>` shows the key's secret once. Export them as `HYPERSCALE_PRODUCT_ID` and `HYPERSCALE_API_KEY`.

## Quickstart

This runs as written in sandbox against the gym program from the [HSX guide](https://hyperscale0.ai/docs/hsx.md#schedules): a `membership` object with `start_membership`, `accept_membership` and `end_membership`.

```ts
import { createClient, HyperscaleError } from "@hyperscale0/sdk";

const hs = createClient({
  apiKey: process.env.HYPERSCALE_API_KEY,
  productId: process.env.HYPERSCALE_PRODUCT_ID,
});

// A test customer with SAR 300 in a sandbox balance. Amounts are halalas.
const { customerId } = await hs.call("customer.create", {
  entity: { kind: "person", displayName: "Noura Alharbi", country: "SA" },
  test: true,
});
const { accountId } = await hs.call("account.create", {
  owner: { type: "customer", id: customerId },
  role: "customer_balance",
  currency: "SAR",
});
await hs.call("sandbox.account.fund", {
  destinationAccountId: accountId,
  amount: "30000",
  currency: "SAR",
});

// A membership Noura owns. The gym starts it tomorrow and she accepts it.
const { effectiveAt } = await hs.call("sandbox.clock.retrieve", {});
const day = (n) =>
  new Date(Date.parse(effectiveAt) + n * 86_400_000).toISOString();
const { objectId } = await hs.call("product.objects.create", {
  kind: "membership",
  fields: { plan: "Monthly" },
  onBehalfOf: customerId,
});
await hs.objects.run("membership", objectId, "start_membership", {
  inputs: { startsAt: day(1) },
});
await hs.objects.run("membership", objectId, "accept_membership", {
  onBehalfOf: customerId,
});

// Two days on, the first SAR 250 of dues runs.
const { ran } = await hs.call("sandbox.clock.advance", { at: day(2) });
console.log(
  ran.map((piece) => `${piece.action} ${piece.amount?.value} ${piece.status}`),
);

// A refusal carries the server's own words.
try {
  await hs.objects.run("membership", objectId, "end_membership", {
    onBehalfOf: customerId,
  });
} catch (error) {
  if (!(error instanceof HyperscaleError)) throw error;
  console.log(error.code, error.message);
}
```

`objects.run` reads the action first. If it can't run, it throws `HyperscaleError` with the reason. Otherwise it sends the revision, Build and target it read.

## Types

Until you generate your Product's types, `kind`, `action`, `fields` and `input` take any string and any record. The generator reads `product.objects.discover` with `HYPERSCALE_API_KEY`, plus `HYPERSCALE_BASE_URL` and `HYPERSCALE_ENVIRONMENT` when set, and prints one declaration:

```sh
npx @hyperscale0/sdk types --product $HYPERSCALE_PRODUCT_ID --name DeskAndKey > desk-and-key.d.ts
```

```ts
import { createClient } from "@hyperscale0/sdk";
import type { DeskAndKey } from "./desk-and-key";

const hs = createClient<DeskAndKey>({
  apiKey: process.env.HYPERSCALE_API_KEY,
  productId: process.env.HYPERSCALE_PRODUCT_ID,
});
```

With the type argument, a kind such as `"day-pass"` when the Product has `day_pass`, or an action such as `"pay_daypass"`, fails to compile instead of failing at runtime. `objects.run`, `product.objects.create`, `product.objects.execute`, `product.objects.preview` and every call that takes a `kind` check against it, and money fields are minor-unit strings. Regenerate the file after each publish. Without a type argument, `createClient()` takes any kind and action.

## Errors and retries

Every refusal is a `HyperscaleError` with the server's `message`, `code`, `status`, `details` and `requestId`, plus `nextStep` and `shortfall` when the server sends them. When no readable answer arrives (a timeout, a dropped connection, an HTML error page), the client throws `HyperscaleConnectionError`: the call may have run, so retry it with the same `idempotencyKey`.

The client makes an idempotency key for every write that needs one and keeps it across its own retries. Pass `{ idempotencyKey }` as the third argument to choose it. Reads retry on 408, 429 and 5xx, and writes retry only under a key. Set `timeout` (default 30000 ms) and `maxRetries` (default 2) in `createClient`.

## Operations

`client.call(name, input)` runs any operation, and names autocomplete. `productId` comes from the client. `client.paginate(name, input)` iterates every record of a list.

| Job          | Operations                                                                                                                                                                                      |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Objects      | `product.objects.discover`, `product.objects.create`, `product.objects.list`, `product.objects.retrieve`, `product.objects.actions`, `product.objects.execute`, `product.objects.timeline.list` |
| Customers    | `customer.create`, `customer.list`, `customer.retrieve`                                                                                                                                         |
| Money        | `account.create`, `account.balance.retrieve`, `account.statement.retrieve`, `internal_transfer.list`, `receipt.list`                                                                            |
| Sandbox      | `sandbox.account.fund`, `sandbox.clock.retrieve`, `sandbox.clock.advance`                                                                                                                       |
| The business | `activity.list`, `product.schedule.list`, `product.work.list`, `product.treasury.retrieve`, `product.books.retrieve`                                                                            |
| Webhooks     | `webhook.endpoint.create`, `webhook.endpoint.list`, `webhook.delivery.list`, `event.list`                                                                                                       |

Your Product's own operations from `key create`, such as `membership_dues_instances_list`, go through `call` too. [openapi.json](https://hyperscale0.ai/openapi.json) lists every platform operation with its schema, and [Product actions](https://hyperscale0.ai/docs/runtime.md) explains how they fit together.

Set `environment: "live"` for live money; the default is sandbox.

## Versions

The SDK ships as `1.0.N` with Hyperscale release rN. `CHANGELOG.md`, in the package, lists what each version added, changed and removed. The `3.x` and `4.x` versions on npm are deprecated and older than every `1.0.N`, so pin `^1.0.0`.

Security: https://hyperscale0.ai/security
