@harperfast/template-vue-ts-studio 1.6.2 → 1.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/.agents/skills/harper-best-practices/AGENTS.md +1428 -303
  2. package/.agents/skills/harper-best-practices/SKILL.md +24 -20
  3. package/.agents/skills/harper-best-practices/rules/adding-tables-with-schemas.md +4 -2
  4. package/.agents/skills/harper-best-practices/rules/automatic-apis.md +144 -16
  5. package/.agents/skills/harper-best-practices/rules/caching.md +134 -21
  6. package/.agents/skills/harper-best-practices/rules/checking-authentication.md +139 -148
  7. package/.agents/skills/harper-best-practices/rules/creating-a-fabric-account-and-cluster.md +2 -0
  8. package/.agents/skills/harper-best-practices/rules/creating-harper-apps.md +2 -0
  9. package/.agents/skills/harper-best-practices/rules/custom-resources.md +5 -3
  10. package/.agents/skills/harper-best-practices/rules/defining-relationships.md +2 -0
  11. package/.agents/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +97 -77
  12. package/.agents/skills/harper-best-practices/rules/extending-tables.md +3 -1
  13. package/.agents/skills/harper-best-practices/rules/handling-binary-data.md +2 -0
  14. package/.agents/skills/harper-best-practices/rules/logging.md +154 -77
  15. package/.agents/skills/harper-best-practices/rules/programmatic-table-requests.md +91 -0
  16. package/.agents/skills/harper-best-practices/rules/querying-rest-apis.md +190 -15
  17. package/.agents/skills/harper-best-practices/rules/real-time-apps.md +80 -21
  18. package/.agents/skills/harper-best-practices/rules/schema-design-tooling.md +4 -2
  19. package/.agents/skills/harper-best-practices/rules/serving-web-content.md +3 -2
  20. package/.agents/skills/harper-best-practices/rules/typescript-type-stripping.md +3 -1
  21. package/.agents/skills/harper-best-practices/rules/using-blob-datatype.md +3 -1
  22. package/.agents/skills/harper-best-practices/rules/vector-indexing.md +85 -120
  23. package/.agents/skills/harper-best-practices/rules.manifest.yaml +258 -0
  24. package/package.json +1 -1
  25. package/skills-lock.json +1 -1
@@ -38,6 +38,8 @@ Reference these guidelines when:
38
38
 
39
39
  See the concrete examples embedded in each rule subsection below (GraphQL schemas, REST query patterns, and deployment workflow snippets).
40
40
 
41
+ <!-- BEGIN GENERATED INDEX -->
42
+
41
43
  ## Rule Categories by Priority
42
44
 
43
45
  | Priority | Category | Impact | Prefix |
