@harperfast/template-react-ts-studio 1.12.7 → 1.12.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/.agents/skills/harper-best-practices/AGENTS.md +514 -445
  2. package/.agents/skills/harper-best-practices/rules/automatic-apis.md +96 -93
  3. package/.agents/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +84 -58
  4. package/.agents/skills/harper-best-practices/rules/extending-tables.md +27 -35
  5. package/.agents/skills/harper-best-practices/rules/handling-binary-data.md +40 -39
  6. package/.agents/skills/harper-best-practices/rules/querying-rest-apis.md +93 -37
  7. package/.agents/skills/harper-best-practices/rules/schema-design-tooling.md +68 -66
  8. package/.agents/skills/harper-best-practices/rules/using-blob-datatype.md +24 -28
  9. package/.agents/skills/harper-best-practices/rules/v5-upgrade.md +98 -105
  10. package/.agents/skills/harper-best-practices/rules.manifest.yaml +28 -3
  11. package/.agents/skills/harper-local-dev/AGENTS.md +197 -0
  12. package/.agents/skills/harper-local-dev/SKILL.md +65 -0
  13. package/.agents/skills/harper-local-dev/rules/running-dev-instances-in-worktrees.md +198 -0
  14. package/.agents/skills/harper-local-dev/rules.manifest.yaml +20 -0
  15. package/.agents/skills/harper-local-dev/scripts/harper-dev.mjs +345 -0
  16. package/.agents/skills/harper-mcp/AGENTS.md +138 -26
  17. package/.agents/skills/harper-mcp/rules/enabling-mcp.md +140 -28
  18. package/agent/skills/harper-best-practices/AGENTS.md +514 -445
  19. package/agent/skills/harper-best-practices/rules/automatic-apis.md +96 -93
  20. package/agent/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +84 -58
  21. package/agent/skills/harper-best-practices/rules/extending-tables.md +27 -35
  22. package/agent/skills/harper-best-practices/rules/handling-binary-data.md +40 -39
  23. package/agent/skills/harper-best-practices/rules/querying-rest-apis.md +93 -37
  24. package/agent/skills/harper-best-practices/rules/schema-design-tooling.md +68 -66
  25. package/agent/skills/harper-best-practices/rules/using-blob-datatype.md +24 -28
  26. package/agent/skills/harper-best-practices/rules/v5-upgrade.md +98 -105
  27. package/agent/skills/harper-best-practices/rules.manifest.yaml +28 -3
  28. package/agent/skills/harper-local-dev/AGENTS.md +197 -0
  29. package/agent/skills/harper-local-dev/SKILL.md +64 -0
  30. package/agent/skills/harper-local-dev/rules/running-dev-instances-in-worktrees.md +198 -0
  31. package/agent/skills/harper-local-dev/rules.manifest.yaml +20 -0
  32. package/agent/skills/harper-local-dev/scripts/harper-dev.mjs +345 -0
  33. package/agent/skills/harper-mcp/AGENTS.md +138 -26
  34. package/agent/skills/harper-mcp/rules/enabling-mcp.md +140 -28
  35. package/package.json +1 -1
  36. package/skills-lock.json +8 -2
@@ -9,21 +9,30 @@ metadata:
9
9
  - reference/v5/database/schema.md#Overview
10
10
  - reference/v5/database/schema.md#Type Directives
11
11
  - reference/v5/database/schema.md#Field Directives
12
- sourceCommit: 677ad213d67822e109c83619e181ca23a59823db
13
- inputHash: ecb06058191ba29f
12
+ sourceCommit: 2cfb81318f17e0aca2d600109e6ad813d2d0443e
13
+ inputHash: a644f4ea2f289f77
14
14
  ---
15
15
 
16
- # Schema Design and GraphQL Tooling
16
+ # Schema Design Tooling
17
17
 
18
- Instructions for the agent to follow when designing Harper database schemas using GraphQL type definitions, core directives, and tooling configuration.
18
+ Instructions for the agent to follow when designing Harper schemas, applying core directives, and configuring GraphQL tooling.
19
19
 
20
20
  ## When to Use
21
21
 
22
- Apply this rule when creating or modifying Harper schema files (`.graphql`), configuring schema loading in `config.yaml`, or deciding which directives to apply to tables and fields. Use it whenever a task involves defining tables, primary keys, indexes, or export behavior.
22
+ Apply this rule when creating or modifying Harper schema files (`.graphql`), configuring `graphqlSchema` in `config.yaml`, or deciding which directives to apply to tables and fields. Use it whenever a task involves defining tables, primary keys, indexes, or export behavior.
23
23
 
24
24
  ## How It Works
25
25
 
26
- 1. **Create a GraphQL schema file** with Harper-specific directives. Schemas ensure required tables exist on deployment, enforce types and constraints, control indexing, and define relationships.
26
+ 1. **Register the schema file** in the component's `config.yaml` using the `graphqlSchema` plugin key:
27
+
28
+ ```yaml
29
+ graphqlSchema:
30
+ files: 'schema.graphql'
31
+ ```
32
+
33
+ Both plugins and applications can specify schemas.
34
+
35
+ 2. **Mark types as tables** using `@table`. The type name becomes the table name by default:
27
36
 
