TypeScript

The files.com package includes TypeScript declarations for all of its models, the upload and download helpers, Attachment, errors, Logger, Api and the Files settings. No separate types package is needed.

Use TypeScript 5.9 or later. The declarations are written for the strictest settings, including exactOptionalPropertyTypes and noUncheckedIndexedAccess, without skipLibCheck. They use Node.js's own types from @types/node, which the package depends on (version 22.20.2 or later, so an application's own @types/node of such a version is shared rather than duplicated).

Each model's types are in a namespace of the same name: User.CreateParams, User.ListParams and User.Attributes, for example. Types shared by the whole SDK, such as Files.RequestOptions and Files.DecimalString, are in the Files namespace.

Example

import Files from 'files.com';
import User from 'files.com/lib/models/User.js';

Files.setApiKey('YOUR_API_KEY');

const params: User.CreateParams = { username: 'alice', name: 'Alice' };
const user = await User.create(params);

Importing

In an ES module ("module": "nodenext"), import each class by its default export, and give deep imports their .js extension. The package root, files.com, is the Files class; models, Attachment and the other modules have their own paths. Errors has a named export for each error class, and Logger a named LogLevel. files.com/index.js has named Attachment, Files and Logger exports.

From CommonJS, import User = require('files.com/lib/models/User') returns the class, as require() does, and so does its .default. Default imports such as import User from 'files.com/lib/models/User' need esModuleInterop, which TypeScript 7 always enables.

With "moduleResolution": "bundler", the declarations of files.com/src/... paths, which the package's module field points bundlers to, describe ES modules, and those of files.com/lib/... paths CommonJS modules. Only the CommonJS modules make each class its own .default, so the package root's types, which serve every route to it, leave that out: use the class itself, or files.com/lib/Files for the CommonJS module's exact types.

TypeScript accepts a named import of a CommonJS class's static method, such as import { list } from 'files.com/lib/models/User.js' or import { setApiKey } from 'files.com', but Node.js provides none, and neither does a bundle's package root: call it on the class instead. Named imports of types, such as import type { RequestOptions } from 'files.com', work everywhere.

Example

import Files from 'files.com';
import Attachment from 'files.com/lib/Attachment.js';
import * as FilesErrors from 'files.com/lib/Errors.js';
import { LogLevel } from 'files.com/lib/Logger.js';
import File from 'files.com/lib/models/File.js';

Models and Absent Values

The API does not say which fields it always returns, or which can be null. So every field of a model can be missing (undefined) and every field the API returns can also be null: user.getName() is a string | null | undefined. The same holds for the fields of an object nested in a model, such as an invoice's line items, which are plain objects rather than models.

A model's state is its attributes. Setters such as user.setName(), updates and save() change it, and getters read it. Each attribute in the state can also be read as a property of its name, such as user.name, which always agrees with the getter; these properties are read-only, so use the setter. A name the model uses for a method keeps that use. Fields the API returns that the SDK does not know yet are in attributes as unknown.

update() resolves with the same object, now holding what the API returned, and save() resolves with true. A model's own methods, such as update() and delete(), take only their parameters: they use the options the model was created with, which it keeps as options.

Example

import User from 'files.com/lib/models/User.js';

const user = await User.find(1);
const displayName = user.getName() ?? user.getUsername() ?? 'unknown';

user.setName('Alice');
await user.save();
console.log(user.name); // 'Alice', once the API has returned it

Values

Decimal amounts, such as an invoice's amount, are strings of type Files.DecimalString ('10.50'), exactly as the API sends them, so that no precision is lost: convert them with a decimal library, or Number() when a floating-point number is precise enough. Dates and times are ISO 8601 strings of type Files.DateTimeString, not Date objects. Other numbers are JavaScript numbers.

A parameter with a list of values accepts only those. A field the API returns with a list of values can also hold values added later, so its type includes Files.OtherString: handle unknown values.

Structured values, such as a behavior's value or an automation's definition, have types of their own, such as Behavior.Value, following the JSON Schema the API publishes.

The API's schema currently describes some decimal parameters as numbers: min_free_cpu and min_free_mem of a RemoteMountBackend are sent as numbers, although the API returns them as decimal strings.

Example

import Files from 'files.com';
import Invoice from 'files.com/lib/models/Invoice.js';