@@ -51,35 +53,37 @@ See the concrete examples embedded in each rule subsection below (GraphQL schema
51
53
 
52
54
  ### 1. Schema & Data Design (HIGH)
53
55
 
54
- - `adding-tables-with-schemas` - Define tables using GraphQL schemas and directives
55
- - `schema-design-tooling` - Core directives and GraphQL IDE/agent configuration
56
- - `defining-relationships` - Link tables using the `@relationship` directive
57
- - `vector-indexing` - Efficient similarity search with vector indexes
58
- - `using-blob-datatype` - Store and retrieve large data (Blobs)
59
- - `handling-binary-data` - Manage binary data like images or MP3s
56
+ - `adding-tables-with-schemas` Guidelines for adding tables to a Harper database using GraphQL schemas.
57
+ - `schema-design-tooling` Best practices for Harper schema design, including core directives and GraphQL tooling configuration.
58
+ - `defining-relationships` How to define and use relationships between tables in Harper using GraphQL.
59
+ - `vector-indexing` How to enable and query vector indexes for similarity search in Harper.
60
+ - `using-blob-datatype` How to use the Blob data type for efficient binary storage in Harper.
61
+ - `handling-binary-data` How to store and serve binary data like images or audio in Harper.
60
62
 
61
63
  ### 2. API & Communication (HIGH)
62
64
 
63
- - `automatic-apis` - Leverage automatically generated CRUD endpoints
64
- - `querying-rest-apis` - Filters, sorting, and pagination in REST requests
65
- - `real-time-apps` - WebSockets and Pub/Sub for Real-Time Apps
66
- - `checking-authentication` - Secure apps with session-based identity verification
65
+ - `automatic-apis` How to use Harper's automatically generated REST and WebSocket APIs.
66
+ - `querying-rest-apis` How to use query parameters to filter, sort, and paginate Harper REST APIs.
67
+ - `real-time-apps` — How to build real-time features in Harper using WebSockets and Pub/Sub.
68
+ - `checking-authentication` How to handle user authentication and sessions in Harper Resources.
67
69
 
68
70
  ### 3. Logic & Extension (MEDIUM)
69
71
 
70
- - `custom-resources` - Define custom REST endpoints using JS/TS
71
- - `extending-tables` - Add custom logic to generated table resources
72
- - `programmatic-table-requests` - Advanced filtering and sorting in code
73
- - `typescript-type-stripping` - Use TypeScript without build tools
74
- - `caching` - Implement and define caching for performance
72
+ - `custom-resources` How to define custom REST endpoints with JavaScript or TypeScript in Harper.
73
+ - `extending-tables` How to add custom logic to automatically generated table resources in Harper.
74
+ - `programmatic-table-requests` How to interact with Harper tables programmatically using the `tables` object.
75
+ - `typescript-type-stripping` How to run TypeScript files directly in Harper without a build step.
76
+ - `caching` How to implement integrated data caching in Harper from external sources.
75
77
 
76
78
  ### 4. Infrastructure & Ops (MEDIUM)
77
79
 
78
- - `deploying-to-harper-fabric` - Scale globally with Harper Fabric
79
- - `creating-a-fabric-account-and-cluster` - Setting up your Harper Fabric cloud infrastructure
80
- - `creating-harper-apps` - Quickstart with `npm create harper@latest`
81
- - `serving-web-content` - Ways to serve web content from Harper
82
- - `logging` - Use standard console and Harper's granular logger
80
+ - `deploying-to-harper-fabric` How to deploy a Harper application to the Harper Fabric cloud.
81
+ - `creating-a-fabric-account-and-cluster` How to create a Harper Fabric account, organization, and cluster.
82
+ - `creating-harper-apps` How to initialize a new Harper application using the CLI.
83
+ - `serving-web-content` How to serve static files and integrated Vite/React applications in Harper.
84
+ - `logging` Best practices for logging in Harper, including console capture, the granular logger interface, and programmatic log retrieval.
85
+
86
+ <!-- END GENERATED INDEX -->
83
87
 
84
88
  ## How to Use
85
89
 
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: adding-tables-with-schemas
3
3
  description: Guidelines for adding tables to a Harper database using GraphQL schemas.
4
+ metadata:
5
+ mode: synthesized
4
6
  ---
5
7
 
6
8
  # Adding Tables with Schemas
@@ -14,9 +16,9 @@ Use this skill when you need to define new data structures or modify existing on
14
16
  ## How It Works
15
17
 
16
18
  1. **Create Dedicated Schema Files**: Prefer having a dedicated schema `.graphql` file for each table. Check the `config.yaml` file under `graphqlSchema.files` to see how it's configured. It typically accepts wildcards (e.g., `schemas/*.graphql`), but may be configured to point at a single file.
17
- 2. **Use Directives**: All available directives for defining your schema are defined in `node_modules/harperdb/schema.graphql`. Common directives include `@table`, `@export`, `@primaryKey`, `@indexed`, and `@relationship`.
19
+ 2. **Use Directives**: All available directives for defining your schema are defined in `node_modules/harper/schema.graphql`. Common directives include `@table`, `@export`, `@primaryKey`, `@indexed`, and `@relationship`.
18
20
  3. **Define Relationships**: Link tables together using the `@relationship` directive. For more details, see the [Defining Relationships](defining-relationships.md) skill.
19
- 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. For a detailed list of available endpoints and how to use them, see the [Automatic REST APIs](automatic-apis.md) skill.
21
+ 4. **Enable Automatic APIs**: If you add `@table @export` to a schema type, Harper automatically sets up REST and WebSocket APIs for basic CRUD operations against that table. **Important**: REST endpoints also require `rest: true` in `config.yaml` — without it, `@export`ed tables will not respond to HTTP requests. For a detailed list of available endpoints and how to use them, see the [Automatic REST APIs](automatic-apis.md) skill.
20
22
  - `GET /{TableName}`: Describes the schema itself.
21
23
  - `GET /{TableName}/`: Lists all records (supports filtering, sorting, and pagination via query parameters). See the [Querying REST APIs](querying-rest-apis.md) skill for details.
22
24
  - `GET /{TableName}/{id}`: Retrieves a single record by its ID.
@@ -1,37 +1,165 @@
1
1
  ---
2
2
  name: automatic-apis
3
3
  description: How to use Harper's automatically generated REST and WebSocket APIs.
4
+ metadata:
5
+ mode: generate
6
+ sources:
7
+ - reference/v5/rest/overview.md
8
+ - reference/v5/rest/websockets.md
9
+ sourceCommit: b7fbddadd42eb4487190b650a9abc4bcfeef5819
10
+ inputHash: 8fcf0cfe190e013e
4
11
  ---
5
12
 
6
13
  # Automatic APIs
7
14
 
8
- Instructions for the agent to follow when utilizing Harper's automatic APIs.
15
+ Instructions for the agent to follow when enabling and using Harper's automatically generated REST and WebSocket APIs.
9
16
 
10
17
  ## When to Use
11
18
 
12
- 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.
19
+ Apply this rule when adding REST or WebSocket API access to Harper tables or custom resources. Use it when configuring `config.yaml` to expose endpoints, mapping HTTP methods to resource operations, or implementing real-time WebSocket connections on a resource class.
13
20
 
14
21
  ## How It Works
15
22
 
16
- 1. **Enable Automatic APIs**: Ensure your GraphQL schema includes the `@export` directive for the table.
17
- 2. **Access REST Endpoints**: Use the standard endpoints for your table (Note: Paths are case-sensitive).
18
- 3. **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).
19
- 4. **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.
20
- 5. **Customize if Needed**: If the automatic APIs don't meet your requirements, [customize the resources](./custom-resources.md).
23
+ 1. **Enable the REST plugin**: Add `rest: true` to your application's `config.yaml`. This activates the HTTP REST interface and enables WebSocket support by default.
24
+
25
+ ```yaml
26
+ rest: true
27
+ ```
28
+
29
+ To configure optional behavior:
30
+
31
+ ```yaml
32
+ rest:
33
+ lastModified: true # enables Last-Modified response header support
34
+ webSocket: false # disables automatic WebSocket support (enabled by default)
35
+ ```
36
+
37
+ 2. **Export your resource in the schema**: Tables are not exposed by default. Use the `@export` directive in your schema definition to make a table available as a REST endpoint. The exported name defines the base URL path, served on the application HTTP server port (default `9926`).
38
+
39
+ 3. **Use the correct URL structure**: The REST interface follows a consistent path convention.
40
+
41
+ | Path | Description |
42
+ | -------------------------------------------- | ---------------------------------------------------------------------------------- |
43
+ | `/my-resource` | Returns a description of the resource (e.g., table metadata) |
44
+ | `/my-resource/` | Trailing slash — represents the full collection; append query parameters to search |
45
+ | `/my-resource/record-id` | A specific record identified by its primary key |
46
+ | `/my-resource/record-id/` | Trailing slash — collection of records with the given id prefix |
47
+ | `/my-resource/record-id/with/multiple/parts` | Record id with multiple path segments |
48
+
49
+ 4. **Map HTTP methods to operations**: Each HTTP method maps to a resource method and operation.
50
+ - **GET** — Retrieve a record or search. Calls `get()`.
51
+
52
+ ```
53
+ GET /MyTable/123
54
+ GET /MyTable/?name=Harper
55
+ GET /MyTable/123.propertyName
56
+ ```
57
+
58
+ Responses include an `ETag` header. Clients may send `If-None-Match` to receive `304 Not Modified` when the record is unchanged.
59
+
60
+ - **PUT** — Create or replace a record (upsert). Calls `put(record)`. Properties not in the body are removed.
61
+
62
+ ```
63
+ PUT /MyTable/123
64
+ Content-Type: application/json
65
+
66
+ { "name": "some data" }
67
+ ```
68
+
69
+ - **POST** — Create a new record without specifying a primary key. Calls `post(data)`. The assigned key is returned in the `Location` response header.
70
+
71
+ ```
72
+ POST /MyTable/
73
+ Content-Type: application/json
74
+
75
+ { "name": "some data" }
76
+ ```
77
+
78
+ - **PATCH** — Partially update a record, merging only provided properties. Unspecified properties are preserved.
79
+
80
+ ```
81
+ PATCH /MyTable/123
82
+ Content-Type: application/json
83
+
84
+ { "status": "active" }
85
+ ```
86
+
87
+ - **DELETE** — Delete a record or all records matching a query.
88
+ ```
89
+ DELETE /MyTable/123
90
+ DELETE /MyTable/?status=archived
91
+ ```
92
+
93
+ 5. **Access the auto-generated OpenAPI spec**: Harper generates an OpenAPI specification for all exported resources. Retrieve it at:
94
+
95
+ ```
96
+ GET /openapi
97
+ ```
98
+
99
+ 6. **Connect via WebSocket**: When `rest` is enabled, WebSocket support is on by default. Connect to a resource URL to subscribe to change events for that resource.
100
+
101
+ ```javascript
102
+ let ws = new WebSocket('wss://server/my-resource/341');
103
+ ws.onmessage = (event) => {
104
+ let data = JSON.parse(event.data);
105
+ };
106
+ ```
107
+
108
+ Connecting to `wss://server/my-resource/341` accesses the `my-resource` resource with record id `341` and subscribes to it. When the record changes or a message is published to it, the WebSocket connection receives the update.
109
+
110
+ 7. **Implement a custom `connect()` handler**: Override `connect(incomingMessages)` on a resource class to control WebSocket behavior. The method must return an async iterable or generator that produces messages to send to the client.
21
111
 
