# JavaScript API reference

> `rift-snapshot` exports, Bun vs Node conditional bindings, FFI request protocol, function signatures, and Node.js 26.1+ FFI requirements.

- Repository: anomalyco/rift
- GitHub: https://github.com/anomalyco/rift
- Human docs: https://grok-wiki.com/public/docs/anomalyco-rift-ea3fd5dbf662
- Complete Markdown: https://grok-wiki.com/public/docs/anomalyco-rift-ea3fd5dbf662/llms-full.txt

## Source Files

- `npm/rift-snapshot/index.d.ts`
- `npm/rift-snapshot/bun/index.js`
- `npm/rift-snapshot/node/index.js`
- `npm/rift-snapshot/package.json`
- `crates/ffi/src/lib.rs`
- `README.md`

---

---
title: "JavaScript API reference"
description: "`rift-snapshot` exports, Bun vs Node conditional bindings, FFI request protocol, function signatures, and Node.js 26.1+ FFI requirements."
---

The `rift-snapshot` npm package exposes six workspace operations (`init`, `create`, `remove`, `list`, `ancestors`, `gc`) and a `RiftError` class through conditional exports that load platform-specific prebuilt shared libraries from `prebuilds/<platform>-<arch>/`. Each JavaScript call serializes a JSON request, invokes the Rust `rift_ffi_call` symbol, parses the JSON response, and either returns a typed value or throws `RiftError`.

## Package surface

| Export | Type | Role |
| --- | --- | --- |
| `init` | function | Register or restore a managed workspace at an exact path |
| `create` | function | Create a copy-on-write child workspace |
| `remove` | function | Trash a workspace subtree, or unregister a source root |
| `list` | function | List direct child workspaces |
| `ancestors` | function | List parent workspaces, nearest first |
| `gc` | function | Physically delete trashed workspaces and prune stale registry entries |
| `RiftError` | class | Typed error with `code`, `message`, and optional `path` |

The package also ships a CLI shim at `bin/rift.js` that spawns the bundled `rift` executable. The JavaScript API does not use that binary; it calls the FFI shared library directly.

<Note>
Install the package globally or as a project dependency. See [Installation](/installation) for platform matrices and prebuild layout.
</Note>

## Conditional exports

`package.json` routes imports by runtime:

```json
"exports": {
  ".": {
    "types": "./index.d.ts",
    "bun": "./bun/index.js",
    "node": "./node/index.js",
    "default": "./node/index.js"
  }
}
```

<Tabs>
<Tab title="Bun">

The Bun binding (`bun/index.js`) uses `bun:ffi` with `dlopen`, `ptr`, and `CString`. It resolves the native library through a dynamic `import` of the prebuild asset with `{ with: { type: "file" } }`, then passes null-terminated UTF-8 request bytes to `rift_ffi_call`.

```ts
import { create, init, list, remove, gc } from "rift-snapshot";
```

No extra runtime flags are required beyond a supported Bun version with FFI support.

</Tab>
<Tab title="Node.js">

The Node binding (`node/index.js`) uses the experimental `node:ffi` module. It resolves the library path with `fileURLToPath` and `fs.existsSync`, then passes the request as a JavaScript string to `rift_ffi_call`.

```ts
import { create, init, list, remove, gc } from "rift-snapshot";
```

Node.js **26.1 or later** is required. Start the process with the experimental FFI flag:

```bash
node --experimental-ffi app.mjs
```

<Warning>
When Node's permission model is enabled, also pass `--allow-ffi` or the `dlopen` call fails at startup.
</Warning>

</Tab>
</Tabs>

### Supported platforms

The package declares `os: ["darwin", "linux", "win32"]` and `cpu: ["arm64", "x64"]`. Both bindings map `os.platform()` and `os.arch()` to prebuild directories:

| Platform key | Library filename |
| --- | --- |
| `linux-x64` | `librift_ffi.so` |
| `darwin-x64` | `librift_ffi.dylib` |
| `darwin-arm64` | `librift_ffi.dylib` |
| `windows-x64` | `rift_ffi.dll` |

Unsupported platform/architecture pairs throw at module load time. A missing prebuild file on Node throws `Unable to locate the Rift Node library for <platform>-<arch>. Reinstall rift-snapshot.`

:::files
npm/rift-snapshot/
├── index.d.ts          # Shared TypeScript declarations
├── bun/index.js        # Bun FFI binding
├── node/index.js       # Node FFI binding
├── bin/rift.js         # CLI launcher (separate from API)
└── prebuilds/
    ├── linux-x64/      # rift + librift_ffi.so
    ├── darwin-x64/     # rift + librift_ffi.dylib
    ├── darwin-arm64/   # rift + librift_ffi.dylib
    └── windows-x64/    # rift.exe + rift_ffi.dll
:::

## FFI request protocol

JavaScript bindings and the Rust `rift-ffi` crate communicate through a single JSON request/response envelope. Two C symbols are exported:

| Symbol | Contract |
| --- | --- |
| `rift_ffi_call(input)` | Accept a null-terminated request buffer; return an allocated response buffer |
| `rift_ffi_free(output)` | Free a buffer previously returned by `rift_ffi_call` |

```mermaid
sequenceDiagram
    participant JS as bun/index.js or node/index.js
    participant FFI as librift_ffi (rift_ffi_call)
    participant Core as Manager (rift core)

    JS->>FFI: JSON request (+ optional database path)
    FFI->>Core: deserialize Command, open Manager
    Core-->>FFI: Result<Value, Failure>
    FFI-->>JS: JSON response pointer
    JS->>FFI: rift_ffi_free(pointer)
    alt status = ok
        JS-->>JS: return response.value
    else status = error
        JS-->>JS: throw new RiftError(response.error)
    end
```

### Request shape

Every request is a JSON object with an optional `database` field and a `command` discriminator:

<ParamField body="database" type="string">
Optional absolute path to the SQLite registry file. When omitted, the core opens the default database at `<user-data-dir>/rift/rift.sqlite`.
</ParamField>

Commands use `snake_case` names in the wire format. The JavaScript bindings translate camelCase option names where needed.

| `command` | Fields | Maps to |
| --- | --- | --- |
| `init` | `at` | `Manager::init` |
| `create` | `from`, `name`, `into`, `copyAll`, `hooks` | `Manager::create_with_options` |
| `remove` | `at`, `all` | `Manager::remove` or `Manager::remove_all` |
| `list` | `of` | `Manager::list` |
| `ancestors` | `of` | `Manager::ancestors` |
| `gc` | _(none)_ | `Manager::gc` |

<RequestExample>

```json
{
  "command": "create",
  "from": "/home/dev/app",
  "name": "schema-work",
  "copyAll": false,
  "hooks": true
}
```

</RequestExample>

### Response shape

Responses are tagged by `status`:

<ResponseField name="status" type="string">
Either `ok` or `error`.
</ResponseField>

<ResponseField name="value" type="null | string | string[]">
Present when `status` is `ok`. Shape depends on the command (see return values below).
</ResponseField>

<ResponseField name="error" type="object">
Present when `status` is `error`. Contains `code`, `message`, and optional `path`.
</ResponseField>

<ResponseExample>

```json
{
  "status": "ok",
  "value": "/home/dev/.rifts/app/schema-work"
}
```

</ResponseExample>

<ResponseExample>

```json
{
  "status": "error",
  "error": {
    "code": "workspace_not_initialized",
    "message": "workspace is not initialized: /tmp/app",
    "path": "/tmp/app"
  }
}
```

</ResponseExample>

The FFI layer also returns protocol-level errors not produced by workspace logic:

| Code | When |
| --- | --- |
| `invalid_request` | Malformed JSON, invalid UTF-8, or null input pointer |
| `panic` | Unwound Rust panic inside `rift_ffi_call` |
| `serialization` | Response JSON could not be serialized, or contained an interior null byte |

## Function reference

TypeScript declarations live in `index.d.ts`. Runtime defaults are applied in the Bun and Node binding implementations.

### `init(options?)`

<ParamField body="at" type="string" default="process.cwd()">
Directory to initialize exactly. Unlike the CLI, there is no Git-root selection or `--here` resolution — pass the precise path you want registered.
</ParamField>

<ParamField body="database" type="string">
Optional registry database path.
</ParamField>

**Returns:** `null`

Registers the directory in the SQLite registry, writes or restores the `.rift` marker, and runs platform-specific initialization (btrfs subvolume conversion, reflink verification, or APFS registration). Idempotent when the workspace is already registered.

### `create(options?)`

<ParamField body="from" type="string" default="process.cwd()">
Source managed workspace. Resolved upward through `.rift` markers.
</ParamField>

<ParamField body="name" type="string">
Child workspace name. When omitted, the core generates a readable random name.
</ParamField>

<ParamField body="into" type="string">
Custom storage parent directory. When omitted, storage defaults to the sibling `.rifts` layout described in [Storage layout](/storage-layout).
</ParamField>

<ParamField body="copyAll" type="boolean" default="false">
`false` uses filtered copy (omits regenerable artifacts such as `node_modules` and `target`). `true` preserves the full tree.
</ParamField>

<ParamField body="hooks" type="boolean" default="true">
`true` runs `.rift.toml` postcreate hooks after registration. `false` skips hooks entirely.
</ParamField>

<ParamField body="database" type="string">
Optional registry database path.
</ParamField>

**Returns:** `string` — absolute path to the new workspace.

Filtered vs exact copy behavior and platform backends are described in [Copy strategies and platforms](/copy-strategies). Hook configuration is in [Configuration reference](/configuration-reference).

### `remove(options?)`

<ParamField body="at" type="string" default="process.cwd()">
Workspace to remove. Resolved upward through `.rift` markers.
</ParamField>

<ParamField body="all" type="boolean" default="false">
`false` trashes the selected workspace (or unregisters a source root). `true` trashes all descendants while preserving the selected workspace — equivalent to CLI `rift remove --children`.
</ParamField>

<ParamField body="database" type="string">
Optional registry database path.
</ParamField>

**Returns:**

| `all` | Return type | Behavior |
| --- | --- | --- |
| `false` | `void` | Trash the workspace, or unregister a source root and trash its descendants |
| `true` | `string[]` | Trash descendant paths; selected workspace remains active |

<Warning>
Removing a source root through the JavaScript API does not require a force flag. The CLI blocks root unregistration unless `-f` is passed; the FFI `remove` command calls `Manager::remove` directly, which unregisters roots without that guard.
</Warning>

### `list(options?)`

<ParamField body="of" type="string" default="process.cwd()">
Workspace whose direct children to list.
</ParamField>

<ParamField body="database" type="string">
Optional registry database path.
</ParamField>

**Returns:** `string[]` — absolute paths of direct active child workspaces.

### `ancestors(options?)`

<ParamField body="of" type="string" default="process.cwd()">
Workspace whose ancestry to trace.
</ParamField>

<ParamField body="database" type="string">
Optional registry database path.
</ParamField>

**Returns:** `string[]` — parent workspace paths, nearest parent first.

### `gc(options?)`

<ParamField body="database" type="string">
Optional registry database path.
</ParamField>

**Returns:** `string[]` — absolute paths physically deleted from trash storage.

## Error handling

Operation failures throw `RiftError`, a subclass of `Error` with:

<ResponseField name="code" type="RiftErrorCode">
Machine-readable error identifier.
</ResponseField>

<ResponseField name="message" type="string">
Human-readable description from the Rust error.
</ResponseField>

<ResponseField name="path" type="string">
Optional filesystem path associated with the error (workspace roots, markers, hook targets).
</ResponseField>

```ts
import { create, RiftError } from "rift-snapshot";

try {
  create({ from: "/uninitialized/project" });
} catch (error) {
  if (error instanceof RiftError) {
    console.error(error.code);    // e.g. "workspace_not_initialized"
    console.error(error.path);    // e.g. "/uninitialized/project"
  }
}
```

The complete code catalog, including path-bearing variants and CLI message mappings, is in [Error codes](/error-codes).

Non-`RiftError` exceptions can occur before the FFI boundary:

- Unsupported platform at module load
- Missing prebuild library file (Node)
- `Rift native library returned no response` when `rift_ffi_call` returns a null pointer

## API vs CLI

The JavaScript API and the `rift` CLI share the same Rust `Manager` implementation but differ in ergonomics:

| Behavior | JavaScript API | CLI |
| --- | --- | --- |
| `init` path selection | Exact `at` path only | Git-root or existing Rift root unless `--here` |
| `create` output | Return value | Prints path to stdout |
| `remove` on source root | Always permitted | Requires `-f` |
| `remove` descendants | `remove({ all: true })` | `rift remove --children` |
| Progress reporting | None | stderr progress during btrfs conversion |
| Shell `cd` after operations | Not available | `rift shell-init` wrapper |
| Registry override | `database` option on every call | Hidden global `--database` flag |

For stdout signals, exit codes, and the full flag inventory, see [CLI reference](/cli-reference).

## Example workflow

<CodeGroup>

```ts title="workspace.mjs"
import { init, create, list, remove, gc } from "rift-snapshot";

// Initialize exactly this directory (not Git-root selection)
init({ at: "/home/dev/my-app" });

// Create a filtered child workspace with hooks enabled (default)
const child = create({
  from: "/home/dev/my-app",
  name: "feature-x",
});

console.log(list({ of: "/home/dev/my-app" }));
// → ["/home/dev/.rifts/my-app/feature-x"]

remove({ at: child });
gc();
```

```bash title="Run on Node.js 26.1+"
node --experimental-ffi workspace.mjs
```

```bash title="Run on Bun"
bun workspace.mjs
```

</CodeGroup>

<Check>
After `create`, the returned string is the canonical absolute path. Use it as `at` in subsequent `remove` calls rather than reconstructing storage paths manually.
</Check>

## Related pages

<CardGroup>
<Card title="Installation" href="/installation">
Install `rift-snapshot` and understand the prebuild binary layout bundled with the package.
</Card>
<Card title="Quickstart" href="/quickstart">
End-to-end init → create → list → remove workflow with expected outputs.
</Card>
<Card title="CLI reference" href="/cli-reference">
Subcommands, flags, and behaviors that differ from the JavaScript API.
</Card>
<Card title="Error codes" href="/error-codes">
Complete `RiftError` code catalog from FFI and TypeScript bindings.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
`.rift.toml` postcreate hooks controlled by the `hooks` create option.
</Card>
</CardGroup>
