@harperfast/template-vue-ssr-studio 1.12.12 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/harper-best-practices/AGENTS.md +184 -151
- package/.agents/skills/harper-best-practices/rules/custom-resources.md +63 -29
- package/.agents/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +56 -71
- package/.agents/skills/harper-best-practices/rules/programmatic-table-requests.md +71 -57
- package/README.md +0 -48
- package/agent/skills/harper-best-practices/AGENTS.md +184 -151
- package/agent/skills/harper-best-practices/rules/custom-resources.md +63 -29
- package/agent/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +56 -71
- package/agent/skills/harper-best-practices/rules/programmatic-table-requests.md +71 -57
- package/package.json +1 -1
- package/skills-lock.json +1 -1
|
@@ -1605,7 +1605,7 @@ Instructions for the agent to follow when defining custom REST endpoints with Ja
|
|
|
1605
1605
|
|
|
1606
1606
|
#### When to Use
|
|
1607
1607
|
|
|
1608
|
-
Apply this rule when
|
|
1608
|
+
Apply this rule when you need to create custom HTTP endpoints, wrap external APIs, or register routes programmatically in a Harper application. Use it any time business logic must live outside a table-backed schema, or when you need fine-grained control over URL shape and path parameters.
|
|
1609
1609
|
|
|
1610
1610
|
#### How It Works
|
|
1611
1611
|
|
|
@@ -1615,7 +1615,13 @@ Apply this rule when creating custom HTTP endpoints, wrapping external APIs, or
|
|
|
1615
1615
|
import { tables, Resource } from 'harper';
|
|
1616
1616
|
```
|
|
1617
1617
|
|
|
1618
|
-
|
|
1618
|
+
CommonJS alternative:
|
|
1619
|
+
|
|
1620
|
+
```javascript
|
|
1621
|
+
const { tables, Resource } = require('harper');
|
|
1622
|
+
```
|
|
1623
|
+
|
|
1624
|
+
2. **Define a class that `extends Resource`**: Use `export class` so Harper exposes it as an endpoint. Implement HTTP methods as `static` methods on the class.
|
|
1619
1625
|
|
|
1620
1626
|
```javascript
|
|
1621
1627
|
export class CustomEndpoint extends Resource {
|
|
@@ -1627,7 +1633,7 @@ Apply this rule when creating custom HTTP endpoints, wrapping external APIs, or
|
|
|
1627
1633
|
}
|
|
1628
1634
|
```
|
|
1629
1635
|
|
|
1630
|
-
3. **Use `
|
|
1636
|
+
3. **Use `static` async methods to call external services**: Each HTTP verb maps to a `static` method of the same lowercase name.
|
|
1631
1637
|
|
|
1632
1638
|
```javascript
|
|
1633
1639
|
export class MyExternalData extends Resource {
|
|
@@ -1645,24 +1651,20 @@ Apply this rule when creating custom HTTP endpoints, wrapping external APIs, or
|
|
|
1645
1651
|
}
|
|
1646
1652
|
```
|
|
1647
1653
|
|
|
1648
|
-
4. **
|
|
1654
|
+
4. **Control the URL by choosing the export form**: The shape of the export determines the resulting URL path.
|
|
1649
1655
|
|
|
1650
1656
|
| Export form | URL | Notes |
|
|
1651
1657
|
| ------------------------------------------- | --------------- | --------------------------------------------------------------- |
|
|
1652
|
-
| `export class Foo extends Resource {}` | `/Foo/` | Class name becomes the path segment
|
|
1658
|
+
| `export class Foo extends Resource {}` | `/Foo/` | Class name becomes the path segment; case-sensitive. |
|
|
1653
1659
|
| `export const Bar = { Foo };` | `/Bar/Foo/` | Nest under an object to add a path prefix. |
|
|
1654
1660
|
| `export const bar = { 'foo-baz': Foo };` | `/bar/foo-baz/` | Use object keys for lowercase, hyphens, or non-identifier URLs. |
|
|
1655
1661
|
| `export { Foo as '/widget/:id' }` | `/widget/:id` | Rename the export to set the path directly. |
|
|
1656
1662
|
| `static path = '/widget/:id'` (class field) | `/widget/:id` | Declare path on the class; overrides the export name. |
|
|
1657
|
-
| `server.resources.set('my-path', Foo);` | `/my-path/` | Programmatic registration
|
|
1663
|
+
| `server.resources.set('my-path', Foo);` | `/my-path/` | Programmatic registration; useful when the path is dynamic. |
|
|
1658
1664
|
|
|
1659
|
-
|
|
1665
|
+
URL path matching is case-sensitive — `/Foo/` and `/foo/` are different endpoints.
|
|
1660
1666
|
|
|
1661
|
-
|
|
1662
|
-
server.resources.set('my-path', Foo);
|
|
1663
|
-
```
|
|
1664
|
-
|
|
1665
|
-
6. **Declare dynamic path segments with `static path`**: Use `:name` for a single segment and `*name` as a catch-all. Matched values are bound onto `target.<name>`.
|
|
1667
|
+
5. **Declare dynamic path segments with `static path`**: Use `:name` for a single segment and `*name` as a catch-all. Matched values are bound onto `target.<name>`.
|
|
1666
1668
|
|
|
1667
1669
|
```javascript
|
|
1668
1670
|
export class Widget extends Resource {
|
|
@@ -1672,13 +1674,36 @@ Apply this rule when creating custom HTTP endpoints, wrapping external APIs, or
|
|
|
1672
1674
|
return { id: target.id, action: target.action };
|
|
1673
1675
|
}
|
|
1674
1676
|
}
|
|
1677
|
+
|
|
1678
|
+
export class Files extends Resource {
|
|
1679
|
+
// GET /files/a/b/c.txt -> target.rest === 'a/b/c.txt'
|
|
1680
|
+
static path = '/files/*rest';
|
|
1681
|
+
static get(target) {
|
|
1682
|
+
return { path: target.rest };
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
```
|
|
1686
|
+
|
|
1687
|
+
- A leading `/` in `static path` makes the path root-relative (top-level), independent of the file's location.
|
|
1688
|
+
- A leading `./` or bare name resolves relative to the component directory.
|
|
1689
|
+
- A bare `*` (no name) binds under `target.wildcard`. A wildcard must be the final segment.
|
|
1690
|
+
- `static path` takes precedence over the export name.
|
|
1691
|
+
- Exact and static paths always win over parameterized ones. Among parameterized routes, more specific paths win: a literal segment beats `:param`, which beats `*`, compared left to right.
|
|
1692
|
+
|
|
1693
|
+
6. **Register programmatically when the path is dynamic**: Use `server.resources.set(` with a path string and the resource class.
|
|
1694
|
+
|
|
1695
|
+
```javascript
|
|
1696
|
+
server.resources.set('my-path', Foo);
|
|
1675
1697
|
```
|
|
1676
1698
|
|
|
1677
|
-
7. **
|
|
1699
|
+
7. **Optionally use a resource as a cache source for a local table**: Call `sourcedFrom` on the target table.
|
|
1700
|
+
```javascript
|
|
1701
|
+
tables.MyCache.sourcedFrom(MyExternalData);
|
|
1702
|
+
```
|
|
1678
1703
|
|
|
1679
1704
|
#### Examples
|
|
1680
1705
|
|
|
1681
|
-
**
|
|
1706
|
+
**Custom endpoint with external API wrapping:**
|
|
1682
1707
|
|
|
1683
1708
|
```javascript
|
|
1684
1709
|
import { tables, Resource } from 'harper';
|
|
@@ -1701,9 +1726,19 @@ export class MyExternalData extends Resource {
|
|
|
1701
1726
|
tables.MyCache.sourcedFrom(MyExternalData);
|
|
1702
1727
|
```
|
|
1703
1728
|
|
|
1704
|
-
**
|
|
1729
|
+
**Path parameters with `static path`:**
|
|
1705
1730
|
|
|
1706
1731
|
```javascript
|
|
1732
|
+
import { Resource } from 'harper';
|
|
1733
|
+
|
|
1734
|
+
export class Widget extends Resource {
|
|
1735
|
+
// GET /widget/10/action/jump -> target.id === '10', target.action === 'jump'
|
|
1736
|
+
static path = '/widget/:id/action/:action';
|
|
1737
|
+
static get(target) {
|
|
1738
|
+
return { id: target.id, action: target.action };
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1707
1742
|
export class Files extends Resource {
|
|
1708
1743
|
// GET /files/a/b/c.txt -> target.rest === 'a/b/c.txt'
|
|
1709
1744
|
static path = '/files/*rest';
|
|
@@ -1713,27 +1748,26 @@ export class Files extends Resource {
|
|
|
1713
1748
|
}
|
|
1714
1749
|
```
|
|
1715
1750
|
|
|
1716
|
-
**
|
|
1751
|
+
**Programmatic registration:**
|
|
1717
1752
|
|
|
1718
1753
|
```javascript
|
|
1719
|
-
|
|
1720
|
-
|
|
1754
|
+
import { Resource } from 'harper';
|
|
1755
|
+
|
|
1756
|
+
export class Foo extends Resource {
|
|
1721
1757
|
static get(target) {
|
|
1722
|
-
return {
|
|
1758
|
+
return { ok: true };
|
|
1723
1759
|
}
|
|
1724
1760
|
}
|
|
1761
|
+
|
|
1762
|
+
server.resources.set('my-path', Foo);
|
|
1725
1763
|
```
|
|
1726
1764
|
|
|
1727
1765
|
#### Notes
|
|
1728
1766
|
|
|
1729
|
-
-
|
|
1730
|
-
-
|
|
1731
|
-
-
|
|
1732
|
-
-
|
|
1733
|
-
- `static path` takes precedence over the export name when both are present.
|
|
1734
|
-
- Parameterized routes appear in the generated OpenAPI document as templated paths (e.g. `/widget/{id}/action/{action}`) and in MCP `resources/templates/list` as URI templates.
|
|
1735
|
-
- When a resource `extends` an existing table, avoid conflicting exports between the schema and the JavaScript implementation.
|
|
1736
|
-
- Link the `harper` package in your component directory to ensure correct typings: `npm link harper`. All installed components have `harper` automatically linked.
|
|
1767
|
+
- Avoid conflicting exports between the schema and the JavaScript implementation when a resource `extends` an existing table.
|
|
1768
|
+
- Parameterized routes appear in the generated OpenAPI document as templated paths (e.g. `/widget/{id}/action/{action}`) and in MCP `resources/templates/list` as `{param}` URI templates.
|
|
1769
|
+
- When bundling for SSR with a tool like Vite, keep `harper` external so it resolves to the runtime: `ssr: { external: ['harper'] }`.
|
|
1770
|
+
- For components in their own directory, run `npm link harper` to ensure typings match the running installation.
|
|
1737
1771
|
|
|
1738
1772
|
### 3.2 Extending Tables
|
|
1739
1773
|
|
|
@@ -1833,25 +1867,31 @@ export class MyTable extends tables.MyTable {
|
|
|
1833
1867
|
|
|
1834
1868
|
### 3.3 Programmatic Table Requests
|
|
1835
1869
|
|
|
1836
|
-
Instructions for the agent to
|
|
1870
|
+
Instructions for the agent to follow when interacting with Harper tables programmatically using the `tables` object.
|
|
1837
1871
|
|
|
1838
1872
|
#### When to Use
|
|
1839
1873
|
|
|
1840
|
-
Apply this rule when writing server-side Harper component code that reads from or writes to tables
|
|
1874
|
+
Apply this rule when writing server-side Harper component code that reads from or writes to tables using the programmatic API — for example, in HTTP handlers, background jobs, timers, or SSR entry points. Use it whenever you need to construct queries with `conditions`, `sort`, `select`, pagination, or transactions outside of the REST layer.
|
|
1841
1875
|
|
|
1842
1876
|
#### How It Works
|
|
1843
1877
|
|
|
1844
|
-
1. **Import `tables` (and other APIs) from `harper`**: Access every table defined in `schema.graphql` as a
|
|
1878
|
+
1. **Import `tables` (and other APIs) from `harper`**: Access every table defined in `schema.graphql` as a property of `tables`. Each property is the table class implementing the Resource API.
|
|
1845
1879
|
|
|
1846
1880
|
```javascript
|
|
1847
1881
|
import { tables, transaction } from 'harper';
|
|
1848
1882
|
const { Product } = tables;
|
|
1849
|
-
//
|
|
1883
|
+
// same as: databases.data.Product
|
|
1850
1884
|
```
|
|
1851
1885
|
|
|
1852
|
-
For
|
|
1886
|
+
For components in their own directory, run:
|
|
1853
1887
|
|
|
1854
|
-
|
|
1888
|
+
```bash
|
|
1889
|
+
npm link harper
|
|
1890
|
+
```
|
|
1891
|
+
|
|
1892
|
+
All installed components have `harper` automatically linked.
|
|
1893
|
+
|
|
1894
|
+
2. **Define your schema with `@table`**: Every type you want to access via `tables` must carry the `@table` directive. Mark queryable fields with `@indexed`.
|
|
1855
1895
|
|
|
1856
1896
|
```graphql
|
|
1857
1897
|
type Product @table {
|
|
@@ -1861,7 +1901,7 @@ Apply this rule when writing server-side Harper component code that reads from o
|
|
|
1861
1901
|
}
|
|
1862
1902
|
```
|
|
1863
1903
|
|
|
1864
|
-
3. **Query records
|
|
1904
|
+
3. **Query records using `search(`**: Pass a Query object to `search()`. The query is an async iterable.
|
|
1865
1905
|
|
|
1866
1906
|
```javascript
|
|
1867
1907
|
const query = {
|
|
@@ -1872,7 +1912,7 @@ Apply this rule when writing server-side Harper component code that reads from o
|
|
|
1872
1912
|
}
|
|
1873
1913
|
```
|
|
1874
1914
|
|
|
1875
|
-
4. **Build `conditions`**: Each condition object supports
|
|
1915
|
+
4. **Build `conditions`**: Each condition object supports the following properties:
|
|
1876
1916
|
|
|
1877
1917
|
| Property | Description |
|
|
1878
1918
|
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -1882,53 +1922,60 @@ Apply this rule when writing server-side Harper component code that reads from o
|
|
|
1882
1922
|
| `conditions` | Nested conditions array |
|
|
1883
1923
|
| `operator` | `and` (default) or `or` for the nested `conditions` |
|
|
1884
1924
|
|
|
1885
|
-
5. **
|
|
1925
|
+
5. **Apply `select` to shape results**: Pass an array of property names, a string for a single property, or nested objects for relationships.
|
|
1886
1926
|
|
|
1887
1927
|
```javascript
|
|
1888
|
-
//
|
|
1928
|
+
// Flat select
|
|
1889
1929
|
Product.search({ select: ['name', 'price'] });
|
|
1890
1930
|
|
|
1891
|
-
//
|
|
1931
|
+
// Nested relationship select
|
|
1892
1932
|
Book.get({ id: 42, select: ['id', 'title', { name: 'author', select: ['name'] }] });
|
|
1893
1933
|
```
|
|
1894
1934
|
|
|
1895
1935
|
Special `select` values: `$id`, `$updatedtime`, `$distance`.
|
|
1896
1936
|
|
|
1897
|
-
6. **
|
|
1937
|
+
6. **Apply `sort`**: Harper uses an index to provide sort order.
|
|
1898
1938
|
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1939
|
+
| Property | Description |
|
|
1940
|
+
| ------------ | ---------------------------------------------------------- |
|
|
1941
|
+
| `attribute` | Property name (or array for chained relationship property) |
|
|
1942
|
+
| `descending` | Sort descending if `true` (default: `false`) |
|
|
1943
|
+
| `next` | Secondary sort to resolve ties (same structure) |
|
|
1944
|
+
- If the sort `attribute` is `@indexed`, no condition is required.
|
|
1945
|
+
- If the sort `attribute` is not indexed, at least one entry in `conditions` (on **any** attribute) is required.
|
|
1946
|
+
- Sorting by a non-indexed attribute with zero conditions raises:
|
|
1947
|
+
> `HdbError: <attribute> is not indexed and not combined with any other conditions`
|
|
1948
|
+
- The bare `@primaryKey` is treated as not indexed for sort purposes. To iterate in primary-key order, add an open-ended condition:
|
|
1902
1949
|
|
|
1903
|
-
|
|
1950
|
+
```javascript
|
|
1951
|
+
Product.search({
|
|
1952
|
+
conditions: [{ attribute: 'id', comparator: 'greater_than', value: '' }],
|
|
1953
|
+
sort: { attribute: 'id' },
|
|
1954
|
+
});
|
|
1955
|
+
```
|
|
1904
1956
|
|
|
1905
|
-
|
|
1957
|
+
- Pass `allowFullScan: true` to permit an unconditional ordered scan, or omit `sort` entirely to iterate without an index requirement.
|
|
1906
1958
|
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
Note: `@primaryKey` alone is treated as not indexed for sort purposes. To scan the whole table in primary-key order, add an open-ended condition:
|
|
1959
|
+
7. **Paginate with `limit` and `offset`**:
|
|
1910
1960
|
|
|
1911
1961
|
```javascript
|
|
1912
|
-
Product.search({
|
|
1913
|
-
conditions: [{ attribute: 'id', comparator: 'greater_than', value: '' }],
|
|
1914
|
-
sort: { attribute: 'id' },
|
|
1915
|
-
});
|
|
1962
|
+
Product.search({ conditions: [...], limit: 20, offset: 40 });
|
|
1916
1963
|
```
|
|
1917
1964
|
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
8. **Debug query planning with `explain` and `enforceExecutionOrder`**:
|
|
1965
|
+
8. **Debug with `explain` and `enforceExecutionOrder`**:
|
|
1921
1966
|
- `explain: true` — returns conditions reordered as Harper will execute them.
|
|
1922
|
-
- `enforceExecutionOrder: true` — forces conditions to execute in the order supplied, disabling automatic re-ordering.
|
|
1967
|
+
- `enforceExecutionOrder: true` — forces conditions to execute in the order supplied, disabling Harper's automatic re-ordering.
|
|
1923
1968
|
|
|
1924
|
-
9. **Use `addTo` for concurrent-safe
|
|
1969
|
+
9. **Use `addTo` for concurrent-safe increments**: `addTo(property, value)` uses CRDT incrementation, safe across threads and nodes.
|
|
1925
1970
|
|
|
1926
1971
|
```javascript
|
|
1927
|
-
|
|
1928
|
-
|
|
1972
|
+
static async post(target, data) {
|
|
1973
|
+
const record = await this.update(target.id);
|
|
1974
|
+
record.addTo('quantity', -1);
|
|
1975
|
+
}
|
|
1929
1976
|
```
|
|
1930
1977
|
|
|
1931
|
-
10. **Wrap background work in `transaction()`**:
|
|
1978
|
+
10. **Wrap background work in `transaction()`**: Harper auto-starts transactions for HTTP handlers. Use `transaction()` explicitly for timers, background jobs, or any code outside a natural transaction context.
|
|
1932
1979
|
|
|
1933
1980
|
```javascript
|
|
1934
1981
|
await transaction(async (txn) => {
|
|
@@ -1940,18 +1987,18 @@ Apply this rule when writing server-side Harper component code that reads from o
|
|
|
1940
1987
|
|
|
1941
1988
|
The `txn` object exposes:
|
|
1942
1989
|
|
|
1943
|
-
| Member | Description |
|
|
1944
|
-
| --------------------- | ------------------------------------------------------ |
|
|
1945
|
-
| `commit()` | Commits the current transaction |
|
|
1946
|
-
| `abort()` | Aborts and resets
|
|
1947
|
-
| `resetReadSnapshot()` | Resets the read snapshot to the latest committed state |
|
|
1948
|
-
| `timestamp` | Timestamp associated with the current transaction |
|
|
1990
|
+
| Member | Type | Description |
|
|
1991
|
+
| --------------------- | --------------- | ------------------------------------------------------ |
|
|
1992
|
+
| `commit()` | `() => Promise` | Commits the current transaction |
|
|
1993
|
+
| `abort()` | `() => void` | Aborts the transaction and resets it |
|
|
1994
|
+
| `resetReadSnapshot()` | `() => void` | Resets the read snapshot to the latest committed state |
|
|
1995
|
+
| `timestamp` | `number` | Timestamp associated with the current transaction |
|
|
1949
1996
|
|
|
1950
|
-
|
|
1997
|
+
On normal callback completion the transaction commits automatically. If the callback throws, the transaction is aborted.
|
|
1951
1998
|
|
|
1952
|
-
|
|
1999
|
+
11. **Understand atomicity**: Transactions span a single database. All tables within the same database share one transactional context — reads return a consistent snapshot and writes across multiple tables commit atomically. Cross-database operations get separate transactions with no cross-database atomicity guarantee.
|
|
1953
2000
|
|
|
1954
|
-
|
|
2001
|
+
12. **Keep `harper` external when bundling for SSR**: In `vite.config`, mark `harper` as external so it resolves to the runtime rather than being bundled.
|
|
1955
2002
|
|
|
1956
2003
|
```javascript
|
|
1957
2004
|
// vite.config
|
|
@@ -1962,7 +2009,7 @@ Apply this rule when writing server-side Harper component code that reads from o
|
|
|
1962
2009
|
|
|
1963
2010
|
#### Examples
|
|
1964
2011
|
|
|
1965
|
-
|
|
2012
|
+
**Full CRUD flow:**
|
|
1966
2013
|
|
|
1967
2014
|
```javascript
|
|
1968
2015
|
import { tables } from 'harper';
|
|
@@ -1974,19 +2021,18 @@ const created = await Product.create({ name: 'Shirt', price: 9.5 });
|
|
|
1974
2021
|
// Patch
|
|
1975
2022
|
await Product.patch(created.id, { price: Math.round(created.price * 0.8 * 100) / 100 });
|
|
1976
2023
|
|
|
1977
|
-
//
|
|
2024
|
+
// Get by primary key
|
|
1978
2025
|
const record = await Product.get(created.id);
|
|
1979
2026
|
|
|
1980
|
-
//
|
|
1981
|
-
const
|
|
2027
|
+
// Search with conditions
|
|
2028
|
+
for await (const record of Product.search({
|
|
1982
2029
|
conditions: [{ attribute: 'price', comparator: 'less_than', value: 8.0 }],
|
|
1983
|
-
}
|
|
1984
|
-
for await (const record of Product.search(query)) {
|
|
2030
|
+
})) {
|
|
1985
2031
|
// process record
|
|
1986
2032
|
}
|
|
1987
2033
|
```
|
|
1988
2034
|
|
|
1989
|
-
|
|
2035
|
+
**Nested conditions with `or`:**
|
|
1990
2036
|
|
|
1991
2037
|
```javascript
|
|
1992
2038
|
Product.search({
|
|
@@ -2003,13 +2049,13 @@ Product.search({
|
|
|
2003
2049
|
});
|
|
2004
2050
|
```
|
|
2005
2051
|
|
|
2006
|
-
|
|
2052
|
+
**Chained attribute reference (join):**
|
|
2007
2053
|
|
|
2008
2054
|
```javascript
|
|
2009
2055
|
Product.search({ conditions: [{ attribute: ['brand', 'name'], value: 'Harper' }] });
|
|
2010
2056
|
```
|
|
2011
2057
|
|
|
2012
|
-
|
|
2058
|
+
**Background job with `transaction()`:**
|
|
2013
2059
|
|
|
2014
2060
|
```javascript
|
|
2015
2061
|
import { isMainThread } from 'node:worker_threads';
|
|
@@ -2037,7 +2083,7 @@ if (isMainThread) {
|
|
|
2037
2083
|
}
|
|
2038
2084
|
```
|
|
2039
2085
|
|
|
2040
|
-
|
|
2086
|
+
**SSR with Harper data:**
|
|
2041
2087
|
|
|
2042
2088
|
```typescript
|
|
2043
2089
|
import { tables } from 'harper';
|
|
@@ -2050,10 +2096,12 @@ export async function render(url: string): Promise<string> {
|
|
|
2050
2096
|
|
|
2051
2097
|
#### Notes
|
|
2052
2098
|
|
|
2053
|
-
- `tables` and `databases`
|
|
2054
|
-
-
|
|
2055
|
-
- `
|
|
2056
|
-
-
|
|
2099
|
+
- `tables` and `databases` calls run in a trusted server-side context and do **not** automatically apply the target table's role permissions.
|
|
2100
|
+
- Programmatic `update`, `patch`, and `delete` calls operate directly on stored data. Scope destructive operations with specific `conditions`, validate the affected set before writing, and gate them behind authorization controls.
|
|
2101
|
+
- If `transaction()` is called with a context that already has an active transaction, it reuses that transaction — making it safe to call defensively.
|
|
2102
|
+
- Always `await` the `transaction()` call and `catch` errors when outside a request context; an unawaited call means a failed write is never observed.
|
|
2103
|
+
- Guard against timer overlap: if a job can outlast its interval, use a `running` flag to skip a tick rather than opening two transactions over the same rows.
|
|
2104
|
+
- CommonJS is also supported: `const { tables, Resource } = require('harper');`
|
|
2057
2105
|
|
|
2058
2106
|
### 3.4 TypeScript Type Stripping in Harper
|
|
2059
2107
|
|
|
@@ -2276,18 +2324,20 @@ Instructions for the agent to follow when deploying a Harper application to a re
|
|
|
2276
2324
|
|
|
2277
2325
|
#### When to Use
|
|
2278
2326
|
|
|
2279
|
-
Apply this rule when deploying a Harper application to a remote Harper
|
|
2327
|
+
Apply this rule when deploying a Harper application to a remote Harper Fabric cluster, whether from a local machine or a CI/CD pipeline. Use it to configure authentication, select a package source, and run `harper deploy` with the correct parameters.
|
|
2328
|
+
|
|
2329
|
+
See [creating-a-fabric-account-and-cluster.md](creating-a-fabric-account-and-cluster.md) to set up a cluster before deploying.
|
|
2280
2330
|
|
|
2281
2331
|
#### How It Works
|
|
2282
2332
|
|
|
2283
|
-
1. **Authenticate against the
|
|
2333
|
+
1. **Authenticate against the cluster**: Run `harper login` once, pointing at the cluster's Application URL (found on the cluster's **Config → Overview** page). The CLI stores the token so subsequent commands need no credentials. If the current directory already has a `.env` file that sets no target, it also appends `HARPER_CLI_TARGET` to it.
|
|
2284
2334
|
|
|
2285
2335
|
```bash
|
|
2286
2336
|
harper login <Application URL>
|
|
2287
2337
|
# Provide cluster username and password when prompted
|
|
2288
2338
|
```
|
|
2289
2339
|
|
|
2290
|
-
2. **Deploy the application**: After
|
|
2340
|
+
2. **Deploy the application**: After logging in, run `harper deploy` without repeating credentials. Omit `package` to package and deploy the current local directory.
|
|
2291
2341
|
|
|
2292
2342
|
```bash
|
|
2293
2343
|
harper deploy \
|
|
@@ -2298,16 +2348,16 @@ Apply this rule when deploying a Harper application to a remote Harper instance
|
|
|
2298
2348
|
replicated=true
|
|
2299
2349
|
```
|
|
2300
2350
|
|
|
2301
|
-
3. **Choose a package source**: Set
|
|
2351
|
+
3. **Choose a package source**: Set `package` to any valid npm dependency value.
|
|
2302
2352
|
|
|
2303
|
-
|
|
|
2304
|
-
|
|
|
2305
|
-
|
|
|
2306
|
-
|
|
|
2307
|
-
|
|
|
2308
|
-
|
|
|
2309
|
-
|
|
|
2310
|
-
|
|
|
2353
|
+
| Value | Meaning |
|
|
2354
|
+
| ------------------------------------------------------------ | ---------------------------------------------- |
|
|
2355
|
+
| _(omit)_ | Package and deploy the current local directory |
|
|
2356
|
+
| `package="@harperdb/status-check"` | npm package |
|
|
2357
|
+
| `package="HarperFast/status-check"` | GitHub shorthand |
|
|
2358
|
+
| `package="https://github.com/HarperFast/status-check"` | GitHub URL |
|
|
2359
|
+
| `package="git+ssh://git@github.com:HarperDB/secret-app.git"` | Private repo (SSH) |
|
|
2360
|
+
| `package="https://example.com/application.tar.gz"` | Tarball |
|
|
2311
2361
|
|
|
2312
2362
|
When using git tags, use the `semver` directive:
|
|
2313
2363
|
|
|
@@ -2315,68 +2365,55 @@ Apply this rule when deploying a Harper application to a remote Harper instance
|
|
|
2315
2365
|
HarperFast/application-template#semver:v1.0.0
|
|
2316
2366
|
```
|
|
2317
2367
|
|
|
2318
|
-
4. **Deploy by reference (pinned commit)
|
|
2368
|
+
4. **Deploy by reference** (pinned commit): Use `by_ref=true` to send a pinned git reference instead of uploading a snapshot. The cluster fetches and builds that exact commit.
|
|
2319
2369
|
|
|
2320
2370
|
```bash
|
|
2321
2371
|
harper deploy by_ref=true restart=true replicated=true
|
|
2322
2372
|
```
|
|
2323
2373
|
|
|
2324
2374
|
- `by_ref` — Build the package reference from the local repository.
|
|
2325
|
-
- `ref` _(optional)_ — Deploy a specific commit, tag, or branch instead of `HEAD`. Implies `by_ref`.
|
|
2375
|
+
- `ref` _(optional)_ — Deploy a specific commit, tag, or branch instead of `HEAD`. Implies `by_ref`. Resolved to a full commit SHA before being sent.
|
|
2326
2376
|
- `credential` _(optional)_ — Set to `true` to authenticate the clone with the stored credential for the repository's host. Omit for public repositories.
|
|
2327
2377
|
|
|
2378
|
+
Deploy a specific tag or commit:
|
|
2379
|
+
|
|
2328
2380
|
```bash
|
|
2329
|
-
# Deploy a specific tag
|
|
2330
2381
|
harper deploy ref=v1.2.0 restart=true replicated=true
|
|
2331
|
-
|
|
2332
|
-
# Roll back by deploying an older commit
|
|
2333
2382
|
harper deploy ref=9f8c2a1 restart=true replicated=true
|
|
2334
2383
|
```
|
|
2335
2384
|
|
|
2336
|
-
|
|
2337
|
-
- A full commit SHA is accepted directly with no resolution.
|
|
2338
|
-
- Every other `ref` must resolve to something a clone can fetch: `refs/tags/*` or `refs/heads/*`, or a bare branch or tag name.
|
|
2339
|
-
- Qualified refs outside those two namespaces (e.g., `refs/pull/123/head`) are rejected.
|
|
2340
|
-
- Commit and push before deploying — the cluster clones from the remote and only sees pushed commits.
|
|
2341
|
-
- Run `git fetch` if a ref can't be resolved, or pass a full commit SHA.
|
|
2385
|
+
Valid `ref` values must resolve to `refs/heads/*` or `refs/tags/*`, or a bare branch or tag name. Qualified refs outside those two namespaces (e.g. `refs/pull/123/head`) are rejected. A full commit SHA is always accepted directly with no resolution attempted. Run `git fetch` if a ref can't be resolved, or pass a full commit SHA.
|
|
2342
2386
|
|
|
2343
|
-
|
|
2387
|
+
**Commit and push before deploying by reference.** The cluster clones from the remote and only sees pushed commits.
|
|
2388
|
+
|
|
2389
|
+
5. **Roll back to a previous release**: Activate a kept release by its `deployment_id` to restore the exact installed release without a fetch, rebuild, or reinstall.
|
|
2344
2390
|
|
|
2345
2391
|
```bash
|
|
2346
|
-
harper deploy
|
|
2392
|
+
harper deploy project=<name> deployment_id=<id> restart=true
|
|
2347
2393
|
```
|
|
2348
2394
|
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
6. **Provision a deploy credential**: Run `harper deploy setup=true` once per component and source to provision credentials for private deploys. This operation requires **super_user** — run it with an administrative credential, not the CI identity.
|
|
2395
|
+
6. **Provision a deploy credential for private repositories**: Run `harper deploy setup=true` once per component and source. This requires **super_user** privileges — run it with an administrative credential.
|
|
2352
2396
|
|
|
2353
2397
|
```bash
|
|
2354
2398
|
harper deploy setup=true
|
|
2355
2399
|
```
|
|
2356
2400
|
|
|
2357
|
-
|
|
2401
|
+
The setup flow:
|
|
2358
2402
|
1. Fetches the cluster's public key with `get_secrets_public_key`.
|
|
2359
2403
|
2. Encrypts the token locally into an `enc:v1:` envelope.
|
|
2360
|
-
3. Stores only the ciphertext with `set_secret`,
|
|
2404
|
+
3. Stores only the ciphertext with `set_secret`, scoped to the component.
|
|
2361
2405
|
4. Grants the component permission to resolve it with `grant_secret`.
|
|
2362
2406
|
5. Prints the `credentials` reference for the deploy to use.
|
|
2363
2407
|
|
|
2364
|
-
|
|
2408
|
+
The plaintext never leaves your machine. Prefer a **fine-grained** personal access token with **Contents: Read-only** on the target repository. Avoid broad session tokens — they typically carry `read:org`, `gist`, and `workflow` scopes across your whole account.
|
|
2365
2409
|
|
|
2366
|
-
7. **
|
|
2410
|
+
7. **Deploy from a private repository**: Pass `credential=true` after provisioning the deploy credential.
|
|
2367
2411
|
|
|
2368
2412
|
```bash
|
|
2369
|
-
|
|
2370
|
-
export HARPER_CLI_PASSWORD=<password>
|
|
2371
|
-
harper deploy \
|
|
2372
|
-
project=<name> \
|
|
2373
|
-
package=<package> \
|
|
2374
|
-
target=<remote> \
|
|
2375
|
-
restart=true \
|
|
2376
|
-
replicated=true
|
|
2413
|
+
harper deploy by_ref=true credential=true restart=true replicated=true
|
|
2377
2414
|
```
|
|
2378
2415
|
|
|
2379
|
-
8. **Use
|
|
2416
|
+
8. **Use one-off authentication parameters** (not recommended for production): Pass `auth_username` and `auth_password` directly. These take precedence over environment variables and saved login tokens.
|
|
2380
2417
|
|
|
2381
2418
|
```bash
|
|
2382
2419
|
harper deploy \
|
|
@@ -2389,65 +2426,61 @@ Apply this rule when deploying a Harper application to a remote Harper instance
|
|
|
2389
2426
|
replicated=true
|
|
2390
2427
|
```
|
|
2391
2428
|
|
|
2429
|
+
9. **Use CI/CD pipelines**: For GitHub Actions, use workload identity (OIDC) so no Harper credential is stored in the pipeline. Alternatively, supply a refresh token from `harper login --for-ci`. On a `pull_request` run, pass the head SHA explicitly:
|
|
2430
|
+
|
|
2431
|
+
```bash
|
|
2432
|
+
harper deploy ref=${{ github.event.pull_request.head.sha }} restart=true replicated=true
|
|
2433
|
+
```
|
|
2434
|
+
|
|
2392
2435
|
#### Examples
|
|
2393
2436
|
|
|
2394
|
-
**
|
|
2437
|
+
**Login and deploy the current directory to a remote cluster:**
|
|
2395
2438
|
|
|
2396
2439
|
```bash
|
|
2397
2440
|
harper login https://my-cluster.harperdbcloud.com
|
|
2398
2441
|
harper deploy \
|
|
2399
2442
|
project=my-app \
|
|
2400
|
-
package="HarperFast/my-app" \
|
|
2401
2443
|
target=https://my-cluster.harperdbcloud.com \
|
|
2402
2444
|
restart=true \
|
|
2403
2445
|
replicated=true
|
|
2404
2446
|
```
|
|
2405
2447
|
|
|
2406
|
-
**Deploy
|
|
2448
|
+
**Deploy a specific GitHub repository by tag:**
|
|
2407
2449
|
|
|
2408
2450
|
```bash
|
|
2409
|
-
harper deploy
|
|
2451
|
+
harper deploy \
|
|
2452
|
+
project=my-app \
|
|
2453
|
+
package="HarperFast/application-template#semver:v1.0.0" \
|
|
2454
|
+
target=https://my-cluster.harperdbcloud.com \
|
|
2455
|
+
restart=true \
|
|
2456
|
+
replicated=true
|
|
2410
2457
|
```
|
|
2411
2458
|
|
|
2412
|
-
**Deploy a private
|
|
2459
|
+
**Deploy by reference from a private repository after provisioning credentials:**
|
|
2413
2460
|
|
|
2414
2461
|
```bash
|
|
2415
|
-
# Provision
|
|
2462
|
+
# Provision once (requires super_user)
|
|
2416
2463
|
harper deploy setup=true
|
|
2417
2464
|
|
|
2418
|
-
# Deploy
|
|
2465
|
+
# Deploy by reference with credential
|
|
2419
2466
|
harper deploy by_ref=true credential=true restart=true replicated=true
|
|
2420
2467
|
```
|
|
2421
2468
|
|
|
2422
|
-
**
|
|
2469
|
+
**Roll back to a previous deployment:**
|
|
2423
2470
|
|
|
2424
2471
|
```bash
|
|
2425
|
-
harper deploy
|
|
2426
|
-
```
|
|
2427
|
-
|
|
2428
|
-
**CI/CD deploy using environment variables:**
|
|
2429
|
-
|
|
2430
|
-
```bash
|
|
2431
|
-
export HARPER_CLI_USERNAME=<username>
|
|
2432
|
-
export HARPER_CLI_PASSWORD=<password>
|
|
2433
|
-
harper deploy \
|
|
2434
|
-
project=my-app \
|
|
2435
|
-
package="@myorg/my-app" \
|
|
2436
|
-
target=https://my-cluster.harperdbcloud.com \
|
|
2437
|
-
restart=true \
|
|
2438
|
-
replicated=true
|
|
2472
|
+
harper deploy project=my-app deployment_id=<id> restart=true
|
|
2439
2473
|
```
|
|
2440
2474
|
|
|
2441
2475
|
#### Notes
|
|
2442
2476
|
|
|
2443
|
-
- The cluster's Application URL is found on the **Config → Overview** page
|
|
2444
|
-
- `harper deploy setup=true`
|
|
2445
|
-
- The `enc:v1:` envelope
|
|
2446
|
-
- Secrets are stored scoped to the component, not in the global `processEnv` tier. If a global secret already exists at the derived name, it is converted to the scoped tier.
|
|
2477
|
+
- The cluster's Application URL is found on the **Config → Overview** page.
|
|
2478
|
+
- `harper deploy setup=true` requires **super_user** — run it with an administrative credential, not the CI identity it provisions for.
|
|
2479
|
+
- Secrets are stored scoped to the component, never in the global `processEnv` tier. The `enc:v1:` envelope is the only form of the token that ever leaves your machine or travels over the operations API.
|
|
2447
2480
|
- For SSH-based private repos, use the `add_ssh_key` operation to register keys before deploying.
|
|
2448
|
-
-
|
|
2481
|
+
- A `ref` pointing to `refs/tags` is resolved to a full commit SHA before the deploy is sent. Tags that move mid-deploy could otherwise leave cluster nodes running different code.
|
|
2449
2482
|
- The unpushed-commit check is skipped under GitHub Actions; the dirty-tree warning still applies.
|
|
2450
|
-
-
|
|
2483
|
+
- Deploying by reference means the cluster installs and builds from source. If your application requires a build step that cannot run on the node, deploy a built payload instead.
|
|
2451
2484
|
|
|
2452
2485
|
### 4.2 Creating a Harper Fabric Account and Cluster
|
|
2453
2486
|
|