22
112
  ## Examples
23
113
 
24
- ### Schema Configuration
114
+ **Simple echo server using an async generator**:
115
+
116
+ ```javascript
117
+ export class Echo extends Resource {
118
+ async *connect(incomingMessages) {
119
+ for await (let message of incomingMessages) {
120
+ yield message; // echo each message back
121
+ }
122
+ }
123
+ }
124
+ ```
125
+
126
+ **Using the default `connect()` with event-style access and a timer**:
127
+
128
+ ```javascript
129
+ export class Example extends Resource {
130
+ connect(incomingMessages) {
131
+ let outgoingMessages = super.connect();
132
+
133
+ let timer = setInterval(() => {
134
+ outgoingMessages.send({ greeting: 'hi again!' });
135
+ }, 1000);
136
+
137
+ incomingMessages.on('data', (message) => {
138
+ outgoingMessages.send(message); // echo incoming messages
139
+ });
25
140
 
26
- ```graphql
27
- type MyTable @table @export {
28
- id: ID @primaryKey
29
- name: String
141
+ outgoingMessages.on('close', () => {
142
+ clearInterval(timer);
143
+ });
144
+
145
+ return outgoingMessages;
146
+ }
30
147
  }
31
148
  ```
32
149
 
33
- ### Common REST Operations
150
+ **Minimal `config.yaml` enabling REST with WebSocket disabled**:
151
+
152
+ ```yaml
153
+ rest:
154
+ webSocket: false
155
+ ```
156
+
157
+ ## Notes
34
158
 
35
- - **List Records**: `GET /MyTable/`
36
- - **Create Record**: `POST /MyTable/`
37
- - **Update Record**: `PATCH /MyTable/{id}`
159
+ - Tables must be explicitly exported using `@export` in the schema — they are not exposed by default.
160
+ - `rest: true` is the minimal configuration to enable both REST and WebSocket support. See [real-time-apps.md](real-time-apps.md) for patterns around real-time WebSocket usage.
161
+ - For full query syntax on `GET` and `DELETE` with query parameters, see [querying-rest-apis.md](querying-rest-apis.md).
162
+ - The default `connect()` returns an iterable with a `send(message)` method and a `close` event for cleanup on disconnect.
163
+ - For MQTT over WebSockets, set the sub-protocol header `Sec-WebSocket-Protocol: mqtt`.
164
+ - In distributed environments, non-retained messages are delivered in the order received per node; retained messages (PUT/updated records) keep only the latest-timestamp version as the winning record across the cluster.
165
+ - Use the `Content-Type` request header to specify body format and the `Accept` header to request a specific response format.
@@ -1,50 +1,163 @@
1
1
  ---
2
2
  name: caching
3
3
  description: How to implement integrated data caching in Harper from external sources.
4
+ metadata:
5
+ mode: generate
6
+ sources:
7
+ - learn/developers/caching-with-harper.md
8
+ sourceCommit: b7fbddadd42eb4487190b650a9abc4bcfeef5819
9
+ inputHash: 8212a7d7dbbd2b18
4
10
  ---
5
11
 
6
- # Caching
12
+ # Caching External Data Sources in Harper
7
13
 
8
- Instructions for the agent to follow when implementing caching in Harper.
14
+ Instructions for the agent to implement integrated data caching in Harper by wrapping external sources with a cache table and `sourcedFrom`.
9
15
 
10
16
  ## When to Use
11
17
 
12
- 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.
18
+ Apply this rule when a Harper application needs to cache responses from an external API, microservice, or database to avoid repeated slow or expensive upstream calls. Use it whenever you need to define TTL-based cache expiration, observe ETag-based conditional responses, or manually invalidate cached entries.
13
19
 
14
20
  ## How It Works
15
21
 
16
- 1. **Configure a Cache Table**: Define a table in your `schema.graphql` with an `expiration` (in seconds).
17
- 2. **Define an External Source**: Create a Resource class that fetches the data from your source.
18
- 3. **Attach Source to Table**: Use `sourcedFrom` to link your resource to the table.
19
- 4. **Implement Active Caching (Optional)**: Use `subscribe()` for proactive updates. See [Real-Time Apps](real-time-apps.md).
20
- 5. **Implement Write-Through Caching (Optional)**: Define `put` or `post` in your resource to propagate updates upstream.
22
+ 1. **Define a cache table with `expiration`**: In `schema.graphql`, add the `expiration` argument to `@table`. The value is in seconds. Any record older than this threshold is considered stale and will be re-fetched on next access.
23
+
24
+ ```graphql
25
+ type JokeCache @table(expiration: 60) @export {
26
+ id: ID @primaryKey
27
+ setup: String
28
+ punchline: String
29
+ }
30
+ ```
31
+
32
+ 2. **Wrap the external source in `resources.js`**: Create an object with a `get(id)` method that fetches from the upstream source. Then call `sourcedFrom` on the table to register it.
33
+
34
+ ```javascript
35
+ const jokeAPI = {
36
+ async get(id) {
37
+ const response = await fetch(`https://official-joke-api.appspot.com/jokes/${id}`);
38
+ return response.json();
39
+ },
40
+ };
41
+
42
+ tables.JokeCache.sourcedFrom(jokeAPI);
43
+ ```
44
+
45
+ Harper's caching behavior after `sourcedFrom` is registered:
46
+ - A request arrives for `/JokeCache/1`.
47
+ - Harper checks if the record with id `1` exists in `JokeCache` and is not stale.
48
+ - If fresh, Harper returns it immediately.
49
+ - If missing or stale, Harper calls `jokeAPI.get()`, stores the result in `JokeCache`, and returns it.
50
+ - Multiple simultaneous requests for the same missing or stale record wait on a single upstream call — Harper prevents cache stampedes automatically.
51
+
52
+ 3. **Configure plugins in `config.yaml`**: Enable the schema, REST API, and JS resource plugins.
53
+
54
+ ```yaml
55
+ graphqlSchema:
56
+ files: 'schema.graphql'
57
+ rest: true
58
+ jsResource:
59
+ files: 'resources.js'
60
+ ```
61
+
62
+ 4. **Observe caching via ETags**: Harper automatically computes an ETag from the record's last-modified timestamp. On the first request you receive a `200` with an `etag` header. Pass that value back in `If-None-Match` on subsequent requests; Harper returns `304 Not Modified` with an empty body if the record is unchanged.
63
+
64
+ ```bash
65
+ curl -i 'http://localhost:9926/JokeCache/1' \
66
+ -H 'If-None-Match: "abCDefGHij"'
67
+ ```
68
+
69
+ 5. **Force a cache bypass**: Send `Cache-Control: no-cache` to make Harper skip the local cache and always call the upstream source, regardless of TTL.
70
+
71
+ ```bash
72
+ curl -i 'http://localhost:9926/JokeCache/1' \
73
+ -H 'Cache-Control: no-cache'
74
+ ```
75
+
76
+ 6. **Invalidate a cache entry on demand**: Remove `@export` from the schema type, then export a class of the same name in `resources.js` that extends the table and implements a `post` handler calling `this.invalidate(target)`.
77
+
78
+ ```graphql
79
+ type JokeCache @table(expiration: 60) {
80
+ id: ID @primaryKey
81
+ setup: String
82
+ punchline: String
83
+ }
84
+ ```
85
+
86
+ ```javascript
87
+ export class JokeCache extends tables.JokeCache {
88
+ static async post(target, data) {
89
+ const body = await data;
90
+ if (body?.action === 'invalidate') {
91
+ this.invalidate(target);
92
+ return { status: 200, data: { message: 'invalidated' } };
93
+ }
94
+ }
95
+ }
96
+ ```
97
+
98
+ Trigger invalidation with a `POST`:
99
+
100
+ ```bash
101
+ curl -X POST 'http://localhost:9926/JokeCache/1' \
102
+ -H 'Content-Type: application/json' \
103
+ -d '{"action": "invalidate"}'
104
+ ```
105
+
106
+ The next `GET /JokeCache/1` will fetch fresh data from the upstream source regardless of TTL.
21
107
 
22
108
  ## Examples
23
109
 
24
- ### Schema Configuration
110
+ Complete `schema.graphql` and `resources.js` for a cached external API with on-demand invalidation:
25
111
 
26
112
  ```graphql
