@harperfast/template-react-studio 1.2.1 → 1.3.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.
- package/.agents/skills/harper-best-practices/AGENTS.md +258 -0
- package/.agents/skills/harper-best-practices/SKILL.md +88 -0
- package/.agents/skills/harper-best-practices/rules/adding-tables-with-schemas.md +40 -0
- package/.agents/skills/harper-best-practices/rules/automatic-apis.md +34 -0
- package/.agents/skills/harper-best-practices/rules/caching.md +46 -0
- package/.agents/skills/harper-best-practices/rules/checking-authentication.md +165 -0
- package/.agents/skills/harper-best-practices/rules/custom-resources.md +35 -0
- package/.agents/skills/harper-best-practices/rules/defining-relationships.md +33 -0
- package/.agents/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +24 -0
- package/.agents/skills/harper-best-practices/rules/extending-tables.md +37 -0
- package/.agents/skills/harper-best-practices/rules/handling-binary-data.md +43 -0
- package/.agents/skills/harper-best-practices/rules/programmatic-table-requests.md +39 -0
- package/.agents/skills/harper-best-practices/rules/querying-rest-apis.md +22 -0
- package/.agents/skills/harper-best-practices/rules/real-time-apps.md +37 -0
- package/.agents/skills/harper-best-practices/rules/serving-web-content.md +34 -0
- package/.agents/skills/harper-best-practices/rules/typescript-type-stripping.md +32 -0
- package/.agents/skills/harper-best-practices/rules/using-blob-datatype.md +36 -0
- package/.agents/skills/harper-best-practices/rules/vector-indexing.md +152 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/resources/README.md +3 -3
- package/schemas/README.md +2 -2
- package/skills-lock.json +10 -0
- package/AGENTS.md +0 -22
- package/skills/adding-tables-with-schemas.md +0 -34
- package/skills/automatic-apis.md +0 -53
- package/skills/automatic-rest-apis.md +0 -41
- package/skills/caching.md +0 -113
- package/skills/checking-authentication.md +0 -281
- package/skills/custom-resources.md +0 -86
- package/skills/defining-relationships.md +0 -71
- package/skills/deploying-to-harper-fabric.md +0 -20
- package/skills/extending-tables.md +0 -70
- package/skills/handling-binary-data.md +0 -67
- package/skills/programmatic-table-requests.md +0 -185
- package/skills/querying-rest-apis.md +0 -69
- package/skills/real-time-apps.md +0 -75
- package/skills/serving-web-content.md +0 -82
- package/skills/typescript-type-stripping.md +0 -47
- package/skills/using-blob-datatype.md +0 -131
- package/skills/vector-indexing.md +0 -215
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
# Extending Table Resources in Harper
|
|
2
|
-
|
|
3
|
-
In Harper, when you define a table in GraphQL and export it, you can extend the automatically generated resource class to add custom logic, validation, or hooks to standard CRUD operations.
|
|
4
|
-
|
|
5
|
-
## Do you need to extend tables?
|
|
6
|
-
|
|
7
|
-
Exported tables have [automatic APIs](./automatic-apis.md) as a part of them. These APIs may be sufficient for what you need.
|
|
8
|
-
|
|
9
|
-
## How to Extend a Table Resource
|
|
10
|
-
|
|
11
|
-
1. Identify the table you want to extend (e.g., `ExamplePeople`).
|
|
12
|
-
2. Create a TypeScript file in your `resources/` folder.
|
|
13
|
-
3. Export a class that extends `tables.YourTableName`.
|
|
14
|
-
4. Override the desired methods (e.g., `post`, `get`, `put`, `patch`, `delete`).
|
|
15
|
-
|
|
16
|
-
### Example: `resources/examplePeople.ts`
|
|
17
|
-
|
|
18
|
-
```typescript
|
|
19
|
-
import { type RequestTargetOrId, tables } from 'harperdb';
|
|
20
|
-
|
|
21
|
-
export interface ExamplePerson {
|
|
22
|
-
id: string;
|
|
23
|
-
name: string;
|
|
24
|
-
tag: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Extend the automatically generated table resource
|
|
28
|
-
export class ExamplePeople extends tables.ExamplePeople<ExamplePerson> {
|
|
29
|
-
// Override the custom POST handler
|
|
30
|
-
async post(target: RequestTargetOrId, newRecord: Omit<ExamplePerson, 'id'>) {
|
|
31
|
-
// Add custom validation or transformation logic
|
|
32
|
-
if (!newRecord.name) {
|
|
33
|
-
throw new Error('Name is required');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
console.log(`Adding new person: ${newRecord.name}`);
|
|
37
|
-
|
|
38
|
-
// Call the super method to perform the actual insertion
|
|
39
|
-
return super.post(target, newRecord);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Override the custom GET handler
|
|
43
|
-
async get(target: RequestTargetOrId) {
|
|
44
|
-
const record = await super.get(target);
|
|
45
|
-
// Modify the record before returning if necessary
|
|
46
|
-
return record;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## Why Extend Tables?
|
|
52
|
-
|
|
53
|
-
- **Validation**: Ensure data meets specific criteria before it's saved to the database.
|
|
54
|
-
- **Side Effects**: Send an email, trigger a webhook, or log an event when a record is created or updated.
|
|
55
|
-
- **Data Transformation**: Format or enrich data before it's returned to the client.
|
|
56
|
-
- **Access Control**: Add custom logic to determine if a user has permission to access or modify a specific record.
|
|
57
|
-
|
|
58
|
-
## Important Note
|
|
59
|
-
|
|
60
|
-
When you extend a table resource, Harper uses your custom class for all REST API interactions with that table. Make sure to call `super[method]` if you still want the default behavior to occur after your custom logic.
|
|
61
|
-
|
|
62
|
-
Extended tables do not need to be `@export`ed in their schema .graphql.
|
|
63
|
-
|
|
64
|
-
```graphql
|
|
65
|
-
type ExamplePerson @table {
|
|
66
|
-
id: ID @primaryKey
|
|
67
|
-
name: String
|
|
68
|
-
tag: String @indexed
|
|
69
|
-
}
|
|
70
|
-
```
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# Handling Binary Data in Harper
|
|
2
|
-
|
|
3
|
-
When working with binary data (such as images or audio files) in Harper, you often receive this data as base64-encoded strings through JSON REST APIs. To store this data efficiently in a `Blob` field, you should convert it to a `Buffer`.
|
|
4
|
-
|
|
5
|
-
## Storing Base64 Strings as Buffers
|
|
6
|
-
|
|
7
|
-
In a custom resource or a table resource override, you can intercept the incoming record and convert base64 strings to buffers before saving them to the database.
|
|
8
|
-
|
|
9
|
-
### Example
|
|
10
|
-
|
|
11
|
-
Suppose you have a table with a `Blob` field named `data`. You can use `Buffer.from(string, 'base64')` to perform the conversion to a buffer, and then use Harper's `createBlob` function to turn that buffer into a blob with a type such as `{ type: 'image/jpeg' }`.
|
|
12
|
-
|
|
13
|
-
```typescript
|
|
14
|
-
import { RequestTargetOrId, Resource } from 'harperdb';
|
|
15
|
-
|
|
16
|
-
export class MyResource extends Resource {
|
|
17
|
-
async post(target: RequestTargetOrId, record: any) {
|
|
18
|
-
if (record.data) {
|
|
19
|
-
// Convert base64-encoded string to a Buffer
|
|
20
|
-
record.data = createBlob(Buffer.from(record.artwork, 'base64'), {
|
|
21
|
-
type: 'image/jpeg',
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
// Call the super method to perform the actual storage
|
|
25
|
-
return super.post(target, record);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Responding with Binary Data
|
|
31
|
-
|
|
32
|
-
When you want to serve binary data (like an image or an MP3 file) back to the client, you can return a response object from your resource's `get` method. This object should include the appropriate `status`, `headers`, and the binary data itself in the `body`.
|
|
33
|
-
|
|
34
|
-
### Example: Responding with a JPEG File
|
|
35
|
-
|
|
36
|
-
In this example, we retrieve a thumbnail from the database. If it contains binary data in the `data` field, we return it with the `image/jpeg` content type.
|
|
37
|
-
|
|
38
|
-
```typescript
|
|
39
|
-
import { RequestTarget, RequestTargetOrId, Resource } from 'harperdb';
|
|
40
|
-
|
|
41
|
-
export class TrackResource extends Resource {
|
|
42
|
-
async get(target: RequestTargetOrId) {
|
|
43
|
-
const id = (target as RequestTarget)?.id;
|
|
44
|
-
if (!id) {
|
|
45
|
-
return super.get(target);
|
|
46
|
-
}
|
|
47
|
-
const thumbnail = await super.get(target);
|
|
48
|
-
if (thumbnail?.data) {
|
|
49
|
-
return {
|
|
50
|
-
status: 200,
|
|
51
|
-
headers: {
|
|
52
|
-
'Content-Type': 'image/jpeg',
|
|
53
|
-
'Content-Disposition': `inline; filename="${thumbnail.name}.jpg"`,
|
|
54
|
-
},
|
|
55
|
-
body: thumbnail.data,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
return thumbnail;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## Why Use Blobs?
|
|
64
|
-
|
|
65
|
-
- **Efficiency**: `Blob` fields are optimized for storing binary data. Buffers are the standard way to handle binary data in Node.js.
|
|
66
|
-
- **Compatibility**: Many Harper features and external libraries expect binary data to be in `Buffer` or `Uint8Array` format.
|
|
67
|
-
- **Storage**: Storing data as binary is more compact than storing it as a base64-encoded string.
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
# Programmatic Requests with Harper Tables
|
|
2
|
-
|
|
3
|
-
In Harper, you can interact with your database tables programmatically using the global `tables` object. Each table defined in your schema is available as a property on this object.
|
|
4
|
-
|
|
5
|
-
## Basic Usage
|
|
6
|
-
|
|
7
|
-
The `tables` object provides a direct way to perform CRUD operations from within your Harper resources or scripts.
|
|
8
|
-
|
|
9
|
-
```typescript
|
|
10
|
-
const track = await tables.ExamplePeople.get(id) as ExamplePerson;
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## Available Methods
|
|
14
|
-
|
|
15
|
-
The following methods are available on each table object:
|
|
16
|
-
|
|
17
|
-
### `get(idOrQuery)`
|
|
18
|
-
|
|
19
|
-
Retrieves a single record by ID or multiple records matching a query.
|
|
20
|
-
|
|
21
|
-
- **By ID**:
|
|
22
|
-
```typescript
|
|
23
|
-
const person = await tables.ExamplePeople.get('person-123');
|
|
24
|
-
```
|
|
25
|
-
- **By Query**:
|
|
26
|
-
```typescript
|
|
27
|
-
const albums = await tables.ExamplePeople.get({
|
|
28
|
-
conditions: [{ attribute: 'name', value: 'John' }],
|
|
29
|
-
});
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### `put(id, record)`
|
|
33
|
-
|
|
34
|
-
Replaces an entire record with the provided data. If the record doesn't exist, it will be created.
|
|
35
|
-
|
|
36
|
-
```typescript
|
|
37
|
-
await tables.ExamplePeople.put('person-123', {
|
|
38
|
-
name: 'Michael Jackson',
|
|
39
|
-
tag: 'entertainer',
|
|
40
|
-
});
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### `patch(id, partialRecord)`
|
|
44
|
-
|
|
45
|
-
Performs a partial update on a record. Only the specified fields will be updated.
|
|
46
|
-
|
|
47
|
-
```typescript
|
|
48
|
-
await tables.ExamplePeople.patch('person-123', {
|
|
49
|
-
tag: 'tragedy',
|
|
50
|
-
});
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### `delete(idOrQuery)`
|
|
54
|
-
|
|
55
|
-
Deletes a record by ID or multiple records matching a query.
|
|
56
|
-
|
|
57
|
-
```typescript
|
|
58
|
-
await tables.ExamplePeople.delete('person-123');
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### `post(record)`
|
|
62
|
-
|
|
63
|
-
Creates a new record. Use this when you want the database to auto-generate a primary key.
|
|
64
|
-
|
|
65
|
-
```typescript
|
|
66
|
-
const newAlbum = await tables.ExamplePeople.post({
|
|
67
|
-
name: 'John Smith',
|
|
68
|
-
tag: 'anonymous',
|
|
69
|
-
});
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### `update(id, updates)`
|
|
73
|
-
|
|
74
|
-
The `update` method is a versatile tool for modifying records. While it can perform simple partial updates like `patch`, its primary power lies in its ability to return an **Updatable Record** object for complex or atomic operations.
|
|
75
|
-
|
|
76
|
-
#### Partial Update (like `patch`)
|
|
77
|
-
|
|
78
|
-
When called with both an ID and an update object, it performs a partial update:
|
|
79
|
-
|
|
80
|
-
```typescript
|
|
81
|
-
await tables.ExamplePeople.update('person-123', {
|
|
82
|
-
name: 'Updated Name',
|
|
83
|
-
});
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
#### Getting an Updatable Record
|
|
87
|
-
|
|
88
|
-
When called without an update object (only an ID), `update` returns a reference to the record that can be modified directly.
|
|
89
|
-
|
|
90
|
-
```typescript
|
|
91
|
-
const person = await tables.ExamplePeople.update('person-123');
|
|
92
|
-
person.name = 'New Person Name';
|
|
93
|
-
// Properties can be assigned directly and are tracked
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
#### Atomic Operations
|
|
97
|
-
|
|
98
|
-
Updatable records provide methods for safe, atomic modifications, which are essential for avoiding race conditions during increments or decrements:
|
|
99
|
-
|
|
100
|
-
- `addTo(attribute, value)`: Atomically adds to a numeric value.
|
|
101
|
-
- `subtractFrom(attribute, value)`: Atomically subtracts from a numeric value.
|
|
102
|
-
|
|
103
|
-
```typescript
|
|
104
|
-
const stats = await tables.Stats.update('daily');
|
|
105
|
-
stats.addTo('viewCount', 1);
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
#### Update without an ID or with Context
|
|
109
|
-
|
|
110
|
-
Calling `update()` without an ID can be used when the target is implied by the context (e.g., inside a resource method) or when you want to get an updatable reference to a collection.
|
|
111
|
-
|
|
112
|
-
```typescript
|
|
113
|
-
// Inside a custom resource method
|
|
114
|
-
const record = await this.update();
|
|
115
|
-
record.set('status', 'processed');
|
|
116
|
-
|
|
117
|
-
// Passing specific context (like a transaction)
|
|
118
|
-
const person = await tables.ExamplePeople.update('person-123', null, {
|
|
119
|
-
transaction,
|
|
120
|
-
});
|
|
121
|
-
person.addTo('playCount', 1);
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### `search(query)`
|
|
125
|
-
|
|
126
|
-
Performs a search based on the provided query criteria. It returns an `AsyncIterable` which is efficient for streaming large result sets.
|
|
127
|
-
|
|
128
|
-
```typescript
|
|
129
|
-
const results = await tables.ExamplePeople.search({
|
|
130
|
-
conditions: [{ attribute: 'artist', value: 'Michael Jackson' }],
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
for await (const person of results) {
|
|
134
|
-
console.log(person.name);
|
|
135
|
-
}
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### `subscribe(query)`
|
|
139
|
-
|
|
140
|
-
Subscribes to real-time changes in the table. You can provide a query to filter which changes trigger a notification.
|
|
141
|
-
|
|
142
|
-
```typescript
|
|
143
|
-
const subscription = await tables.ExamplePeople.subscribe({
|
|
144
|
-
conditions: [{ attribute: 'name', value: 'Thriller' }],
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
for await (const event of subscription) {
|
|
148
|
-
console.log('Record changed:', event.value);
|
|
149
|
-
}
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### `publish(id, message)`
|
|
153
|
-
|
|
154
|
-
Publishes a message to a specific record or topic. This triggers any active subscriptions without necessarily persisting data to the table.
|
|
155
|
-
|
|
156
|
-
```typescript
|
|
157
|
-
await tables.ExamplePeople.publish('person-123', {
|
|
158
|
-
type: 'REVALIDATE',
|
|
159
|
-
timestamp: Date.now(),
|
|
160
|
-
});
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
## Querying Options
|
|
164
|
-
|
|
165
|
-
Many methods accept a `Query` (or `RequestTarget`) object. Common options include:
|
|
166
|
-
|
|
167
|
-
- `conditions`: Array of filter conditions.
|
|
168
|
-
- `limit`: Number of records to return.
|
|
169
|
-
- `offset`: Number of records to skip.
|
|
170
|
-
- `sort`: Attribute and direction for sorting.
|
|
171
|
-
- `select`: Array of specific attributes to return.
|
|
172
|
-
|
|
173
|
-
Example of a complex query:
|
|
174
|
-
|
|
175
|
-
```typescript
|
|
176
|
-
const recentAlbums = await tables.ExamplePeople.get({
|
|
177
|
-
conditions: [{
|
|
178
|
-
attribute: 'releaseDate',
|
|
179
|
-
comparator: 'ge',
|
|
180
|
-
value: '2020-01-01',
|
|
181
|
-
}],
|
|
182
|
-
sort: { attribute: 'releaseDate', descending: true },
|
|
183
|
-
limit: 10,
|
|
184
|
-
});
|
|
185
|
-
```
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
# Querying through REST APIs in Harper
|
|
2
|
-
|
|
3
|
-
Harper's automatic REST APIs support powerful querying capabilities directly through URL query parameters. This allows you to filter, sort, paginate, and join data without writing complex queries.
|
|
4
|
-
|
|
5
|
-
## Basic Filtering
|
|
6
|
-
|
|
7
|
-
The simplest way to filter is by using attribute names as query parameters:
|
|
8
|
-
|
|
9
|
-
`GET /ExamplePeople/?tag=friend`
|
|
10
|
-
|
|
11
|
-
This returns all records where `tag` equals `friend`.
|
|
12
|
-
|
|
13
|
-
## Comparison Operators (FIQL-style)
|
|
14
|
-
|
|
15
|
-
You can use standard comparison operators by appending them to the attribute name with an `=` sign:
|
|
16
|
-
|
|
17
|
-
- `gt`: Greater than
|
|
18
|
-
- `ge`: Greater than or equal to
|
|
19
|
-
- `lt`: Less than
|
|
20
|
-
- `le`: Less than or equal to
|
|
21
|
-
- `ne`: Not equal
|
|
22
|
-
|
|
23
|
-
Example:
|
|
24
|
-
`GET /Products/?price=gt=100&price=lt=200`
|
|
25
|
-
|
|
26
|
-
## Logic Operators
|
|
27
|
-
|
|
28
|
-
- **AND**: Use the `&` operator (default).
|
|
29
|
-
- **OR**: Use the `|` operator.
|
|
30
|
-
|
|
31
|
-
Example:
|
|
32
|
-
`GET /Products/?rating=5|featured=true`
|
|
33
|
-
|
|
34
|
-
## Grouping
|
|
35
|
-
|
|
36
|
-
Use parentheses `()` to group conditions and indicate order of operations.
|
|
37
|
-
|
|
38
|
-
Example:
|
|
39
|
-
`GET /Products/?(rating=5|featured=true)&price=lt=50`
|
|
40
|
-
|
|
41
|
-
## Selection
|
|
42
|
-
|
|
43
|
-
Use `select()` to limit the returned fields:
|
|
44
|
-
|
|
45
|
-
`GET /Products/?select(name,price)`
|
|
46
|
-
|
|
47
|
-
## Pagination
|
|
48
|
-
|
|
49
|
-
Use `limit(start, end)` or `limit(end)`:
|
|
50
|
-
|
|
51
|
-
- `limit(10)`: Returns the first 10 records.
|
|
52
|
-
- `limit(20, 10)`: Skips the first 20 records and returns the next 10.
|
|
53
|
-
|
|
54
|
-
Example:
|
|
55
|
-
`GET /Products/?limit(0,20)`
|
|
56
|
-
|
|
57
|
-
## Sorting
|
|
58
|
-
|
|
59
|
-
Use `sort()` with `+` (ascending) or `-` (descending) prefixes:
|
|
60
|
-
|
|
61
|
-
`GET /Products/?sort(+price,-rating)`
|
|
62
|
-
|
|
63
|
-
## Joins and Chained Attributes
|
|
64
|
-
|
|
65
|
-
If you have defined relationships in your schema using the `@relationship` directive, you can use dot syntax to query across tables. For more on defining these, see the [Defining Relationships](defining-relationships.md) skill.
|
|
66
|
-
|
|
67
|
-
`GET /Book/?author.name=Harper`
|
|
68
|
-
|
|
69
|
-
This will perform an automatic join and filter books based on the related author's name.
|
package/skills/real-time-apps.md
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# Real-time Applications in Harper
|
|
2
|
-
|
|
3
|
-
Harper provides built-in support for real-time data synchronization using WebSockets and a Pub/Sub mechanism. This allows clients to receive immediate updates when data changes in the database.
|
|
4
|
-
|
|
5
|
-
## Automatic WebSockets
|
|
6
|
-
|
|
7
|
-
For many use cases, the [Automatic APIs](automatic-apis.md) provided by Harper are more than enough. When you `@export` a table, Harper automatically provides a WebSocket endpoint that publishes events whenever data in that table is updated.
|
|
8
|
-
|
|
9
|
-
## Implementing a WebSocket Resource
|
|
10
|
-
|
|
11
|
-
Customizing resources by implementing a `connect` method is only necessary when you want to come up with a more specific back-and-forth or custom message handling. To handle WebSocket connections, implement the `connect` method in your custom resource class.
|
|
12
|
-
|
|
13
|
-
### Example: `resources/exampleSocket.ts`
|
|
14
|
-
|
|
15
|
-
```typescript
|
|
16
|
-
import {
|
|
17
|
-
type IterableEventQueue,
|
|
18
|
-
RequestTarget,
|
|
19
|
-
Resource,
|
|
20
|
-
tables,
|
|
21
|
-
} from 'harperdb';
|
|
22
|
-
|
|
23
|
-
export class ExampleSocket extends Resource {
|
|
24
|
-
async *connect(
|
|
25
|
-
target: RequestTarget,
|
|
26
|
-
incomingMessages: IterableEventQueue<any>,
|
|
27
|
-
): AsyncIterable<any> {
|
|
28
|
-
// Subscribe to changes in a specific table
|
|
29
|
-
const subscription = await tables.ExamplePeople.subscribe(target);
|
|
30
|
-
|
|
31
|
-
if (!incomingMessages) {
|
|
32
|
-
// Server-Sent Events (SSE) mode: only outgoing messages
|
|
33
|
-
return subscription;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Handle incoming messages from the client
|
|
37
|
-
for await (let message of incomingMessages) {
|
|
38
|
-
// Process message and optionally yield responses
|
|
39
|
-
yield { received: message };
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## Pub/Sub with `tables.subscribe()`
|
|
46
|
-
|
|
47
|
-
You can subscribe to change events on any table using the `subscribe()` method. This is typically used within the `connect` method of a resource to stream updates to a connected client.
|
|
48
|
-
|
|
49
|
-
- `tables.TableName.subscribe(target)`: Subscribes to all changes in the specified table.
|
|
50
|
-
- The `target` parameter can include filters to only subscribe to a subset of changes.
|
|
51
|
-
|
|
52
|
-
## Server-Sent Events (SSE)
|
|
53
|
-
|
|
54
|
-
If the client connects using a protocol that only supports one-way communication from the server (like standard SSE), the `incomingMessages` parameter will be null. Your `connect` method should handle this by only returning the subscription or yielding messages.
|
|
55
|
-
|
|
56
|
-
## Using WebSockets from the Client
|
|
57
|
-
|
|
58
|
-
You can connect to your real-time resource using a standard WebSocket client.
|
|
59
|
-
|
|
60
|
-
```javascript
|
|
61
|
-
const socket = new WebSocket('ws://your-harper-instance/ExampleSocket');
|
|
62
|
-
|
|
63
|
-
socket.onmessage = (event) => {
|
|
64
|
-
const data = JSON.parse(event.data);
|
|
65
|
-
console.log('Real-time update:', data);
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
socket.send(JSON.stringify({ type: 'ping' }));
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
## Key Real-time Features
|
|
72
|
-
|
|
73
|
-
- **Automatic Table Subscriptions**: Easily stream changes from any database table.
|
|
74
|
-
- **Bi-directional Communication**: Send and receive messages in real-time.
|
|
75
|
-
- **Scalable Pub/Sub**: Harper handles the efficient distribution of messages to subscribers.
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
# Serving Web Content with Harper
|
|
2
|
-
|
|
3
|
-
Harper provides two primary ways to include and serve HTTP web content such as HTML, CSS, JavaScript, and React applications. These methods are mutually exclusive.
|
|
4
|
-
|
|
5
|
-
## 1. Using the Static Plugin
|
|
6
|
-
|
|
7
|
-
The `static` plugin is the simplest way to serve static files. It maps a directory on your filesystem to your Harper REST endpoint.
|
|
8
|
-
|
|
9
|
-
### Configuration
|
|
10
|
-
|
|
11
|
-
Add the `static` configuration to your `config.yaml`:
|
|
12
|
-
|
|
13
|
-
```yaml
|
|
14
|
-
static:
|
|
15
|
-
files: 'web/*'
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
### Usage
|
|
19
|
-
|
|
20
|
-
1. Create a `web` folder in your project root.
|
|
21
|
-
2. Place your static files (e.g., `index.html`, `styles.css`, `app.js`) inside the `web` folder.
|
|
22
|
-
3. Your files will be accessible from your REST endpoint. For example, if Harper is running on `http://localhost:9926/`, your `index.html` will be available at that address.
|
|
23
|
-
|
|
24
|
-
### Key Characteristics
|
|
25
|
-
|
|
26
|
-
- **Precedence**: Static files are searched first. If a matching file is found, it is served; otherwise, Harper proceeds to check your other resource and table APIs.
|
|
27
|
-
- **No Directory Listings**: Browsing the directory structure via the browser is not supported.
|
|
28
|
-
- **Simple Deployment**: Ideal for pre-built applications or simple static sites.
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## 2. Using the Vite Plugin
|
|
33
|
-
|
|
34
|
-
The Vite plugin integrates Vite's development server directly into Harper, providing features like Hot Module Replacement (HMR) during development.
|
|
35
|
-
|
|
36
|
-
### Configuration
|
|
37
|
-
|
|
38
|
-
Add the Vite plugin to your `config.yaml`:
|
|
39
|
-
|
|
40
|
-
```yaml
|
|
41
|
-
'@harperfast/vite-plugin':
|
|
42
|
-
package: '@harperfast/vite-plugin'
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### Setup
|
|
46
|
-
|
|
47
|
-
This plugin expects a `vite.config.ts` and an `index.html` file in your project root. It handles rendering your app through Vite during development.
|
|
48
|
-
|
|
49
|
-
### Package Configuration
|
|
50
|
-
|
|
51
|
-
To use the Vite plugin effectively, you should configure your `package.json` with the appropriate scripts and dependencies.
|
|
52
|
-
|
|
53
|
-
#### Scripts
|
|
54
|
-
|
|
55
|
-
Copy these script examples to manage your development and deployment workflows:
|
|
56
|
-
|
|
57
|
-
```json
|
|
58
|
-
"scripts": {
|
|
59
|
-
"dev": "harper run .",
|
|
60
|
-
"build": "tsc -b && vite build",
|
|
61
|
-
"build-and-deploy": "rm -Rf deploy && npm run build && mkdir deploy && mv web deploy/ && cp -R deploy-template/* deploy/ && dotenv -- npm run deploy-web && rm -Rf deploy",
|
|
62
|
-
"deploy-web": "(cd deploy && harperdb deploy_component . project=web restart=rolling replicated=true)"
|
|
63
|
-
}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
#### Dependencies and Overrides
|
|
67
|
-
|
|
68
|
-
Include the Vite plugin and other related dependencies in your `devDependencies`:
|
|
69
|
-
|
|
70
|
-
```bash
|
|
71
|
-
npm install --save-dev vite @harperfast/vite-plugin @vitejs/plugin-react dotenv-cli
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### Deploying to Production
|
|
75
|
-
|
|
76
|
-
Vite's HMR server is meant for development, not production. For that, the `build-and-deploy` script above builds the Vite app into a `web` folder, places the static handler, and then deploys that subdirectory as a Harper app to production.
|
|
77
|
-
|
|
78
|
-
### Key Characteristics
|
|
79
|
-
|
|
80
|
-
- **Development Experience**: Provides Vite's HMR for a fast development cycle.
|
|
81
|
-
- **Integrated Rendering**: Automatically handles the rendering of your React/Vite app.
|
|
82
|
-
- **Mutual Exclusivity**: Use this approach **instead of** the static plugin if you want Vite integration.
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
# TypeScript Type Stripping
|
|
2
|
-
|
|
3
|
-
Harper supports using TypeScript directly without any additional build tools (like `tsc` or `esbuild`) by leveraging Node.js's native Type Stripping capability. This allows you to write `.ts` files for your Custom Resources and have them run directly in Harper.
|
|
4
|
-
|
|
5
|
-
## Requirements
|
|
6
|
-
|
|
7
|
-
- **Node.js Version**: You must be running a version of Node.js that supports type stripping (Node.js v22.6.0 or higher).
|
|
8
|
-
- **No Experimental Flags**: When running on supported Node.js versions, Harper can automatically handle type stripping for your resource files.
|
|
9
|
-
|
|
10
|
-
## Benefits
|
|
11
|
-
|
|
12
|
-
- **Faster Development**: No need to wait for a build step or manage complex build pipelines.
|
|
13
|
-
- **Simplified Tooling**: You don't need to install or configure `ts-node`, `tsx`, or other TypeScript execution engines for your Harper resources.
|
|
14
|
-
- **Native Performance**: Leverages Node.js's built-in support for stripping types, which is highly efficient.
|
|
15
|
-
|
|
16
|
-
## Usage
|
|
17
|
-
|
|
18
|
-
Simply name your resource files with a `.ts` extension in your `resources/` directory.
|
|
19
|
-
|
|
20
|
-
### Example: `resources/my-resource.ts`
|
|
21
|
-
|
|
22
|
-
```typescript
|
|
23
|
-
import { Resource } from 'harperdb';
|
|
24
|
-
|
|
25
|
-
export class MyResource extends Resource {
|
|
26
|
-
async get() {
|
|
27
|
-
return { message: 'This is running directly from TypeScript!' };
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
When cross-referencing between modules, ensure that the file path contains the appropriate extension.
|
|
33
|
-
|
|
34
|
-
```typescript
|
|
35
|
-
import { MyResource } from './my-resource.ts';
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
## Configuration
|
|
39
|
-
|
|
40
|
-
In your `config.yaml`, ensure your `jsResource` points to your `.ts` files:
|
|
41
|
-
|
|
42
|
-
```yaml
|
|
43
|
-
jsResource:
|
|
44
|
-
files: 'resources/*.ts'
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
When Harper starts, it will detect the `.ts` files and, if running on a compatible Node.js version, will execute them using type stripping.
|