diff --git a/fabric/cluster-creation-management.md b/fabric/cluster-creation-management.md index 6cd2212d..88017c0e 100644 --- a/fabric/cluster-creation-management.md +++ b/fabric/cluster-creation-management.md @@ -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 +# 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. diff --git a/reference/components/javascript-environment.md b/reference/components/javascript-environment.md index 5942b4d4..d41608c5 100644 --- a/reference/components/javascript-environment.md +++ b/reference/components/javascript-environment.md @@ -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). diff --git a/reference/components/overview.md b/reference/components/overview.md index 199ea208..2f0e5096 100644 --- a/reference/components/overview.md +++ b/reference/components/overview.md @@ -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 diff --git a/reference/database/api.md b/reference/database/api.md index c503e377..b2b849aa 100644 --- a/reference/database/api.md +++ b/reference/database/api.md @@ -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 diff --git a/reference/database/schema.md b/reference/database/schema.md index 070040c6..cb1792d8 100644 --- a/reference/database/schema.md +++ b/reference/database/schema.md @@ -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 | diff --git a/reference/logging/api.md b/reference/logging/api.md index ddd2e206..32cb1b04 100644 --- a/reference/logging/api.md +++ b/reference/logging/api.md @@ -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` diff --git a/reference/resources/overview.md b/reference/resources/overview.md index d87e7b75..50db2dd1 100644 --- a/reference/resources/overview.md +++ b/reference/resources/overview.md @@ -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: @@ -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 | diff --git a/reference/resources/resource-api.md b/reference/resources/resource-api.md index 5e74eea6..71a0297b 100644 --- a/reference/resources/resource-api.md +++ b/reference/resources/resource-api.md @@ -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: @@ -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` diff --git a/reference/security/jwt-authentication.md b/reference/security/jwt-authentication.md index f65bd2eb..9d25d5a9 100644 --- a/reference/security/jwt-authentication.md +++ b/reference/security/jwt-authentication.md @@ -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: