@harperfast/template-vue-ts-studio 1.6.3 → 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.
- package/.agents/skills/harper-best-practices/AGENTS.md +1425 -300
- package/.agents/skills/harper-best-practices/SKILL.md +24 -20
- package/.agents/skills/harper-best-practices/rules/adding-tables-with-schemas.md +2 -0
- package/.agents/skills/harper-best-practices/rules/automatic-apis.md +141 -18
- package/.agents/skills/harper-best-practices/rules/caching.md +134 -21
- package/.agents/skills/harper-best-practices/rules/checking-authentication.md +139 -148
- package/.agents/skills/harper-best-practices/rules/creating-a-fabric-account-and-cluster.md +2 -0
- package/.agents/skills/harper-best-practices/rules/creating-harper-apps.md +2 -0
- package/.agents/skills/harper-best-practices/rules/custom-resources.md +2 -0
- package/.agents/skills/harper-best-practices/rules/defining-relationships.md +2 -0
- package/.agents/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +97 -77
- package/.agents/skills/harper-best-practices/rules/extending-tables.md +2 -0
- package/.agents/skills/harper-best-practices/rules/handling-binary-data.md +2 -0
- package/.agents/skills/harper-best-practices/rules/logging.md +154 -77
- package/.agents/skills/harper-best-practices/rules/programmatic-table-requests.md +32 -26
- package/.agents/skills/harper-best-practices/rules/querying-rest-apis.md +190 -15
- package/.agents/skills/harper-best-practices/rules/real-time-apps.md +80 -21
- package/.agents/skills/harper-best-practices/rules/schema-design-tooling.md +2 -0
- package/.agents/skills/harper-best-practices/rules/serving-web-content.md +2 -0
- package/.agents/skills/harper-best-practices/rules/typescript-type-stripping.md +2 -0
- package/.agents/skills/harper-best-practices/rules/using-blob-datatype.md +2 -0
- package/.agents/skills/harper-best-practices/rules/vector-indexing.md +85 -120
- package/.agents/skills/harper-best-practices/rules.manifest.yaml +258 -0
- package/package.json +1 -1
- 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`
|
|
55
|
-
- `schema-design-tooling`
|
|
56
|
-
- `defining-relationships`
|
|
57
|
-
- `vector-indexing`
|
|
58
|
-
- `using-blob-datatype`
|
|
59
|
-
- `handling-binary-data`
|
|
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`
|
|
64
|
-
- `querying-rest-apis`
|
|
65
|
-
- `real-time-apps` - WebSockets and Pub/Sub
|
|
66
|
-
- `checking-authentication`
|
|
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`
|
|
71
|
-
- `extending-tables`
|
|
72
|
-
- `programmatic-table-requests`
|
|
73
|
-
- `typescript-type-stripping`
|
|
74
|
-
- `caching`
|
|
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`
|
|
79
|
-
- `creating-a-fabric-account-and-cluster`
|
|
80
|
-
- `creating-harper-apps`
|
|
81
|
-
- `serving-web-content`
|
|
82
|
-
- `logging`
|
|
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,42 +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
|
|
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
|
-
|
|
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 REST
|
|
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
|
+
|
|
17
25
|
```yaml
|
|
18
26
|
rest: true
|
|
19
27
|
```
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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.
|
|
26
111
|
|
|
27
112
|
## Examples
|
|
28
113
|
|
|
29
|
-
|
|
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);
|
|
30
136
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
137
|
+
incomingMessages.on('data', (message) => {
|
|
138
|
+
outgoingMessages.send(message); // echo incoming messages
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
outgoingMessages.on('close', () => {
|
|
142
|
+
clearInterval(timer);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return outgoingMessages;
|
|
146
|
+
}
|
|
35
147
|
}
|
|
36
148
|
```
|
|
37
149
|
|
|
38
|
-
|
|
150
|
+
**Minimal `config.yaml` enabling REST with WebSocket disabled**:
|
|
151
|
+
|
|
152
|
+
```yaml
|
|
153
|
+
rest:
|
|
154
|
+
webSocket: false
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Notes
|
|
39
158
|
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
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
|
|
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
|
-
|
|
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. **
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
110
|
+
Complete `schema.graphql` and `resources.js` for a cached external API with on-demand invalidation:
|
|
25
111
|
|
|
26
112
|
```graphql
|
|
27
|
-
type
|
|
113
|
+
type JokeCache @table(expiration: 60) {
|
|
28
114
|
id: ID @primaryKey
|
|
115
|
+
setup: String
|
|
116
|
+
punchline: String
|
|
29
117
|
}
|
|
30
118
|
```
|
|
31
119
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
```js
|
|
35
|
-
import { Resource, tables } from 'harper';
|
|
120
|
+
```javascript
|
|
121
|
+
// resources.js
|
|
36
122
|
|
|
37
|
-
|
|
123
|
+
const jokeAPI = {
|
|
38
124
|
async get() {
|
|
39
125
|
const id = this.getId();
|
|
40
|
-
const response = await fetch(`https://api.
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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`.
|