createSpace() and space

The client, every option, the environment variables it reads, and how every call pages, retries, times out, and is cancelled.

import { createSpace, space } from "digitospace"

space is a ready-made client that reads its settings from the environment. createSpace() makes a client with settings of your own. Both have the same parts:

PartProductReference
fsStoragewrite() and the other space.fs functions
aiAIchat() and the other space.ai functions
scheduleSchedulerevery() and the other space.schedule functions
hooksWebhookssources and the other space.hooks parts
projectThe project ID the client works in, or undefined when none is set

createSpace()

createSpace(options?: SpaceOptions): Space

Makes a client. Every option is optional.

apiKeystring

The API key for every product, starting with dsk_. Defaults to DIGITOSPACE_API_KEY.

apiKeys{ fs?, ai?, schedule?, hooks? }

A key for each product, for when you keep products apart with separate keys. A product without a key here uses apiKey. One key can also cover every product; see API keys.

projectstring

The project every call works in: its 26-character ID from Settings in the console. Defaults to DIGITOSPACE_PROJECT.

timeoutMsnumber

How long one try of a call may take, in milliseconds, from 1,000 to 600,000. Defaults to 30,000. A chat call uses 600,000 unless you pass timeoutMs to the call. See Timeouts.

retriesnumber

How many more times to try a call that failed and is safe to repeat, from 0 to 5. Defaults to 2. See Retries.

modelstring

The model space.ai.chat uses when a call names none, such as openai/gpt-oss-120b. With no default, every chat call must name its model.

urls{ fs?, ai?, schedule?, hooks? }

Another address for a product, as a full http or https URL. Leave it out to use ours.

fetchtypeof fetch

The fetch function to send requests with. Defaults to the runtime’s own. Pass one for tests, or in a runtime without fetch.

userAgentstring

The User-Agent header, up to 200 characters. Defaults to digitospace/<version>.

createSpace() checks its options straight away. A key, project, or setting that is not in the right form, or an option it does not know, throws a SpaceError with the code validation_failed. A runtime without fetch, when you pass none, throws unsupported_runtime.

It does not check that you have a key or a project until a call needs one. See When a key or project is missing.

clients.ts
import { createSpace } from "digitospace"

export const shop = createSpace({
  apiKey: process.env.SHOP_API_KEY,
  project: process.env.SHOP_PROJECT,
  timeoutMs: 10_000,
  retries: 3,
  model: "openai/gpt-oss-120b",
})

export const reports = createSpace({
  apiKey: process.env.REPORTS_API_KEY,
  project: process.env.REPORTS_PROJECT,
})

Make one client for each set of settings and reuse it. A client needs no closing.

space

const space: Space

A client made with no options, so it reads DIGITOSPACE_API_KEY and DIGITOSPACE_PROJECT. It is made the first time you use one of its parts, so importing it never fails, and it reads the environment then. A variable that is not in the right form throws validation_failed at that first use.

list-jobs.ts
import { space } from "digitospace"

for await (const job of space.schedule.list()) {
  console.log(job.name)
}

Environment variables

VariableHoldsUsed for
DIGITOSPACE_API_KEYAn API key, starting with dsk_apiKey when you pass none
DIGITOSPACE_PROJECTA project IDproject when you pass none

The SDK reads them from process.env in Node and Bun, and from Deno.env in Deno, which needs --allow-env. A Cloudflare worker gets its variables in env, so pass them to createSpace() there. An option you pass always wins over a variable.

When a key or project is missing

A call that needs a key and has none for its product fails with missing_api_key. A call that needs a project and has none fails with missing_project. Both fail before anything is sent. space.schedule.verify and space.hooks.verify need neither.

Call options

Every function that calls a service takes these options last, together with its own:

signalAbortSignal

Cancels the call. See Cancelling.

timeoutMsnumber

How long one try of this call may take, in milliseconds, from 1 to 600,000. Overrides the client’s timeoutMs. Any other value fails with validation_failed, before anything is sent.

read-quickly.ts
import { space } from "digitospace"