// Invoice.list() returns account line items.
const items = await Invoice.list();
const amounts: Files.DecimalString[] = items.flatMap(item => item.getAmount() ?? []);

Parameters and Options

A static method takes its path arguments and primary key first, then the parameters it requires besides them, then the options of the call: User.find(1, {}, { workspaceId: 0 }) rather than User.find(1, { workspaceId: 0 }). A misspelled parameter or option is an error. A model's own methods take its primary key, and any other required value, from its attributes when not given.

The options of one call, Files.RequestOptions, include credentials, headers, agents, autoPaginate and timeoutSecs. workspaceId: null or workspaceId: undefined sends no workspace; leave the option out to keep the configured one.

timeoutSecs, like the configured networkTimeout, limits each attempt of a request as one budget: from sending it, through its redirects, until its response body has been read to its end, whatever the body's type, a body returned unread as a stream included: read such a stream within the budget. If the budget runs out before it has been read to its end, the stream is stopped and fails with an error, as the call has already resolved. Each retry has a budget of its own, waiting between attempts counts toward none, and nothing limits the call as a whole, which can make several attempts and request several pages. Leave timeoutSecs out, rather than giving it as undefined, to keep the configured timeout.

Aborting the call's signal stops its requests at once, a wait before a retry included. The call then rejects with the signal's reason, unchanged, or, when the runtime gives none, an Error named AbortError: not a FilesError, so check signal.aborted. Once the call has resolved, aborting the signal still stops a body returned unread that has not been read to its end: that stream fails with an error.

A list method, such as User.list(), resolves with an array: every page's items, or only the first page's with autoPaginate: false.

Failures are not limited to FilesErrors, such as a download's errors: catch errors as unknown and narrow them.

Example

import * as FilesErrors from 'files.com/lib/Errors.js';
import User from 'files.com/lib/models/User.js';

try {
  const firstPage = await User.list({ per_page: 100 }, { autoPaginate: false });
  console.log(firstPage.length);
} catch (error) {
  if (error instanceof FilesErrors.NotAuthenticatedError) {
    console.error(error.title);
  } else {
    throw error;
  }
}

Attachments and Logging

A file parameter, such as a behavior's attachment_file, takes an Attachment of a Buffer or other Uint8Array, or of a Node.js Readable stream (Attachment.Source), with its filename, type and maxBytes options (Attachment.Options). Only an Attachment made with its class is sent as a file: not the bytes themselves, and not an object with the same fields.

Files.setLogger() takes any object with error, warn, info and debug methods (Files.ApplicationLogger), or null to log to the console again.

Example (not available in browser)

import { readFile } from 'node:fs/promises';
import Files from 'files.com';
import Attachment from 'files.com/lib/Attachment.js';
import Behavior from 'files.com/lib/models/Behavior.js';

Files.setLogger(console);

const behavior = await Behavior.create({
  path: 'invoices',
  behavior: 'watermark',
  value: { gravity: 'SouthEast', max_height_or_width: 20, transparency: 25 },
  attachment_file: new Attachment(await readFile('watermark.png'), { filename: 'watermark.png', type: 'image/png' }),
});

File Transfers

File.uploadData() accepts a string, a Buffer, a Node.js readable stream, or an iterable or async iterable of string or Uint8Array chunks. A bare Uint8Array is not accepted: wrap it, as [bytes] or Buffer.from(bytes). uploadStream() takes a Node.js readable stream, which the SDK does not close. A determinePartUploadUri option must return the part's URL synchronously.

Uploads resolve with the uploaded File. Copy and move helpers, such as copyTo(), resolve with the API's response, of type File.ActionResponse, whose data describes the operation.

Disk and stream transfers, uploadFile(), downloadToFile(), downloadToStream() and downloadToString(), work in Node.js only; their types do not change in a browser, where they reject with a NotImplementedError. Downloads take only their destination. downloadToStream() takes any writable Node.js stream, and ends it when the download completes.

Example (not available in browser)

import { createWriteStream } from 'node:fs';
import File from 'files.com/lib/models/File.js';

const file = await File.uploadData('reports/today.csv', Buffer.from('a,b\n'), { mkdir_parents: true });
await file.downloadToStream(createWriteStream('today.csv'));