27
- type MyCache @table(expiration: 3600) @export {
113
+ type JokeCache @table(expiration: 60) {
28
114
  id: ID @primaryKey
115
+ setup: String
116
+ punchline: String
29
117
  }
30
118
  ```
31
119
 
32
- ### Resource Implementation
33
-
34
- ```js
35
- import { Resource, tables } from 'harperdb';
120
+ ```javascript
121
+ // resources.js
36
122
 
37
- export class ThirdPartyAPI extends Resource {
123
+ const jokeAPI = {
38
124
  async get() {
39
125
  const id = this.getId();
40
- const response = await fetch(`https://api.example.com/items/${id}`);
41
- if (!response.ok) {
42
- throw new Error('Source fetch failed');
126
+ const response = await fetch(`https://official-joke-api.appspot.com/jokes/${id}`);
127
+ return response.json();
128
+ },
129
+ };
130
+
131
+ tables.JokeCache.sourcedFrom(jokeAPI);
132
+
133
+ export class JokeCache extends tables.JokeCache {
134
+ static async post(target, data) {
135
+ const body = await data;
136
+ if (body?.action === 'invalidate') {
137
+ this.invalidate(target);
138
+ return { status: 200, data: { message: 'invalidated' } };
43
139
  }
44
- return await response.json();
45
140
  }
46
141
  }
