@harperfast/skills 1.5.1 → 1.6.0

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,755 @@ 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 to support approximate nearest-neighbor (similarity) search on high-dimensional float arrays. Use it whenever a query requires ranking results by vector similarity, optionally combined with filter conditions.
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. **Define the table schema with a vector index**: Add `@indexed(type: "HNSW")` to a `[Float]` attribute on a `@table` type. See [adding-tables-with-schemas](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**: Call `.search()` with a `sort` parameter specifying the indexed `attribute` and a `target` vector. The `target` is the query vector to compare against.
156
+
157
+ ```javascript
158
+ let results = Document.search({
159
+ sort: { attribute: 'textEmbeddings', target: searchVector },
160
+ limit: 5,
161
+ });
162
+ ```
163
+
164
+ 3. **Combine with filter conditions**: Add a `conditions` array alongside `sort` to filter results 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. **Tune HNSW parameters**: Pass additional parameters directly in the `@indexed` directive to control index quality and performance.
175
+
176
+ | Parameter | Default | Description |
177
+ | ---------------------- | ----------------- | --------------------------------------------------------------------------------------------------- |
178
+ | `distance` | `"cosine"` | Distance function: `"euclidean"` or `"cosine"` (negative cosine similarity) |
179
+ | `efConstruction` | `100` | Max nodes explored during index construction. Higher = better recall, lower = better performance |
180
+ | `M` | `16` | Preferred connections per graph layer. Higher = more space, better recall for high-dimensional data |
181
+ | `optimizeRouting` | `0.5` | Heuristic aggressiveness for omitting redundant connections (0 = off, 1 = most aggressive) |
182
+ | `mL` | computed from `M` | Normalization factor for level generation |
183
+ | `efSearchConstruction` | `50` | Max nodes explored during search |
188
184
 
189
- #### Example
185
+ #### Examples
186
+
187
+ Schema with default settings:
190
188
 
191
189
  ```graphql
192
- type Document @table @export {
193
- id: ID @primaryKey
194
- content: String
195
- embedding: [Float] @indexed(type: "HNSW")
190
+ type Document @table {
191
+ id: Long @primaryKey
192
+ textEmbeddings: [Float] @indexed(type: "HNSW")
193
+ }
194
+ ```
195
+
196
+ Schema with custom parameters (euclidean distance, routing disabled, higher search recall):
197
+
198
+ ```graphql
199
+ type Document @table {
200
+ id: Long @primaryKey
201
+ textEmbeddings: [Float]
202
+ @indexed(type: "HNSW", distance: "euclidean", optimizeRouting: 0, efSearchConstruction: 100)
196
203
  }
197
204
  ```
198
205
 
199
- ### 1.5 Using Blobs
206
+ Filtered nearest-neighbor search:
200
207
 
201
- How to store and retrieve large data in Harper.
208
+ ```javascript
209
+ let results = Document.search({
210
+ conditions: [{ attribute: 'price', comparator: 'lt', value: 50 }],
211
+ sort: { attribute: 'textEmbeddings', target: searchVector },
212
+ limit: 5,
213
+ });
214
+ ```
215
+
216
+ #### Notes
217
+
218
+ - The default `distance` function is `cosine`. Use `"euclidean"` when your vectors are not normalized or when Euclidean geometry better fits your use case.
219
+ - Increasing `efConstruction` improves index recall at the cost of build performance.
220
+ - `mL` is computed automatically from `M` unless explicitly overridden.
221
+ - Always pair `sort` with a `limit` to bound the number of nearest-neighbor results returned.
222
+
223
+ ### 1.5 Using Blob Datatype
224
+
225
+ Instructions for the agent to follow when working with the Blob data type in Harper.
202
226
 
203
227
  #### When to Use
204
228
 
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.
229
+ 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
230
 
207
231
  #### How It Works
208
232
 
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.
233
+ 1. **Define Blob Fields**: In your GraphQL schema, use the `Blob` type:
234
+ ```graphql
235
+ type MyTable @table {
236
+ id: ID @primaryKey
237
+ data: Blob
238
+ }
239
+ ```
240
+ 2. **Create and Store Blobs**: Use `createBlob()` from Harper's globals to wrap Buffers or Streams:
241
+ ```javascript
242
+ import { tables } from 'harper';
243
+ const blob = createBlob(largeBuffer);
244
+ await tables.MyTable.put('my-id', { data: blob });
245
+ ```
246
+ 3. **Use Streaming (Optional)**: For very large files, pass a stream to `createBlob()` to avoid loading the entire file into memory.
247
+ 4. **Read Blob Data**: Retrieve the record and use `.bytes()` or streaming interfaces on the blob field:
248
+ ```javascript
249
+ const record = await tables.MyTable.get('my-id');
250
+ const buffer = await record.data.bytes();
251
+ ```
252
+ 5. **Ensure Write Completion**: Use `saveBeforeCommit: true` in `createBlob` options if you need the blob fully written before the record is committed.
253
+ 6. **Handle Errors**: Attach error listeners to the blob object to handle streaming failures.
212
254
 
213
255
  ### 1.6 Handling Binary Data
214
256
 
215
- How to store and serve binary data like images or MP3s.
257
+ Instructions for the agent to follow when handling binary data in Harper.
216
258
 
217
259
  #### When to Use
218
260
 
219
- Use this when your application needs to handle binary files, particularly for storage and retrieval.
261
+ 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
262
 
221
263
  #### How It Works
222
264
 
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.
265
+ 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:
266
+
267
+ ```typescript
268
+ async post(target, record) {
269
+ if (record.data) {
270
+ record.data = createBlob(Buffer.from(record.data, record.encoding || 'base64'), {
271
+ type: record.contentType || 'application/octet-stream',
272
+ });
273
+ }
274
+ return super.post(target, record);
275
+ }
276
+ ```
226
277
 
227
- ---
278
+ 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`:
279
+ ```typescript
280
+ async get(target) {
281
+ const record = await super.get(target);
282
+ if (record?.data) {
283
+ return {
284
+ status: 200,
285
+ headers: { 'Content-Type': record.data.type || 'application/octet-stream' },
286
+ body: record.data,
287
+ };
288
+ }
289
+ return record;
290
+ }
291
+ ```
292
+ 3. **Use the Blob Type**: Ensure your GraphQL schema uses the `Blob` scalar for binary fields.
228
293
 
229
294
  ## 2. API & Communication
230
295
 
231
- **Impact: HIGH**
296
+ ### 2.1 Automatic APIs
297
+
298
+ Instructions for the agent to follow when utilizing Harper's automatic APIs.
232
299
 
233
- ### 2.1 Automatic REST APIs
300
+ #### When to Use
301
+
302
+ 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.
234
303
 
235
- Details on the CRUD endpoints automatically generated for exported tables.
304
+ #### How It Works
236
305
 
237
- #### Endpoints
306
+ 1. **Enable REST in `config.yaml`**: REST endpoints are **not active by default**. You must explicitly enable them:
307
+ ```yaml
308
+ rest: true
309
+ ```
310
+ Without this, `@export`ed tables will not respond to HTTP requests.
311
+ 2. **Enable Automatic APIs**: Ensure your GraphQL schema includes the `@export` directive for the table.
312
+ 3. **Access REST Endpoints**: Use the standard endpoints for your table (Note: Paths are case-sensitive).
313
+ 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).
314
+ 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.
315
+ 6. **Customize if Needed**: If the automatic APIs don't meet your requirements, [customize the resources](./custom-resources.md).
238
316
 
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.
317
+ #### Examples
318
+
319
+ ##### Schema Configuration
320
+
321
+ ```graphql
322
+ type MyTable @table @export {
323
+ id: ID @primaryKey
324
+ name: String
325
+ }
326
+ ```
327
+
328
+ ##### Common REST Operations
329
+
330
+ - **List Records**: `GET /MyTable/`
331
+ - **Create Record**: `POST /MyTable/`
332
+ - **Update Record**: `PATCH /MyTable/{id}`
247
333
 
248
334
  ### 2.2 Querying REST APIs
249
335
 
250
- How to use filters, operators, sorting, and pagination in REST requests.
336
+ Instructions for the agent to follow when querying Harper's REST APIs.
337
+
338
+ #### When to Use
251
339
 
252
- #### Query Parameters
340
+ Use this skill when you need to perform advanced data retrieval (filtering, sorting, pagination, joins) using Harper's automatic REST endpoints.
253
341
 
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`.
342
+ #### How It Works
343
+
344
+ 1. **Basic Filtering**: Use attribute names as query parameters: `GET /Table/?key=value`.
345
+ 2. **Use Comparison Operators**: Append operators like `gt`, `ge`, `lt`, `le`, `ne` using FIQL-style syntax: `GET /Table/?price=gt=100`.
346
+ 3. **Apply Logic and Grouping**: Use `&` for AND, `|` for OR, and `()` for grouping: `GET /Table/?(rating=5|featured=true)&price=lt=50`.
347
+ 4. **Select Specific Fields**: Use `select()` to limit returned attributes: `GET /Table/?select(name,price)`.
348
+ 5. **Paginate Results**: Use `limit(count)` or `limit(offset, count)` to set the number of records to return and skip.
349
+ - Example (first 10): `GET /Table/?limit(10)`
350
+ - Example (skip 20, return 10): `GET /Table/?limit(20, 10)`
351
+ 6. **Sort Results**: Use `sort()` with `+` (asc) or `-` (desc) before the field name. Avoid `sort=field` format.
352
+ - Example (asc): `GET /Table/?sort(+name)`
353
+ - Example (desc): `GET /Table/?sort(-price)`
354
+ - Example (combined): `GET /Table/?sort(-price,+name)`
355
+ 7. **Query Relationships**: Use dot syntax for tables linked with `@relationship`: `GET /Book/?author.name=Harper`.
260
356
 
261
357
  ### 2.3 Real-time Applications
262
358
 
263
- Implementing WebSockets and Pub/Sub for live data updates.
359
+ Instructions for the agent to follow when building real-time applications in Harper.
264
360
 
265
361
  #### When to Use
266
362
 
267
- Use this for applications that require live updates, such as chat apps, live dashboards, or collaborative tools.
363
+ 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
364
 
269
365
  #### How It Works
270
366
 
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.
367
+ 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.
368
+ 2. **Implement `connect` in a Resource**: For custom bi-directional logic, implement the `connect` method.
369
+ 3. **Use Pub/Sub**: Use `tables.TableName.subscribe(query)` to listen for specific data changes and stream them to the client.
370
+ 4. **Handle SSE**: Ensure your `connect` method gracefully handles cases where `incomingMessages` is null (Server-Sent Events).
371
+ 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).
372
+
373
+ #### Examples
374
+
375
+ ##### Bi-directional WebSocket Resource
376
+
377
+ ```typescript
378
+ import { Resource, tables } from 'harper';
379
+
380
+ export class MySocket extends Resource {
381
+ async *connect(target, incomingMessages) {
382
+ // Subscribe to table changes
383
+ const subscription = await tables.MyTable.subscribe(target);
384
+ if (!incomingMessages) {
385
+ return subscription; // SSE mode
386
+ }
387
+
388
+ // Handle incoming client messages
389
+ for await (let message of incomingMessages) {
390
+ yield { received: message };
391
+ }
392
+ }
393
+ }
394
+ ```
274
395
 
