@harperfast/template-react-studio 0.9.3 → 0.11.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.md CHANGED
@@ -7,10 +7,12 @@ This repository contains "skills" that guide AI agents in developing Harper appl
7
7
  - [Adding Tables](skills/adding-tables.md): Learn how to define schemas and enable automatic REST APIs for your database tables.
8
8
  - [Automatic REST APIs](skills/automatic-rest-apis.md): Details on the CRUD endpoints automatically generated for exported tables.
9
9
  - [Querying REST APIs](skills/querying-rest-apis.md): How to use filters, operators, sorting, and pagination in REST requests.
10
+ - [Programmatic Table Requests](skills/programmatic-table-requests.md): How to use filters, operators, sorting, and pagination in programmatic table requests.
10
11
  - [Custom Resources](skills/custom-resources.md): How to define custom REST endpoints using JavaScript or TypeScript (Note: Paths are case-sensitive).
11
12
  - [Extending Table Resources](skills/extending-tables.md): Adding custom logic to automatically generated table resources.
12
13
  - [Defining Relationships](skills/defining-relationships.md): Using the `@relationship` directive to link tables.
13
14
  - [Real-time Applications](skills/real-time-apps.md): Implementing WebSockets and Pub/Sub for live data updates.
14
15
  - [TypeScript Type Stripping](skills/typescript-type-stripping.md): Using TypeScript directly without build tools via Node.js Type Stripping.