142
+ ```
47
143
 
48
- // Attach source to table
49
- tables.MyCache.sourcedFrom(ThirdPartyAPI);
144
+ First request cache miss, upstream is called, `200` returned:
145
+
146
+ ```bash
147
+ curl -i 'http://localhost:9926/JokeCache/1'
148
+ ```
149
+
150
+ Second request with ETag — cache hit, `304 Not Modified`:
151
+
152
+ ```bash
153
+ curl -i 'http://localhost:9926/JokeCache/1' \
154
+ -H 'If-None-Match: "abCDefGHij"'
50
155
  ```
156
+
157
+ ## Notes
158
+
159
+ - `expiration` is measured in seconds. Harper also supports separate `eviction` and `scanInterval` arguments on `@table` for fine-grained control over physical record removal.
160
+ - The `@export` directive on the schema type is not required when you export a Resource class of the same name from `resources.js` — the class export serves as the endpoint registration. See [custom-resources.md](custom-resources.md) for details on building Resource classes.
161
+ - Harper's REST layer automatically exposes `@export`-ed tables and Resource classes as HTTP endpoints. See [automatic-apis.md](automatic-apis.md) for how endpoints are structured and named.
162
+ - ETag values include their double quotes as part of the value — include them verbatim when passing the value in `If-None-Match`.
163
+ - `sourcedFrom` must be called after the table reference (`tables.JokeCache`) is available, which is guaranteed when the call is at the top level of `resources.js`.