const text = await space.fs.read("notes/today.txt", {
  as: "text",
  timeoutMs: 5_000,
})
console.log(text)

Paging

A function that lists things, such as space.schedule.list() or space.fs.list(), returns a Paged list straight away, without a promise. Nothing is fetched until you read it.

interface Paged<T> extends AsyncIterable<T> {
  page(cursor?: string | null): Promise<Page<T>>
  all(): Promise<T[]>
}

interface Page<T> {
  items: T[]
  nextCursor: string | null
}

Read it one of three ways:

  • for await gives every item, fetching the next page when you reach the end of the one before. Stop early with break, and no more pages are fetched.
  • .page() fetches one page. Pass its nextCursor to .page() to fetch the next one. nextCursor is null on the last page.
  • .all() fetches every page and gives every item in one array. Use it only when you know the list is short.
list-files.ts
import { space } from "digitospace"

for await (const entry of space.fs.list("invoices/2026")) {
  console.log(entry.type === "file" ? entry.key : entry.prefix)
}

const first = await space.fs.list("invoices", { recursive: true }).page()
console.log(first.items.length)
if (first.nextCursor) {
  const second = await space.fs
    .list("invoices", { recursive: true })
    .page(first.nextCursor)
  console.log(second.items.length)
}

const buckets = await space.fs.buckets.list().all()
console.log(buckets.map((bucket) => bucket.name))

Each page holds up to 200 items. space.fs.list() holds up to 1,000, which you can lower with pageSize.

Retries

The SDK tries a call again on its own, up to retries more times, when trying again cannot do the work twice:

What happenedTried again
429 or 503Always, because the service refused the call before doing anything
Another status of 502 or higherOnly a call that is safe to repeat
A network failure or a timeoutOnly a call that is safe to repeat
Any other statusNever

A call is safe to repeat when doing it twice leaves the same result as doing it once: reads, replacements, deletes, and a few others such as pausing a job or copying a file. Creating something, running a job now, resending a delivery, and sending a chat call are never repeated after a failure that may have reached the service.

The wait between tries grows each time, starting under a quarter of a second, with some randomness so many clients do not try again at once. A Retry-After header sets the wait instead, up to 30 seconds.

When the last try fails, the call fails with that try’s error. Its retryable field says whether trying later could work. See Handle errors.

Timeouts

Each try of a call may take up to timeoutMs, 30 seconds by default. A chat call may take up to 10 minutes, because a model can take a long time to answer. A try that takes longer fails with timeout, and is tried again when the call is safe to repeat.

Set the limit for a client with the timeoutMs option, or for one call with the timeoutMs call option.

Cancelling

Pass an AbortSignal as signal to stop a call. The call fails with aborted, whether it was waiting for an answer or waiting to try again, and it is not tried again.

cancel.ts
import { isSpaceError, space } from "digitospace"

const controller = new AbortController()
setTimeout(() => controller.abort(), 2_000)

try {
  const reply = await space.ai.chat("Write a long story about Kilimanjaro.", {
    model: "openai/gpt-oss-20b",
    signal: controller.signal,
  })
  console.log(reply.text)
} catch (error) {
  if (isSpaceError(error) && error.code === "aborted") console.log("Stopped")
  else throw error
}

Cancelling stops waiting for the answer. A write the service already received may still happen.

Checked before sending

Every input is checked before anything is sent: options, paths, bucket names, keys, IDs, messages, and settings. A value that is not in the right form fails with validation_failed, with an issues list naming each field, and no request is made.

Checked on arrival

Every answer is checked against the shape its type describes before your code sees it. An answer that does not match fails with unexpected_response, and so does an answer that is not JSON where JSON was expected. If you see it, update digitospace to the latest version.

Only on servers

The SDK holds an API key, so it only runs on servers. When it finds itself in a web page, every call that needs a key fails with browser_not_allowed before anything is sent. Call it from your backend instead, and send your web page only what it needs. See Limits and runtimes.

Types

The package exports the types of every option and object, so you can name them in your own code:

import type { CallOptions, Page, Paged, Space, SpaceOptions } from "digitospace"

version is the package’s version, as a string.

Last updated