275
396
  ### 2.4 Checking Authentication
276
397
 
277
- How to use sessions to verify user identity and roles.
398
+ Instructions for the agent to follow when handling authentication and sessions.
278
399
 
279
400
  #### When to Use
280
401
 
281
- Use this to secure your application by ensuring that only authorized users can access certain resources or perform specific actions.
402
+ 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
403
 
283
404
  #### How It Works
284
405
 
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.
406
+ 1. **Configure Harper for Sessions**: Ensure `harper-config.yaml` has sessions enabled and local auto-authorization disabled for testing:
407
+ ```yaml
408
+ authentication:
409
+ authorizeLocal: false
410
+ enableSessions: true
411
+ ```
412
+ 2. **Implement Sign In**: Use `this.getContext().login(username, password)` to create a session:
413
+ ```typescript
414
+ async post(_target, data) {
415
+ const context = this.getContext();
416
+ try {
417
+ await context.login(data.username, data.password);
418
+ } catch {
419
+ return new Response('Invalid credentials', { status: 403 });
420
+ }
421
+ return new Response('Logged in', { status: 200 });
422
+ }
423
+ ```
424
+ 3. **Identify Current User**: Use `this.getCurrentUser()` to access session data:
425
+ ```typescript
426
+ async get() {
427
+ const user = this.getCurrentUser?.();
428
+ if (!user) return new Response(null, { status: 401 });
429
+ return { username: user.username, role: user.role };
430
+ }
431
+ ```
432
+ 4. **Implement Sign Out**: Use `this.getContext().logout()` or delete the session from context:
433
+ ```typescript
434
+ async post() {
435
+ const context = this.getContext();
436
+ await context.session?.delete?.(context.session.id);
437
+ return new Response('Logged out', { status: 200 });
438
+ }
439
+ ```
440
+ 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`.
441
+
442
+ #### Examples
288
443
 
289
- ---
444
+ ##### Sign In Implementation
290
445
 
291
- ## 3. Logic & Extension
446
+ ```typescript
447
+ async post(_target, data) {
448
+ const context = this.getContext();
449
+ try {
450
+ await context.login(data.username, data.password);
451
+ } catch {
452
+ return new Response('Invalid credentials', { status: 403 });
453
+ }
454
+ return new Response('Logged in', { status: 200 });
455
+ }
456
+ ```
457
+
458
+ ##### Identify Current User
459
+
460
+ ```typescript
461
+ async get() {
462
+ const user = this.getCurrentUser?.();
463
+ if (!user) return new Response(null, { status: 401 });
464
+ return { username: user.username, role: user.role };
465
+ }
466
+ ```
467
+
468
+ ##### Sign Out Implementation
469
+
470
+ ```typescript
471
+ async post() {
472
+ const context = this.getContext();
473
+ await context.session?.delete?.(context.session.id);
474
+ return new Response('Logged out', { status: 200 });
475
+ }
476
+ ```
477
+
478
+ #### Status code conventions used here
479
+
480
+ - 200: Successful operation. For `GET /me`, a `200` with empty body means “not signed in”.
481
+ - 400: Missing required fields (e.g., username/password on sign-in).
482
+ - 401: No current session for an action that requires one (e.g., sign out when not signed in).
483
+ - 403: Authenticated but not authorized (bad credentials on login attempt, or insufficient privileges).
484
+
485
+ #### Client considerations
486
+
487
+ - 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.
488
+ - If developing locally, double-check the server config still has `authentication.authorizeLocal: false` to avoid accidental superuser bypass.
489
+
490
+ #### Token-based auth (JWT + refresh token) for non-browser clients
491
+
492
+ 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**:
493
+
494
+ - **JWT (`operation_token`)**: short-lived bearer token used to authorize API requests.
495
+ - **Refresh token (`refresh_token`)**: longer-lived token used to mint a new JWT when it expires.
496
+
497
+ This project includes two Resource patterns for that flow:
292
498
 
293
- **Impact: MEDIUM**
499
+ ##### Issuing tokens: `IssueTokens`
500
+
501
+ **Description / use case:** Generate `{ refreshToken, jwt }` either:
502
+
503
+ - with an existing Authorization token (either Basic Auth or a JWT) and you want to issue new tokens, or
504
+ - from an explicit `{ username, password }` payload (useful for direct “login” from a CLI/mobile client).
505
+
506
+ ```javascript
507
+ export class IssueTokens extends Resource {
508
+ static loadAsInstance = false;
509
+
510
+ async get(target) {
511
+ const { refresh_token: refreshToken, operation_token: jwt } =
512
+ await databases.system.hdb_user.operation(
513
+ { operation: 'create_authentication_tokens' },
514
+ this.getContext(),
515
+ );
516
+ return { refreshToken, jwt };
517
+ }
518
+
519
+ async post(target, data) {
520
+ if (!data.username || !data.password) {
521
+ throw new Error('username and password are required');
522
+ }
523
+
524
+ const { refresh_token: refreshToken, operation_token: jwt } =
525
+ await databases.system.hdb_user.operation({
526
+ operation: 'create_authentication_tokens',
527
+ username: data.username,
528
+ password: data.password,
529
+ });
530
+ return { refreshToken, jwt };
531
+ }
532
+ }
533
+ ```
534
+
535
+ **Recommended documentation notes to include:**
536
+
537
+ - `GET` variant: intended for “I already have an Authorization token, give me new tokens”.
538
+ - `POST` variant: intended for “I have credentials, give me tokens”.
539
+ - Response shape:
540
+ - `refreshToken`: store securely (long-lived).
541
+ - `jwt`: attach to requests (short-lived).
542
+
543
+ ##### Refreshing a JWT: `RefreshJWT`
544
+
545
+ **Description / use case:** When the JWT expires, the client uses the refresh token to get a new JWT without re-supplying username/password.
546
+
547
+ ```javascript
548
+ export class RefreshJWT extends Resource {
549
+ static loadAsInstance = false;
550
+
551
+ async post(target, data) {
552
+ if (!data.refreshToken) {
553
+ throw new Error('refreshToken is required');
554
+ }
555
+
556
+ const { operation_token: jwt } = await databases.system.hdb_user.operation({
557
+ operation: 'refresh_operation_token',
558
+ refresh_token: data.refreshToken,
559
+ });
560
+ return { jwt };
561
+ }
562
+ }
563
+ ```
564
+
565
+ **Recommended documentation notes to include:**
566
+
567
+ - Requires `refreshToken` in the request body.
568
+ - Returns a new `{ jwt }`.
569
+ - If refresh fails (expired/revoked), client must re-authenticate (e.g., call `IssueTokens.post` again).
570
+
571
+ ##### Suggested client flow (high-level)
572
+
573
+ 1. **Sign in (token flow)**
574
+ - POST /IssueTokens/ with a body of `{ "username": "your username", "password": "your password" }` or GET /IssueTokens/ with an existing Authorization token.
575
+ - Receive `{ jwt, refreshToken }` in the response
576
+ 2. **Call protected APIs**
577
+ - Send the JWT with each request in the Authorization header (as your auth mechanism expects)
578
+ 3. **JWT expires**
579
+ - POST /RefreshJWT/ with a body of `{ "refreshToken": "your refresh token" }`.
580
+ - Receive `{ jwt }` in the response and continue
581
+
582
+ #### Quick checklist
583
+
584
+ - [ ] Public endpoints explicitly `allowRead`/`allowCreate` as needed.
585
+ - [ ] Sign-in uses `context.login` and handles 400/403 correctly.
586
+ - [ ] Protected routes call `ensureSuperUser(this.getCurrentUser())` (or another role check) before doing work.
587
+ - [ ] Sign-out verifies a session and deletes it.
588
+ - [ ] `authentication.authorizeLocal` is `false` and `enableSessions` is `true` in Harper config.
589
+ - [ ] If using tokens: `IssueTokens` issues `{ jwt, refreshToken }`, `RefreshJWT` refreshes `{ jwt }` with a `refreshToken`.
590
+
591
+ ## 3. Logic & Extension
294
592
 
295
593
  ### 3.1 Custom Resources
296
594
 
297
- How to define custom REST endpoints using JavaScript or TypeScript.
595
+ Instructions for the agent to follow when creating custom resources in Harper.
596
+
597
+ #### When to Use
598
+
599
+ 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
600
 
299
601
  #### How It Works
300
602
 
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:
603
+ 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.
604
+ 2. **Create the Resource File**: Create a `.ts` or `.js` file in the directory specified by `jsResource` in `config.yaml` (typically `resources/`).
605
+ 3. **Define the Resource Class**: Export a class extending `Resource` from `harper`:
606
+
607
+ ```typescript
608
+ import { type RequestTargetOrId, Resource } from 'harper';
609
+
610
+ export class MyResource extends Resource {
611
+ async get(target?: RequestTargetOrId) {
612
+ return { message: 'Hello from custom GET!' };
613
+ }
614
+ }
615
+ ```
616
+
617
+ 4. **Implement HTTP Methods**: Add methods like `get`, `post`, `put`, `patch`, or `delete` to handle corresponding requests.
618
+ 5. **Route Nesting and Naming**: You can control the URL structure by how you export your resources:
305
619
  - **Direct Class Export**: `export class Foo extends Resource` creates endpoints at `/Foo/`. Class names are case-sensitive in the URL.
306
620
  - **Nested Objects**: `export const Bar = { Foo };` creates endpoints at `/Bar/Foo/`.
307
621
  - **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.
622
+ 6. **Access Tables (Optional)**: Import and use the `tables` object to interact with your data:
623
+ ```typescript
624
+ import { tables } from 'harper';
625
+ // ... inside a method
626
+ const results = await tables.MyTable.list();
627
+ ```
628
+ 7. **Configure Loading**: Ensure `config.yaml` points to your resource files (e.g., `jsResource: { files: 'resources/*.ts' }`).
309
629
 
310
- ### 3.2 Extending Table Resources
630
+ ### 3.2 Extending Tables
311
631
 
312
- Adding custom logic to automatically generated table resources.
632
+ Instructions for the agent to follow when extending table resources in Harper.
633
+
634
+ #### When to Use
635
+
636
+ 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
637
 
314
638
  #### How It Works
315
639
 
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.
640
+ 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.
641
+ ```graphql
642
+ type MyTable @table {
643
+ id: ID @primaryKey
644
+ name: String
645
+ }
646
+ ```
647
+ 2. **Create the Extension File**: Create a `.ts` file in your `resources/` directory.
648
+ 3. **Extend the Table Resource**: Export a class that extends `tables.YourTableName`:
649
+
650
+ ```typescript
651
+ import { type RequestTargetOrId, tables } from 'harper';
652
+
653
+ export class MyTable extends tables.MyTable {
654
+ async post(target: RequestTargetOrId, record: any) {
655
+ // Custom logic here
656
+ if (!record.name) {
657
+ throw new Error('Name required');
658
+ }
659
+ return super.post(target, record);
660
+ }
661
+ }
662
+ ```
663
+
664
+ 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.
665
+ 5. **Implement Logic**: Use overrides for validation, side effects, or transforming data before/after database operations.
319
666
 
320
667
  ### 3.3 Programmatic Table Requests
321
668
 
322
- How to use filters, operators, sorting, and pagination in programmatic table requests.
669
+ Instructions for the agent to follow when interacting with Harper tables via code.
323
670
 
324
- #### Usage
671
+ #### When to Use
325
672
 
326
- When writing custom resources, use the internal API to query tables with full support for advanced filtering and sorting.
673
+ Use this skill when you need to perform database operations (CRUD, search, subscribe) from within Harper Resources or scripts.
327
674
 
328
- ### 3.4 TypeScript Type Stripping
675
+ #### How It Works
329
676
 
330
- Using TypeScript directly without build tools via Node.js Type Stripping.
677
+ 1. **Access the Table**: Use the global `tables` object followed by your table name (e.g., `tables.MyTable`).
678
+ 2. **Perform CRUD Operations**:
679
+ - **Get**: `await tables.MyTable.get(id)` for a single record or `await tables.MyTable.get({ conditions: [...] })` for multiple.
680
+ - **Create**: `await tables.MyTable.post(record)` (auto-generates ID) or `await tables.MyTable.put(id, record)`.
681
+ - **Update**: `await tables.MyTable.patch(id, partialRecord)` for partial updates.
682
+ - **Delete**: `await tables.MyTable.delete(id)`.
683
+ 3. **Use Updatable Records for Atomic Ops**: Call `update(id)` to get a reference, then use `addTo` or `subtractFrom` for atomic increments/decrements:
684
+ ```typescript
685
+ const stats = await tables.Stats.update('daily');
686
+ stats.addTo('viewCount', 1);
687
+ ```
688
+ 4. **Search and Stream**: Use `search(query)` for efficient streaming of large result sets:
689
+ ```typescript
690
+ for await (const record of tables.MyTable.search({ conditions: [...] })) {
691
+ // process record
692
+ }
693
+ ```
694
+ See the [Query Conditions](#query-conditions) section below for the full query object reference.
695
+ 5. **Real-time Subscriptions**: Use `subscribe(query)` to listen for changes:
696
+ ```typescript
697
+ for await (const event of tables.MyTable.subscribe(query)) {
698
+ // handle event
699
+ }
700
+ ```
701
+ 6. **Publish Events**: Use `publish(id, message)` to trigger subscriptions without necessarily persisting data.
702
+
703
+ #### Query Conditions
704
+
705
+ When passing a query to `search()`, `get()`, or `subscribe()`, use a query object with a `conditions` array.
706
+
707
+ ##### Condition Object Shape
708
+
709
+ | Property | Description |
710
+ | ------------ | ------------------------------------------------------------------------------------------ |
711
+ | `attribute` | Field name, or array of field names to traverse a relationship (e.g., `['brand', 'name']`) |
712
+ | `value` | The value to compare against |
713
+ | `comparator` | One of the comparator strings below (default: `equals`) |
714
+ | `operator` | `and` (default) or `or` — applies to a nested `conditions` block |
715
+ | `conditions` | Nested array of condition objects for complex AND/OR logic |
716
+
717
+ ##### Comparator Values
718
+
719
+ Use these exact strings — incorrect comparator names will silently fail or error:
720
+
721
+ | Comparator | Meaning |
722
+ | -------------------- | ---------------------------------------------------------- |
723
+ | `equals` | Exact match (default) |
724
+ | `not_equal` | Not equal |
725
+ | `greater_than` | `>` |
726
+ | `greater_than_equal` | `>=` |
727
+ | `less_than` | `<` |
728
+ | `less_than_equal` | `<=` |
729
+ | `starts_with` | String starts with value |
730
+ | `contains` | String contains value |
731
+ | `ends_with` | String ends with value |
732
+ | `between` | Value is between two bounds (pass `value` as `[min, max]`) |
733
+
734
+ ##### Query Object Parameters
735
+
736
+ | Property | Description |
737
+ | ------------ | ------------------------------------------------------------------------------------ |
738
+ | `conditions` | Array of condition objects |
739
+ | `limit` | Maximum number of records to return |
740
+ | `offset` | Number of records to skip (for pagination) |
741
+ | `select` | Array of attribute names to return; supports `$id` and `$updatedtime` |
742
+ | `sort` | Object with `attribute`, `descending` (bool), and optional `next` for secondary sort |
743
+
744
+ ##### Examples
745
+
746
+ **Simple filter:**
747
+
748
+ ```javascript
749
+ for await (const record of tables.Product.search({
750
+ conditions: [{ attribute: 'price', comparator: 'less_than', value: 100 }],
751
+ limit: 20,
752
+ })) { ... }
753
+ ```
331
754
 
332
- #### Configuration
755
+ **AND + nested OR:**
756
+
757
+ ```javascript
758
+ for await (const record of tables.Product.search({
759
+ conditions: [
760
+ { attribute: 'price', comparator: 'less_than', value: 100 },
761
+ {
762
+ operator: 'or',
763
+ conditions: [
764
+ { attribute: 'rating', comparator: 'greater_than', value: 4 },
765
+ { attribute: 'featured', value: true },
766
+ ],
767
+ },
768
+ ],
769
+ })) { ... }
770
+ ```
333
771
 
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.
772
+ **Relationship traversal:**
335
773
 
336
- ### 3.5 Caching
774
+ ```javascript
775
+ for await (const record of tables.Book.search({
776
+ conditions: [{ attribute: ['brand', 'name'], comparator: 'equals', value: 'Harper' }],
777
+ })) { ... }
778
+ ```
337
779
 
338
- How caching is defined and implemented in Harper applications.
780
+ **Sort and paginate:**
339
781
 
340
- #### Strategies
782
+ ```javascript
783
+ for await (const record of tables.Product.search({
784
+ conditions: [{ attribute: 'inStock', value: true }],
785
+ sort: { attribute: 'price', descending: false },
786
+ limit: 10,
787
+ offset: 20,
788
+ })) { ... }
789
+ ```
341
790
 
342
- - **In-memory**: For fast access to frequently used data.
343
- - **Distributed**: For scaling across multiple nodes in Harper Fabric.
791
+ #### Cautions
344
792
 
345
- ---
793
+ 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
794
 
347
- ## 4. Infrastructure & Ops
795
+ ### 3.4 TypeScript Type Stripping
348
796
 
349
- **Impact: MEDIUM**
797
+ Instructions for the agent to follow when using TypeScript in Harper.
350
798
 
351
- ### 4.1 Creating Harper Applications
799
+ #### When to Use
352
800
 
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.
801
+ 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
802
 
355
- #### When to Use
803
+ #### How It Works
356
804
 
357
- Use this command when starting a new Harper application or adding a new Harper microservice to an existing architecture.
805
+ 1. **Verify Node.js Version**: Ensure you are using Node.js v22.6.0 or higher.
806
+ 2. **Name Files with `.ts`**: Create your resource files in the `resources/` directory with a `.ts` extension.
807
+ 3. **Use TypeScript Syntax**: Write your resource classes using standard TypeScript (interfaces, types, etc.).
808
+ ```typescript
809
+ import { Resource } from 'harper';
810
+ export class MyResource extends Resource {
811
+ async get(): Promise<{ message: string }> {
812
+ return { message: 'Running TS directly!' };
813
+ }
814
+ }
815
+ ```
816
+ 4. **Use Explicit Extensions in Imports**: When importing other local modules, include the `.ts` extension: `import { helper } from './helper.ts'`.
817
+ 5. **Configure `config.yaml`**: Ensure `jsResource` points to your `.ts` files:
818
+ ```yaml
819
+ jsResource:
820
+ files: 'resources/*.ts'
821
+ ```
358
822
 
359
- #### Commands
823
+ ### 3.5 Caching
360
824
 
361
- Initialize a project using your preferred package manager:
825
+ Instructions for the agent to follow when implementing caching in Harper.
362
826
 
363
- **NPM**
827
+ #### When to Use
364
828
 
365
- ```bash
366
- npm create harper@latest
367
- ```
829
+ 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.
368
830
 
369
- **PNPM**
831
+ #### How It Works
370
832
 
371
- ```bash
372
- pnpm create harper@latest
373
- ```
833
+ 1. **Configure a Cache Table**: Define a table in your `schema.graphql` with an `expiration` (in seconds).
834
+ 2. **Define an External Source**: Create a Resource class that fetches the data from your source.
835
+ 3. **Attach Source to Table**: Use `sourcedFrom` to link your resource to the table.
836
+ 4. **Implement Active Caching (Optional)**: Use `subscribe()` for proactive updates. See [Real-Time Apps](real-time-apps.md).
837
+ 5. **Implement Write-Through Caching (Optional)**: Define `put` or `post` in your resource to propagate updates upstream.
374
838
 
375
- **Bun**
839
+ #### Examples
376
840
 
377
- ```bash
378
- bun create harper@latest
841
+ ##### Schema Configuration
842
+
843
+ ```graphql
844
+ type MyCache @table(expiration: 3600) @export {
845
+ id: ID @primaryKey
846
+ }
379
847
  ```