15
16
  - [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.
17
+ - [Serving Web Content](skills/serving-web-content): Two ways to serve web content from a Harper application.
18
+ - [Checking Authentication](skills/checking-authentication.md): How to use sessions to verify user identity and roles.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harperfast/template-react-studio",
3
- "version": "0.9.3",
3
+ "version": "0.11.0",
4
4
  "type": "module",
5
5
  "repository": "github:HarperFast/create-harper",
6
6
  "scripts": {},
@@ -0,0 +1,6 @@
1
+ ## This table is @export'ed, which means API endpoints are stood up for it automatically.
2
+ type ExampleCat @table @export {
3
+ id: ID @primaryKey # Here we define primary key (must be one)
4
+ name: String # we can define any other attributes here
5
+ tag: String @indexed # we can specify any attributes that should be indexed
6
+ }
@@ -21,12 +21,14 @@ To add tables to a Harper database, follow these guidelines:
21
21
 
22
22
  ### Example
23
23
 
24
- In `schemas/ExampleTable.graphql`:
24
+ In `schemas/ExamplePerson.graphql`:
25
25
 
26
26
  ```graphql
27
- type ExampleTable @table @export {
27
+ type ExamplePerson @table @export {
28
28
  id: ID @primaryKey
29
29
  name: String
30
- email: String @indexed
30
+ tag: String @indexed
31
31
  }
32
32
  ```
33
+
34
+ Tip: if you are going to [extend the table](./extending-tables.md) in your resources, then do not `@export` the table from the schema.
@@ -1,52 +1,186 @@
1
- # Checking Authentication in HarperDB
1
+ # Checking Authentication and Sessions in this app (HarperDB Resources)
2
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.
3
+ This project uses HarperDB Resource classes with cookie-backed sessions to enforce authentication and authorization. Below are the concrete patterns used across resources like `resources/me.ts`, `resources/signIn.ts`, `resources/signOut.ts`, and protected endpoints such as `resources/downloadAlbumArtwork.ts`.
4
4
 
5
- ## Using `this.getCurrentUser()`
5
+ Important: To actually enforce sessions (even on localhost), HarperDB must not auto-authorize the local loopback as the superuser. Ensure the following in your HarperDB config (see `~/hdb/harperdb-config.yaml`):
6
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`.
7
+ ```yaml
8
+ authentication:
9
+ authorizeLocal: false
10
+ enableSessions: true
11
+ ```
8
12
 
9
- ### Example: Role-Based Access Control
13
+ With `authorizeLocal: true`, all local requests would be auto-authorized as the superuser, bypassing these checks. We keep it off to ensure session checks are respected.
10
14
 
11
- You can check the user's role to determine if they have permission to perform a specific action.
15
+ ## Public vs protected routes
12
16
 
13
- ```typescript
14
- import { RequestTargetOrId, Resource } from 'harperdb';
17
+ - Public resources explicitly allow the method via `allowRead()` or `allowCreate()` or similara returning `true`.
18
+ - Protected handlers perform checks up-front using the current session user (and, for privileged actions, a helper like `ensureSuperUser`).
15
19
 
16
- export class MyResource extends Resource {
17
- async post(target: RequestTargetOrId, record: any) {
18
- const user = this.getCurrentUser();
20
+ ## Creating a session (sign in)
21
+
22
+ Pattern from `resources/signIn.ts`:
23
+
24
+ ```ts
25
+ import { type Context, type RequestTargetOrId, Resource } from 'harperdb';
26
+
27
+ export interface LoginBody {
28
+ username?: string;
29
+ password?: string;
30
+ }
31
+
32
+ export class SignIn extends Resource {
33
+ static loadAsInstance = false;
19
34
 
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
- };
35
+ allowCreate() {
36
+ return true; // public endpoint
37
+ }
38
+
39
+ async post(_target: RequestTargetOrId, data: LoginBody) {
40
+ const errors: string[] = [];
41
+ if (!data.username) { errors.push('username'); }
42
+ if (!data.password) { errors.push('password'); }
43
+ if (errors.length) {
44
+ return new Response(
45
+ `Please include the ${errors.join(' and ')} in your request.`,
46
+ { status: 400 },
47
+ );
26
48
  }
27
49
 
28
- return super.post(target, record);
50
+ const context = this.getContext() as Context as any;
51
+ try {
52
+ await context.login(data.username, data.password);
53
+ } catch {
54
+ return new Response('Please check your credentials and try again.', {
55
+ status: 403,
56
+ });
57
+ }
58
+ return new Response('Welcome back!', { status: 200 });
29
59
  }
30
60
  }
31
61
  ```
32
62
 
33
- ## Handling Unauthenticated Requests
63
+ - `context.login(username, password)` creates a session and sets the session cookie on the response.
64
+ - Missing fields → `400 Bad Request`.
65
+ - Invalid credentials → `403 Forbidden` (don’t leak which field was wrong).
66
+
67
+ ## Reading the current user (who am I)
68
+
69
+ Pattern from `resources/me.ts`:
70
+
71
+ ```ts
72
+ import { Resource } from 'harperdb';
34
73
 
35
- Always ensure you handle cases where `this.getCurrentUser()` returns `null`, especially if your resource is accessible to unauthenticated users.
74
+ export class Me extends Resource {
75
+ static loadAsInstance = false;
36
76
 
37
- ```typescript
38
- const user = this.getCurrentUser();
39
- if (!user) {
40
- throw {
41
- message: 'Authentication required',
42
- statusCode: 401,
43
- };
77
+ allowRead() {
78
+ return true; // public: returns data only if session exists
79
+ }
80
+
81
+ async get() {
82
+ const user = this.getCurrentUser?.();
83
+ if (!user?.username) {
84
+ // Not signed in; return 200 with no body to make polling simple on the client
85
+ return new Response(null, { status: 200 });
86
+ }
87
+ return {
88
+ active: user.active,
89
+ role: user.role,
90
+ username: user.username,
91
+ created: user.__createdtime__,
92
+ updated: user.__updatedtime__,
93
+ };
94
+ }
44
95
  }
45
96
  ```
46
97
 
47
- ## User Object Structure
98
+ - Use `this.getCurrentUser?.()` to access the session’s user (if any).
99
+ - It may be `undefined` when unauthenticated. Handle that case explicitly.
100
+
101
+ ## Destroying a session (sign out)
102
+
103
+ Pattern from `resources/signOut.ts`:
104
+
105
+ ```ts
106
+ import { type Context, Resource } from 'harperdb';
107
+
108
+ export class SignOut extends Resource {
109
+ static loadAsInstance = false;
110
+
111
+ allowCreate() {
112
+ return true; // public endpoint, but requires a session to actually act
113
+ }
114
+
115
+ async post() {
116
+ const user = this.getCurrentUser();
117
+ if (!user?.username) {
118
+ return new Response('Not signed in.', { status: 401 });
119
+ }
120
+
121
+ const context = this.getContext() as Context as any;
122
+ await context.session?.delete?.(context.session.id);
123
+ return new Response('Signed out successfully.', { status: 200 });
124
+ }
125
+ }
126
+ ```
127
+
128
+ - If the request has no session, return `401 Unauthorized`.
129
+ - Otherwise delete the current session via `context.session.delete(sessionId)`.
130
+
131
+ ## Protecting privileged endpoints
132
+
133
+ For admin-only or otherwise privileged actions, use the `ensureSuperUser` helper with the current user.
134
+
135
+ ```ts
136
+ import {
137
+ RequestTarget,
138
+ type RequestTargetOrId,
139
+ Resource,
140
+ tables,
141
+ } from 'harperdb';
142
+ import { ensureSuperUser } from './common/ensureSuperUser.ts';
143
+
144
+ export class DoSomethingInteresting extends Resource {
145
+ static loadAsInstance = false;
146
+
147
+ async get(target: RequestTargetOrId) {
148
+ ensureSuperUser(this.getCurrentUser());
149
+ // … fetch and return the artwork
150
+ }
151
+ }
152
+ ```
153
+
154
+ `ensureSuperUser` throws a `403` if the user is not a super user:
155
+
156
+ ```ts
157
+ import { type User } from 'harperdb';
158
+
159
+ export function ensureSuperUser(user: User | undefined) {
160
+ if (!user?.role?.permission?.super_user) {
161
+ let error = new Error('You do not have permission to perform this action.');
162
+ (error as any).statusCode = 403;
163
+ throw error;
164
+ }
165
+ }
166
+ ```
167
+
168
+ ## Status code conventions used here
169
+
170
+ - 200: Successful operation. For `GET /me`, a `200` with empty body means “not signed in”.
171
+ - 400: Missing required fields (e.g., username/password on sign-in).
172
+ - 401: No current session for an action that requires one (e.g., sign out when not signed in).
173
+ - 403: Authenticated but not authorized (bad credentials on login attempt, or insufficient privileges).
174
+
175
+ ## Client considerations
176
+
177
+ - Sessions are cookie-based; the server handles setting and reading the cookie via HarperDB. If you make cross-origin requests, ensure the appropriate `credentials` mode and CORS settings.
178
+ - If developing locally, double-check the server config still has `authentication.authorizeLocal: false` to avoid accidental superuser bypass.
48
179
 
49
- The object returned by `this.getCurrentUser()` typically includes:
180
+ ## Quick checklist
50
181
 
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`).
182
+ - [ ] Public endpoints explicitly `allowRead`/`allowCreate` as needed.
183
+ - [ ] Sign-in uses `context.login` and handles 400/403 correctly.
184
+ - [ ] Protected routes call `ensureSuperUser(this.getCurrentUser())` (or another role check) before doing work.
185
+ - [ ] Sign-out verifies a session and deletes it.
186
+ - [ ] `authentication.authorizeLocal` is `false` and `enableSessions` is `true` in HarperDB config.
@@ -54,3 +54,13 @@ export class ExamplePeople extends tables.ExamplePeople<ExamplePerson> {
54
54
  ## Important Note
55
55
 
56
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.
57
+
58
+ Extended tables do not need to be `@export`ed in their schema .graphql.
59
+
60
+ ```graphql
61
+ type ExamplePerson @table {
62
+ id: ID @primaryKey
63
+ name: String
64
+ tag: String @indexed
65
+ }
66
+ ```
@@ -8,7 +8,7 @@ In a custom resource or a table resource override, you can intercept the incomin
8
8
 
9
9
  ### Example
10
10
 
11
- Suppose you have a table with a `Blob` field named `data`. You can use `Buffer.from(string, 'base64')` to perform the conversion.
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
12
 
13
13
  ```typescript
14
14
  import { RequestTargetOrId, Resource } from 'harperdb';
@@ -17,7 +17,9 @@ export class MyResource extends Resource {
17
17
  async post(target: RequestTargetOrId, record: any) {
18
18
  if (record.data) {
19
19
  // Convert base64-encoded string to a Buffer
20
- record.data = Buffer.from(record.data, 'base64');
20
+ record.data = createBlob(Buffer.from(record.artwork, 'base64'), {
21
+ type: 'image/jpeg',
22
+ });
21
23
  }
22
24
  // Call the super method to perform the actual storage
23
25
  return super.post(target, record);
@@ -29,9 +31,9 @@ export class MyResource extends Resource {
29
31
 
30
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`.
31
33
 
32
- ### Example: Streaming an MP3 File
34
+ ### Example: Responding with a JPEG File
33
35
 
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.
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.
35
37
 
36
38
  ```typescript
37
39
  import { RequestTarget, RequestTargetOrId, Resource } from 'harperdb';
@@ -42,23 +44,23 @@ export class TrackResource extends Resource {
42
44
  if (!id) {
43
45
  return super.get(target);
44
46
  }
45
- const track = await super.get(target) as any;
46
- if (track?.data) {
47
+ const thumbnail = await super.get(target);
48
+ if (thumbnail?.data) {
47
49
  return {
48
50
  status: 200,
49
51
  headers: {
50
- 'Content-Type': 'audio/mpeg',
51
- 'Content-Disposition': `inline; filename="${track.name}.mp3"`,
52
+ 'Content-Type': 'image/jpeg',
53
+ 'Content-Disposition': `inline; filename="${thumbnail.name}.jpg"`,
52
54
  },
53
- body: track.data,
55
+ body: thumbnail.data,
54
56
  };
55
57
  }
56
- return track;
58
+ return thumbnail;
57
59
  }
58
60
  }
59
61
  ```
60
62
 
61
- ## Why Use Buffers?
63
+ ## Why Use Blobs?
62
64
 
63
65
  - **Efficiency**: `Blob` fields are optimized for storing binary data. Buffers are the standard way to handle binary data in Node.js.
64
66
  - **Compatibility**: Many HarperDB features and external libraries expect binary data to be in `Buffer` or `Uint8Array` format.
@@ -0,0 +1,185 @@
1
+ # Programmatic Requests with HarperDB Tables
2
+
3
+ In HarperDB, 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 HarperDB 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
+ ```
@@ -0,0 +1,82 @@
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.