28
37
  ```graphql
29
38
  type Dog @table {
@@ -34,79 +43,71 @@ Apply this rule when creating or modifying Harper schema files (`.graphql`), con
34
43
  }
35
44
  ```
36
45
 
37
- 2. **Register the schema in `config.yaml`** using the `graphqlSchema` plugin:
46
+ 3. **Designate a primary key** on every table using `@primaryKey`. Primary keys must be unique; duplicate inserts are rejected. If no primary key is provided on insert, Harper auto-generates one:
47
+ - `String` or `ID` → UUID string
48
+ - `Int`, `Long`, or `Any` → auto-incrementing integer
38
49
 
39
- ```yaml
40
- graphqlSchema:
41
- files: 'schema.graphql'
42
- ```
43
-
44
- Both plugins and applications can specify schemas.
50
+ Use `Long` or `Any` for auto-generated numeric keys; `Int` is 32-bit and may be insufficient for large tables.
45
51
 
46
- 3. **Mark each type as a table** with `@table`. The type name becomes the table name by default.
52
+ 4. **Index fields for querying** using `@indexed`. Required for filtering by an attribute in REST queries, SQL, or NoSQL operations:
47
53
 
48
54
  ```graphql
49
- type MyTable @table {
55
+ type Breed @table {
50
56
  id: Long @primaryKey
57
+ name: String @indexed
51
58
  }
52
59
  ```
53
60
 
54
- Key `@table` arguments:
61
+ If the field value is an array, each element is individually indexed. Null values are indexed by default.
55
62
 
56
- | Argument | Type | Default | Description |
57
- | -------------------- | --------- | ----------------------------- | --------------------------------------------------------------- |
58
- | `table` | `String` | type name | Override the table name |
59
- | `database` | `String` | `"data"` | Database to place the table in |
60
- | `expiration` | `Int` | — | Seconds until a record goes stale |
61
- | `eviction` | `Int` | `0` | Additional seconds after `expiration` before physical removal |
62
- | `scanInterval` | `Int` | `(expiration + eviction) / 4` | Seconds between eviction scans |
63
- | `replicate` | `Boolean` | `true` | Enable replication of this table |
64
- | `cacheControl` | `String` | — | `Cache-Control` header for anonymous GET/HEAD 200/304 responses |
65
- | `randomAccessFields` | `Boolean` | `storage.randomAccessFields` | Pin this table's record encoding |
66
-
67
- 4. **Designate a primary key** on every table using `@primaryKey`. Primary keys must be unique; duplicate inserts are rejected. If no primary key is provided on insert, Harper auto-generates one:
68
- - **UUID string** — when type is `String` or `ID`
69
- - **Auto-incrementing integer** — when type is `Int`, `Long`, or `Any`
70
-
71
- Use `Long` or `Any` for auto-generated numeric keys; `Int` is 32-bit and may be insufficient for large tables.
63
+ 5. **Expose tables as REST/MQTT endpoints** using `@export`. The optional `name` parameter sets the URL path segment; without it, the type name is used:
72
64
 
73
65
  ```graphql
74
- type Product @table {
66
+ type MyTable @table @export(name: "my-table") {
75
67
  id: Long @primaryKey
76
- name: String
77
68
  }
78
69
  ```
79
70
 
80
- 5. **Add secondary indexes** with `@indexed` on any attribute that will be used for filtering in REST queries, SQL, or NoSQL operations. If the field value is an array, each element is individually indexed.
71
+ `@export` alone does not serve HTTP traffic — REST must also be enabled for the application via `rest: true` in `config.yaml` or Harper's built-in default. `@export` is a routing directive, not access control; omitting it returns 404 but does not protect the data.
72
+
73
+ 6. **Configure `@table` arguments** to control database placement, expiration, replication, and caching behavior. Key arguments:
74
+
75
+ | Argument | Type | Default | Description |
76
+ | -------------------- | --------- | ----------------------------- | -------------------------------------------------------------- |
77
+ | `table` | `String` | type name | Override the table name |
78
+ | `database` | `String` | `"data"` | Database to place the table in |
79
+ | `expiration` | `Int` | — | Seconds until a record goes stale |
80
+ | `eviction` | `Int` | `0` | Additional seconds after `expiration` before physical removal |
81
+ | `scanInterval` | `Int` | `(expiration + eviction) / 4` | Seconds between eviction scans |
82
+ | `replicate` | `Boolean` | `true` | Enable replication of this table |
83
+ | `cacheControl` | `String` | — | `Cache-Control` header on anonymous GET/HEAD 200/304 responses |
84
+ | `randomAccessFields` | `Boolean` | `storage.randomAccessFields` | Pin this table's record encoding |
85
+
86
+ 7. **Seal a type** with `@sealed` to prevent records from including properties beyond those declared:
81
87
 