380
848
 
381
- ### 4.2 Creating a Fabric Account and Cluster
849
+ ##### Resource Implementation
382
850
 
383
- Follow these steps to set up your Harper Fabric environment for deployment.
851
+ ```js
852
+ import { Resource, tables } from 'harper';
384
853
 
385
- #### How It Works
854
+ export class ThirdPartyAPI extends Resource {
855
+ async get() {
856
+ const id = this.getId();
857
+ const response = await fetch(`https://api.example.com/items/${id}`);
858
+ if (!response.ok) {
859
+ throw new Error('Source fetch failed');
860
+ }
861
+ return await response.json();
862
+ }
863
+ }
386
864
 
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
- ```
865
+ // Attach source to table
866
+ tables.MyCache.sourcedFrom(ThirdPartyAPI);
867
+ ```
398
868
 
399
- ### 4.3 Deploying to Harper Fabric
869
+ ## 4. Infrastructure & Ops
400
870
 
401
- Globally scaling your Harper application.
871
+ ### 4.1 Deploying to Harper Fabric
402
872
 
403
- #### Benefits
873
+ Instructions for the agent to follow when deploying to Harper Fabric.
404
874
 
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.
875
+ #### When to Use
876
+
877
+ Use this skill when you are ready to move your Harper application from local development to a cloud-hosted environment.
408
878
 
