@harperfast/skills 1.5.1 → 1.6.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.
@@ -2,41 +2,8 @@
2
2
 
3
3
  Guidelines for building scalable, secure, and performant applications on Harper. These practices cover everything from initial schema design to advanced deployment strategies.
4
4
 
5
- ---
6
-
7
- ## Table of Contents
8
-
9
- 1. [Schema & Data Design](#1-schema--data-design) — **HIGH**
10
- - 1.1 [Adding Tables with Schemas](#11-adding-tables-with-schemas)
11
- - 1.2 [Schema Design & Tooling](#12-schema-design--tooling)
12
- - 1.3 [Defining Relationships](#13-defining-relationships)
13
- - 1.4 [Vector Indexing](#14-vector-indexing)
14
- - 1.5 [Using Blobs](#15-using-blobs)
15
- - 1.6 [Handling Binary Data](#16-handling-binary-data)
16
- 2. [API & Communication](#2-api--communication) — **HIGH**
17
- - 2.1 [Automatic REST APIs](#21-automatic-rest-apis)
18
- - 2.2 [Querying REST APIs](#22-querying-rest-apis)
19
- - 2.3 [Real-time Applications](#23-real-time-applications)
20
- - 2.4 [Checking Authentication](#24-checking-authentication)
21
- 3. [Logic & Extension](#3-logic--extension) — **MEDIUM**
22
- - 3.1 [Custom Resources](#31-custom-resources)
23
- - 3.2 [Extending Table Resources](#32-extending-table-resources)
24
- - 3.3 [Programmatic Table Requests](#33-programmatic-table-requests)
25
- - 3.4 [TypeScript Type Stripping](#34-typescript-type-stripping)
26
- - 3.5 [Caching](#35-caching)
27
- 4. [Infrastructure & Ops](#4-infrastructure--ops) — **MEDIUM**
28
- - 4.1 [Creating Harper Applications](#41-creating-harper-applications)
29
- - 4.2 [Creating a Fabric Account and Cluster](#42-creating-a-fabric-account-and-cluster)
30
- - 4.3 [Deploying to Harper Fabric](#43-deploying-to-harper-fabric)
31
- - 4.4 [Serving Web Content](#44-serving-web-content)
32
- - 4.5 [Logging Best Practices](#45-logging-best-practices)
33
-
34
- ---
35
-
36
5
  ## 1. Schema & Data Design
37
6
 
38
- **Impact: HIGH**
39
-
40
7
  ### 1.1 Adding Tables with Schemas
41
8
 
42
9
  Instructions for the agent to follow when adding tables to a Harper database.
@@ -49,11 +16,21 @@ Use this skill when you need to define new data structures or modify existing on
49
16
 
50
17
  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.
51
18
  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`.
52
- 3. **Define Relationships**: Link tables together using the `@relationship` directive.
53
- 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.
54
- 5. **Consider Table Extensions**: If you are going to extend the table in your resources, then do not `@export` the table from the schema.
55
-
56
- #### Example
19
+ 3. **Define Relationships**: Link tables together using the `@relationship` directive. For more details, see the [Defining Relationships](defining-relationships.md) skill.
20
+ 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.
21
+ - `GET /{TableName}`: Describes the schema itself.
22
+ - `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.
23
+ - `GET /{TableName}/{id}`: Retrieves a single record by its ID.
24
+ - `POST /{TableName}/`: Creates a new record.
25
+ - `PUT /{TableName}/{id}`: Updates an existing record.
26
+ - `PATCH /{TableName}/{id}`: Performs a partial update on a record.
27
+ - `DELETE /{TableName}/`: Deletes all records or filtered records.
28
+ - `DELETE /{TableName}/{id}`: Deletes a single record by its ID.
29
+ 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.
30
+
31
+ #### Examples
32
+
33
+ In a hypothetical `schemas/ExamplePerson.graphql`:
57
34
 
58
35
  ```graphql
59
36
  type ExamplePerson @table @export {
@@ -129,16 +106,16 @@ my-harper-app/
129
106
 
130
107
  ### 1.3 Defining Relationships
131
108
 
132
- Using the `@relationship` directive to link tables.
109
+ Instructions for the agent to follow when defining relationships between Harper tables.
133
110
 
134
111
  #### When to Use
135
112
 
136
- Use this when you have two or more tables that need to be logically linked (e.g., a "Product" table and a "Category" table).
113
+ Use this skill when you need to link data across different tables, enabling automatic joins and efficient related-data fetching via REST APIs.
137
114
 
138
115
  #### How It Works
139
116
 
140
117
  1. **Identify the Relationship Type**: Determine if it's one-to-one, many-to-one, or one-to-many.
141
- 2. **Apply the `@relationship` Directive**: In your GraphQL schema, use the `@relationship` directive on the field that links to another table.
118
+ 2. **Use the `@relationship` Directive**: Apply it to a field in your GraphQL schema.
142
119
  - **Many-to-One (Current table holds FK)**: Use `from`.
143
120
  ```graphql
144
121
  type Book @table @export {
@@ -153,262 +130,782 @@ Use this when you have two or more tables that need to be logically linked (e.g.
153
130
  }
154
131
  ```
155
132
  3. **Query with Relationships**: Use dot syntax in REST API calls for filtering or the `select()` operator for including related data.
156
-
157
- #### Example
158
-
159
- ```graphql
160
- type Product @table @export {
161
- id: ID @primaryKey
162
- name: String
163
- categoryId: ID
164
- category: Category @relationship(from: "categoryId")
165
- }
166
-
167
- type Category @table @export {
168
- id: ID @primaryKey
169
- name: String
170
- products: [Product] @relationship(to: "categoryId")
171
- }
172
- ```
133
+ - Example Filter: `GET /Book/?author.name=Harper`
134
+ - Example Select: `GET /Author/?select(name,books(title))`
173
135
 
174
136
  ### 1.4 Vector Indexing
175
137
 
176
- How to define and use vector indexes for efficient similarity search.
138
+ Instructions for the agent to follow when enabling and querying vector indexes for similarity search in Harper using the HNSW algorithm.
177
139
 
178
140
  #### When to Use
179
141
 
180
- Use this when you need to perform similarity searches on high-dimensional data, such as image embeddings, text embeddings, or any other numeric vectors.
142
+ Apply this rule when adding a vector index to a Harper table schema or writing similarity search queries against high-dimensional vector fields. Use it whenever you need approximate nearest-neighbor search, distance-threshold filtering, or distance-scored results.
181
143
 
182
144
  #### How It Works
183
145
 
184
- 1. **Define the Vector Field**: In your GraphQL schema, define a field with a list of floats (e.g., `[Float]`).
185
- 2. **Apply the `@indexed` Directive**: Use the `@indexed` directive on the vector field and specify the index type as `HNSW`.
186
- 3. **Configure the Index (Optional)**: You can provide additional configuration for the vector index, such as the distance metric (e.g., `cosine`, `euclidean`).
187
- 4. **Querying**: Use the `vector` operator in your REST or programmatic requests to perform similarity searches.
146
+ 1. **Declare a vector index on a `[Float]` field**: Add `@indexed(type: "HNSW")` to any `[Float]` attribute in a `@table` type. See [adding-tables-with-schemas.md](adding-tables-with-schemas.md) for general schema setup.
147
+
148
+ ```graphql
149
+ type Document @table {
150
+ id: Long @primaryKey
151
+ textEmbeddings: [Float] @indexed(type: "HNSW")
152
+ }
153
+ ```
154
+
155
+ 2. **Query by nearest neighbors using `sort`**: Call `Document.search()` with a `sort` object specifying `attribute` (the indexed field) and `target` (the query vector). Include `limit` to cap results.
156
+
157
+ ```javascript
158
+ let results = Document.search({
159
+ sort: { attribute: 'textEmbeddings', target: searchVector },
160
+ limit: 5,
161
+ });
162
+ ```
163
+
164
+ 3. **Combine HNSW with filter conditions**: Add a `conditions` array alongside `sort` to pre-filter records before ranking by similarity.
165
+
166
+ ```javascript
167
+ let results = Document.search({
168
+ conditions: [{ attribute: 'price', comparator: 'lt', value: 50 }],
169
+ sort: { attribute: 'textEmbeddings', target: searchVector },
170
+ limit: 5,
171
+ });
172
+ ```
173
+
174
+ 4. **Filter by distance threshold**: Place `target` directly on a condition (alongside `attribute`, `comparator`, and `value`) to return only records whose distance to the target vector is below a threshold. Use this form to bound result quality by a similarity cutoff rather than ranking.
175
+
176
+ ```javascript
177
+ let results = Document.search({
178
+ conditions: {
179
+ attribute: 'textEmbeddings',
180
+ comparator: 'lt',
181
+ value: 0.1,
182
+ target: searchVector,
183
+ },
184
+ });
185
+ ```
186
+
187
+ 5. **Include computed distance in results**: Add `'$distance'` to the `select` array to return the computed distance from the target vector alongside each record. `$distance` works in both `sort`-based and `conditions`-based queries.
188
+
189
+ ```javascript
190
+ let results = Document.search({
191
+ select: ['name', '$distance'],
192
+ sort: { attribute: 'textEmbeddings', target: searchVector },
193
+ limit: 5,
194
+ });
195
+ ```
196
+
197
+ 6. **Tune HNSW parameters**: Pass additional parameters to `@indexed(type: "HNSW", ...)` to control index quality and performance:
188
198
 
189
- #### Example
199
+ | Parameter | Default | Description |
200
+ | ---------------------- | ----------------- | --------------------------------------------------------------------------------------------------- |
201
+ | `distance` | `"cosine"` | Distance function: `"euclidean"` or `"cosine"` (negative cosine similarity) |
202
+ | `efConstruction` | `100` | Max nodes explored during index construction. Higher = better recall, lower = better performance |
203
+ | `M` | `16` | Preferred connections per graph layer. Higher = more space, better recall for high-dimensional data |
204
+ | `optimizeRouting` | `0.5` | Heuristic aggressiveness for omitting redundant connections (0 = off, 1 = most aggressive) |
205
+ | `mL` | computed from `M` | Normalization factor for level generation |
206
+ | `efSearchConstruction` | `50` | Max nodes explored during search |
207
+
208
+ #### Examples
209
+
210
+ **Schema with custom HNSW parameters:**
190
211
 
191
212
  ```graphql
192
- type Document @table @export {
193
- id: ID @primaryKey
194
- content: String
195
- embedding: [Float] @indexed(type: "HNSW")
213
+ type Document @table {
214
+ id: Long @primaryKey
215
+ textEmbeddings: [Float]
216
+ @indexed(type: "HNSW", distance: "euclidean", optimizeRouting: 0, efSearchConstruction: 100)
196
217
  }
197
218
  ```
198
219
 
199
- ### 1.5 Using Blobs
220
+ **Nearest-neighbor search with distance output:**
221
+
222
+ ```javascript
223
+ let results = Document.search({
224
+ select: ['name', '$distance'],
225
+ sort: { attribute: 'textEmbeddings', target: searchVector },
226
+ limit: 5,
227
+ });
228
+ ```
229
+
230
+ **Distance-threshold filter (no ranking):**
231
+
232
+ ```javascript
233
+ let results = Document.search({
234
+ conditions: {
235
+ attribute: 'textEmbeddings',
236
+ comparator: 'lt',
237
+ value: 0.1,
238
+ target: searchVector,
239
+ },
240
+ });
241
+ ```
242
+
243
+ #### Notes
244
+
245
+ - The default `distance` function is `cosine`. To use Euclidean distance, set `distance: "euclidean"` in the `@indexed` directive.
246
+ - `efConstruction` controls index build quality; increase it to improve recall at the cost of slower indexing.
247
+ - `$distance` is a special field — prefix it with `$` exactly as shown; it is not a schema attribute.
248
+ - `target` is required in both `sort`-based and threshold-based condition queries to identify the reference vector for distance computation.
249
+
250
+ ### 1.5 Using Blob Datatype
200
251
 
201
- How to store and retrieve large data in Harper.
252
+ Instructions for the agent to follow when working with the Blob data type in Harper.
202
253
 
203
254
  #### When to Use
204
255
 
205
- Use this when you need to store large, unstructured data such as files, images, or large text documents that exceed the typical size of a standard database field.
256
+ Use this skill when you need to store unstructured or large binary data (media, documents) that is too large for standard JSON fields. Blobs provide efficient storage and integrated streaming support.
206
257
 
207
258
  #### How It Works
208
259
 
209
- 1. **Define the Blob Field**: Use the `Blob` scalar type in your GraphQL schema.
210
- 2. **Storing Data**: Send the data as a buffer or a stream when creating or updating a record.
211
- 3. **Retrieving Data**: Access the blob field, which will return the data as a stream or buffer.
260
+ 1. **Define Blob Fields**: In your GraphQL schema, use the `Blob` type:
261
+ ```graphql
262
+ type MyTable @table {
263
+ id: ID @primaryKey
264
+ data: Blob
265
+ }
266
+ ```
267
+ 2. **Create and Store Blobs**: Use `createBlob()` from Harper's globals to wrap Buffers or Streams:
268
+ ```javascript
269
+ import { tables } from 'harper';
270
+ const blob = createBlob(largeBuffer);
271
+ await tables.MyTable.put('my-id', { data: blob });
272
+ ```
273
+ 3. **Use Streaming (Optional)**: For very large files, pass a stream to `createBlob()` to avoid loading the entire file into memory.
274
+ 4. **Read Blob Data**: Retrieve the record and use `.bytes()` or streaming interfaces on the blob field:
275
+ ```javascript
276
+ const record = await tables.MyTable.get('my-id');
277
+ const buffer = await record.data.bytes();
278
+ ```
279
+ 5. **Ensure Write Completion**: Use `saveBeforeCommit: true` in `createBlob` options if you need the blob fully written before the record is committed.
280
+ 6. **Handle Errors**: Attach error listeners to the blob object to handle streaming failures.
212
281
 
213
282
  ### 1.6 Handling Binary Data
214
283
 
215
- How to store and serve binary data like images or MP3s.
284
+ Instructions for the agent to follow when handling binary data in Harper.
216
285
 
217
286
  #### When to Use
218
287
 
219
- Use this when your application needs to handle binary files, particularly for storage and retrieval.
288
+ Use this skill when you need to store binary files (images, audio, etc.) in the database or serve them back to clients via REST endpoints.
220
289
 
221
290
  #### How It Works
222
291
 
223
- 1. **Use the `Blob` type**: As with general large data, the `Blob` type is best for binary files. Ensure you store and retrieve the appropriate MIME type (e.g., `image/jpeg`, `audio/mpeg`) for the data.
224
- 2. **Streaming**: For large files, use streaming to minimize memory usage during upload and download.
225
- 3. **MIME Types**: Store the MIME type alongside the binary data to ensure it is served correctly by your application logic.
292
+ 1. **Store Binary Data**: In your resource's `post` or `put` method, convert incoming data to Buffers and then to Blobs using `createBlob` from Harper's globals. Include the MIME type if available:
293
+
294
+ ```typescript
295
+ async post(target, record) {
296
+ if (record.data) {
297
+ record.data = createBlob(Buffer.from(record.data, record.encoding || 'base64'), {
298
+ type: record.contentType || 'application/octet-stream',
299
+ });
300
+ }
301
+ return super.post(target, record);
302
+ }
303
+ ```
226
304
 
227
- ---
305
+ 2. **Serve Binary Data**: In your resource's `get` method, return a response object with the appropriate `Content-Type` and the binary data in the `body`:
306
+ ```typescript
307
+ async get(target) {
308
+ const record = await super.get(target);
309
+ if (record?.data) {
310
+ return {
311
+ status: 200,
312
+ headers: { 'Content-Type': record.data.type || 'application/octet-stream' },
313
+ body: record.data,
314
+ };
315
+ }
316
+ return record;
317
+ }
318
+ ```
319
+ 3. **Use the Blob Type**: Ensure your GraphQL schema uses the `Blob` scalar for binary fields.
228
320
 
229
321
  ## 2. API & Communication
230
322
 
231
- **Impact: HIGH**
323
+ ### 2.1 Automatic APIs
324
+
325
+ Instructions for the agent to follow when utilizing Harper's automatic APIs.
232
326
 
233
- ### 2.1 Automatic REST APIs
327
+ #### When to Use
328
+
329
+ Use this skill when you want to interact with Harper tables via REST or WebSockets without writing custom resource logic. This is ideal for basic CRUD operations and real-time updates.
330
+
331
+ #### How It Works
332
+
333
+ 1. **Enable REST in `config.yaml`**: REST endpoints are **not active by default**. You must explicitly enable them:
334
+ ```yaml
335
+ rest: true
336
+ ```
337
+ Without this, `@export`ed tables will not respond to HTTP requests.
338
+ 2. **Enable Automatic APIs**: Ensure your GraphQL schema includes the `@export` directive for the table.
339
+ 3. **Access REST Endpoints**: Use the standard endpoints for your table (Note: Paths are case-sensitive).
340
+ 4. **Use Automatic WebSockets**: Connect to `wss://your-harper-instance/{TableName}` to receive events whenever updates are made to that table. This is the easiest way to add real-time capabilities. (Use `ws://` for local development without SSL). For more complex needs, see [Real-time Apps](real-time-apps.md).
341
+ 5. **Apply Filtering and Querying**: Use query parameters with `GET /{TableName}/` and `DELETE /{TableName}/`. See the [Querying REST APIs](querying-rest-apis.md) skill for advanced details.
342
+ 6. **Customize if Needed**: If the automatic APIs don't meet your requirements, [customize the resources](./custom-resources.md).
343
+
344
+ #### Examples
345
+
346
+ ##### Schema Configuration
234
347
 
235
- Details on the CRUD endpoints automatically generated for exported tables.
348
+ ```graphql
349
+ type MyTable @table @export {
350
+ id: ID @primaryKey
351
+ name: String
352
+ }
353
+ ```
236
354
 
237
- #### Endpoints
355
+ ##### Common REST Operations
238
356
 
239
- - `GET /{TableName}`: Describes the schema.
240
- - `GET /{TableName}/`: Lists records (supports filtering/sorting).
241
- - `GET /{TableName}/{id}`: Gets a record by ID.
242
- - `POST /{TableName}/`: Creates a record.
243
- - `PUT /{TableName}/{id}`: Updates a record.
244
- - `PATCH /{TableName}/{id}`: Partial update.
245
- - `DELETE /{TableName}/`: Deletes records.
246
- - `DELETE /{TableName}/{id}`: Deletes by ID.
357
+ - **List Records**: `GET /MyTable/`
358
+ - **Create Record**: `POST /MyTable/`
359
+ - **Update Record**: `PATCH /MyTable/{id}`
247
360
 
248
361
  ### 2.2 Querying REST APIs
249
362
 
250
- How to use filters, operators, sorting, and pagination in REST requests.
363
+ Instructions for the agent to follow when querying Harper's REST APIs.
364
+
365
+ #### When to Use
366
+
367
+ Use this skill when you need to perform advanced data retrieval (filtering, sorting, pagination, joins) using Harper's automatic REST endpoints.
251
368
 
252
- #### Query Parameters
369
+ #### How It Works
253
370
 
254
- - `limit(count)` or `limit(offset, count)`: Number of records to return and optional skip. Example: `?limit(10)`, `?limit(10, 20)`.
255
- - `sort(+field1, -field2)`: Fields to sort by. Use `+` for ascending and `-` for descending. Example: `?sort(+name)`, `?sort(-price, +name)`.
256
- - `select(field1, field2)`: Specific fields to return. Example: `?select(id, name)`.
257
- - `filter`: Advanced filtering using comparison operators and logic.
258
- - Operators: `gt`, `ge`, `lt`, `le`, `ne`. Example: `?price=gt=100`.
259
- - Logic: `&` (AND), `|` (OR), `()` (grouping). Example: `?(category=electronics|category=books)&price=lt=500`.
371
+ 1. **Basic Filtering**: Use attribute names as query parameters: `GET /Table/?key=value`.
372
+ 2. **Use Comparison Operators**: Append operators like `gt`, `ge`, `lt`, `le`, `ne` using FIQL-style syntax: `GET /Table/?price=gt=100`.
373
+ 3. **Apply Logic and Grouping**: Use `&` for AND, `|` for OR, and `()` for grouping: `GET /Table/?(rating=5|featured=true)&price=lt=50`.
374
+ 4. **Select Specific Fields**: Use `select()` to limit returned attributes: `GET /Table/?select(name,price)`.
375
+ 5. **Paginate Results**: Use `limit(count)` or `limit(offset, count)` to set the number of records to return and skip.
376
+ - Example (first 10): `GET /Table/?limit(10)`
377
+ - Example (skip 20, return 10): `GET /Table/?limit(20, 10)`
378
+ 6. **Sort Results**: Use `sort()` with `+` (asc) or `-` (desc) before the field name. Avoid `sort=field` format.
379
+ - Example (asc): `GET /Table/?sort(+name)`
380
+ - Example (desc): `GET /Table/?sort(-price)`
381
+ - Example (combined): `GET /Table/?sort(-price,+name)`
382
+ 7. **Query Relationships**: Use dot syntax for tables linked with `@relationship`: `GET /Book/?author.name=Harper`.
260
383
 
261
384
  ### 2.3 Real-time Applications
262
385
 
263
- Implementing WebSockets and Pub/Sub for live data updates.
386
+ Instructions for the agent to follow when building real-time applications in Harper.
264
387
 
265
388
  #### When to Use
266
389
 
267
- Use this for applications that require live updates, such as chat apps, live dashboards, or collaborative tools.
390
+ Use this skill when you need to stream live updates to clients, implement chat features, or provide real-time data synchronization between the database and a frontend.
268
391
 
269
392
  #### How It Works
270
393
 
271
- 1. **WebSocket Connection**: Connect to the Harper WebSocket endpoint. Use `wss://` for secure connections over HTTPS, or `ws://` for local development.
272
- 2. **Subscribing**: Subscribe to table updates or specific records.
273
- 3. **Pub/Sub**: Use the internal bus to publish and subscribe to custom events.
394
+ 1. **Check Automatic WebSockets**: If you only need to stream table changes, use [Automatic APIs](automatic-apis.md) which provide a WebSocket endpoint for every `@export`ed table.
395
+ 2. **Implement `connect` in a Resource**: For custom bi-directional logic, implement the `connect` method.
396
+ 3. **Use Pub/Sub**: Use `tables.TableName.subscribe(query)` to listen for specific data changes and stream them to the client.
397
+ 4. **Handle SSE**: Ensure your `connect` method gracefully handles cases where `incomingMessages` is null (Server-Sent Events).
398
+ 5. **Connect from Client**: Use standard WebSockets (`new WebSocket('wss://...')`) to connect to your resource endpoint. Ensure you use the appropriate scheme (`ws://` for HTTP, `wss://` for HTTPS).
399
+
400
+ #### Examples
401
+
402
+ ##### Bi-directional WebSocket Resource
403
+
404
+ ```typescript
405
+ import { Resource, tables } from 'harper';
406
+
407
+ export class MySocket extends Resource {
408
+ async *connect(target, incomingMessages) {
409
+ // Subscribe to table changes
410
+ const subscription = await tables.MyTable.subscribe(target);
411
+ if (!incomingMessages) {
412
+ return subscription; // SSE mode
413
+ }
414
+
415
+ // Handle incoming client messages
416
+ for await (let message of incomingMessages) {
417
+ yield { received: message };
418
+ }
419
+ }
420
+ }
421
+ ```
274
422
 
275
423
  ### 2.4 Checking Authentication
276
424
 
277
- How to use sessions to verify user identity and roles.
425
+ Instructions for the agent to follow when handling authentication and sessions.
278
426
 
279
427
  #### When to Use
280
428
 
281
- Use this to secure your application by ensuring that only authorized users can access certain resources or perform specific actions.
429
+ Use this skill when you need to implement sign-in/sign-out functionality, protect specific resource endpoints, or identify the currently logged-in user in a Harper application.
282
430
 
283
431
  #### How It Works
284
432
 
285
- 1. **Session Handling**: Access the session object from the request context.
286
- 2. **Identity Verification**: Check for the presence of a user ID or token.
287
- 3. **Role Checks**: Verify if the user has the required roles for the action.
433
+ 1. **Configure Harper for Sessions**: Ensure `harper-config.yaml` has sessions enabled and local auto-authorization disabled for testing:
434
+ ```yaml
435
+ authentication:
436
+ authorizeLocal: false
437
+ enableSessions: true
438
+ ```
439
+ 2. **Implement Sign In**: Use `this.getContext().login(username, password)` to create a session:
440
+ ```typescript
441
+ async post(_target, data) {
442
+ const context = this.getContext();
443
+ try {
444
+ await context.login(data.username, data.password);
445
+ } catch {
446
+ return new Response('Invalid credentials', { status: 403 });
447
+ }
448
+ return new Response('Logged in', { status: 200 });
449
+ }
450
+ ```
451
+ 3. **Identify Current User**: Use `this.getCurrentUser()` to access session data:
452
+ ```typescript
453
+ async get() {
454
+ const user = this.getCurrentUser?.();
455
+ if (!user) return new Response(null, { status: 401 });
456
+ return { username: user.username, role: user.role };
457
+ }
458
+ ```
459
+ 4. **Implement Sign Out**: Use `this.getContext().logout()` or delete the session from context:
460
+ ```typescript
461
+ async post() {
462
+ const context = this.getContext();
463
+ await context.session?.delete?.(context.session.id);
464
+ return new Response('Logged out', { status: 200 });
465
+ }
466
+ ```
467
+ 5. **Protect Routes**: In your Resource, use `allowRead()`, `allowUpdate()`, etc., to enforce authorization logic based on `this.getCurrentUser()`. For privileged actions, verify `user.role.permission.super_user`.
288
468
 
289
- ---
469
+ #### Examples
290
470
 
291
- ## 3. Logic & Extension
471
+ ##### Sign In Implementation
472
+
473
+ ```typescript
474
+ async post(_target, data) {
475
+ const context = this.getContext();
476
+ try {
477
+ await context.login(data.username, data.password);
478
+ } catch {
479
+ return new Response('Invalid credentials', { status: 403 });
480
+ }
481
+ return new Response('Logged in', { status: 200 });
482
+ }
483
+ ```
484
+
485
+ ##### Identify Current User
486
+
487
+ ```typescript
488
+ async get() {
489
+ const user = this.getCurrentUser?.();
490
+ if (!user) return new Response(null, { status: 401 });
491
+ return { username: user.username, role: user.role };
492
+ }
493
+ ```
494
+
495
+ ##### Sign Out Implementation
496
+
497
+ ```typescript
498
+ async post() {
499
+ const context = this.getContext();
500
+ await context.session?.delete?.(context.session.id);
501
+ return new Response('Logged out', { status: 200 });
502
+ }
503
+ ```
504
+
505
+ #### Status code conventions used here
506
+
507
+ - 200: Successful operation. For `GET /me`, a `200` with empty body means “not signed in”.
508
+ - 400: Missing required fields (e.g., username/password on sign-in).
509
+ - 401: No current session for an action that requires one (e.g., sign out when not signed in).
510
+ - 403: Authenticated but not authorized (bad credentials on login attempt, or insufficient privileges).
511
+
512
+ #### Client considerations
513
+
514
+ - Sessions are cookie-based; the server handles setting and reading the cookie via Harper. If you make cross-origin requests, ensure the appropriate `credentials` mode and CORS settings.
515
+ - If developing locally, double-check the server config still has `authentication.authorizeLocal: false` to avoid accidental superuser bypass.
516
+
517
+ #### Token-based auth (JWT + refresh token) for non-browser clients
292
518
 
293
- **Impact: MEDIUM**
519
+ Cookie-backed sessions are great for browser flows. For CLI tools, mobile apps, or other non-browser clients, it’s often easier to use **explicit tokens**:
520
+
521
+ - **JWT (`operation_token`)**: short-lived bearer token used to authorize API requests.
522
+ - **Refresh token (`refresh_token`)**: longer-lived token used to mint a new JWT when it expires.
523
+
524
+ This project includes two Resource patterns for that flow:
525
+
526
+ ##### Issuing tokens: `IssueTokens`
527
+
528
+ **Description / use case:** Generate `{ refreshToken, jwt }` either:
529
+
530
+ - with an existing Authorization token (either Basic Auth or a JWT) and you want to issue new tokens, or
531
+ - from an explicit `{ username, password }` payload (useful for direct “login” from a CLI/mobile client).
532
+
533
+ ```javascript
534
+ export class IssueTokens extends Resource {
535
+ static loadAsInstance = false;
536
+
537
+ async get(target) {
538
+ const { refresh_token: refreshToken, operation_token: jwt } =
539
+ await databases.system.hdb_user.operation(
540
+ { operation: 'create_authentication_tokens' },
541
+ this.getContext(),
542
+ );
543
+ return { refreshToken, jwt };
544
+ }
545
+
546
+ async post(target, data) {
547
+ if (!data.username || !data.password) {
548
+ throw new Error('username and password are required');
549
+ }
550
+
551
+ const { refresh_token: refreshToken, operation_token: jwt } =
552
+ await databases.system.hdb_user.operation({
553
+ operation: 'create_authentication_tokens',
554
+ username: data.username,
555
+ password: data.password,
556
+ });
557
+ return { refreshToken, jwt };
558
+ }
559
+ }
560
+ ```
561
+
562
+ **Recommended documentation notes to include:**
563
+
564
+ - `GET` variant: intended for “I already have an Authorization token, give me new tokens”.
565
+ - `POST` variant: intended for “I have credentials, give me tokens”.
566
+ - Response shape:
567
+ - `refreshToken`: store securely (long-lived).
568
+ - `jwt`: attach to requests (short-lived).
569
+
570
+ ##### Refreshing a JWT: `RefreshJWT`
571
+
572
+ **Description / use case:** When the JWT expires, the client uses the refresh token to get a new JWT without re-supplying username/password.
573
+
574
+ ```javascript
575
+ export class RefreshJWT extends Resource {
576
+ static loadAsInstance = false;
577
+
578
+ async post(target, data) {
579
+ if (!data.refreshToken) {
580
+ throw new Error('refreshToken is required');
581
+ }
582
+
583
+ const { operation_token: jwt } = await databases.system.hdb_user.operation({
584
+ operation: 'refresh_operation_token',
585
+ refresh_token: data.refreshToken,
586
+ });
587
+ return { jwt };
588
+ }
589
+ }
590
+ ```
591
+
592
+ **Recommended documentation notes to include:**
593
+
594
+ - Requires `refreshToken` in the request body.
595
+ - Returns a new `{ jwt }`.
596
+ - If refresh fails (expired/revoked), client must re-authenticate (e.g., call `IssueTokens.post` again).
597
+
598
+ ##### Suggested client flow (high-level)
599
+
600
+ 1. **Sign in (token flow)**
601
+ - POST /IssueTokens/ with a body of `{ "username": "your username", "password": "your password" }` or GET /IssueTokens/ with an existing Authorization token.
602
+ - Receive `{ jwt, refreshToken }` in the response
603
+ 2. **Call protected APIs**
604
+ - Send the JWT with each request in the Authorization header (as your auth mechanism expects)
605
+ 3. **JWT expires**
606
+ - POST /RefreshJWT/ with a body of `{ "refreshToken": "your refresh token" }`.
607
+ - Receive `{ jwt }` in the response and continue
608
+
609
+ #### Quick checklist
610
+
611
+ - [ ] Public endpoints explicitly `allowRead`/`allowCreate` as needed.
612
+ - [ ] Sign-in uses `context.login` and handles 400/403 correctly.
613
+ - [ ] Protected routes call `ensureSuperUser(this.getCurrentUser())` (or another role check) before doing work.
614
+ - [ ] Sign-out verifies a session and deletes it.
615
+ - [ ] `authentication.authorizeLocal` is `false` and `enableSessions` is `true` in Harper config.
616
+ - [ ] If using tokens: `IssueTokens` issues `{ jwt, refreshToken }`, `RefreshJWT` refreshes `{ jwt }` with a `refreshToken`.
617
+
618
+ ## 3. Logic & Extension
294
619
 
295
620
  ### 3.1 Custom Resources
296
621
 
297
- How to define custom REST endpoints using JavaScript or TypeScript.
622
+ Instructions for the agent to follow when creating custom resources in Harper.
623
+
624
+ #### When to Use
625
+
626
+ Use this skill when the automatic CRUD operations provided by `@table @export` are insufficient, and you need custom logic, third-party API integration, or specialized data handling for your REST endpoints.
298
627
 
299
628
  #### How It Works
300
629
 
301
- 1. **Create Resource File**: Define your logic in a JS or TS file.
302
- 2. **Define Resource Class**: Export a class extending `Resource` from `harper`.
303
- 3. **Implement HTTP Methods**: Add methods like `get`, `post`, `put`, `patch`, or `delete` to handle corresponding requests.
304
- 4. **Route Nesting and Naming**: You can control the URL structure by how you export your resources:
630
+ 1. **Check if a Custom Resource is Necessary**: Verify if [Automatic APIs](./automatic-apis.md) or [Extending Tables](./extending-tables.md) can satisfy the requirement first.
631
+ 2. **Create the Resource File**: Create a `.ts` or `.js` file in the directory specified by `jsResource` in `config.yaml` (typically `resources/`).
632
+ 3. **Define the Resource Class**: Export a class extending `Resource` from `harper`:
633
+
634
+ ```typescript
635
+ import { type RequestTargetOrId, Resource } from 'harper';
636
+
637
+ export class MyResource extends Resource {
638
+ async get(target?: RequestTargetOrId) {
639
+ return { message: 'Hello from custom GET!' };
640
+ }
641
+ }
642
+ ```
643
+
644
+ 4. **Implement HTTP Methods**: Add methods like `get`, `post`, `put`, `patch`, or `delete` to handle corresponding requests.
645
+ 5. **Route Nesting and Naming**: You can control the URL structure by how you export your resources:
305
646
  - **Direct Class Export**: `export class Foo extends Resource` creates endpoints at `/Foo/`. Class names are case-sensitive in the URL.
306
647
  - **Nested Objects**: `export const Bar = { Foo };` creates endpoints at `/Bar/Foo/`.
307
648
  - **Lowercase and Hyphens**: Use object keys to define custom paths: `export const bar = { 'foo-baz': Foo };` exposes endpoints at `/bar/foo-baz/`.
308
- 5. **Registration**: Ensure the resource is correctly registered in your application configuration.
649
+ 6. **Access Tables (Optional)**: Import and use the `tables` object to interact with your data:
650
+ ```typescript
651
+ import { tables } from 'harper';
652
+ // ... inside a method
653
+ const results = await tables.MyTable.list();
654
+ ```
655
+ 7. **Configure Loading**: Ensure `config.yaml` points to your resource files (e.g., `jsResource: { files: 'resources/*.ts' }`).
656
+
657
+ ### 3.2 Extending Tables
658
+
659
+ Instructions for the agent to follow when extending table resources in Harper.
309
660
 
310
- ### 3.2 Extending Table Resources
661
+ #### When to Use
311
662
 
312
- Adding custom logic to automatically generated table resources.
663
+ Use this skill when you need to add custom validation, side effects (like webhooks), data transformation, or custom access control to the standard CRUD operations of a Harper table.
313
664
 
314
665
  #### How It Works
315
666
 
316
- 1. **Define Extension**: Create a resource file that targets an existing table.
317
- 2. **Intercept Requests**: Use handlers to add custom validation or data transformation.
318
- 3. **No `@export`**: If extending, remember not to `@export` the table in the schema.
667
+ 1. **Define the Table in GraphQL**: In your `.graphql` schema, define the table using the `@table` directive. **Do not** use `@export` if you plan to extend it.
668
+ ```graphql
669
+ type MyTable @table {
670
+ id: ID @primaryKey
671
+ name: String
672
+ }
673
+ ```
674
+ 2. **Create the Extension File**: Create a `.ts` file in your `resources/` directory.
675
+ 3. **Extend the Table Resource**: Export a class that extends `tables.YourTableName`:
676
+
677
+ ```typescript
678
+ import { type RequestTargetOrId, tables } from 'harper';
679
+
680
+ export class MyTable extends tables.MyTable {
681
+ async post(target: RequestTargetOrId, record: any) {
682
+ // Custom logic here
683
+ if (!record.name) {
684
+ throw new Error('Name required');
685
+ }
686
+ return super.post(target, record);
687
+ }
688
+ }
689
+ ```
690
+
691
+ 4. **Override Methods**: Override `get`, `post`, `put`, `patch`, or `delete` as needed. Always call `super[method]` to maintain default Harper functionality unless you intend to replace it entirely.
692
+ 5. **Implement Logic**: Use overrides for validation, side effects, or transforming data before/after database operations.
319
693
 
320
694
  ### 3.3 Programmatic Table Requests
321
695
 
322
- How to use filters, operators, sorting, and pagination in programmatic table requests.
696
+ Instructions for the agent to follow when interacting with Harper tables via code.
323
697
 
324
- #### Usage
698
+ #### When to Use
325
699
 
326
- When writing custom resources, use the internal API to query tables with full support for advanced filtering and sorting.
700
+ Use this skill when you need to perform database operations (CRUD, search, subscribe) from within Harper Resources or scripts.
327
701
 
328
- ### 3.4 TypeScript Type Stripping
702
+ #### How It Works
329
703
 
330
- Using TypeScript directly without build tools via Node.js Type Stripping.
704
+ 1. **Access the Table**: Use the global `tables` object followed by your table name (e.g., `tables.MyTable`).
705
+ 2. **Perform CRUD Operations**:
706
+ - **Get**: `await tables.MyTable.get(id)` for a single record or `await tables.MyTable.get({ conditions: [...] })` for multiple.
707
+ - **Create**: `await tables.MyTable.post(record)` (auto-generates ID) or `await tables.MyTable.put(id, record)`.
708
+ - **Update**: `await tables.MyTable.patch(id, partialRecord)` for partial updates.
709
+ - **Delete**: `await tables.MyTable.delete(id)`.
710
+ 3. **Use Updatable Records for Atomic Ops**: Call `update(id)` to get a reference, then use `addTo` or `subtractFrom` for atomic increments/decrements:
711
+ ```typescript
712
+ const stats = await tables.Stats.update('daily');
713
+ stats.addTo('viewCount', 1);
714
+ ```
715
+ 4. **Search and Stream**: Use `search(query)` for efficient streaming of large result sets:
716
+ ```typescript
717
+ for await (const record of tables.MyTable.search({ conditions: [...] })) {
718
+ // process record
719
+ }
720
+ ```
721
+ See the [Query Conditions](#query-conditions) section below for the full query object reference.
722
+ 5. **Real-time Subscriptions**: Use `subscribe(query)` to listen for changes:
723
+ ```typescript
724
+ for await (const event of tables.MyTable.subscribe(query)) {
725
+ // handle event
726
+ }
727
+ ```
728
+ 6. **Publish Events**: Use `publish(id, message)` to trigger subscriptions without necessarily persisting data.
729
+
730
+ #### Query Conditions
731
+
732
+ When passing a query to `search()`, `get()`, or `subscribe()`, use a query object with a `conditions` array.
733
+
734
+ ##### Condition Object Shape
735
+
736
+ | Property | Description |
737
+ | ------------ | ------------------------------------------------------------------------------------------ |
738
+ | `attribute` | Field name, or array of field names to traverse a relationship (e.g., `['brand', 'name']`) |
739
+ | `value` | The value to compare against |
740
+ | `comparator` | One of the comparator strings below (default: `equals`) |
741
+ | `operator` | `and` (default) or `or` — applies to a nested `conditions` block |
742
+ | `conditions` | Nested array of condition objects for complex AND/OR logic |
743
+
744
+ ##### Comparator Values
745
+
746
+ Use these exact strings — incorrect comparator names will silently fail or error:
747
+
748
+ | Comparator | Meaning |
749
+ | -------------------- | ---------------------------------------------------------- |
750
+ | `equals` | Exact match (default) |
751
+ | `not_equal` | Not equal |
752
+ | `greater_than` | `>` |
753
+ | `greater_than_equal` | `>=` |
754
+ | `less_than` | `<` |
755
+ | `less_than_equal` | `<=` |
756
+ | `starts_with` | String starts with value |
757
+ | `contains` | String contains value |
758
+ | `ends_with` | String ends with value |
759
+ | `between` | Value is between two bounds (pass `value` as `[min, max]`) |
760
+
761
+ ##### Query Object Parameters
762
+
763
+ | Property | Description |
764
+ | ------------ | ------------------------------------------------------------------------------------ |
765
+ | `conditions` | Array of condition objects |
766
+ | `limit` | Maximum number of records to return |
767
+ | `offset` | Number of records to skip (for pagination) |
768
+ | `select` | Array of attribute names to return; supports `$id` and `$updatedtime` |
769
+ | `sort` | Object with `attribute`, `descending` (bool), and optional `next` for secondary sort |
770
+
771
+ ##### Examples
772
+
773
+ **Simple filter:**
774
+
775
+ ```javascript
776
+ for await (const record of tables.Product.search({
777
+ conditions: [{ attribute: 'price', comparator: 'less_than', value: 100 }],
778
+ limit: 20,
779
+ })) { ... }
780
+ ```
331
781
 
332
- #### Configuration
782
+ **AND + nested OR:**
783
+
784
+ ```javascript
785
+ for await (const record of tables.Product.search({
786
+ conditions: [
787
+ { attribute: 'price', comparator: 'less_than', value: 100 },
788
+ {
789
+ operator: 'or',
790
+ conditions: [
791
+ { attribute: 'rating', comparator: 'greater_than', value: 4 },
792
+ { attribute: 'featured', value: true },
793
+ ],
794
+ },
795
+ ],
796
+ })) { ... }
797
+ ```
333
798
 
334
- Harper supports native TypeScript type stripping, allowing you to run `.ts` files directly. Ensure your environment is configured to take advantage of this for faster development cycles.
799
+ **Relationship traversal:**
335
800
 
336
- ### 3.5 Caching
801
+ ```javascript
802
+ for await (const record of tables.Book.search({
803
+ conditions: [{ attribute: ['brand', 'name'], comparator: 'equals', value: 'Harper' }],
804
+ })) { ... }
805
+ ```
337
806
 
338
- How caching is defined and implemented in Harper applications.
807
+ **Sort and paginate:**
339
808
 
340
- #### Strategies
809
+ ```javascript
810
+ for await (const record of tables.Product.search({
811
+ conditions: [{ attribute: 'inStock', value: true }],
812
+ sort: { attribute: 'price', descending: false },
813
+ limit: 10,
814
+ offset: 20,
815
+ })) { ... }
816
+ ```
341
817
 
342
- - **In-memory**: For fast access to frequently used data.
343
- - **Distributed**: For scaling across multiple nodes in Harper Fabric.
818
+ #### Cautions
344
819
 
345
- ---
820
+ Be very careful when performing updates and deletions! You may be dealing with live production data. The wrong request to delete, without approval from a human, could be devastating to a business. Always use the proper approval process.
346
821
 
347
- ## 4. Infrastructure & Ops
822
+ ### 3.4 TypeScript Type Stripping
348
823
 
349
- **Impact: MEDIUM**
824
+ Instructions for the agent to follow when using TypeScript in Harper.
350
825
 
351
- ### 4.1 Creating Harper Applications
826
+ #### When to Use
352
827
 
353
- The fastest way to start a new Harper project is using the `create-harper` CLI tool. This command initializes a project with a standard folder structure, essential configuration files, and basic schema definitions.
828
+ Use this skill when you want to write Harper Resources in TypeScript and have them execute directly in Node.js without an intermediate build or compilation step.
354
829
 
355
- #### When to Use
830
+ #### How It Works
356
831
 
357
- Use this command when starting a new Harper application or adding a new Harper microservice to an existing architecture.
832
+ 1. **Verify Node.js Version**: Ensure you are using Node.js v22.6.0 or higher.
833
+ 2. **Name Files with `.ts`**: Create your resource files in the `resources/` directory with a `.ts` extension.
834
+ 3. **Use TypeScript Syntax**: Write your resource classes using standard TypeScript (interfaces, types, etc.).
835
+ ```typescript
836
+ import { Resource } from 'harper';
837
+ export class MyResource extends Resource {
838
+ async get(): Promise<{ message: string }> {
839
+ return { message: 'Running TS directly!' };
840
+ }
841
+ }
842
+ ```
843
+ 4. **Use Explicit Extensions in Imports**: When importing other local modules, include the `.ts` extension: `import { helper } from './helper.ts'`.
844
+ 5. **Configure `config.yaml`**: Ensure `jsResource` points to your `.ts` files:
845
+ ```yaml
846
+ jsResource:
847
+ files: 'resources/*.ts'
848
+ ```
358
849
 
359
- #### Commands
850
+ ### 3.5 Caching
360
851
 
361
- Initialize a project using your preferred package manager:
852
+ Instructions for the agent to follow when implementing caching in Harper.
853
+
854
+ #### When to Use
362
855
 
363
- **NPM**
856
+ Use this skill when you need high-performance, low-latency storage for data from external sources. It's ideal for reducing API calls to third-party services, preventing cache stampedes, and making external data queryable as if it were native Harper tables.
364
857
 
365
- ```bash
366
- npm create harper@latest
367
- ```
858
+ #### How It Works
368
859
 
369
- **PNPM**
860
+ 1. **Configure a Cache Table**: Define a table in your `schema.graphql` with an `expiration` (in seconds).
861
+ 2. **Define an External Source**: Create a Resource class that fetches the data from your source.
862
+ 3. **Attach Source to Table**: Use `sourcedFrom` to link your resource to the table.
863
+ 4. **Implement Active Caching (Optional)**: Use `subscribe()` for proactive updates. See [Real-Time Apps](real-time-apps.md).
864
+ 5. **Implement Write-Through Caching (Optional)**: Define `put` or `post` in your resource to propagate updates upstream.
370
865
 
371
- ```bash
372
- pnpm create harper@latest
373
- ```
866
+ #### Examples
374
867
 
375
- **Bun**
868
+ ##### Schema Configuration
376
869
 
377
- ```bash
378
- bun create harper@latest
870
+ ```graphql
871
+ type MyCache @table(expiration: 3600) @export {
872
+ id: ID @primaryKey
873
+ }
379
874
  ```
380
875
 
381
- ### 4.2 Creating a Fabric Account and Cluster
876
+ ##### Resource Implementation
382
877
 
383
- Follow these steps to set up your Harper Fabric environment for deployment.
878
+ ```js
879
+ import { Resource, tables } from 'harper';
384
880
 
385
- #### How It Works
881
+ export class ThirdPartyAPI extends Resource {
882
+ async get() {
883
+ const id = this.getId();
884
+ const response = await fetch(`https://api.example.com/items/${id}`);
885
+ if (!response.ok) {
886
+ throw new Error('Source fetch failed');
887
+ }
888
+ return await response.json();
889
+ }
890
+ }
386
891
 
387
- 1. **Sign Up/In**: Go to [https://fabric.harper.fast/](https://fabric.harper.fast/) and sign up or sign in.
388
- 2. **Create an Organization**: Create an organization (org) to manage your projects.
389
- 3. **Create a Cluster**: Create a new cluster. This can be on the free tier, no credit card required.
390
- 4. **Set Credentials**: During setup, set the cluster username and password to finish configuring it.
391
- 5. **Get Application URL**: Navigate to the **Config** tab and copy the **Application URL**.
392
- 6. **Configure Environment**: Update your `.env` file or GitHub Actions secrets with these cluster-specific credentials:
393
- ```bash
394
- CLI_TARGET_USERNAME='YOUR_CLUSTER_USERNAME'
395
- CLI_TARGET_PASSWORD='YOUR_CLUSTER_PASSWORD'
396
- CLI_TARGET='YOUR_CLUSTER_URL'
397
- ```
892
+ // Attach source to table
893
+ tables.MyCache.sourcedFrom(ThirdPartyAPI);
894
+ ```
398
895
 
399
- ### 4.3 Deploying to Harper Fabric
896
+ ## 4. Infrastructure & Ops
897
+
898
+ ### 4.1 Deploying to Harper Fabric
400
899
 
401
- Globally scaling your Harper application.
900
+ Instructions for the agent to follow when deploying to Harper Fabric.
402
901
 
403
- #### Benefits
902
+ #### When to Use
404
903
 
405
- - **Global Distribution**: Low latency for users everywhere.
406
- - **Automatic Sync**: Data is synced across the fabric automatically.
407
- - **Free Tier**: Start for free and scale as you grow.
904
+ Use this skill when you are ready to move your Harper application from local development to a cloud-hosted environment.
408
905
 
409
906
  #### How It Works
410
907
 
411
- 1. **Sign up**: Follow the [Creating a Fabric Account and Cluster](#42-creating-a-fabric-account-and-cluster) steps to create a Harper Fabric account, organization, and cluster.
908
+ 1. **Sign up**: Follow the [creating-a-fabric-account-and-cluster](creating-a-fabric-account-and-cluster.md) rule to create a Harper Fabric account, organization, and cluster.
412
909
  2. **Configure Environment**: Add your cluster credentials and cluster application URL to `.env`:
413
910
  ```bash
414
911
  CLI_TARGET_USERNAME='YOUR_CLUSTER_USERNAME'
@@ -422,7 +919,7 @@ Globally scaling your Harper application.
422
919
 
423
920
  If your application was not created with `npm create harper`, you'll need to manually configure the deployment scripts and CI/CD workflow.
424
921
 
425
- #### 1. Update `package.json`
922
+ ##### 1. Update `package.json`
426
923
 
427
924
  Add the following scripts and dependencies to your `package.json`:
428
925
 
@@ -439,7 +936,7 @@ Add the following scripts and dependencies to your `package.json`:
439
936
  }
440
937
  ```
441
938
 
442
- #### Why split the scripts?
939
+ ###### Why split the scripts?
443
940
 
444
941
  The `deploy` script is separated from `deploy:component` to ensure environment variables from your `.env` file are properly loaded and passed to the Harper CLI.
445
942
 
@@ -448,7 +945,7 @@ The `deploy` script is separated from `deploy:component` to ensure environment v
448
945
 
449
946
  By using `dotenv -- npm run deploy:component`, the environment variables are correctly set in the shell session before `harper deploy_component` is called, allowing it to authenticate with your cluster.
450
947
 
451
- #### 2. Configure GitHub Actions
948
+ ##### 2. Configure GitHub Actions
452
949
 
453
950
  Create a `.github/workflows/deploy.yaml` file with the following content:
454
951
 
@@ -467,12 +964,15 @@ jobs:
467
964
  runs-on: ubuntu-latest
468
965
  steps:
469
966
  - name: Checkout code
470
- uses: actions/checkout@v4
967
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
968
+ with:
969
+ fetch-depth: 0
970
+ fetch-tags: true
471
971
  - name: Set up Node.js
472
- uses: actions/setup-node@v4
972
+ uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
473
973
  with:
474
974
  cache: 'npm'
475
- node-version: '20'
975
+ node-version-file: '.nvmrc'
476
976
  - name: Install dependencies
477
977
  run: npm ci
478
978
  - name: Run unit tests
@@ -487,20 +987,140 @@ jobs:
487
987
  CLI_TARGET_PASSWORD: ${{ secrets.CLI_TARGET_PASSWORD }}
488
988
  ```
489
989
 
490
- Be sure to set the following repository secrets in your GitHub repository:
990
+ Be sure to set the following repository secrets in your GitHub repository's /settings/secrets/actions:
491
991
 
492
992
  - `CLI_TARGET`
493
993
  - `CLI_TARGET_USERNAME`
494
994
  - `CLI_TARGET_PASSWORD`
495
995
 
996
+ ### 4.2 Creating a Harper Fabric Account and Cluster
997
+
998
+ Follow these steps to set up your Harper Fabric environment for deployment.
999
+
1000
+ #### How It Works
1001
+
1002
+ 1. **Sign Up/In**: Go to [https://fabric.harper.fast/](https://fabric.harper.fast/) and sign up or sign in.
1003
+ 2. **Create an Organization**: Create an organization (org) to manage your projects.
1004
+ 3. **Create a Cluster**: Create a new cluster. This can be on the free tier, no credit card required.
1005
+ 4. **Set Credentials**: During setup, set the cluster username and password to finish configuring it.
1006
+ 5. **Get Application URL**: Navigate to the **Config** tab and copy the **Application URL**.
1007
+ 6. **Configure Environment**: Update your `.env` file or GitHub Actions secrets with cluster-specific credentials.
1008
+ 7. **Next Steps**: See the [deploying-to-harper-fabric](deploying-to-harper-fabric.md) rule for detailed instructions on deploying your application successfully.
1009
+
1010
+ #### Examples
1011
+
1012
+ ##### Environment Configuration
1013
+
1014
+ ```bash
1015
+ CLI_TARGET_USERNAME='YOUR_CLUSTER_USERNAME'
1016
+ CLI_TARGET_PASSWORD='YOUR_CLUSTER_PASSWORD'
1017
+ CLI_TARGET='YOUR_CLUSTER_URL'
1018
+ ```
1019
+
1020
+ ### 4.3 Creating Harper Applications
1021
+
1022
+ The fastest way to start a new Harper project is using the `create-harper` CLI tool. This command initializes a project with a standard folder structure, essential configuration files, and basic schema definitions.
1023
+
1024
+ #### When to Use
1025
+
1026
+ Use this command when starting a new Harper application or adding a new Harper microservice to an existing architecture.
1027
+
1028
+ #### Commands
1029
+
1030
+ Initialize a project using your preferred package manager:
1031
+
1032
+ ##### NPM
1033
+
1034
+ ```bash
1035
+ npm create harper@latest
1036
+ ```
1037
+
1038
+ ##### PNPM
1039
+
1040
+ ```bash
1041
+ pnpm create harper@latest
1042
+ ```
1043
+
1044
+ ##### Bun
1045
+
1046
+ ```bash
1047
+ bun create harper@latest
1048
+ ```
1049
+
1050
+ #### Options
1051
+
1052
+ You can specify the project name and template directly:
1053
+
1054
+ ```bash
1055
+ npm create harper@latest my-app --template default
1056
+ ```
1057
+
1058
+ #### Next Steps
1059
+
1060
+ 1. **Configure Environment**: Set up your `.env` file with local or cloud credentials.
1061
+ 2. **Define Schema**: Modify `schema.graphql` to fit your application's data model.
1062
+ 3. **Start Development**: Run `npm run dev` to start the local Harper instance.
1063
+ 4. **Deploy**: Use `npm run deploy` to push your application to Harper Fabric.
1064
+
496
1065
  ### 4.4 Serving Web Content
497
1066
 
498
- Two ways to serve web content from a Harper application.
1067
+ Instructions for the agent to follow when serving web content from Harper.
1068
+
1069
+ #### When to Use
499
1070
 
500
- #### Methods
1071
+ Use this skill when you need to serve a frontend (HTML, CSS, JS, or a React app) directly from your Harper instance.
501
1072
 
502
- 1. **Static Serving**: Serve HTML, CSS, and JS files directly. If using the Vite plugin for development, ensure Harper is running (e.g., `harper run .`) to allow for Hot Module Replacement (HMR).
503
- 2. **Dynamic Rendering**: Use custom resources to render content on the fly.
1073
+ #### How It Works
1074
+
1075
+ 1. **Choose a Method**: Decide between the simple Static Plugin or the integrated Vite Plugin.
1076
+ 2. **Option A: Static Plugin (Simple)**:
1077
+ - Add to `config.yaml`:
1078
+ ```yaml
1079
+ static:
1080
+ files: 'web/*'
1081
+ ```
1082
+ - Place files in a `web/` folder in the project root.
1083
+ - Files are served at the root URL (e.g., `http://localhost:9926/index.html`).
1084
+ 3. **Option B: Vite Plugin (Advanced/Development)**:
1085
+ - Add to `config.yaml`:
1086
+ ```yaml
1087
+ '@harperfast/vite-plugin':
1088
+ package: '@harperfast/vite-plugin'
1089
+ ```
1090
+ - Ensure `vite.config.ts` and `index.html` are in the project root.
1091
+
1092
+ ```javascript
1093
+ import vue from '@vitejs/plugin-vue';
1094
+ import path from 'node:path';
1095
+ import { defineConfig } from 'vite';
1096
+
1097
+ // https://vite.dev/config/
1098
+ export default defineConfig({
1099
+ plugins: [vue()],
1100
+ resolve: {
1101
+ alias: {
1102
+ '@': path.resolve(import.meta.dirname, './src'),
1103
+ },
1104
+ },
1105
+ build: {
1106
+ outDir: 'web',
1107
+ emptyOutDir: true,
1108
+ rolldownOptions: {
1109
+ external: ['**/*.test.*', '**/*.spec.*'],
1110
+ },
1111
+ },
1112
+ });
1113
+ ```
1114
+
1115
+ - Install dependencies: `npm install --save-dev vite @harperfast/vite-plugin`.
1116
+ - Then `harper run .` will start up Harper and Vite with HMR. Vite does _not_ need to be executed separately.
1117
+
1118
+ 4. **Deploy for Production**: For Vite apps, use a build script to generate static files into a `web/` folder and deploy them using the static handler pattern. For example, these scripts in a package.json can perform the necessary steps:
1119
+ ```json
1120
+ "build": "vite build",
1121
+ "deploy": "rm -Rf deploy && npm run build && mkdir deploy && mv web deploy/ && cp -R deploy-template/* deploy/ && cp -R schemas resources deploy/ && (cd deploy && harper deploy_component . project=web restart=rolling replicated=true) && rm -Rf deploy",
1122
+ ```
1123
+ Then in production, the "Static Plugin" option will performantly and securely serve your assets. `npm create harper@latest` scaffolds all of this for you.
504
1124
 
505
1125
  ### 4.5 Logging Best Practices
506
1126