82
88
  ```graphql
83
- type Product @table {
89
+ type StrictRecord @table @sealed {
84
90
  id: Long @primaryKey
85
- category: String @indexed
86
- price: Float @indexed
91
+ name: String
87
92
  }
88
93
  ```
89
94
 
90
- 6. **Expose tables via REST and other interfaces** using `@export`. Without `@export`, the table has no REST/MQTT route (callers get 404). The optional `name` parameter sets the URL path segment.
95
+ 8. **Use timestamp directives** for automatic record lifecycle tracking:
96
+ - `@createdTime` — assigns Unix epoch milliseconds on record creation
97
+ - `@updatedTime` — assigns Unix epoch milliseconds on each update
98
+ - `@expiresAt` — marks a field as the record's absolute expiration time (Unix epoch milliseconds); authoritative over the table-level `expiration` default
99
+
100
+ 9. **Use `@embed`** to automatically compute an embedding vector for an attribute whenever the source field is written (requires `source` and `model` arguments; field type must be `[Float]`):
91
101
 
92
102
  ```graphql
93
- type MyTable @table @export(name: "my-table") {
103
+ type Document @table {
94
104
  id: Long @primaryKey
105
+ text: String
106
+ embedding: [Float] @embed(source: "text", model: "default")
95
107
  }
96
108
  ```
97
109
 