409
879
  #### How It Works
410
880
 
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.
881
+ 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
882
  2. **Configure Environment**: Add your cluster credentials and cluster application URL to `.env`:
413
883
  ```bash
414
884
  CLI_TARGET_USERNAME='YOUR_CLUSTER_USERNAME'
@@ -422,7 +892,7 @@ Globally scaling your Harper application.
422
892
 
423
893
  If your application was not created with `npm create harper`, you'll need to manually configure the deployment scripts and CI/CD workflow.
424
894
 
425
- #### 1. Update `package.json`
895
+ ##### 1. Update `package.json`
426
896
 
427
897
  Add the following scripts and dependencies to your `package.json`:
428
898
 
@@ -439,7 +909,7 @@ Add the following scripts and dependencies to your `package.json`:
439
909
  }
440
910
  ```
441
911
 
442
- #### Why split the scripts?
912
+ ###### Why split the scripts?
443
913
 
444
914
  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
915
 
@@ -448,7 +918,7 @@ The `deploy` script is separated from `deploy:component` to ensure environment v
448
918
 
449
919
  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
920
 
451
- #### 2. Configure GitHub Actions
921
+ ##### 2. Configure GitHub Actions
452
922
 
453
923
  Create a `.github/workflows/deploy.yaml` file with the following content:
454
924
 
@@ -467,12 +937,15 @@ jobs:
467
937
  runs-on: ubuntu-latest
468
938
  steps:
469
939
  - name: Checkout code
470
- uses: actions/checkout@v4
940
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
941
+ with:
942
+ fetch-depth: 0
943
+ fetch-tags: true
471
944
  - name: Set up Node.js
472
- uses: actions/setup-node@v4
945
+ uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
473
946
  with:
474
947
  cache: 'npm'
475
- node-version: '20'
948
+ node-version-file: '.nvmrc'
476
949
  - name: Install dependencies
477
950
  run: npm ci
478
951
  - name: Run unit tests
@@ -487,20 +960,140 @@ jobs:
487
960
  CLI_TARGET_PASSWORD: ${{ secrets.CLI_TARGET_PASSWORD }}
488
961
  ```
489
962
 
490
- Be sure to set the following repository secrets in your GitHub repository:
963
+ Be sure to set the following repository secrets in your GitHub repository's /settings/secrets/actions:
491
964
 
492
965
  - `CLI_TARGET`
493
966
  - `CLI_TARGET_USERNAME`
494
967
  - `CLI_TARGET_PASSWORD`
495
968
 
969
+ ### 4.2 Creating a Harper Fabric Account and Cluster
970
+
971
+ Follow these steps to set up your Harper Fabric environment for deployment.
972
+
973
+ #### How It Works
974
+
975
+ 1. **Sign Up/In**: Go to [https://fabric.harper.fast/](https://fabric.harper.fast/) and sign up or sign in.
976
+ 2. **Create an Organization**: Create an organization (org) to manage your projects.
977
+ 3. **Create a Cluster**: Create a new cluster. This can be on the free tier, no credit card required.
978
+ 4. **Set Credentials**: During setup, set the cluster username and password to finish configuring it.
979
+ 5. **Get Application URL**: Navigate to the **Config** tab and copy the **Application URL**.
980
+ 6. **Configure Environment**: Update your `.env` file or GitHub Actions secrets with cluster-specific credentials.
981
+ 7. **Next Steps**: See the [deploying-to-harper-fabric](deploying-to-harper-fabric.md) rule for detailed instructions on deploying your application successfully.
982
+
983
+ #### Examples
984
+
985
+ ##### Environment Configuration
986
+
987
+ ```bash
988
+ CLI_TARGET_USERNAME='YOUR_CLUSTER_USERNAME'
989
+ CLI_TARGET_PASSWORD='YOUR_CLUSTER_PASSWORD'
990
+ CLI_TARGET='YOUR_CLUSTER_URL'
991
+ ```
992
+
993
+ ### 4.3 Creating Harper Applications
994
+
995
+ 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.
996
+
997
+ #### When to Use
998
+
999
+ Use this command when starting a new Harper application or adding a new Harper microservice to an existing architecture.
1000
+
1001
+ #### Commands
1002
+
1003
+ Initialize a project using your preferred package manager:
1004
+
1005
+ ##### NPM
1006
+
1007
+ ```bash
1008
+ npm create harper@latest
1009
+ ```
1010
+
1011
+ ##### PNPM
1012
+
1013
+ ```bash
1014
+ pnpm create harper@latest
1015
+ ```
1016
+
1017
+ ##### Bun
1018
+
1019
+ ```bash
1020
+ bun create harper@latest
1021
+ ```
1022
+
1023
+ #### Options
1024
+
1025
+ You can specify the project name and template directly:
1026
+
1027
+ ```bash
1028
+ npm create harper@latest my-app --template default
1029
+ ```
1030
+
1031
+ #### Next Steps
1032
+
1033
+ 1. **Configure Environment**: Set up your `.env` file with local or cloud credentials.
1034
+ 2. **Define Schema**: Modify `schema.graphql` to fit your application's data model.
1035
+ 3. **Start Development**: Run `npm run dev` to start the local Harper instance.
1036
+ 4. **Deploy**: Use `npm run deploy` to push your application to Harper Fabric.
1037
+
496
1038
  ### 4.4 Serving Web Content
497
1039
 
498
- Two ways to serve web content from a Harper application.
1040
+ Instructions for the agent to follow when serving web content from Harper.
1041
+
1042
+ #### When to Use
499
1043
 
500
- #### Methods
1044
+ Use this skill when you need to serve a frontend (HTML, CSS, JS, or a React app) directly from your Harper instance.
501
1045
 
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.
1046
+ #### How It Works
1047
+
1048
+ 1. **Choose a Method**: Decide between the simple Static Plugin or the integrated Vite Plugin.
1049
+ 2. **Option A: Static Plugin (Simple)**:
1050
+ - Add to `config.yaml`:
1051
+ ```yaml
1052
+ static:
1053
+ files: 'web/*'
1054
+ ```
1055
+ - Place files in a `web/` folder in the project root.
1056
+ - Files are served at the root URL (e.g., `http://localhost:9926/index.html`).
1057
+ 3. **Option B: Vite Plugin (Advanced/Development)**:
1058
+ - Add to `config.yaml`:
1059
+ ```yaml
1060
+ '@harperfast/vite-plugin':
1061
+ package: '@harperfast/vite-plugin'
1062
+ ```
1063
+ - Ensure `vite.config.ts` and `index.html` are in the project root.
1064
+
1065
+ ```javascript
1066
+ import vue from '@vitejs/plugin-vue';
1067
+ import path from 'node:path';
1068
+ import { defineConfig } from 'vite';
1069
+
1070
+ // https://vite.dev/config/
1071
+ export default defineConfig({
1072
+ plugins: [vue()],
1073
+ resolve: {
1074
+ alias: {
1075
+ '@': path.resolve(import.meta.dirname, './src'),
1076
+ },
1077
+ },
1078
+ build: {
1079
+ outDir: 'web',
1080
+ emptyOutDir: true,
1081
+ rolldownOptions: {
1082
+ external: ['**/*.test.*', '**/*.spec.*'],
1083
+ },
1084
+ },
1085
+ });
1086
+ ```
1087
+
1088
+ - Install dependencies: `npm install --save-dev vite @harperfast/vite-plugin`.
1089
+ - Then `harper run .` will start up Harper and Vite with HMR. Vite does _not_ need to be executed separately.
1090
+
1091
+ 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:
1092
+ ```json
1093
+ "build": "vite build",
1094
+ "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",
1095
+ ```
1096
+ 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
1097
 
505
1098
  ### 4.5 Logging Best Practices
506
1099