@harperfast/template-vue-ts-studio 1.8.2 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/.agents/skills/harper-best-practices/AGENTS.md +16 -12
  2. package/.agents/skills/harper-best-practices/rules/custom-resources.md +5 -4
  3. package/.agents/skills/harper-best-practices/rules/extending-tables.md +11 -8
  4. package/agent/skills/harper-best-practices/AGENTS.md +1950 -0
  5. package/agent/skills/harper-best-practices/SKILL.md +96 -0
  6. package/agent/skills/harper-best-practices/rules/adding-tables-with-schemas.md +42 -0
  7. package/agent/skills/harper-best-practices/rules/automatic-apis.md +165 -0
  8. package/agent/skills/harper-best-practices/rules/caching.md +163 -0
  9. package/agent/skills/harper-best-practices/rules/checking-authentication.md +190 -0
  10. package/agent/skills/harper-best-practices/rules/creating-a-fabric-account-and-cluster.md +30 -0
  11. package/agent/skills/harper-best-practices/rules/creating-harper-apps.md +54 -0
  12. package/agent/skills/harper-best-practices/rules/custom-resources.md +44 -0
  13. package/agent/skills/harper-best-practices/rules/defining-relationships.md +35 -0
  14. package/agent/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +122 -0
  15. package/agent/skills/harper-best-practices/rules/extending-tables.md +46 -0
  16. package/agent/skills/harper-best-practices/rules/handling-binary-data.md +45 -0
  17. package/agent/skills/harper-best-practices/rules/load-env.md +111 -0
  18. package/agent/skills/harper-best-practices/rules/logging.md +169 -0
  19. package/agent/skills/harper-best-practices/rules/programmatic-table-requests.md +134 -0
  20. package/agent/skills/harper-best-practices/rules/querying-rest-apis.md +202 -0
  21. package/agent/skills/harper-best-practices/rules/real-time-apps.md +102 -0
  22. package/agent/skills/harper-best-practices/rules/schema-design-tooling.md +161 -0
  23. package/agent/skills/harper-best-practices/rules/serving-web-content.md +85 -0
  24. package/agent/skills/harper-best-practices/rules/typescript-type-stripping.md +64 -0
  25. package/agent/skills/harper-best-practices/rules/using-blob-datatype.md +38 -0
  26. package/agent/skills/harper-best-practices/rules/vector-indexing.md +124 -0
  27. package/agent/skills/harper-best-practices/rules.manifest.yaml +302 -0
  28. package/package.json +1 -1
  29. package/skills-lock.json +1 -1
@@ -0,0 +1,96 @@
1
+ ---
2
+ description: "Best practices for building Harper applications, covering schema definition, automatic APIs, authentication, custom resources, and data handling. Triggers on tasks involving Harper database design, API implementation, and deployment."
3
+ license: "Apache-2.0"
4
+ metadata: {"author":"harper","version":"1.0.0"}
5
+ ---
6
+ # Harper Best Practices
7
+
8
+ Guidelines for building scalable, secure, and performant applications on Harper. These practices cover everything from initial schema design to advanced deployment strategies.
9
+
10
+ ## When to Use
11
+
12
+ Reference these guidelines when:
13
+
14
+ - Defining or modifying database schemas
15
+ - Implementing or extending REST/WebSocket APIs
16
+ - Handling authentication and session management
17
+ - Working with custom resources and extensions
18
+ - Optimizing data storage and retrieval (Blobs, Vector Indexing)
19
+ - Deploying applications to Harper Fabric
20
+
21
+ ## How It Works
22
+
23
+ 1. Review the requirements for the task (schema design, API needs, or infrastructure setup).
24
+ 2. Consult the relevant category under "Rule Categories by Priority" to understand the impact of your decisions.
25
+ 3. Apply specific rules from the "Quick Reference" section below by reading their detailed rule files.
26
+ 4. If you're building a new table, prioritize the `schema-` rules.
27
+ 5. If you're extending functionality, consult the `logic-` and `api-` rules.
28
+ 6. Validate your implementation against the `ops-` rules before deployment.
29
+
30
+ ## Examples
31
+
32
+ See the concrete examples embedded in each rule subsection below (GraphQL schemas, REST query patterns, and deployment workflow snippets).
33
+
34
+ <!-- BEGIN GENERATED INDEX -->
35
+
36
+ ## Rule Categories by Priority
37
+
38
+ | Priority | Category | Impact | Prefix |
39
+ | -------- | -------------------- | ------ | --------- |
40
+ | 1 | Schema & Data Design | HIGH | `schema-` |
41
+ | 2 | API & Communication | HIGH | `api-` |
42
+ | 3 | Logic & Extension | MEDIUM | `logic-` |
43
+ | 4 | Infrastructure & Ops | MEDIUM | `ops-` |
44
+
45
+ ## Quick Reference
46
+
47
+ ### 1. Schema & Data Design (HIGH)
48
+
49
+ - `adding-tables-with-schemas` — Guidelines for adding tables to a Harper database using GraphQL schemas.
50
+ - `schema-design-tooling` — Best practices for Harper schema design, including core directives and GraphQL tooling configuration.
51
+ - `defining-relationships` — How to define and use relationships between tables in Harper using GraphQL.
52
+ - `vector-indexing` — How to enable and query vector indexes for similarity search in Harper.
53
+ - `using-blob-datatype` — How to use the Blob data type for efficient binary storage in Harper.
54
+ - `handling-binary-data` — How to store and serve binary data like images or audio in Harper.
55
+
56
+ ### 2. API & Communication (HIGH)
57
+
58
+ - `automatic-apis` — How to use Harper's automatically generated REST and WebSocket APIs.
59
+ - `querying-rest-apis` — How to use query parameters to filter, sort, and paginate Harper REST APIs.
60
+ - `real-time-apps` — How to build real-time features in Harper using WebSockets and Pub/Sub.
61
+ - `checking-authentication` — How to handle user authentication and sessions in Harper Resources.
62
+
63
+ ### 3. Logic & Extension (MEDIUM)
64
+
65
+ - `custom-resources` — How to define custom REST endpoints with JavaScript or TypeScript in Harper.
66
+ - `extending-tables` — How to add custom logic to automatically generated table resources in Harper.
67
+ - `programmatic-table-requests` — How to interact with Harper tables programmatically using the `tables` object.
68
+ - `typescript-type-stripping` — How to run TypeScript files directly in Harper without a build step.
69
+ - `caching` — How to implement integrated data caching in Harper from external sources.
70
+
71
+ ### 4. Infrastructure & Ops (MEDIUM)
72
+
73
+ - `deploying-to-harper-fabric` — How to deploy a Harper application to the Harper Fabric cloud.
74
+ - `creating-a-fabric-account-and-cluster` — How to create a Harper Fabric account, organization, and cluster.
75
+ - `creating-harper-apps` — How to initialize a new Harper application using the CLI.
76
+ - `serving-web-content` — How to serve static files and integrated Vite/React applications in Harper.
77
+ - `logging` — Best practices for logging in Harper, including console capture, the granular logger interface, and programmatic log retrieval.
78
+ - `load-env` — How to load environment variables from .env files into a Harper application using the loadEnv plugin.
79
+
80
+ <!-- END GENERATED INDEX -->
81
+
82
+ ## How to Use
83
+
84
+ Read individual rule files for detailed explanations and code examples:
85
+
86
+ ```
87
+ rules/adding-tables-with-schemas.md
88
+ rules/schema-design-tooling.md
89
+ rules/automatic-apis.md
90
+ rules/creating-harper-apps.md
91
+ rules/logging.md
92
+ ```
93
+
94
+ ## Full Compiled Document
95
+
96
+ For the complete guide with all rules expanded: `AGENTS.md`
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: adding-tables-with-schemas
3
+ description: Guidelines for adding tables to a Harper database using GraphQL schemas.
4
+ metadata:
5
+ mode: synthesized
6
+ ---
7
+
8
+ # Adding Tables with Schemas
9
+
10
+ Instructions for the agent to follow when adding tables to a Harper database.
11
+
12
+ ## When to Use
13
+
14
+ Use this skill when you need to define new data structures or modify existing ones in a Harper database.
15
+
16
+ ## How It Works
17
+
18
+ 1. **Create Dedicated Schema Files**: Prefer having a dedicated schema `.graphql` file for each table. Check the `config.yaml` file under `graphqlSchema.files` to see how it's configured. It typically accepts wildcards (e.g., `schemas/*.graphql`), but may be configured to point at a single file.
19
+ 2. **Use Directives**: All available directives for defining your schema are defined in `node_modules/harper/schema.graphql`. Common directives include `@table`, `@export`, `@primaryKey`, `@indexed`, and `@relationship`.
20
+ 3. **Define Relationships**: Link tables together using the `@relationship` directive. For more details, see the [Defining Relationships](defining-relationships.md) skill.
21
+ 4. **Enable Automatic APIs**: If you add `@table @export` to a schema type, Harper automatically sets up REST and WebSocket APIs for basic CRUD operations against that table. **Important**: REST endpoints also require `rest: true` in `config.yaml` — without it, `@export`ed tables will not respond to HTTP requests. For a detailed list of available endpoints and how to use them, see the [Automatic REST APIs](automatic-apis.md) skill.
22
+ - `GET /{TableName}`: Describes the schema itself.
23
+ - `GET /{TableName}/`: Lists all records (supports filtering, sorting, and pagination via query parameters). See the [Querying REST APIs](querying-rest-apis.md) skill for details.
24
+ - `GET /{TableName}/{id}`: Retrieves a single record by its ID.
25
+ - `POST /{TableName}/`: Creates a new record.
26
+ - `PUT /{TableName}/{id}`: Updates an existing record.
27
+ - `PATCH /{TableName}/{id}`: Performs a partial update on a record.
28
+ - `DELETE /{TableName}/`: Deletes all records or filtered records.
29
+ - `DELETE /{TableName}/{id}`: Deletes a single record by its ID.
30
+ 5. **Consider Table Extensions**: If you are going to [extend the table](./extending-tables.md) in your resources, then do not `@export` the table from the schema.
31
+
32
+ ## Examples
33
+
34
+ In a hypothetical `schemas/ExamplePerson.graphql`:
35
+
36
+ ```graphql
37
+ type ExamplePerson @table @export {
38
+ id: ID @primaryKey
39
+ name: String
40
+ tag: String @indexed
41
+ }
42
+ ```
@@ -0,0 +1,165 @@
1
+ ---
2
+ name: automatic-apis
3
+ description: How to use Harper's automatically generated REST and WebSocket APIs.
4
+ metadata:
5
+ mode: generate
6
+ sources:
7
+ - reference/v5/rest/overview.md
8
+ - reference/v5/rest/websockets.md
9
+ sourceCommit: b7fbddadd42eb4487190b650a9abc4bcfeef5819
10
+ inputHash: 8fcf0cfe190e013e
11
+ ---
12
+
13
+ # Automatic APIs
14
+
15
+ Instructions for the agent to follow when enabling and using Harper's automatically generated REST and WebSocket APIs.
16
+
17
+ ## When to Use
18
+
19
+ Apply this rule when adding REST or WebSocket API access to Harper tables or custom resources. Use it when configuring `config.yaml` to expose endpoints, mapping HTTP methods to resource operations, or implementing real-time WebSocket connections on a resource class.
20
+
21
+ ## How It Works
22
+
23
+ 1. **Enable the REST plugin**: Add `rest: true` to your application's `config.yaml`. This activates the HTTP REST interface and enables WebSocket support by default.
24
+
25
+ ```yaml
26
+ rest: true
27
+ ```
28
+
29
+ To configure optional behavior:
30
+
31
+ ```yaml
32
+ rest:
33
+ lastModified: true # enables Last-Modified response header support
34
+ webSocket: false # disables automatic WebSocket support (enabled by default)
35
+ ```
36
+
37
+ 2. **Export your resource in the schema**: Tables are not exposed by default. Use the `@export` directive in your schema definition to make a table available as a REST endpoint. The exported name defines the base URL path, served on the application HTTP server port (default `9926`).
38
+
39
+ 3. **Use the correct URL structure**: The REST interface follows a consistent path convention.
40
+
41
+ | Path | Description |
42
+ | -------------------------------------------- | ---------------------------------------------------------------------------------- |
43
+ | `/my-resource` | Returns a description of the resource (e.g., table metadata) |
44
+ | `/my-resource/` | Trailing slash — represents the full collection; append query parameters to search |
45
+ | `/my-resource/record-id` | A specific record identified by its primary key |
46
+ | `/my-resource/record-id/` | Trailing slash — collection of records with the given id prefix |
47
+ | `/my-resource/record-id/with/multiple/parts` | Record id with multiple path segments |
48
+
49
+ 4. **Map HTTP methods to operations**: Each HTTP method maps to a resource method and operation.
50
+ - **GET** — Retrieve a record or search. Calls `get()`.
51
+
52
+ ```
53
+ GET /MyTable/123
54
+ GET /MyTable/?name=Harper
55
+ GET /MyTable/123.propertyName
56
+ ```
57
+
58
+ Responses include an `ETag` header. Clients may send `If-None-Match` to receive `304 Not Modified` when the record is unchanged.
59
+
60
+ - **PUT** — Create or replace a record (upsert). Calls `put(record)`. Properties not in the body are removed.
61
+
62
+ ```
63
+ PUT /MyTable/123
64
+ Content-Type: application/json
65
+
66
+ { "name": "some data" }
67
+ ```
68
+
69
+ - **POST** — Create a new record without specifying a primary key. Calls `post(data)`. The assigned key is returned in the `Location` response header.
70
+
71
+ ```
72
+ POST /MyTable/
73
+ Content-Type: application/json
74
+
75
+ { "name": "some data" }
76
+ ```
77
+
78
+ - **PATCH** — Partially update a record, merging only provided properties. Unspecified properties are preserved.
79
+
80
+ ```
81
+ PATCH /MyTable/123
82
+ Content-Type: application/json
83
+
84
+ { "status": "active" }
85
+ ```
86
+
87
+ - **DELETE** — Delete a record or all records matching a query.
88
+ ```
89
+ DELETE /MyTable/123
90
+ DELETE /MyTable/?status=archived
91
+ ```
92
+
93
+ 5. **Access the auto-generated OpenAPI spec**: Harper generates an OpenAPI specification for all exported resources. Retrieve it at:
94
+
95
+ ```
96
+ GET /openapi
97
+ ```
98
+
99
+ 6. **Connect via WebSocket**: When `rest` is enabled, WebSocket support is on by default. Connect to a resource URL to subscribe to change events for that resource.
100
+
101
+ ```javascript
102
+ let ws = new WebSocket('wss://server/my-resource/341');
103
+ ws.onmessage = (event) => {
104
+ let data = JSON.parse(event.data);
105
+ };
106
+ ```
107
+
108
+ Connecting to `wss://server/my-resource/341` accesses the `my-resource` resource with record id `341` and subscribes to it. When the record changes or a message is published to it, the WebSocket connection receives the update.
109
+
110
+ 7. **Implement a custom `connect()` handler**: Override `connect(incomingMessages)` on a resource class to control WebSocket behavior. The method must return an async iterable or generator that produces messages to send to the client.
111
+
112
+ ## Examples
113
+
114
+ **Simple echo server using an async generator**:
115
+
116
+ ```javascript
117
+ export class Echo extends Resource {
118
+ async *connect(incomingMessages) {
119
+ for await (let message of incomingMessages) {
120
+ yield message; // echo each message back
121
+ }
122
+ }
123
+ }
124
+ ```
125
+
126
+ **Using the default `connect()` with event-style access and a timer**:
127
+
128
+ ```javascript
129
+ export class Example extends Resource {
130
+ connect(incomingMessages) {
131
+ let outgoingMessages = super.connect();
132
+
133
+ let timer = setInterval(() => {
134
+ outgoingMessages.send({ greeting: 'hi again!' });
135
+ }, 1000);
136
+
137
+ incomingMessages.on('data', (message) => {
138
+ outgoingMessages.send(message); // echo incoming messages
139
+ });
140
+
141
+ outgoingMessages.on('close', () => {
142
+ clearInterval(timer);
143
+ });
144
+
145
+ return outgoingMessages;
146
+ }
147
+ }
148
+ ```
149
+
150
+ **Minimal `config.yaml` enabling REST with WebSocket disabled**:
151
+
152
+ ```yaml
153
+ rest:
154
+ webSocket: false
155
+ ```
156
+
157
+ ## Notes
158
+
159
+ - Tables must be explicitly exported using `@export` in the schema — they are not exposed by default.
160
+ - `rest: true` is the minimal configuration to enable both REST and WebSocket support. See [real-time-apps.md](real-time-apps.md) for patterns around real-time WebSocket usage.
161
+ - For full query syntax on `GET` and `DELETE` with query parameters, see [querying-rest-apis.md](querying-rest-apis.md).
162
+ - The default `connect()` returns an iterable with a `send(message)` method and a `close` event for cleanup on disconnect.
163
+ - For MQTT over WebSockets, set the sub-protocol header `Sec-WebSocket-Protocol: mqtt`.
164
+ - In distributed environments, non-retained messages are delivered in the order received per node; retained messages (PUT/updated records) keep only the latest-timestamp version as the winning record across the cluster.
165
+ - Use the `Content-Type` request header to specify body format and the `Accept` header to request a specific response format.
@@ -0,0 +1,163 @@
1
+ ---
2
+ name: caching
3
+ description: How to implement integrated data caching in Harper from external sources.
4
+ metadata:
5
+ mode: generate
6
+ sources:
7
+ - learn/developers/caching-with-harper.md
8
+ sourceCommit: b7fbddadd42eb4487190b650a9abc4bcfeef5819
9
+ inputHash: 8212a7d7dbbd2b18
10
+ ---
11
+
12
+ # Caching External Data Sources in Harper
13
+
14
+ Instructions for the agent to implement integrated data caching in Harper by wrapping external sources with a cache table and `sourcedFrom`.
15
+
16
+ ## When to Use
17
+
18
+ Apply this rule when a Harper application needs to cache responses from an external API, microservice, or database to avoid repeated slow or expensive upstream calls. Use it whenever you need to define TTL-based cache expiration, observe ETag-based conditional responses, or manually invalidate cached entries.
19
+
20
+ ## How It Works
21
+
22
+ 1. **Define a cache table with `expiration`**: In `schema.graphql`, add the `expiration` argument to `@table`. The value is in seconds. Any record older than this threshold is considered stale and will be re-fetched on next access.
23
+
24
+ ```graphql
25
+ type JokeCache @table(expiration: 60) @export {
26
+ id: ID @primaryKey
27
+ setup: String
28
+ punchline: String
29
+ }
30
+ ```
31
+
32
+ 2. **Wrap the external source in `resources.js`**: Create an object with a `get(id)` method that fetches from the upstream source. Then call `sourcedFrom` on the table to register it.
33
+
34
+ ```javascript
35
+ const jokeAPI = {
36
+ async get(id) {
37
+ const response = await fetch(`https://official-joke-api.appspot.com/jokes/${id}`);
38
+ return response.json();
39
+ },
40
+ };
41
+
42
+ tables.JokeCache.sourcedFrom(jokeAPI);
43
+ ```
44
+
45
+ Harper's caching behavior after `sourcedFrom` is registered:
46
+ - A request arrives for `/JokeCache/1`.
47
+ - Harper checks if the record with id `1` exists in `JokeCache` and is not stale.
48
+ - If fresh, Harper returns it immediately.
49
+ - If missing or stale, Harper calls `jokeAPI.get()`, stores the result in `JokeCache`, and returns it.
50
+ - Multiple simultaneous requests for the same missing or stale record wait on a single upstream call — Harper prevents cache stampedes automatically.
51
+
52
+ 3. **Configure plugins in `config.yaml`**: Enable the schema, REST API, and JS resource plugins.
53
+
54
+ ```yaml
55
+ graphqlSchema:
56
+ files: 'schema.graphql'
57
+ rest: true
58
+ jsResource:
59
+ files: 'resources.js'
60
+ ```
61
+
62
+ 4. **Observe caching via ETags**: Harper automatically computes an ETag from the record's last-modified timestamp. On the first request you receive a `200` with an `etag` header. Pass that value back in `If-None-Match` on subsequent requests; Harper returns `304 Not Modified` with an empty body if the record is unchanged.
63
+
64
+ ```bash
65
+ curl -i 'http://localhost:9926/JokeCache/1' \
66
+ -H 'If-None-Match: "abCDefGHij"'
67
+ ```
68
+
69
+ 5. **Force a cache bypass**: Send `Cache-Control: no-cache` to make Harper skip the local cache and always call the upstream source, regardless of TTL.
70
+
71
+ ```bash
72
+ curl -i 'http://localhost:9926/JokeCache/1' \
73
+ -H 'Cache-Control: no-cache'
74
+ ```
75
+
76
+ 6. **Invalidate a cache entry on demand**: Remove `@export` from the schema type, then export a class of the same name in `resources.js` that extends the table and implements a `post` handler calling `this.invalidate(target)`.
77
+
78
+ ```graphql
79
+ type JokeCache @table(expiration: 60) {
80
+ id: ID @primaryKey
81
+ setup: String
82
+ punchline: String
83
+ }
84
+ ```
85
+
86
+ ```javascript
87
+ export class JokeCache extends tables.JokeCache {
88
+ static async post(target, data) {
89
+ const body = await data;
90
+ if (body?.action === 'invalidate') {
91
+ this.invalidate(target);
92
+ return { status: 200, data: { message: 'invalidated' } };
93
+ }
94
+ }
95
+ }
96
+ ```
97
+
98
+ Trigger invalidation with a `POST`:
99
+
100
+ ```bash
101
+ curl -X POST 'http://localhost:9926/JokeCache/1' \
102
+ -H 'Content-Type: application/json' \
103
+ -d '{"action": "invalidate"}'
104
+ ```
105
+
106
+ The next `GET /JokeCache/1` will fetch fresh data from the upstream source regardless of TTL.
107
+
108
+ ## Examples
109
+
110
+ Complete `schema.graphql` and `resources.js` for a cached external API with on-demand invalidation:
111
+
112
+ ```graphql
113
+ type JokeCache @table(expiration: 60) {
114
+ id: ID @primaryKey
115
+ setup: String
116
+ punchline: String
117
+ }
118
+ ```
119
+
120
+ ```javascript
121
+ // resources.js
122
+
123
+ const jokeAPI = {
124
+ async get() {
125
+ const id = this.getId();
126
+ const response = await fetch(`https://official-joke-api.appspot.com/jokes/${id}`);
127
+ return response.json();
128
+ },
129
+ };
130
+
131
+ tables.JokeCache.sourcedFrom(jokeAPI);
132
+
133
+ export class JokeCache extends tables.JokeCache {
134
+ static async post(target, data) {
135
+ const body = await data;
136
+ if (body?.action === 'invalidate') {
137
+ this.invalidate(target);
138
+ return { status: 200, data: { message: 'invalidated' } };
139
+ }
140
+ }
141
+ }
142
+ ```
143
+
144
+ First request — cache miss, upstream is called, `200` returned:
145
+
146
+ ```bash
147
+ curl -i 'http://localhost:9926/JokeCache/1'
148
+ ```
149
+
150
+ Second request with ETag — cache hit, `304 Not Modified`:
151
+
152
+ ```bash
153
+ curl -i 'http://localhost:9926/JokeCache/1' \
154
+ -H 'If-None-Match: "abCDefGHij"'
155
+ ```
156
+
157
+ ## Notes
158
+
159
+ - `expiration` is measured in seconds. Harper also supports separate `eviction` and `scanInterval` arguments on `@table` for fine-grained control over physical record removal.
160
+ - The `@export` directive on the schema type is not required when you export a Resource class of the same name from `resources.js` — the class export serves as the endpoint registration. See [custom-resources.md](custom-resources.md) for details on building Resource classes.
161
+ - Harper's REST layer automatically exposes `@export`-ed tables and Resource classes as HTTP endpoints. See [automatic-apis.md](automatic-apis.md) for how endpoints are structured and named.
162
+ - ETag values include their double quotes as part of the value — include them verbatim when passing the value in `If-None-Match`.
163
+ - `sourcedFrom` must be called after the table reference (`tables.JokeCache`) is available, which is guaranteed when the call is at the top level of `resources.js`.
@@ -0,0 +1,190 @@
1
+ ---
2
+ name: checking-authentication
3
+ description: How to handle user authentication and sessions in Harper Resources.
4
+ metadata:
5
+ mode: generate
6
+ sources:
7
+ - >-
8
+ reference/v5/resources/resource-api.md#`getCurrentUser(): User |
9
+ undefined`
10
+ - reference/v5/resources/resource-api.md#Session and Login from a Resource
11
+ - reference/v5/security/jwt-authentication.md
12
+ sourceCommit: b7fbddadd42eb4487190b650a9abc4bcfeef5819
13
+ inputHash: fdd9ec3b11011490
14
+ ---
15
+
16
+ # Checking Authentication
17
+
18
+ Instructions for the agent to follow when handling user authentication and session management inside Harper Resources.
19
+
20
+ ## When to Use
21
+
22
+ Apply this rule when implementing authentication checks, login/logout flows, or token issuance inside a custom Resource. Use it any time a Resource needs to identify the current user, establish a session, or issue JWTs to clients. See [custom-resources.md](custom-resources.md) for the general Resource authoring pattern.
23
+
24
+ ## How It Works
25
+
26
+ 1. **Check the current user** with `getCurrentUser()`. Call it inside any Resource method to retrieve the authenticated user or `undefined` if no user is authenticated. Guard protected endpoints by returning a `401` when the result is `undefined`.
27
+
28
+ ```javascript
29
+ async get(target) {
30
+ const user = this.getCurrentUser();
31
+ if (!user) return new Response(null, { status: 401 });
32
+ return { username: user.username, role: user.role };
33
+ }
34
+ ```
35
+
36
+ The returned object exposes `username`, `role`, and `role.permission` flags.
37
+
38
+ 2. **Enable sessions** before using session-based login. Set `authentication.enableSessions: true` in `harperdb-config.yaml`:
39
+
40
+ ```yaml
41
+ authentication:
42
+ enableSessions: true
43
+ ```
44
+
45
+ 3. **Access login and session helpers** via `getContext()`. The context object exposes `context.login` and `context.session` for sign-in/out flows.
46
+ - Call `context.login(username, password)` to verify credentials and establish a session cookie on success.
47
+ - To end a session, delete it via `context.session.delete(context.session.id)`.
48
+
49
+ 4. **Implement sign-in and sign-out Resources** using the context helpers:
50
+
51
+ ```javascript
52
+ export class SignIn extends Resource {
53
+ async post(_target, data) {
54
+ const context = this.getContext();
55
+ try {
56
+ await context.login(data.username, data.password);
57
+ } catch {
58
+ return new Response('Invalid credentials', { status: 403 });
59
+ }
60
+ return new Response('Logged in', { status: 200 });
61
+ }
62
+ }
63
+
64
+ export class SignOut extends Resource {
65
+ async post() {
66
+ const context = this.getContext();
67
+ if (!context.session) return new Response(null, { status: 401 });
68
+ await context.session.delete(context.session.id);
69
+ return new Response('Logged out', { status: 200 });
70
+ }
71
+ }
72
+ ```
73
+
74
+ 5. **Issue JWTs for non-browser clients** (CLI tools, mobile apps, service-to-service). Cookie-based sessions are intended for browser clients. For other clients, mint tokens programmatically using `server.operation()`:
75
+
76
+ ```javascript
77
+ import { Resource, server } from 'harper';
78
+
79
+ export class IssueTokens extends Resource {
80
+ static async get(_target, context) {
81
+ const { operation_token, refresh_token } = await server.operation(
82
+ { operation: 'create_authentication_tokens' },
83
+ context,
84
+ true,
85
+ );
86
+ return { operation_token, refresh_token };
87
+ }
88
+
89
+ static async post(_target, data) {
90
+ const { username, password } = await data;
91
+ if (!username || !password) {
92
+ return new Response('username and password required', { status: 400 });
93
+ }
94
+ const { operation_token, refresh_token } = await server.operation({
95
+ operation: 'create_authentication_tokens',
96
+ username,
97
+ password,
98
+ });
99
+ return { operation_token, refresh_token };
100
+ }
101
+ }
102
+
103
+ export class RefreshJWT extends Resource {
104
+ static async post(_target, data) {
105
+ const { refresh_token } = await data;
106
+ if (!refresh_token) {
107
+ return new Response('refresh_token required', { status: 400 });
108
+ }
109
+ const { operation_token } = await server.operation({
110
+ operation: 'refresh_operation_token',
111
+ refresh_token,
112
+ });
113
+ return { operation_token };
114
+ }
115
+ }
116
+ ```
117
+
118
+ Pass `true` as the third argument to `server.operation()` when the operation should run as the current authenticated user. Omit it or pass `false` when the operation supplies its own credentials.
119
+
120
+ 6. **Configure JWT token expiry** in `harperdb-config.yaml` under the `authentication` section:
121
+
122
+ ```yaml
123
+ authentication:
124
+ operationTokenTimeout: 1d
125
+ refreshTokenTimeout: 30d
126
+ ```
127
+
128
+ Duration strings follow the `jsonwebtoken` package format (e.g., `1d`, `12h`, `60m`).
129
+
130
+ ## Examples
131
+
132
+ **Protecting a resource endpoint and returning user info:**
133
+
134
+ ```javascript
135
+ async get(target) {
136
+ const user = this.getCurrentUser();
137
+ if (!user) return new Response(null, { status: 401 });
138
+ return { username: user.username, role: user.role };
139
+ }
140
+ ```
141
+
142
+ **Full session-based sign-in/sign-out flow:**
143
+
144
+ ```javascript
145
+ export class SignIn extends Resource {
146
+ async post(_target, data) {
147
+ const context = this.getContext();
148
+ try {
149
+ await context.login(data.username, data.password);
150
+ } catch {
151
+ return new Response('Invalid credentials', { status: 403 });
152
+ }
153
+ return new Response('Logged in', { status: 200 });
154
+ }
155
+ }
156
+
157
+ export class SignOut extends Resource {
158
+ async post() {
159
+ const context = this.getContext();
160
+ if (!context.session) return new Response(null, { status: 401 });
161
+ await context.session.delete(context.session.id);
162
+ return new Response('Logged out', { status: 200 });
163
+ }
164
+ }
165
+ ```
166
+
167
+ **JWT token refresh endpoint:**
168
+
169
+ ```javascript
170
+ export class RefreshJWT extends Resource {
171
+ static async post(_target, data) {
172
+ const { refresh_token } = await data;
173
+ if (!refresh_token) {
174
+ return new Response('refresh_token required', { status: 400 });
175
+ }
176
+ const { operation_token } = await server.operation({
177
+ operation: 'refresh_operation_token',
178
+ refresh_token,
179
+ });
180
+ return { operation_token };
181
+ }
182
+ }
183
+ ```
184
+
185
+ ## Notes
186
+
187
+ - `getCurrentUser()` and `getContext()` are instance methods; call them with `this` inside non-static Resource methods.
188
+ - `enableSessions` must be `true` in config before `context.login` or `context.session` will function.
189
+ - Cookie-based sessions target browser clients. Use JWT issuance via `server.operation()` for all other client types.
190
+ - When both `operation_token` and `refresh_token` have expired, the client must call `create_authentication_tokens` again with credentials.
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: creating-a-fabric-account-and-cluster
3
+ description: How to create a Harper Fabric account, organization, and cluster.
4
+ metadata:
5
+ mode: synthesized
6
+ ---
7
+
8
+ # Creating a Harper Fabric Account and Cluster
9
+
10
+ Follow these steps to set up your Harper Fabric environment for deployment.
11
+
12
+ ## How It Works
13
+
14
+ 1. **Sign Up/In**: Go to [https://fabric.harper.fast/](https://fabric.harper.fast/) and sign up or sign in.
15
+ 2. **Create an Organization**: Create an organization (org) to manage your projects.
16
+ 3. **Create a Cluster**: Create a new cluster. This can be on the free tier, no credit card required.
17
+ 4. **Set Credentials**: During setup, set the cluster username and password to finish configuring it.
18
+ 5. **Get Application URL**: Navigate to the **Config** tab and copy the **Application URL**.
19
+ 6. **Configure Environment**: Update your `.env` file or GitHub Actions secrets with cluster-specific credentials.
20
+ 7. **Next Steps**: See the [deploying-to-harper-fabric](deploying-to-harper-fabric.md) rule for detailed instructions on deploying your application successfully.
21
+
22
+ ## Examples
23
+
24
+ ### Environment Configuration
25
+
26
+ ```bash
27
+ CLI_TARGET_USERNAME='YOUR_CLUSTER_USERNAME'
28
+ CLI_TARGET_PASSWORD='YOUR_CLUSTER_PASSWORD'
29
+ CLI_TARGET='YOUR_CLUSTER_URL'
30
+ ```