98
- `@export` is a routing directive, not access control. The table remains accessible through the Operations API and SQL regardless. REST must also be enabled for the application (via `rest: true` in `config.yaml` or Harper's built-in default).
99
-
100
- 7. **Apply additional type directives** as needed:
101
- - `@sealed` — prevents records from including properties beyond those declared in the schema.
102
- - `@hidden` — suppresses the type from MCP tool descriptors and the OpenAPI document. Does not restrict data access.
103
-
104
- 8. **Apply field directives** for computed and lifecycle behavior:
105
- - `@createdTime` — assigns Unix epoch milliseconds on record creation.
106
- - `@updatedTime` — assigns Unix epoch milliseconds on each update.
107
- - `@expiresAt` — marks a field as the record's absolute expiration time (Unix epoch milliseconds); authoritative over the table-level `expiration` default.
108
- - `@embed` — computes an embedding vector when the source field is written (requires `source` and `model` arguments; field type must be `[Float]`).
109
- - `@hidden` (field) — suppresses the field from generated specs and MCP tool schemas; does not restrict data access.
110
+ 10. **Use `@hidden`** on types or fields to suppress them from MCP tool descriptors and the OpenAPI document. This is a metadata-visibility directive only — it does not restrict data access. Use `attribute_permissions` on roles for field-level access control.
110
111
 
111
112
  ## Examples
112
113
 
@@ -136,7 +137,7 @@ type WeatherCache @table(expiration: 300, eviction: 3300, scanInterval: 600) {
136
137
  }
137
138
  ```
138
139
 
139
- **Exported table with cache control:**
140
+ **Exported table with public cache control:**
140
141
 
141
142
  ```graphql
142
143
  type Product @table(cacheControl: "public, max-age=60") @export {
@@ -146,14 +147,12 @@ type Product @table(cacheControl: "public, max-age=60") @export {
146
147
  }
147
148
  ```
148
149
 
149
- **Table with lifecycle fields and indexing:**
150
+ **Table with multiple `@table` arguments combined:**
150
151
 
151
152
  ```graphql
152
153
  type Event @table(database: "analytics", expiration: 86400) {
153
154
  id: Long @primaryKey
154
155
  name: String @indexed
155
- createdAt: Long @createdTime
156
- updatedAt: Long @updatedTime
157
156
  }
158
157
  ```
159
158
 
@@ -167,12 +166,15 @@ type Session @table {
167
166
  }
168
167
  ```
169
168
 
170
- **Sealed table preventing extra properties:**
169
+ **Table with timestamp tracking and indexed fields:**
171
170
 
172
171
  ```graphql
173
- type StrictRecord @table @sealed {
172
+ type Order @table(database: "commerce") @export {
174
173
  id: Long @primaryKey
175
- name: String
174
+ userId: String @indexed
175
+ status: String @indexed
176
+ createdAt: Long @createdTime
177
+ updatedAt: Long @updatedTime
176
178
  }
177
179
  ```
178
180
 
@@ -185,10 +187,10 @@ graphqlSchema:
185
187
 
186
188
  ## Notes
187
189
 
188
- - Schemas are flexible by default — records may include additional properties beyond those declared. Use `@sealed` to prevent this.
189
- - Use unique `database` names in plugins or applications to avoid table naming collisions, since all tables default to the `"data"` database.
190
- - Replication is enabled by default. If you disable replication and re-enable it later, the table will not catch up on writes made while replication was disabled.
191
- - `@hidden` (type or field) is a metadata-visibility directive only. Use table-level role permissions and `attribute_permissions` whitelists to restrict actual data access.
192
- - `@export` absence causes 404 on REST/MQTT routes but does not protect data from the Operations API or SQL.
193
- - The `cacheControl` argument emits headers only on anonymous (unauthenticated) GET/HEAD 200/304 responses. Authenticated responses receive `Cache-Control: private, no-cache`.
194
- - `randomAccessFields` on `@table` pins the record encoding at table creation time. Editing the argument later does not repin an existing table.
190
+ - Use unique `database` names in plugins and applications to avoid table naming collisions, since all tables default to the `"data"` database.
191
+ - Disabling replication (`replicate: false`) and re-enabling it later will not catch up on writes made while replication was disabled.
192
+ - `cacheControl` emits the header only on anonymous (unauthenticated) GET/HEAD `200`/`304` responses. Authenticated responses receive `Cache-Control: private, no-cache` regardless of the declaration. The header is never emitted on `401` responses.
193
+ - `@expiresAt` requires an absolute Unix epoch millisecond timestamp, not a duration. Negative values are ignored and fall back to the table default. A full-record `put` that omits the field clears it; a `patch` preserves it.
194
+ - Eviction removes non-indexed record data but does not remove a record from its secondary indexes; indexes remain functional for evicted records, with full records fetched on demand.
195
+ - `scanInterval` is clock-aligned to the server's local timezone, not startup-aligned — the server's startup time does not affect when eviction runs.
196
+ - `randomAccessFields` pins the table's encoding at creation time. Editing the argument later does not repin an existing table. Omit it to follow the global `storage.randomAccessFields` setting.
@@ -8,8 +8,8 @@ metadata:
8
8
  - reference/v5/database/api.md#Streaming
9
9
  - reference/v5/database/api.md#`BlobOptions`
10
10
  - reference/v5/database/api.md#Blob Coercion
11
- sourceCommit: f37a8c4021e20d5c74c1d339a6b6c8c196b5603e
12
- inputHash: 92e03eb0b830f335
11
+ sourceCommit: 2cfb81318f17e0aca2d600109e6ad813d2d0443e
12
+ inputHash: 753b85cbdcef9188
13
13
  ---
14
14
 
15
15
  # Using the Blob Data Type
@@ -18,11 +18,11 @@ Instructions for the agent to follow when storing and retrieving large binary co
18
18
 
19
19
  ## When to Use
20
20
 
21
- Apply this rule when a schema field needs to store large binary content such as images, video, audio, or large HTML — typically content larger than 20KB. Use `Blob` instead of `Bytes` when streaming support and out-of-record storage are required. See [handling-binary-data.md](handling-binary-data.md) for broader binary data guidance.
21
+ Apply this rule when a schema field needs to store large binary content such as images, video, audio, or large HTML — typically content larger than 20KB. Use `Blob` instead of `Bytes` when streaming support and out-of-record storage are required. See [handling-binary-data](handling-binary-data.md) for broader binary data guidance.
22
22
 
23
23
  ## How It Works
24
24
 
25
- 1. **Declare a `Blob` field in your schema**: Add a field typed as `Blob` to your `@table` type.
25
+ 1. **Declare a `Blob` field in your schema**: Add a field typed as `Blob` to your table definition.
26
26
 
27
27
  ```graphql
28
28
  type MyTable @table {
@@ -31,14 +31,14 @@ Apply this rule when a schema field needs to store large binary content such as
31
31
  }
32
32
  ```
33
33
 
34
- 2. **Create and store a blob with `createBlob()`**: Pass a buffer or stream to `createBlob()`, then `put` the record.
34
+ 2. **Create and store a blob with `createBlob()`**: Pass a buffer, string, or stream to `createBlob()`, then `put` the record.
35
35
 
36
36
  ```javascript
37
37
  let blob = createBlob(largeBuffer);
38
38
  await MyTable.put({ id: 'my-record', data: blob });
39
39
  ```
40
40
 
41
- 3. **Retrieve blob data using standard Web API methods**: The `Blob` type implements the Web API `Blob` interface. Use `.bytes()`, `.text()`, `.arrayBuffer()`, `.stream()`, or `.slice()` as needed.
41
+ 3. **Retrieve blob data using standard Web API methods**: Use `.bytes()`, `.text()`, or `.stream()` on the retrieved field.
42
42
 
43
43
  ```javascript
44
44
  let record = await MyTable.get('my-record');
@@ -47,7 +47,7 @@ Apply this rule when a schema field needs to store large binary content such as
47
47
  let stream = record.data.stream(); // ReadableStream
48
48
  ```
49
49
 
50
- 4. **Use `saveBeforeCommit` when full write must precede commit**: By default, `Blob` is not ACID-compliant — a record can reference a blob before it is fully written. Set `saveBeforeCommit: true` to block the transaction until the blob is fully saved.
50
+ 4. **Use `saveBeforeCommit` when full write must complete before commit**: By default, blobs are not ACID-compliant — a record can reference a blob before it is fully written. Set `saveBeforeCommit: true` to block the transaction until the blob is fully saved.
51
51
 
52
52
  ```javascript
53
53
  let blob = createBlob(stream, { saveBeforeCommit: true });
@@ -55,13 +55,14 @@ Apply this rule when a schema field needs to store large binary content such as
55
55
  // put() resolves only after blob is fully written and record is committed
56
56
  ```
57
57
 
58
- 5. **Register an error handler when returning a blob via REST**: Interrupted streams must be handled explicitly.
58
+ 5. **Register an error handler when returning a blob via REST**: Handle interrupted streams to avoid stale references.
59
59
 
60
60
  ```javascript
61
61
  export class MyEndpoint extends MyTable {
62
62
  static async get(target) {
63
- const record = super.get(target);
64
- let blob = record.data;
63
+ const record = await super.get(target);
64
+ let blob = record?.data;
65
+ if (!blob) return record;
65
66
  blob.on('error', () => {
66
67
  MyTable.invalidate(target);
67
68
  });
@@ -70,19 +71,15 @@ Apply this rule when a schema field needs to store large binary content such as
70
71
  }
71
72
  ```
72
73
 
73
- 6. **Rely on automatic coercion where applicable**: When a field is typed as `Blob` in the schema, any string or buffer assigned via `put`, `patch`, or `publish` is automatically coerced to a `Blob` — no manual `createBlob()` call is needed in those cases.
74
+ 6. **Pass `BlobOptions` to control storage behavior**: `createBlob()` accepts an options object as its second argument.
74
75
 
75
- ### `BlobOptions` reference
76
-
77
- Pass an options object as the second argument to `createBlob()`.
78
-
79
- | Option | Type | Default | Description |
80
- | ------------------ | --------- | ----------- | ------------------------------------------------------------------------------------------------------------------------ |
81
- | `type` | `string` | `undefined` | MIME type to associate with the blob (e.g., `image/jpeg`). Readable via `blob.type` and used when serving HTTP. |
82
- | `size` | `number` | `undefined` | Size of the data in bytes, if known ahead of time. Otherwise inferred from a buffer or determined as a stream completes. |
83
- | `saveBeforeCommit` | `boolean` | `false` | Wait until the blob is fully written before the transaction commits. |
84
- | `compress` | `boolean` | `false` | Compress the stored data with deflate. |
85
- | `flush` | `boolean` | `false` | Flush the file to disk after writing, before the `createBlob` promise chain resolves. |
76
+ | Option | Type | Default | Description |
77
+ | ------------------ | --------- | ----------- | ----------------------------------------------------------------------------------- |
78
+ | `type` | `string` | `undefined` | MIME type (e.g., `image/jpeg`). Used when serving HTTP and readable via `blob.type` |
79
+ | `size` | `number` | `undefined` | Size in bytes if known ahead of time; otherwise inferred |
80
+ | `saveBeforeCommit` | `boolean` | `false` | Wait until blob is fully written before the transaction commits |
81
+ | `compress` | `boolean` | `false` | Compress stored data with deflate |
82
+ | `flush` | `boolean` | `false` | Flush file to disk after writing, before the `createBlob` promise chain resolves |
86
83
 
87
84
  ## Examples
88
85
 
@@ -97,15 +94,13 @@ await Photo.put({ id, data: blob });
97
94
 
98
95
  ```javascript
99
96
  let blob = createBlob(incomingStream);
100
- // blob exists, but data is still streaming to storage
101
97
  await MyTable.put({ id: 'my-record', data: blob });
102
98
 
103
99
  let record = await MyTable.get('my-record');
104
- // blob data is accessible as it arrives
105
100
  let outgoingStream = record.data.stream();
106
101
  ```
107
102
 
108
- **Guarantee full write before commit using `saveBeforeCommit`:**
103
+ **Wait for full write before commit:**
109
104
 
110
105
  ```javascript
111
106
  let blob = createBlob(stream, { saveBeforeCommit: true });
@@ -114,6 +109,7 @@ await MyTable.put({ id: 'my-record', data: blob });
114
109
 
115
110
  ## Notes
116
111
 
117
- - `Blob` stores data separately from the record. If you need the binary data to be a true, ACID-committed part of the record, use a `Bytes` field instead.
118
- - All standard Web API `Blob` methods — `.text()`, `.arrayBuffer()`, `.stream()`, `.slice()`, and `.bytes()` — are available on retrieved blob fields.
119
- - Without `saveBeforeCommit: true`, blobs are **not** ACID-compliant by default; a record can reference a blob before it is fully written to storage.
112
+ - `Blob` implements the Web API `Blob` interface. All standard methods — `.text()`, `.arrayBuffer()`, `.stream()`, `.slice()`, and `.bytes()` — are available on retrieved blob fields.
113
+ - Blobs are stored **separately from the record**, not within it. If you need the binary data to be a true ACID-committed part of the record, use a `Bytes` field instead.
114
+ - Any string or buffer assigned to a `Blob`-typed field in a `put`, `patch`, or `publish` is automatically coerced to a `Blob` — manual `createBlob()` calls are not required in those cases.
115
+ - Use `saveBeforeCommit: true` whenever downstream consumers must not read a partially written blob.
@@ -7,60 +7,82 @@ metadata:
7
7
  mode: generate
8
8
  sources:
9
9
  - release-notes/v5-lincoln/v5-migration.md
10
- sourceCommit: 677ad213d67822e109c83619e181ca23a59823db
11
- inputHash: 01c0fafb9afa68a0
10
+ sourceCommit: 2cfb81318f17e0aca2d600109e6ad813d2d0443e
11
+ inputHash: 66ca7a9e265d006e
12
12
  ---
13
13
 
14
14
  # v5 Upgrade: Breaking Changes and Migration Guide
15
15
 
16
- Instructions for the agent to apply when migrating a Harper application to v5, covering all breaking changes and required code updates.
16
+ Instructions for the agent to follow when migrating a Harper application to v5, covering all breaking changes and required code updates.
17
17
 
18
18
  ## When to Use
19
19
 
20
- Apply this rule when upgrading an existing Harper application to v5, when encountering runtime errors related to renamed packages, changed APIs, or security restrictions after a v5 upgrade, or when scaffolding new v5-compatible application code.
20
+ Apply this rule when upgrading an existing Harper application from v4 to v5, when encountering runtime errors after upgrading, or when reviewing application code for v5 compatibility. Every breaking change listed here must be addressed before the application will behave correctly under v5.
21
21
 
22
22
  ## How It Works
23
23
 
24
- 1. **Update the package import from `harperdb` to `harper`**: All application code must import from `harper`, not `harperdb`.
24
+ 1. **Update the package import**: Replace all imports from `'harperdb'` with `from 'harper'`.
25
25
 
26
26
  ```javascript
27
27
  import { tables } from 'harper';
28
28
  ```
29
29
 
30
- 2. **Enable `allowInstallScripts` if packages require install scripts**: Harper v5 uses `--ignore-scripts` by default when installing packages. If a package requires execution of install scripts (e.g., to install native binaries), set the `allowInstallScripts` option when deploying.
30
+ 2. **Enable install scripts if needed**: Harper v5 runs `npm install` with `--ignore-scripts` by default. If your application requires install scripts (e.g., to compile native binaries), set `allowInstallScripts` in your deployment options.
31
31
 
32
- 3. **Update `Table.get` usage — return value is now a frozen record object**: `Table.get` now returns a plain record object, not a table class instance. The record is frozen; you cannot add or mutate properties directly.
33
- - Replace direct property mutation:
32
+ 3. **Update `Table.get` usage**: `Table.get` now returns a plain frozen record object, not a table class instance. The `wasLoadedFromSource()` method no longer exists on the returned object. Replace it with `loadedFromSource` on the `RequestTarget`:
34
33
 
35
- ```javascript
36
- let record = await Table.get(id);
37
- record = { ...record, property: 'changed' };
38
- ```
34
+ ```javascript
35
+ // Before
36
+ const record = await Table.get(id);
37
+ if (record.wasLoadedFromSource()) {
38
+ // record was loaded from origin (not cache)
39
+ }
40
+ ```
39
41
 
40
- - Replace `wasLoadedFromSource()` with `loadedFromSource` on the `target` object:
42
+ ```javascript
43
+ // After
44
+ import { getContext } from 'harper';
45
+ const target = new RequestTarget();
46
+ target.id = id;
47
+ const record = await Table.get(target);
48
+ if (target.loadedFromSource) {
49
+ // record was loaded from origin (not cache)
50
+ }
51
+ ```
41
52
 
42
- ```javascript
43
- const target = new RequestTarget();
44
- target.id = id;
45
- const record = await Table.get(target);
46
- if (target.loadedFromSource) {
47
- // record was loaded from origin (not cache)
48
- }
49
- ```
53
+ The record objects do have `getUpdatedTime` and `getExpiresAt` methods available.
50
54
 
51
- The record objects still expose `getUpdatedTime` and `getExpiresAt` methods.
55
+ 4. **Handle frozen records**: The record object returned by `Table.get` is frozen — you cannot mutate it directly. Copy it before modifying:
52
56
 
53
- 4. **Update transaction and context handling using `getContext`**: Harper v5 uses asynchronous context tracking. Context and the current transaction are automatically carried to all downstream calls — you no longer pass context explicitly. Import `getContext` and `transaction` from `harper`:
57
+ ```javascript
58
+ // Before
59
+ const record = await Table.get(id);
60
+ record.property = 'changed';
61
+ ```
54
62
 
55
63
  ```javascript
56
- import { getContext, transaction } from 'harper';
64
+ // After
65
+ let record = await Table.get(id);
66
+ record = { ...record, property: 'changed' };
57
67
  ```
58
68
 
59
- If your code previously omitted context to escape a transaction (e.g., to poll for updated data), explicitly commit the transaction and/or wrap each read in a new `transaction()` call:
69
+ 5. **Register allowed spawn commands via `allowedSpawnCommands`**: `spawn` and `execFile` may only launch executables listed in `applications.allowedSpawnCommands` in `harper-config.yaml`. Only the first token of the command is matched. `exec` is not usable through the substituted module, and `execSync` always throws.
70
+
71
+ ```yaml
72
+ applications:
73
+ allowedSpawnCommands:
74
+ - npm
75
+ - node
76
+ ```
77
+
78
+ Additionally, `spawn`, `execFile`, and `fork` now require a `name` property in the `options` argument to prevent process multiplication across threads.
79
+
80
+ 6. **Update transaction and context usage**: Harper v5 uses asynchronous context tracking. `Table.get` and other calls now automatically inherit the current transaction. Code that previously omitted context to bypass a transaction will no longer work as expected. Explicitly commit the transaction or wrap calls in a new transaction to read updated data:
60
81
 
61
82
  ```javascript
62
83
  import { setTimeout as delay } from 'node:timers/promises';
63
84
  import { getContext, transaction } from 'harper';
85
+
64
86
  class MyResource {
65
87
  static async get(target) {
66
88
  await getContext().transaction.commit();
@@ -72,79 +94,70 @@ Apply this rule when upgrading an existing Harper application to v5, when encoun
72
94
  }
73
95
  ```
74
96
 
75
- 5. **Register allowed spawn commands via `allowedSpawnCommands`**: `spawn` and `execFile` may only launch executables listed in `applications.allowedSpawnCommands` in `harperdb-config.yaml`. Only the first token of the command is matched. `exec` is not usable through the substituted module; `execSync` always throws.
97
+ Use `getContext` (exported from `'harper'`) to access the current transaction anywhere without passing context explicitly.
76
98
 
77
- ```yaml
78
- applications:
79
- allowedSpawnCommands:
80
- - npm
81
- - node
82
- ```
99
+ 7. **Replace `blob.save()`**: The `blob.save()` method has been removed. Use the `saveBeforeCommit` flag in the options passed to the `Blob` constructor instead.
83
100
 
84
- Additionally, `spawn`, `execFile`, and `fork` now require a `name` property in the `options` argument to prevent process multiplication across threads.
101
+ 8. **Handle response `headers`**: If you return an object from a REST method with a `headers` property, Harper v5 will use it as the response headers. Ensure any object you return that incidentally has a `headers` property is intentional.
85
102
 
86
- 6. **Use `saveBeforeCommit` instead of `blob.save()`**: The `blob.save()` method has been removed. Pass the `saveBeforeCommit` flag in the options to the `Blob` constructor instead.
103
+ 9. **Configure the module loader**: Harper v5 loads application modules through Node.js's VM module API. Control this with the `moduleLoader` setting:
87
104
 
88
- 7. **Handle `headers` on returned response objects**: If you return an object from a REST method with a `headers` property, Harper v5 will use it as the response headers.
105
+ | Value | Behavior |
106
+ | -------------------- | ------------------------------------------------ |
107
+ | `vm-current-context` | Default. Shares intrinsics with Harper. |
108
+ | `vm` | Separate context and intrinsics per application. |
109
+ | `native` | Standard `import()`, no application context. |
110
+ | `compartment` | SES Compartments; advanced and heavier. |
89
111
 
90
- 8. **Configure the VM module loader and `lockdown` in `harperdb-config.yaml`**: v5 loads application modules through Node.js's VM module API. Control all behavior under the `applications` key:
112
+ The default (`vm-current-context`) avoids `instanceof` failures for values crossing the application/Harper boundary. Choose `vm` only if you need separate per-application intrinsics. Use `native` if the VM loader causes compatibility problems you cannot otherwise resolve.
113
+
114
+ To disable the VM loader entirely and restore pre-v5 behavior:
91
115
 
92
116
  ```yaml
93
117
  applications:
94
- lockdown: freeze-after-load
95
- moduleLoader: vm-current-context
96
- dependencyLoader: auto
97
- allowedDirectory: app
98
- allowedSpawnCommands:
99
- - npm
100
- - node
118
+ moduleLoader: native
101
119
  ```
102
120
 
103
- **`moduleLoader` options:**
121
+ Note: `logger` and per-app `config` are not available in `native` mode.
104
122
 
105
- | Value | Behavior |
106
- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
107
- | `vm-current-context` | Default. VM loader in Harper's own context; shares intrinsics with Harper. Best compatibility. |
108
- | `vm` | VM loader in a separate per-application context with its own intrinsics. Stronger isolation but may cause `instanceof` failures. |
109
- | `native` | Standard Node.js `import()`. No VM loader; application-specific context (`logger`, `config`) unavailable. |
110
- | `compartment` | SES Compartment-based loading. For specialized sandboxing only. |
123
+ If the goal is only to fix package compatibility while keeping application context for first-party code, `dependencyLoader: native` is a narrower option — it uses native loading only for npm packages while keeping the VM loader for application source files.
111
124
 
112
- **`lockdown` options:**
125
+ 10. **Handle intrinsic `lockdown`**: The default `lockdown` mode (`freeze-after-load`) freezes JavaScript intrinsics (`Object`, `Array`, `Promise`, `Map`, `Set`, etc.) after all application code loads. Any code or dependency that modifies intrinsic prototypes at runtime will throw a `TypeError`. If a dependency requires a temporary workaround, set:
113
126
 
114
- | Value | Behavior |
115
- | ------------------- | ------------------------------------------------------- |
116
- | `freeze-after-load` | Default. Freezes intrinsics after all components load. |
117
- | `freeze` | Freezes intrinsics before loading any application code. |
118
- | `ses` | Full SES lockdown via the `ses` package. Strictest. |
119
- | `none` | No lockdown. Use as a temporary workaround only. |
127
+ ```yaml
128
+ applications:
129
+ lockdown: none
130
+ ```
120
131
 
121
- To disable the VM loader entirely and restore pre-v5 behavior:
132
+ 11. **Check `allowedDirectory` in production**: In production, applications can only load modules from within their own directory tree (`allowedDirectory: app`). If your application loads files from outside its directory, set:
122
133
 
123
- ```yaml
124
- applications:
125
- moduleLoader: native
126
- ```
134
+ ```yaml
135
+ applications:
136
+ allowedDirectory: any
137
+ ```
127
138
 
128
139
  ## Examples
129
140
 
130
- **Full transaction polling pattern (v5):**
141
+ ### Full `harper-config.yaml` module loading block
142
+
143
+ ```yaml
144
+ applications:
145
+ lockdown: freeze-after-load
146
+ moduleLoader: vm-current-context
147
+ dependencyLoader: auto
148
+ allowedDirectory: app
149
+ allowedSpawnCommands:
150
+ - npm
151
+ - node
152
+ ```
153
+
154
+ ### Updated import and context access
131
155
 
132
156
  ```javascript
133
- import { setTimeout as delay } from 'node:timers/promises';
134
- import { getContext, transaction } from 'harper';
135
-
136
- class MyResource {
137
- static async get(target) {
138
- await getContext().transaction.commit();
139
- while ((await transaction(() => Table.get(target))).status !== 'ready') {
140
- await delay(100);
141
- }
142
- return Table.get(target);
143
- }
144
- }
157
+ import { tables, getContext, transaction } from 'harper';
145
158
  ```
146
159
 
147
- **Checking `loadedFromSource` after `Table.get`:**
160
+ ### Checking `loadedFromSource` after `Table.get`
148
161
 
149
162
  ```javascript
150
163
  const target = new RequestTarget();
@@ -155,37 +168,17 @@ if (target.loadedFromSource) {
155
168
  }
156
169
  ```
157
170
 
158
- **Full `harperdb-config.yaml` `applications` block:**
159
-
160
- ```yaml
161
- applications:
162
- lockdown: freeze-after-load
163
- moduleLoader: vm-current-context
164
- dependencyLoader: auto
165
- allowedDirectory: app
166
- allowedSpawnCommands:
167
- - npm
168
- - node
169
- ```
170
-
171
- **Restricting allowed built-in modules:**
171
+ ### Copying a frozen record before mutation
172
172
 
173
- ```yaml
174
- applications:
175
- allowedBuiltinModules:
176
- - fs
177
- - path
178
- - http
173
+ ```javascript
174
+ let record = await Table.get(id);
175
+ record = { ...record, property: 'changed' };
179
176
  ```
180
177
 
181
178
  ## Notes
182
179
 
183
- - Always import Harper APIs from `'harper'`, not from global variables or `'harperdb'`.
184
- - `getContext` is exported from `'harper'` and provides access to the current transaction without passing context explicitly.
185
- - Record objects returned by `Table.get` are frozen — spread into a new object before modifying.
186
- - `loadedFromSource` is a property on the `target` object, replacing the removed `wasLoadedFromSource()` instance method.
187
- - `saveBeforeCommit` replaces the removed `blob.save()` method.
188
- - The `headers` property on a returned REST response object is used as response headers.
189
- - Under `lockdown: ses`, the constrained `fetch` applies only in `vm` mode. In `vm-current-context` and `native` modes, application code uses the standard global `fetch`.
190
- - In production, `allowedDirectory: app` is the default; modules outside the application directory tree will throw. Set `allowedDirectory: any` only if legitimately required.
191
- - `dependencyLoader: native` is a narrower option than `moduleLoader: native` — it uses native loading only for npm packages while keeping the VM loader for first-party application source files.
180
+ - The `logger` exported from `'harper'` is tagged with the application name when using the VM module loader. It is not available in `native` mode.
181
+ - `instanceof` checks for values crossing the application/Harper boundary will fail when using `moduleLoader: vm` (separate intrinsics). Use `vm-current-context` (the default) to avoid this.
182
+ - `import()` behavior depends on the `moduleLoader` setting. In `native` mode, standard Node.js `import()` is used with no application context.
183
+ - Automatic context tracking simplifies code but requires explicit `commit()` or new `transaction()` calls when you need to observe data written within the same transaction.
184
+ - Use `getContext` from `'harper'` rather than passing context manually through every call — this is the recommended pattern in v5.