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
11 changes: 11 additions & 0 deletions fabric/cluster-creation-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,14 @@ Clusters are provisioned in real time, as soon as selections are complete

- Cannot guarantee any provisioning time for self-hosted. (**Note**: All performance metrics are estimates unless otherwise noted.)
- Once a cluster is created, you will be prompted to set a username and password for each cluster.

## Connecting the Harper CLI to a Cluster

The cluster's **Config → Overview** page exposes its **Application URL** — the hostname the Harper CLI and SDKs target. Pass it to `harper login` to authenticate; the CLI stores the token (and writes `HARPER_CLI_TARGET` to a local `.env`) so subsequent commands don't need credentials repeated.

```bash
harper login <Application URL>
# Provide cluster username and password when prompted
```

See [CLI Authentication](/reference/v5/cli/authentication) for the full set of authentication methods — including environment variables for CI/CD pipelines.
30 changes: 30 additions & 0 deletions reference/components/javascript-environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,36 @@ npm link harper

All installed components have `harper` automatically linked.

## TypeScript Support

Harper runs `.ts` files directly via Node.js's built-in [type stripping](https://nodejs.org/api/typescript.html#type-stripping). No build step or transpiler is required.

Requirements and conventions:

- **Node.js 22.6 or later.** Type stripping is unavailable on earlier versions.
- **Use the `.ts` extension on the source files** referenced from `config.yaml`. The `jsResource` plugin loads `.js` and `.ts` files; point its `files` glob at the `.ts` files you want loaded:
```yaml
jsResource:
files: 'resources/*.ts'
```
- **Use explicit `.ts` extensions in imports** between local modules. Node's loader does not resolve `'./helper'` to `'./helper.ts'`:
```typescript
import { helper } from './helper.ts';
```
- **Only type-stripping is performed.** Enum values, namespaces with runtime semantics, and other features that require code transformation are not supported — declarations and type annotations are simply removed.

Type imports from the `harper` package work as usual:

```typescript
import { type RequestTargetOrId, Resource, tables } from 'harper';

export class MyResource extends Resource {
async get(target?: RequestTargetOrId): Promise<{ message: string }> {
return { message: 'Hello from TS' };
}
}
```

## Harper API

The following objects and functions are available as exports from the `harper` package (and also available as global variables).
Expand Down
9 changes: 6 additions & 3 deletions reference/components/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,12 @@ Extensions require an `extensionModule` option pointing to the extension source.

### Extensions

- [`@harperdb/nextjs`](https://github.com/HarperDB/nextjs)
- [`@harperdb/apollo`](https://github.com/HarperDB/apollo)
- [`@harperdb/astro`](https://github.com/HarperDB/astro)
- [`@harperdb/nextjs`](https://github.com/HarperDB/nextjs) — Run a Next.js application on Harper
- [`@harperdb/apollo`](https://github.com/HarperDB/apollo) — Serve an Apollo GraphQL server backed by Harper
- [`@harperdb/astro`](https://github.com/HarperDB/astro) — Run an Astro application on Harper
- [`@harperfast/vite-plugin`](https://github.com/HarperFast/vite-plugin) — Develop and serve Vite-built front-ends from Harper, with HMR via `harper run`

Each plugin owns its own documentation in its repository — refer to the linked README for installation, configuration, and version-specific details.

## Component Status Monitoring

Expand Down
54 changes: 51 additions & 3 deletions reference/database/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,57 @@ await MyTable.put({ id: 'my-record', data: blob });

### `BlobOptions`

| Option | Type | Default | Description |
| ------------------ | --------- | ------- | ----------------------------------------------------------------------- |
| `saveBeforeCommit` | `boolean` | `false` | Wait for the blob to be fully written before committing the transaction |
| Option | Type | Default | Description |
| ------------------ | --------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `type` | `string` | `undefined` | MIME type to associate with the blob (e.g., `image/jpeg`, `audio/mpeg`). Readable via `blob.type` and used when serving HTTP |
| `size` | `number` | `undefined` | Size of the data in bytes, if known ahead of time. Otherwise inferred from a buffer or determined as a stream completes |
| `saveBeforeCommit` | `boolean` | `false` | Wait for the blob to be fully written before committing the transaction |
| `compress` | `boolean` | `false` | Compress the stored data with deflate |
| `flush` | `boolean` | `false` | Flush the file to disk after writing, before the `createBlob` promise chain resolves |

Example with MIME type:

```javascript
let blob = createBlob(imageBuffer, { type: 'image/jpeg' });
await Photo.put({ id, data: blob });
```

### Accepting Binary in JSON Requests

REST clients that can't post raw binary typically send base64 inside JSON. Decode in the resource override and wrap with `createBlob`, recording the MIME type so it round-trips on read:

```typescript
import { type RequestTargetOrId, tables, createBlob } from 'harper';

export class Photo extends tables.Photo {
async post(target: RequestTargetOrId, record: any) {
if (record.data) {
record.data = createBlob(Buffer.from(record.data, record.encoding || 'base64'), {
type: record.contentType || 'application/octet-stream',
});
}
return super.post(target, record);
}
}
```

### Serving Binary from a Resource

Return a response object with the blob's MIME type in headers and the blob itself as the body. Harper will stream it to the client:

```typescript
async get(target: RequestTargetOrId) {
const record = await super.get(target);
if (record?.data) {
return {
status: 200,
headers: { 'Content-Type': record.data.type || 'application/octet-stream' },
body: record.data,
};
}
return record;
}
```

### Error Handling

Expand Down
31 changes: 31 additions & 0 deletions reference/database/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,37 @@ let results = Document.search({
});
```

### Filtering by Distance Threshold

To return only records whose distance to a target vector is below a threshold, place `target` directly on the condition (alongside `comparator` and `value`). This returns matches within the threshold without using `sort`:

```javascript
let results = Document.search({
conditions: {
attribute: 'textEmbeddings',
comparator: 'lt',
value: 0.1,
target: searchVector,
},
});
```

This form is useful when you want to bound result quality by a similarity cutoff rather than ranking by similarity.

### Selecting the Distance

Use the special `$distance` field in `select` to include the computed distance from the target vector in returned records:

```javascript
let results = Document.search({
select: ['name', '$distance'],
sort: { attribute: 'textEmbeddings', target: searchVector },
limit: 5,
});
```

`$distance` is available in both `sort`-based ranking and `conditions`-based threshold queries.

### HNSW Parameters

| Parameter | Default | Description |
Expand Down
6 changes: 6 additions & 0 deletions reference/logging/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ interface TaggedLogger {

`TaggedLogger` does not have a `withTag()` method.

### Console Capture

When `logging.console: true` is set, writes to `process.stdout` and `process.stderr` from component code are also written verbatim to the Harper log file. This includes anything written via `console.log`, `console.warn`, `console.error`, etc. Captured lines do not pass through `logger`'s level filter — they are appended as-is.

Prefer `logger` directly in production code so that level filtering and tagging apply. Console capture is intended as a convenience for porting existing code and for debugging.

### Usage

#### Basic logging with `logger`
Expand Down
13 changes: 13 additions & 0 deletions reference/resources/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ type MyTable @table {
}
```

`@export` on the schema type registers Harper's default table resource at `/MyTable`. When you extend the table in JavaScript and want your subclass to serve those endpoints instead, **omit `@export`** from the schema and let the exported JavaScript class own the URL. Leaving `@export` on the schema while also exporting a subclass with the same name produces conflicting endpoints. When overriding handlers, call `super.get/post/...` to preserve Harper's default behavior unless you intend to replace it entirely.

> For more info on the schema API see [`Database / Schema`](../database/schema.md)

Then, in a `resources.js` extend from the `tables.MyTable` global:
Expand Down Expand Up @@ -106,6 +108,17 @@ Resources are the true customization point for Harper. This is where the busines

Resources become HTTP/MQTT endpoints when they are exported. As the examples demonstrated if a Resource extends an existing table, make sure to not have conflicting exports between the schema and the JavaScript implementation. Alternatively, you can register resources programmatically using `server.resources.set()`. See [HTTP API](../http/api.md) for `server` API documentation.

The shape of the export controls the resulting URL:

| Export form | URL | Notes |
| ---------------------------------------- | --------------- | ---------------------------------------------------------------------------- |
| `export class Foo extends Resource {}` | `/Foo/` | The class name becomes the path segment. Path segments are case-sensitive. |
| `export const Bar = { Foo };` | `/Bar/Foo/` | Nest a class under an object to add a path prefix. |
| `export const bar = { 'foo-baz': Foo };` | `/bar/foo-baz/` | Use object keys when you need lowercase, hyphens, or any non-identifier URL. |
| `server.resources.set('my-path', Foo);` | `/my-path/` | Programmatic registration; useful when the path is dynamic. |

URL path matching is case-sensitive — `/Foo/` and `/foo/` are different endpoints.

## Pages in This Section

| Page | Description |
Expand Down
48 changes: 48 additions & 0 deletions reference/resources/resource-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,53 @@ Executes a Harper operations API call using this table as the target. Set `autho

---

### `getCurrentUser(): User | undefined`

Returns the user associated with the current request, or `undefined` if no user is authenticated. The returned object exposes the username, role, and `role.permission` flags.

```javascript
async get(target) {
const user = this.getCurrentUser();
if (!user) return new Response(null, { status: 401 });
return { username: user.username, role: user.role };
}
```

---

### Session and Login from a Resource

The context returned by `getContext()` exposes `login` and `session` for handling sign-in/out flows in a custom Resource. Sessions require `authentication.enableSessions: true` in `harperdb-config.yaml`.

```typescript
export class SignIn extends Resource {
async post(_target, data) {
const context = this.getContext();
try {
await context.login(data.username, data.password);
} catch {
return new Response('Invalid credentials', { status: 403 });
}
return new Response('Logged in', { status: 200 });
}
}

export class SignOut extends Resource {
async post() {
const context = this.getContext();
if (!context.session) return new Response(null, { status: 401 });
await context.session.delete(context.session.id);
return new Response('Logged out', { status: 200 });
}
}
```

`context.login(username, password)` verifies credentials and establishes the session cookie on success. To end a session, delete it via `context.session.delete(context.session.id)`.

Cookie-based sessions are intended for browser clients. For non-browser clients (CLI tools, mobile apps, service-to-service), use JWT issuance — see [JWT Authentication](../security/jwt-authentication.md).

---

## Resource Instance Methods

A Resource instance is used to update and interact with a single record/resource. It provides functionality for updating properties, accessing property values, and managing record lifecycle. The Resource instance is normally retrieved from the static `update()` method. An instance from a table has updatable properties that can used to access and update individual properties (for properties declared in the table's schema), as well methods for more advanced updates and saving data. For example:
Expand Down Expand Up @@ -650,6 +697,7 @@ Special properties:

- `$id` — Returns the primary key regardless of its name
- `$updatedtime` — Returns the last-updated timestamp
- `$distance` — When the query ranks or filters by a vector index, returns the computed distance from the target vector. See [Vector Indexing](../database/schema.md#vector-indexing).

### `sort`

Expand Down
51 changes: 51 additions & 0 deletions reference/security/jwt-authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,57 @@ Response:

When both tokens have expired, call `create_authentication_tokens` again with your username and password.

## Issuing Tokens from a Custom Resource

Custom Resources can mint tokens programmatically by invoking the same operations via [`server.operation()`](../http/api.md#serveroperationoperation-context-authorize). This is useful when you want a Resource-style endpoint (e.g., `POST /IssueTokens`) instead of (or in addition to) the raw Operations API.

```typescript
import { Resource, server } from 'harper';

export class IssueTokens extends Resource {
static async get(_target, context) {
// Caller is already authenticated (Basic Auth or an existing JWT) — issue
// tokens for the current user.
const { operation_token, refresh_token } = await server.operation(
{ operation: 'create_authentication_tokens' },
context,
true
);
return { operation_token, refresh_token };
}

static async post(_target, data) {
// Caller provides credentials in the body — issue tokens directly.
const { username, password } = await data;
if (!username || !password) {
return new Response('username and password required', { status: 400 });
}
const { operation_token, refresh_token } = await server.operation({
operation: 'create_authentication_tokens',
username,
password,
});
return { operation_token, refresh_token };
}
}

export class RefreshJWT extends Resource {
static async post(_target, data) {
const { refresh_token } = await data;
if (!refresh_token) {
return new Response('refresh_token required', { status: 400 });
}
const { operation_token } = await server.operation({
operation: 'refresh_operation_token',
refresh_token,
});
return { operation_token };
}
}
```

Pass `authorize: true` (third argument) when the operation should run as the current authenticated user; omit it (or pass `false`) when the operation supplies its own credentials.

## Token Expiry Configuration

Token timeouts are configurable in `harper-config.yaml` under the top-level `authentication` section:
Expand Down