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
2 changes: 1 addition & 1 deletion reference/components/applications.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ harper deploy ref=9f8c2a1 restart=true replicated=true

If a `ref` can't be resolved either way, the deploy stops rather than sending the name for the cluster to resolve. Run `git fetch` and retry, or pass a full commit SHA — that needs no resolution and is always accepted.

A `ref` must also name something a clone can fetch: `refs/heads/*` and `refs/tags/*`, or a bare branch or tag name. Anything else — `refs/pull/123/head`, say — is rejected up front, even if your own checkout can resolve it, because the cluster could resolve that commit and still never check it out.
**A full commit SHA is accepted directly.** An object ID is already immutable, so there is nothing to pin it to and no resolution is attempted — SHA-based rollbacks are always valid. Every other `ref` must name something a clone can fetch: `refs/heads/*` or `refs/tags/*` if you qualify it, or a bare branch or tag name that resolves locally or on the remote. A qualified ref outside those two namespaces — `refs/pull/123/head`, say — is rejected up front, even if your own checkout can resolve it, because the cluster could resolve that commit and still never check it out.

**Commit and push first.** The cluster clones from the remote, so it only sees commits that have been pushed. `by_ref` warns in both directions: when the working tree is dirty (those changes won't be part of the deploy) and when the commit being deployed isn't on any remote branch (the cluster won't be able to clone it). The second check reads your local remote-tracking refs, so run `git fetch` if you get it for a commit you know you pushed.

Expand Down
29 changes: 22 additions & 7 deletions reference/database/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,21 +104,36 @@ For most operations — HTTP request handlers, for example — Harper automatica
### Basic Usage

```javascript
import { tables } from 'harper';
import { isMainThread } from 'node:worker_threads';
import { tables, transaction } from 'harper';
Comment on lines +107 to +108

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The code example uses logger.error in the catch block, but logger is not imported from 'harper'. Since logger is a package export of 'harper', omitting it will result in a ReferenceError at runtime when an error is caught. Please add logger to the imported members.

Suggested change
import { isMainThread } from 'node:worker_threads';
import { tables, transaction } from 'harper';
import { isMainThread } from 'node:worker_threads';
import { logger, tables, transaction } from 'harper';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logger is available in the globals, though

const { MyTable } = tables;

if (isMainThread) {
let running = false;
setInterval(async () => {
let data = await (await fetch('https://example.com/data')).json();
transaction(async (txn) => {
for (let item of data) {
await MyTable.put(item, txn);
}
});
if (running) return; // the previous run has not committed yet
running = true;
try {
let data = await (await fetch('https://example.com/data')).json();
await transaction(async (txn) => {
for (let item of data) {
await MyTable.put(item, txn);
}
});
} catch (error) {
logger.error('hourly import failed', error);
} finally {
running = false;
}
}, 3600000); // every hour
}
```

Two details matter outside a request context:

- **`await` the `transaction()` call, and catch it.** An unawaited call lets the timer callback finish before the commit resolves, so a failed write is never observed; awaiting without a `catch` still leaves the rejection unhandled, because nothing awaits the callback itself.
- **Guard against overlap when a job can outlast its interval.** A timer fires on schedule regardless of whether the previous run has committed, so two runs would open two transactions over the same rows. The `running` flag above skips a tick instead.

### Nesting

If `transaction()` is called with a context that already has an active transaction, it reuses that transaction, executes the callback immediately, and returns. This makes `transaction()` safe to call defensively to ensure a transaction is always active.
Expand Down
6 changes: 3 additions & 3 deletions reference/rest/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ rest:

This section describes the **default table Resource** — the endpoints Harper registers automatically for a table, with no handler code of your own. Harper serves that default Resource only when **both** of the following are true:

1. The table is exported in a schema definition with [`@export`](../database/schema.md#export).
2. REST is enabled for the application — normally `rest: true` in `config.yaml` (see [Configuration](#configuration)); a component directory with **no configuration file at all** gets it from Harper's built-in default instead, as described below.
1. The table is exported in a schema definition with [`@export`](../database/schema.md#export). This is always required.
2. REST is enabled for the application. `rest: true` must be present in `config.yaml` **whenever a configuration file exists** (see [Configuration](#configuration)); a component directory with no configuration file at all inherits `rest` from Harper's built-in default instead, as described below.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The fallback behavior of inheriting rest from Harper's built-in default is buried behind a semicolon. Please present this fallback behavior in a separate, distinct sentence to make it more prominent and easier to scan.

For example:

2. REST is enabled for the application. `rest: true` must be present in `config.yaml` **whenever a configuration file exists** (see [Configuration](#configuration)). A component directory with no configuration file at all inherits `rest` from Harper's built-in default instead, as described below.
References
  1. Ensure critical security warnings, such as unauthorized access risks or fallback behaviors, are presented in separate, distinct sentences rather than being combined with other concepts or buried behind semicolons, so that readers scanning the documentation can easily find them.


```graphql
# schema.graphql
Expand All @@ -58,7 +58,7 @@ graphqlSchema:
rest: true
```

Neither half is sufficient on its own. Without `@export` Harper registers no default Resource for the table, so it has no REST route and callers get `404`. Without REST enabled the REST handler is never registered for the application, so even an exported table does not respond to HTTP requests.
Neither half is sufficient on its own. Without `@export` Harper registers no default Resource for the table, so it has no REST route and callers get `404`. Without REST enabled the REST handler is never registered for the application, so even an exported table does not respond to HTTP requests. Writing `rest: true` is what enables it in a `config.yaml` — the only way REST is enabled without that line is the no-configuration-file case below.

`@export` is how the **table itself** claims the URL. When a JavaScript subclass of `tables.MyTable` should own that URL instead, omit `@export` from the schema and export the class — the class claims the route and serves whatever it implements, and REST still has to be enabled. Leaving `@export` on the schema while also exporting a same-named subclass produces conflicting endpoints. See [Extending a Table](../resources/overview.md#extending-a-table).

Expand Down
20 changes: 18 additions & 2 deletions reference/security/jwt-authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ JWT authentication uses two token types:

## Create Authentication Tokens

Call `create_authentication_tokens` with your Harper credentials. No `Authorization` header is required for this operation.
Call `create_authentication_tokens` with your Harper credentials. When the request carries a `username` and `password` in the body, no `Authorization` header is required — the operation authenticates from the body. Other shapes of this operation do need an authenticated caller: sending no credentials at all mints tokens for the user the request is already authenticated as, and [minting a scoped token](#scoped-tokens-inline-role) requires an authenticated `super_user`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The fallback behavior regarding sending no credentials is combined with other concepts in a single sentence. To ensure readers scanning the documentation can easily find it, please present this fallback behavior in separate, distinct sentences.

For example:

Call `create_authentication_tokens` with your Harper credentials. When the request carries a `username` and `password` in the body, no `Authorization` header is required — the operation authenticates from the body. Other shapes of this operation do need an authenticated caller. Sending no credentials at all mints tokens for the user the request is already authenticated as. Minting a scoped token (#scoped-tokens-inline-role) requires an authenticated `super_user`.
References
  1. Ensure critical security warnings, such as unauthorized access risks or fallback behaviors, are presented in separate, distinct sentences rather than being combined with other concepts or buried behind semicolons, so that readers scanning the documentation can easily find them.


```json
{
Expand Down Expand Up @@ -95,7 +95,7 @@ Available since: v5.2.0

A super user can mint a **scoped token**: a single JWT whose permissions are embedded in the token itself, so the bearer needs no pre-existing user or role record. This is useful for handing a limited credential (for example, read-only access) to an external service or script without provisioning it in `hdb_user`.

Pass `role` as an inline role-shaped object (the same `permission` structure used by [`add_role`](../users-and-roles/overview.md), including the `operations` allowlist). The request must be authenticated as a `super_user`; no `password` may be included:
Pass `role` as an inline role-shaped object (the same `permission` structure used by [`add_role`](../users-and-roles/overview.md), including the `operations` allowlist). Unlike the username/password flow above, this shape carries no credentials of its own — the **minter** must be authenticated as a `super_user`, and no `password` may be included in the body:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The critical security requirement that the minter must be authenticated as a super_user is combined with other concepts in a single sentence. Please present this security requirement in separate, distinct sentences to improve readability and scannability.

For example:

Pass `role` as an inline role-shaped object (the same `permission` structure used by [`add_role`](../users-and-roles/overview.md), including the `operations` allowlist). Unlike the username/password flow above, this shape carries no credentials of its own. The **minter** must be authenticated as a `super_user`. No `password` may be included in the body:
References
  1. Ensure critical security warnings, such as unauthorized access risks or fallback behaviors, are presented in separate, distinct sentences rather than being combined with other concepts or buried behind semicolons, so that readers scanning the documentation can easily find them.


```json
{
Expand All @@ -115,6 +115,22 @@ Pass `role` as an inline role-shaped object (the same `permission` structure use
}
```

Authenticate that request the way you would any other privileged operation — Basic Auth with a `super_user`'s credentials, or a `Bearer` `operation_token` already held by one:

```bash
curl --location --request POST 'http://localhost:9925' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic <base64 of super_user:password>' \
--data-raw '{
"operation": "create_authentication_tokens",
"username": "reporting-service",
"role": { "permission": { "operations": ["read_only"] } },
"expires_in": "7d"
}'
```

Without an authenticated `super_user` the mint is rejected with `403 Only super_user can create a token with an inline role`. From inside a component, pass the request context and `authorize: true` to [`server.operation()`](../http/api.md#serveroperationoperation-context-authorize) so the mint is attributed to — and permission-checked against — the calling user.

Response:

```json
Expand Down
Loading