@harperfast/template-react-ts-studio 0.8.8 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +16 -0
- package/package.json +1 -1
- package/skills/adding-tables.md +32 -0
- package/skills/automatic-rest-apis.md +41 -0
- package/skills/checking-authentication.md +52 -0
- package/skills/custom-resources.md +82 -0
- package/skills/defining-relationships.md +71 -0
- package/skills/extending-tables.md +56 -0
- package/skills/handling-binary-data.md +65 -0
- package/skills/querying-rest-apis.md +69 -0
- package/skills/real-time-apps.md +71 -0
- package/skills/typescript-type-stripping.md +47 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# HarperDB Agent Skills
|
|
2
|
+
|
|
3
|
+
This repository contains "skills" that guide AI agents in developing Harper applications.
|
|
4
|
+
|
|
5
|
+
## Available Skills
|
|
6
|
+
|
|
7
|
+
- [Adding Tables](skills/adding-tables.md): Learn how to define schemas and enable automatic REST APIs for your database tables.
|
|
8
|
+
- [Automatic REST APIs](skills/automatic-rest-apis.md): Details on the CRUD endpoints automatically generated for exported tables.
|
|
9
|
+
- [Querying REST APIs](skills/querying-rest-apis.md): How to use filters, operators, sorting, and pagination in REST requests.
|
|
10
|
+
- [Custom Resources](skills/custom-resources.md): How to define custom REST endpoints using JavaScript or TypeScript (Note: Paths are case-sensitive).
|
|
11
|
+
- [Extending Table Resources](skills/extending-tables.md): Adding custom logic to automatically generated table resources.
|
|
12
|
+
- [Defining Relationships](skills/defining-relationships.md): Using the `@relationship` directive to link tables.
|
|
13
|
+
- [Real-time Applications](skills/real-time-apps.md): Implementing WebSockets and Pub/Sub for live data updates.
|
|
14
|
+
- [TypeScript Type Stripping](skills/typescript-type-stripping.md): Using TypeScript directly without build tools via Node.js Type Stripping.
|
|
15
|
+
- [Handling Binary Data](skills/handling-binary-data.md): How to store and serve binary data like images or MP3s.
|
|
16
|
+
- [Checking Authentication](skills/checking-authentication.md): How to use `this.getCurrentUser()` to verify user identity and roles.
|
package/package.json
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Adding Tables to HarperDB
|
|
2
|
+
|
|
3
|
+
To add tables to a Harper database, follow these guidelines:
|
|
4
|
+
|
|
5
|
+
1. **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.
|
|
6
|
+
|
|
7
|
+
2. **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`.
|
|
8
|
+
|
|
9
|
+
3. **Defining Relationships**: You can link tables together using the `@relationship` directive. For more details, see the [Defining Relationships](defining-relationships.md) skill.
|
|
10
|
+
|
|
11
|
+
4. **Automatic REST APIs**: If you add `@table @export` to a schema type, HarperDB automatically sets up REST 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-rest-apis.md) skill.
|
|
12
|
+
|
|
13
|
+
- `GET /{TableName}`: Describes the schema itself.
|
|
14
|
+
- `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.
|
|
15
|
+
- `GET /{TableName}/{id}`: Retrieves a single record by its ID.
|
|
16
|
+
- `POST /{TableName}/`: Creates a new record.
|
|
17
|
+
- `PUT /{TableName}/{id}`: Updates an existing record.
|
|
18
|
+
- `PATCH /{TableName}/{id}`: Performs a partial update on a record.
|
|
19
|
+
- `DELETE /{TableName}/`: Deletes all records or filtered records.
|
|
20
|
+
- `DELETE /{TableName}/{id}`: Deletes a single record by its ID.
|
|
21
|
+
|
|
22
|
+
### Example
|
|
23
|
+
|
|
24
|
+
In `schemas/ExampleTable.graphql`:
|
|
25
|
+
|
|
26
|
+
```graphql
|
|
27
|
+
type ExampleTable @table @export {
|
|
28
|
+
id: ID @primaryKey
|
|
29
|
+
name: String
|
|
30
|
+
email: String @indexed
|
|
31
|
+
}
|
|
32
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Automatic REST APIs in HarperDB
|
|
2
|
+
|
|
3
|
+
When you define a GraphQL type with the `@table` and `@export` directives, HarperDB automatically generates a fully-functional REST API for that table. This allows for immediate CRUD (Create, Read, Update, Delete) operations without writing any additional code.
|
|
4
|
+
|
|
5
|
+
## Enabling REST APIs
|
|
6
|
+
|
|
7
|
+
To enable the automatic REST API for a table, ensure your GraphQL schema includes the `@export` directive:
|
|
8
|
+
|
|
9
|
+
```graphql
|
|
10
|
+
type MyTable @table @export {
|
|
11
|
+
id: ID @primaryKey
|
|
12
|
+
# ... other fields
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Available Endpoints
|
|
17
|
+
|
|
18
|
+
The following endpoints are automatically created for a table named `TableName` (Note: Paths are **case-sensitive**, so `GET /TableName/` is valid while `GET /tablename/` is not):
|
|
19
|
+
|
|
20
|
+
- **Describe Schema**: `GET /{TableName}`
|
|
21
|
+
Returns the schema definition and metadata for the table.
|
|
22
|
+
- **List Records**: `GET /{TableName}/`
|
|
23
|
+
Lists all records in the table. This endpoint supports advanced filtering, sorting, and pagination. For more details, see the [Querying REST APIs](querying-rest-apis.md) skill.
|
|
24
|
+
- **Get Single Record**: `GET /{TableName}/{id}`
|
|
25
|
+
Retrieves a single record by its primary key (`id`).
|
|
26
|
+
- **Create Record**: `POST /{TableName}/`
|
|
27
|
+
Creates a new record. The request body should be a JSON object containing the record data.
|
|
28
|
+
- **Update Record (Full)**: `PUT /{TableName}/{id}`
|
|
29
|
+
Replaces the entire record at the specified `id` with the provided JSON data.
|
|
30
|
+
- **Update Record (Partial)**: `PATCH /{TableName}/{id}`
|
|
31
|
+
Updates only the specified fields of the record at the given `id`.
|
|
32
|
+
- **Delete All/Filtered Records**: `DELETE /{TableName}/`
|
|
33
|
+
Deletes all records in the table, or a subset of records if filtering parameters are provided.
|
|
34
|
+
- **Delete Single Record**: `DELETE /{TableName}/{id}`
|
|
35
|
+
Deletes the record with the specified `id`.
|
|
36
|
+
|
|
37
|
+
## Filtering and Querying
|
|
38
|
+
|
|
39
|
+
The `GET /{TableName}/` and `DELETE /{TableName}/` endpoints can be filtered using query parameters. While basic equality filters are straightforward, HarperDB supports a rich set of operators, sorting, and pagination.
|
|
40
|
+
|
|
41
|
+
For a comprehensive guide on advanced querying, see the [Querying REST APIs](querying-rest-apis.md) skill.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Checking Authentication in HarperDB
|
|
2
|
+
|
|
3
|
+
Custom resources and table resource overrides often need to restrict access based on the current user's identity or role. HarperDB provides the `this.getCurrentUser()` method within resource classes to access information about the authenticated user making the request.
|
|
4
|
+
|
|
5
|
+
## Using `this.getCurrentUser()`
|
|
6
|
+
|
|
7
|
+
The `this.getCurrentUser()` method returns an object containing details about the authenticated user, such as their username and role. If the request is unauthenticated, it may return `null` or `undefined`.
|
|
8
|
+
|
|
9
|
+
### Example: Role-Based Access Control
|
|
10
|
+
|
|
11
|
+
You can check the user's role to determine if they have permission to perform a specific action.
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { RequestTargetOrId, Resource } from 'harperdb';
|
|
15
|
+
|
|
16
|
+
export class MyResource extends Resource {
|
|
17
|
+
async post(target: RequestTargetOrId, record: any) {
|
|
18
|
+
const user = this.getCurrentUser();
|
|
19
|
+
|
|
20
|
+
// Check if the user has the 'super_user' role
|
|
21
|
+
if (user?.role?.role !== 'super_user') {
|
|
22
|
+
throw {
|
|
23
|
+
message: 'Only super users are allowed to make changes',
|
|
24
|
+
statusCode: 403,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return super.post(target, record);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Handling Unauthenticated Requests
|
|
34
|
+
|
|
35
|
+
Always ensure you handle cases where `this.getCurrentUser()` returns `null`, especially if your resource is accessible to unauthenticated users.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
const user = this.getCurrentUser();
|
|
39
|
+
if (!user) {
|
|
40
|
+
throw {
|
|
41
|
+
message: 'Authentication required',
|
|
42
|
+
statusCode: 401,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## User Object Structure
|
|
48
|
+
|
|
49
|
+
The object returned by `this.getCurrentUser()` typically includes:
|
|
50
|
+
|
|
51
|
+
- `username`: The username of the authenticated user.
|
|
52
|
+
- `role`: An object containing role information, including the `role` name itself (e.g., `user.role.role`).
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Custom Resources in HarperDB
|
|
2
|
+
|
|
3
|
+
Custom Resources allow you to define your own REST endpoints with custom logic by writing JavaScript or TypeScript code. This is useful when the automatic CRUD operations provided by `@table @export` are not enough.
|
|
4
|
+
|
|
5
|
+
HarperDB supports [TypeScript Type Stripping](typescript-type-stripping.md), allowing you to use TypeScript directly without additional build tools on supported Node.js versions.
|
|
6
|
+
|
|
7
|
+
## Defining a Custom Resource
|
|
8
|
+
|
|
9
|
+
To create a custom resource:
|
|
10
|
+
|
|
11
|
+
1. Create a `.ts` (or `.js`) file in the directory specified by `jsResource` in `config.yaml` (usually `resources/`).
|
|
12
|
+
2. Export a class that extends the `Resource` class from the `harperdb` module.
|
|
13
|
+
|
|
14
|
+
### Example: `resources/greeting.ts`
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { type RecordObject, type RequestTargetOrId, Resource } from 'harperdb';
|
|
18
|
+
|
|
19
|
+
interface GreetingRecord {
|
|
20
|
+
greeting: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class Greeting extends Resource<GreetingRecord> {
|
|
24
|
+
// Set to false if you want HarperDB to manage the instance lifecycle
|
|
25
|
+
static loadAsInstance = false;
|
|
26
|
+
|
|
27
|
+
async get(target?: RequestTargetOrId): Promise<GreetingRecord> {
|
|
28
|
+
return { greeting: 'Hello from a custom GET endpoint!' };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async post(
|
|
32
|
+
target: RequestTargetOrId,
|
|
33
|
+
newRecord: Partial<GreetingRecord & RecordObject>,
|
|
34
|
+
): Promise<GreetingRecord> {
|
|
35
|
+
// Custom logic for handling POST requests
|
|
36
|
+
return { greeting: `Hello, ${newRecord.greeting || 'stranger'}!` };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Supported HTTP Methods
|
|
42
|
+
|
|
43
|
+
You can implement any of the following methods in your resource class to handle the corresponding HTTP requests:
|
|
44
|
+
|
|
45
|
+
- `get(target?: RequestTargetOrId)`
|
|
46
|
+
- `post(target: RequestTargetOrId, body: any)`
|
|
47
|
+
- `put(target: RequestTargetOrId, body: any)`
|
|
48
|
+
- `patch(target: RequestTargetOrId, body: any)`
|
|
49
|
+
- `delete(target: RequestTargetOrId)`
|
|
50
|
+
|
|
51
|
+
The `target` parameter typically contains the ID or sub-path from the URL.
|
|
52
|
+
|
|
53
|
+
## Accessing Tables
|
|
54
|
+
|
|
55
|
+
Within your custom resource, you can easily access your database tables using the `tables` object:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { Resource, tables } from 'harperdb';
|
|
59
|
+
|
|
60
|
+
export class MyCustomResource extends Resource {
|
|
61
|
+
async get() {
|
|
62
|
+
// Query a table
|
|
63
|
+
const results = await tables.ExamplePeople.list();
|
|
64
|
+
return results;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Configuration
|
|
70
|
+
|
|
71
|
+
Ensure your `config.yaml` is configured to load your resources:
|
|
72
|
+
|
|
73
|
+
```yaml
|
|
74
|
+
jsResource:
|
|
75
|
+
files: 'resources/*.ts'
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Once defined and configured, your resource will be available at a REST endpoint matching the class name exactly.
|
|
79
|
+
|
|
80
|
+
### Case Sensitivity
|
|
81
|
+
|
|
82
|
+
Paths in HarperDB are **case-sensitive**. A resource class named `MyResource` will be accessible only at `/MyResource/`, not `/myresource/`.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Defining Relationships in HarperDB
|
|
2
|
+
|
|
3
|
+
HarperDB allows you to define relationships between tables using the `@relationship` directive in your GraphQL schema. This enables powerful features like automatic joins when querying through REST APIs.
|
|
4
|
+
|
|
5
|
+
## The `@relationship` Directive
|
|
6
|
+
|
|
7
|
+
The `@relationship` directive is applied to a field in your GraphQL type and takes two optional arguments:
|
|
8
|
+
|
|
9
|
+
- `from`: The field in the _current_ table that holds the foreign key.
|
|
10
|
+
- `to`: The field in the _related_ table that holds the foreign key.
|
|
11
|
+
|
|
12
|
+
## Relationship Types
|
|
13
|
+
|
|
14
|
+
### One-to-One and Many-to-One
|
|
15
|
+
|
|
16
|
+
To define a relationship where the current table holds the foreign key, use the `from` argument.
|
|
17
|
+
|
|
18
|
+
```graphql
|
|
19
|
+
type Author @table @export {
|
|
20
|
+
id: ID @primaryKey
|
|
21
|
+
name: String
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type Book @table @export {
|
|
25
|
+
id: ID @primaryKey
|
|
26
|
+
title: String
|
|
27
|
+
authorId: ID
|
|
28
|
+
# This field resolves to an Author object using authorId
|
|
29
|
+
author: Author @relationship(from: "authorId")
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### One-to-Many and Many-to-Many
|
|
34
|
+
|
|
35
|
+
To define a relationship where the _related_ table holds the foreign key, use the `to` argument. The field type should be an array.
|
|
36
|
+
|
|
37
|
+
```graphql
|
|
38
|
+
type Author @table @export {
|
|
39
|
+
id: ID @primaryKey
|
|
40
|
+
name: String
|
|
41
|
+
# This field resolves to an array of Books that have this author's ID in their authorId field
|
|
42
|
+
books: [Book] @relationship(to: "authorId")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type Book @table @export {
|
|
46
|
+
id: ID @primaryKey
|
|
47
|
+
title: String
|
|
48
|
+
authorId: ID
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Querying Relationships
|
|
53
|
+
|
|
54
|
+
Once relationships are defined, you can use them in your REST API queries using dot syntax.
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
`GET /Book/?author.name=Harper`
|
|
58
|
+
|
|
59
|
+
This will automatically perform a join and return all books whose author's name is "Harper".
|
|
60
|
+
|
|
61
|
+
You can also use the `select()` operator to include related data in the response:
|
|
62
|
+
|
|
63
|
+
`GET /Author/?select(name,books(title))`
|
|
64
|
+
|
|
65
|
+
This returns authors with their names and a list of their books (only the titles).
|
|
66
|
+
|
|
67
|
+
## Benefits of `@relationship`
|
|
68
|
+
|
|
69
|
+
- **Simplified Queries**: No need for complex manual joins in your code.
|
|
70
|
+
- **Efficient Data Fetching**: HarperDB optimizes relationship lookups.
|
|
71
|
+
- **Improved API Discoverability**: Related data structures are clearly defined in your schema.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Extending Table Resources in HarperDB
|
|
2
|
+
|
|
3
|
+
In HarperDB, 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
|
+
## How to Extend a Table Resource
|
|
6
|
+
|
|
7
|
+
1. Identify the table you want to extend (e.g., `ExamplePeople`).
|
|
8
|
+
2. Create a TypeScript file in your `resources/` folder.
|
|
9
|
+
3. Export a class that extends `tables.YourTableName`.
|
|
10
|
+
4. Override the desired methods (e.g., `post`, `get`, `put`, `patch`, `delete`).
|
|
11
|
+
|
|
12
|
+
### Example: `resources/examplePeople.ts`
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { type RequestTargetOrId, tables } from 'harperdb';
|
|
16
|
+
|
|
17
|
+
export interface ExamplePerson {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
tag: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Extend the automatically generated table resource
|
|
24
|
+
export class ExamplePeople extends tables.ExamplePeople<ExamplePerson> {
|
|
25
|
+
// Override the custom POST handler
|
|
26
|
+
async post(target: RequestTargetOrId, newRecord: Omit<ExamplePerson, 'id'>) {
|
|
27
|
+
// Add custom validation or transformation logic
|
|
28
|
+
if (!newRecord.name) {
|
|
29
|
+
throw new Error('Name is required');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
console.log(`Adding new person: ${newRecord.name}`);
|
|
33
|
+
|
|
34
|
+
// Call the super method to perform the actual insertion
|
|
35
|
+
return super.post(target, newRecord);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Override the custom GET handler
|
|
39
|
+
async get(target: RequestTargetOrId) {
|
|
40
|
+
const record = await super.get(target);
|
|
41
|
+
// Modify the record before returning if necessary
|
|
42
|
+
return record;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Why Extend Tables?
|
|
48
|
+
|
|
49
|
+
- **Validation**: Ensure data meets specific criteria before it's saved to the database.
|
|
50
|
+
- **Side Effects**: Send an email, trigger a webhook, or log an event when a record is created or updated.
|
|
51
|
+
- **Data Transformation**: Format or enrich data before it's returned to the client.
|
|
52
|
+
- **Access Control**: Add custom logic to determine if a user has permission to access or modify a specific record.
|
|
53
|
+
|
|
54
|
+
## Important Note
|
|
55
|
+
|
|
56
|
+
When you extend a table resource, HarperDB 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.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Handling Binary Data in HarperDB
|
|
2
|
+
|
|
3
|
+
When working with binary data (such as images or audio files) in HarperDB, 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.
|
|
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 = Buffer.from(record.data, 'base64');
|
|
21
|
+
}
|
|
22
|
+
// Call the super method to perform the actual storage
|
|
23
|
+
return super.post(target, record);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Responding with Binary Data
|
|
29
|
+
|
|
30
|
+
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`.
|
|
31
|
+
|
|
32
|
+
### Example: Streaming an MP3 File
|
|
33
|
+
|
|
34
|
+
In this example, we retrieve a track from the database. If it contains binary data in the `data` field, we return it with the `audio/mpeg` content type.
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { RequestTarget, RequestTargetOrId, Resource } from 'harperdb';
|
|
38
|
+
|
|
39
|
+
export class TrackResource extends Resource {
|
|
40
|
+
async get(target: RequestTargetOrId) {
|
|
41
|
+
const id = (target as RequestTarget)?.id;
|
|
42
|
+
if (!id) {
|
|
43
|
+
return super.get(target);
|
|
44
|
+
}
|
|
45
|
+
const track = await super.get(target) as any;
|
|
46
|
+
if (track?.data) {
|
|
47
|
+
return {
|
|
48
|
+
status: 200,
|
|
49
|
+
headers: {
|
|
50
|
+
'Content-Type': 'audio/mpeg',
|
|
51
|
+
'Content-Disposition': `inline; filename="${track.name}.mp3"`,
|
|
52
|
+
},
|
|
53
|
+
body: track.data,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return track;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Why Use Buffers?
|
|
62
|
+
|
|
63
|
+
- **Efficiency**: `Blob` fields are optimized for storing binary data. Buffers are the standard way to handle binary data in Node.js.
|
|
64
|
+
- **Compatibility**: Many HarperDB features and external libraries expect binary data to be in `Buffer` or `Uint8Array` format.
|
|
65
|
+
- **Storage**: Storing data as binary is more compact than storing it as a base64-encoded string.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Querying through REST APIs in HarperDB
|
|
2
|
+
|
|
3
|
+
HarperDB'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.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Real-time Applications in HarperDB
|
|
2
|
+
|
|
3
|
+
HarperDB 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
|
+
## Implementing a WebSocket Resource
|
|
6
|
+
|
|
7
|
+
To handle WebSocket connections, implement the `connect` method in your custom resource class.
|
|
8
|
+
|
|
9
|
+
### Example: `resources/exampleSocket.ts`
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import {
|
|
13
|
+
type IterableEventQueue,
|
|
14
|
+
RequestTarget,
|
|
15
|
+
Resource,
|
|
16
|
+
tables,
|
|
17
|
+
} from 'harperdb';
|
|
18
|
+
|
|
19
|
+
export class ExampleSocket extends Resource {
|
|
20
|
+
async *connect(
|
|
21
|
+
target: RequestTarget,
|
|
22
|
+
incomingMessages: IterableEventQueue<any>,
|
|
23
|
+
): AsyncIterable<any> {
|
|
24
|
+
// Subscribe to changes in a specific table
|
|
25
|
+
const subscription = await tables.ExamplePeople.subscribe(target);
|
|
26
|
+
|
|
27
|
+
if (!incomingMessages) {
|
|
28
|
+
// Server-Sent Events (SSE) mode: only outgoing messages
|
|
29
|
+
return subscription;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Handle incoming messages from the client
|
|
33
|
+
for await (let message of incomingMessages) {
|
|
34
|
+
// Process message and optionally yield responses
|
|
35
|
+
yield { received: message };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Pub/Sub with `tables.subscribe()`
|
|
42
|
+
|
|
43
|
+
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.
|
|
44
|
+
|
|
45
|
+
- `tables.TableName.subscribe(target)`: Subscribes to all changes in the specified table.
|
|
46
|
+
- The `target` parameter can include filters to only subscribe to a subset of changes.
|
|
47
|
+
|
|
48
|
+
## Server-Sent Events (SSE)
|
|
49
|
+
|
|
50
|
+
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.
|
|
51
|
+
|
|
52
|
+
## Using WebSockets from the Client
|
|
53
|
+
|
|
54
|
+
You can connect to your real-time resource using a standard WebSocket client.
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
const socket = new WebSocket('ws://your-harper-instance/ExampleSocket');
|
|
58
|
+
|
|
59
|
+
socket.onmessage = (event) => {
|
|
60
|
+
const data = JSON.parse(event.data);
|
|
61
|
+
console.log('Real-time update:', data);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
socket.send(JSON.stringify({ type: 'ping' }));
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Key Real-time Features
|
|
68
|
+
|
|
69
|
+
- **Automatic Table Subscriptions**: Easily stream changes from any database table.
|
|
70
|
+
- **Bi-directional Communication**: Send and receive messages in real-time.
|
|
71
|
+
- **Scalable Pub/Sub**: HarperDB handles the efficient distribution of messages to subscribers.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# TypeScript Type Stripping
|
|
2
|
+
|
|
3
|
+
HarperDB 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 HarperDB.
|
|
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, HarperDB 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 HarperDB 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 HarperDB starts, it will detect the `.ts` files and, if running on a compatible Node.js version, will execute them using type stripping.
|