@harperfast/template-react-ts-studio 1.12.2 → 1.12.4

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 (30) hide show
  1. package/.agents/skills/harper-best-practices/AGENTS.md +778 -580
  2. package/.agents/skills/harper-best-practices/rules/adding-tables-with-schemas.md +7 -7
  3. package/.agents/skills/harper-best-practices/rules/automatic-apis.md +131 -55
  4. package/.agents/skills/harper-best-practices/rules/checking-authentication.md +265 -127
  5. package/.agents/skills/harper-best-practices/rules/custom-resources.md +28 -45
  6. package/.agents/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +63 -85
  7. package/.agents/skills/harper-best-practices/rules/programmatic-table-requests.md +125 -52
  8. package/.agents/skills/harper-best-practices/rules/querying-rest-apis.md +38 -70
  9. package/.agents/skills/harper-best-practices/rules/schema-design-tooling.md +81 -69
  10. package/.agents/skills/harper-best-practices/rules/v5-upgrade.md +82 -96
  11. package/.agents/skills/harper-best-practices/rules.manifest.yaml +273 -2
  12. package/.agents/skills/harper-mcp/AGENTS.md +77 -31
  13. package/.agents/skills/harper-mcp/rules/custom-mcp-tools.md +79 -33
  14. package/.agents/skills/harper-mcp/rules.manifest.yaml +1 -1
  15. package/agent/skills/harper-best-practices/AGENTS.md +778 -580
  16. package/agent/skills/harper-best-practices/rules/adding-tables-with-schemas.md +7 -7
  17. package/agent/skills/harper-best-practices/rules/automatic-apis.md +131 -55
  18. package/agent/skills/harper-best-practices/rules/checking-authentication.md +265 -127
  19. package/agent/skills/harper-best-practices/rules/custom-resources.md +28 -45
  20. package/agent/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +63 -85
  21. package/agent/skills/harper-best-practices/rules/programmatic-table-requests.md +125 -52
  22. package/agent/skills/harper-best-practices/rules/querying-rest-apis.md +38 -70
  23. package/agent/skills/harper-best-practices/rules/schema-design-tooling.md +81 -69
  24. package/agent/skills/harper-best-practices/rules/v5-upgrade.md +82 -96
  25. package/agent/skills/harper-best-practices/rules.manifest.yaml +273 -2
  26. package/agent/skills/harper-mcp/AGENTS.md +77 -31
  27. package/agent/skills/harper-mcp/rules/custom-mcp-tools.md +79 -33
  28. package/agent/skills/harper-mcp/rules.manifest.yaml +1 -1
  29. package/package.json +1 -1
  30. package/skills-lock.json +2 -2
@@ -7,146 +7,77 @@ metadata:
7
7
  - >-
8
8
  reference/v5/resources/resource-api.md#`getCurrentUser(): User |
9
9
  undefined`
10
+ - 'reference/v5/resources/resource-api.md#`getContext(): Context`'
10
11
  - reference/v5/resources/resource-api.md#Session and Login from a Resource
11
- - reference/v5/security/jwt-authentication.md
12
- sourceCommit: b7fbddadd42eb4487190b650a9abc4bcfeef5819
13
- inputHash: fdd9ec3b11011490
12
+ - reference/v5/security/jwt-authentication.md#Create Authentication Tokens
13
+ - reference/v5/security/jwt-authentication.md#Using the Operation Token
14
+ - reference/v5/security/jwt-authentication.md#Refreshing the Operation Token
15
+ - reference/v5/security/jwt-authentication.md#Scoped Tokens (Inline Role)
16
+ - >-
17
+ reference/v5/security/jwt-authentication.md#Issuing Tokens from a Custom
18
+ Resource
19
+ - reference/v5/security/jwt-authentication.md#Token Expiry Configuration
20
+ - reference/v5/security/jwt-authentication.md#When to Use JWT Auth
21
+ - reference/v5/security/jwt-authentication.md#Security Notes
22
+ sourceCommit: 9e6ecf87dd25bb488ad44dc2876ca6439ccd682e
23
+ inputHash: 1837f39fdfe06fac
14
24
  ---
15
25
 
16
26
  # Checking Authentication
17
27
 
18
- Instructions for the agent to follow when handling user authentication and session management inside Harper Resources.
28
+ Instructions for the agent to handle user authentication, sessions, and JWT token issuance in Harper Resources.
19
29
 
20
30
  ## When to Use
21
31
 
22
- Apply this rule when implementing authentication checks, login/logout flows, or token issuance inside a custom Resource. Use it any time a Resource needs to identify the current user, establish a session, or issue JWTs to clients. See [custom-resources.md](custom-resources.md) for the general Resource authoring pattern.
32
+ Apply this rule when implementing login/logout flows, reading the current authenticated user, issuing or refreshing JWT tokens, or minting scoped tokens from a custom Resource. Use it whenever a Resource must gate behavior on identity or credentials. See [custom-resources.md](custom-resources.md) for the broader Resource authoring model.
23
33
 
24
34
  ## How It Works
25
35
 
26
- 1. **Check the current user** with `getCurrentUser()`. Call it inside any Resource method to retrieve the authenticated user or `undefined` if no user is authenticated. Guard protected endpoints by returning a `401` when the result is `undefined`.
27
-
28
- ```javascript
29
- async get(target) {
30
- const user = this.getCurrentUser();
31
- if (!user) return new Response(null, { status: 401 });
32
- return { username: user.username, role: user.role };
33
- }
34
- ```
35
-
36
- The returned object exposes `username`, `role`, and `role.permission` flags.
37
-
38
- 2. **Enable sessions** before using session-based login. Set `authentication.enableSessions: true` in `harperdb-config.yaml`:
39
-
40
- ```yaml
41
- authentication:
42
- enableSessions: true
43
- ```
44
-
45
- 3. **Access login and session helpers** via `getContext()`. The context object exposes `context.login` and `context.session` for sign-in/out flows.
46
- - Call `context.login(username, password)` to verify credentials and establish a session cookie on success.
47
- - To end a session, delete it via `context.session.delete(context.session.id)`.
48
-
49
- 4. **Implement sign-in and sign-out Resources** using the context helpers:
50
-
51
- ```javascript
52
- export class SignIn extends Resource {
53
- async post(_target, data) {
54
- const context = this.getContext();
55
- try {
56
- await context.login(data.username, data.password);
57
- } catch {
58
- return new Response('Invalid credentials', { status: 403 });
59
- }
60
- return new Response('Logged in', { status: 200 });
61
- }
62
- }
63
-
64
- export class SignOut extends Resource {
65
- async post() {
66
- const context = this.getContext();
67
- if (!context.session) return new Response(null, { status: 401 });
68
- await context.session.delete(context.session.id);
69
- return new Response('Logged out', { status: 200 });
70
- }
71
- }
72
- ```
73
-
74
- 5. **Issue JWTs for non-browser clients** (CLI tools, mobile apps, service-to-service). Cookie-based sessions are intended for browser clients. For other clients, mint tokens programmatically using `server.operation()`:
75
-
76
- ```javascript
77
- import { Resource, server } from 'harper';
78
-
79
- export class IssueTokens extends Resource {
80
- static async get(_target, context) {
81
- const { operation_token, refresh_token } = await server.operation(
82
- { operation: 'create_authentication_tokens' },
83
- context,
84
- true,
85
- );
86
- return { operation_token, refresh_token };
87
- }
88
-
89
- static async post(_target, data) {
90
- const { username, password } = await data;
91
- if (!username || !password) {
92
- return new Response('username and password required', { status: 400 });
93
- }
94
- const { operation_token, refresh_token } = await server.operation({
95
- operation: 'create_authentication_tokens',
96
- username,
97
- password,
98
- });
99
- return { operation_token, refresh_token };
100
- }
101
- }
102
-
103
- export class RefreshJWT extends Resource {
104
- static async post(_target, data) {
105
- const { refresh_token } = await data;
106
- if (!refresh_token) {
107
- return new Response('refresh_token required', { status: 400 });
108
- }
109
- const { operation_token } = await server.operation({
110
- operation: 'refresh_operation_token',
111
- refresh_token,
112
- });
113
- return { operation_token };
114
- }
115
- }
116
- ```
117
-
118
- Pass `true` as the third argument to `server.operation()` when the operation should run as the current authenticated user. Omit it or pass `false` when the operation supplies its own credentials.
119
-
120
- 6. **Configure JWT token expiry** in `harperdb-config.yaml` under the `authentication` section:
121
-
122
- ```yaml
123
- authentication:
124
- operationTokenTimeout: 1d
125
- refreshTokenTimeout: 30d
126
- ```
127
-
128
- Duration strings follow the `jsonwebtoken` package format (e.g., `1d`, `12h`, `60m`).
36
+ ### 1. Reading the Current Authenticated User
129
37
 
130
- ## Examples
38
+ **In an instance method**, call `getCurrentUser()` to get the user associated with the current request, or `undefined` if unauthenticated. The returned object exposes `username`, `role`, and `role.permission` flags.
131
39
 
132
- **Protecting a resource endpoint and returning user info:**
40
+ **In a static verb**, read the user from the `context` argument instead — `context.user`:
133
41
 
134
42
  ```javascript
135
- async get(target) {
136
- const user = this.getCurrentUser();
137
- if (!user) return new Response(null, { status: 401 });
138
- return { username: user.username, role: user.role };
43
+ static async get(_target, context) {
44
+ const user = context?.user;
45
+ if (!user) return new Response(null, { status: 401 });
46
+ return { username: user.username, role: user.role };
139
47
  }
140
48
  ```
141
49
 
142
- **Full session-based sign-in/sign-out flow:**
50
+ ### 2. Reading the Full Context
51
+
52
+ **In an instance method**, call `getContext()` to retrieve the current context, which includes:
53
+
54
+ - `user` — User object with username, role, and authorization information
55
+ - `transaction` — The current transaction
56
+
57
+ **In a static verb**, the context is the trailing argument:
58
+
59
+ - `(target, context)` for `get`/`delete`
60
+ - `(target, data, context)` for `put`/`patch`/`post`
61
+
62
+ When triggered by HTTP, the context also exposes `url`, `method`, `headers`, `responseHeaders`, `pathname`, `host`, `ip`, `body`, `data`, `lastModified`, and `requestContext`.
63
+
64
+ ### 3. Handling Sessions and Login
65
+
66
+ Enable sessions in `harper-config.yaml`:
67
+
68
+ ```yaml
69
+ authentication:
70
+ enableSessions: true
71
+ ```
72
+
73
+ Use `context.login` in a static `post` verb to verify credentials and establish a session cookie:
143
74
 
144
75
  ```javascript
145
76
  export class SignIn extends Resource {
146
- async post(_target, data) {
147
- const context = this.getContext();
77
+ static async post(_target, data, context) {
78
+ const { username, password } = (await data) ?? {};
148
79
  try {
149
- await context.login(data.username, data.password);
80
+ await context.login(username, password);
150
81
  } catch {
151
82
  return new Response('Invalid credentials', { status: 403 });
152
83
  }
@@ -155,18 +86,161 @@ export class SignIn extends Resource {
155
86
  }
156
87
 
157
88
  export class SignOut extends Resource {
158
- async post() {
159
- const context = this.getContext();
160
- if (!context.session) return new Response(null, { status: 401 });
161
- await context.session.delete(context.session.id);
89
+ static async post(_target, _data, context) {
90
+ if (!context?.session?.user) return new Response(null, { status: 401 });
91
+ await context.session.update({ user: null });
162
92
  return new Response('Logged out', { status: 200 });
163
93
  }
164
94
  }
165
95
  ```
166
96
 
167
- **JWT token refresh endpoint:**
97
+ - `context.login(username, password)` verifies credentials and sets the session cookie on success.
98
+ - To end a session, call `context.session.update({ user: null })` — `update` is the session's only mutator.
99
+ - `context.session` is an empty object (not `undefined`) when sessions are enabled and no session cookie is present; test `context?.session?.user` to detect an established session.
100
+ - Cookie-based sessions are for browser clients. For non-browser clients, use JWT issuance.
101
+
102
+ ### 4. Creating Authentication Tokens
103
+
104
+ Call `create_authentication_tokens` with credentials in the body — no `Authorization` header is required when `username` and `password` are supplied:
105
+
106
+ ```json
107
+ {
108
+ "operation": "create_authentication_tokens",
109
+ "username": "username",
110
+ "password": "password"
111
+ }
112
+ ```
113
+
114
+ Response:
115
+
116
+ ```json
117
+ {
118
+ "operation_token": "<jwt-operation-token>",
119
+ "refresh_token": "<jwt-refresh-token>"
120
+ }
121
+ ```
122
+
123
+ Pass the `operation_token` as a `Bearer` token on subsequent requests:
124
+
125
+ ```bash
126
+ curl --location --request POST 'http://localhost:9925' \
127
+ --header 'Content-Type: application/json' \
128
+ --header 'Authorization: Bearer <operation_token>' \
129
+ --data-raw '{
130
+ "operation": "search_by_hash",
131
+ "schema": "dev",
132
+ "table": "dog",
133
+ "hash_values": [1],
134
+ "get_attributes": ["*"]
135
+ }'
136
+ ```
137
+
138
+ ### 5. Refreshing the Operation Token
139
+
140
+ When the `operation_token` expires, pass the `refresh_token` as `Bearer <refresh_token>` and call `refresh_operation_token`:
141
+
142
+ ```bash
143
+ curl --location --request POST 'http://localhost:9925' \
144
+ --header 'Content-Type: application/json' \
145
+ --header 'Authorization: Bearer <refresh_token>' \
146
+ --data-raw '{
147
+ "operation": "refresh_operation_token"
148
+ }'
149
+ ```
150
+
151
+ Response:
152
+
153
+ ```json
154
+ {
155
+ "operation_token": "<new-jwt-operation-token>"
156
+ }
157
+ ```
158
+
159
+ When both tokens have expired, call `create_authentication_tokens` again with username and password.
160
+
161
+ ### 6. Minting Scoped Tokens
162
+
163
+ A `super_user` can mint a scoped token with embedded permissions using `create_authentication_tokens` with an inline `role`. The minter must be authenticated; no `password` may be included in the body. Use `add_role`-style `permission` structure:
164
+
165
+ ```json
166
+ {
167
+ "operation": "create_authentication_tokens",
168
+ "username": "reporting-service",
169
+ "role": {
170
+ "permission": {
171
+ "operations": ["read_only"],
172
+ "dev": {
173
+ "tables": {
174
+ "dog": {
175
+ "read": true,
176
+ "insert": false,
177
+ "update": false,
178
+ "delete": false,
179
+ "attribute_permissions": []
180
+ }
181
+ }
182
+ }
183
+ }
184
+ },
185
+ "expires_in": "7d"
186
+ }
187
+ ```
188
+
189
+ Authenticate the mint request with Basic Authentication or an existing `super_user` `operation_token`:
190
+
191
+ ```bash
192
+ curl --location --request POST 'http://localhost:9925' \
193
+ --header 'Content-Type: application/json' \
194
+ --header 'Authorization: Basic <base64 of super_user:password>' \
195
+ --data-raw '{
196
+ "operation": "create_authentication_tokens",
197
+ "username": "reporting-service",
198
+ "role": { "permission": { "operations": ["read_only"] } },
199
+ "expires_in": "7d"
200
+ }'
201
+ ```
202
+
203
+ Key constraints for scoped tokens:
204
+
205
+ - `username` is attribution only and must not name an existing user.
206
+ - No refresh token is issued; no user record is created.
207
+ - Scoped tokens cannot be revoked before expiry — choose `expires_in` carefully and prefer short lifetimes.
208
+ - `super_user` and `cluster_user` are always forced to `false` in the embedded role.
209
+ - In **mixed-version** clusters, only nodes with scoped-token support accept these tokens; older nodes reject them with 401.
210
+
211
+ ### 7. Issuing Tokens from a Custom Resource
212
+
213
+ Use `server.operation()` to mint tokens programmatically inside a Resource. Pass `authorize: true` as the **third argument** when the operation should run as the current authenticated user:
168
214
 
169
215
  ```javascript
216
+ import { Resource, server } from 'harper';
217
+
218
+ export class IssueTokens extends Resource {
219
+ static async get(_target, context) {
220
+ // Issue tokens for the current authenticated user
221
+ const { operation_token, refresh_token } = await server.operation(
222
+ { operation: 'create_authentication_tokens' },
223
+ context,
224
+ true, // third argument: authorize as current user
225
+ );
226
+ return { operation_token, refresh_token };
227
+ }
228
+
229
+ static async post(_target, data) {
230
+ // Issue tokens from credentials supplied in the body
231
+ const { username, password } = await data;
232
+ if (!username || !password) {
233
+ return new Response('username and password required', { status: 400 });
234
+ }
235
+ const { operation_token, refresh_token } = await server.operation({
236
+ operation: 'create_authentication_tokens',
237
+ username,
238
+ password,
239
+ });
240
+ return { operation_token, refresh_token };
241
+ }
242
+ }
243
+
170
244
  export class RefreshJWT extends Resource {
171
245
  static async post(_target, data) {
172
246
  const { refresh_token } = await data;
@@ -182,9 +256,73 @@ export class RefreshJWT extends Resource {
182
256
  }
183
257
  ```
184
258
 
259
+ Omit the third argument (or pass `false`) when the operation supplies its own credentials.
260
+
261
+ ### 8. Configuring Token Expiry
262
+
263
+ Set timeouts in `harper-config.yaml` under the `authentication` section:
264
+
265
+ ```yaml
266
+ authentication:
267
+ operationTokenTimeout: 1d # Default: 1 day
268
+ refreshTokenTimeout: 30d # Default: 30 days
269
+ ```
270
+
271
+ Valid duration strings follow the `jsonwebtoken` package format (e.g., `1d`, `12h`, `60m`). The `expires_in` field on scoped token minting accepts the same format.
272
+
273
+ ## Examples
274
+
275
+ ### Full Sign-In / Sign-Out Resource
276
+
277
+ ```javascript
278
+ export class SignIn extends Resource {
279
+ static async post(_target, data, context) {
280
+ const { username, password } = (await data) ?? {};
281
+ try {
282
+ await context.login(username, password);
283
+ } catch {
284
+ return new Response('Invalid credentials', { status: 403 });
285
+ }
286
+ return new Response('Logged in', { status: 200 });
287
+ }
288
+ }
289
+
290
+ export class SignOut extends Resource {
291
+ static async post(_target, _data, context) {
292
+ if (!context?.session?.user) return new Response(null, { status: 401 });
293
+ await context.session.update({ user: null });
294
+ return new Response('Logged out', { status: 200 });
295
+ }
296
+ }
297
+ ```
298
+
299
+ ### Reading the Current Authenticated User in a Static Verb
300
+
301
+ ```javascript
302
+ static async get(_target, context) {
303
+ const user = context?.user;
304
+ if (!user) return new Response(null, { status: 401 });
305
+ return { username: user.username, role: user.role };
306
+ }
307
+ ```
308
+
309
+ ### Minting Tokens via cURL
310
+
311
+ ```bash
312
+ curl --location --request POST 'http://localhost:9925' \
313
+ --header 'Content-Type: application/json' \
314
+ --data-raw '{
315
+ "operation": "create_authentication_tokens",
316
+ "username": "username",
317
+ "password": "password"
318
+ }'
319
+ ```
320
+
185
321
  ## Notes
186
322
 
187
- - `getCurrentUser()` and `getContext()` are instance methods; call them with `this` inside non-static Resource methods.
188
- - `enableSessions` must be `true` in config before `context.login` or `context.session` will function.
189
- - Cookie-based sessions target browser clients. Use JWT issuance via `server.operation()` for all other client types.
190
- - When both `operation_token` and `refresh_token` have expired, the client must call `create_authentication_tokens` again with credentials.
323
+ - JWT authentication is **preferred over Basic Auth** when you want to avoid sending credentials on every request, your client can store tokens, or you have multiple sequential requests. For simple or **server-to-server** scenarios, use **Basic Authentication**.
324
+ - Always use **HTTPS** in production to protect tokens in transit.
325
+ - Treat tokens like passwords. If a token is compromised, it remains valid until expiry — use shorter `operationTokenTimeout` values in high-security environments.
326
+ - `context.login` and `context.session` require `enableSessions: true` in `harperdb-config.yaml`; they are for browser clients only.
327
+ - The `server.operation()` third argument (`authorize: true`) attributes the operation to — and permission-checks against — the current authenticated user. Omit it when supplying credentials directly in the operation body.
328
+ - In **mixed-version** clusters, scoped tokens are only accepted by nodes that support them; older nodes return 401.
@@ -6,9 +6,9 @@ metadata:
6
6
  sources:
7
7
  - reference/v5/resources/overview.md#Custom External Data Source
8
8
  - reference/v5/resources/overview.md#Exporting Resources as Endpoints
9
- - reference/v5/components/javascript-environment.md#Module Loading
10
- sourceCommit: f37a8c4021e20d5c74c1d339a6b6c8c196b5603e
11
- inputHash: df69870433c0b3e5
9
+ - reference/v5/components/javascript-environment.md#Module Formats
10
+ sourceCommit: 9e6ecf87dd25bb488ad44dc2876ca6439ccd682e
11
+ inputHash: 86858206e33925f0
12
12
  ---
13
13
 
14
14
  # Custom Resources
@@ -17,11 +17,11 @@ Instructions for the agent to follow when defining custom REST endpoints with Ja
17
17
 
18
18
  ## When to Use
19
19
 
20
- Apply this rule when creating custom HTTP endpoints, wrapping external APIs, or registering routes programmatically in a Harper application. Use it any time business logic must live outside a table-backed schema, or when a specific URL shape is required.
20
+ Apply this rule when creating custom HTTP endpoints, wrapping external APIs, or registering routes programmatically in a Harper application. Use it any time business logic must live outside a table-backed resource or when a specific URL shape is required.
21
21
 
22
22
  ## How It Works
23
23
 
24
- 1. **Import `Resource` from `harper`**: Always import from the `harper` package rather than relying on globals.
24
+ 1. **Import `Resource` from the `harper` package**: Always import explicitly rather than relying on globals.
25
25
 
26
26
  ```javascript
27
27
  import { tables, Resource } from 'harper';
@@ -39,7 +39,7 @@ Apply this rule when creating custom HTTP endpoints, wrapping external APIs, or
39
39
  }
40
40
  ```
41
41
 
42
- 3. **Use `async` static methods for external calls**: Await fetch or other async operations inside `static` handlers.
42
+ 3. **Use `async` static methods to call external services**: Return or forward the response directly.
43
43
 
44
44
  ```javascript
45
45
  export class MyExternalData extends Resource {
@@ -57,7 +57,7 @@ Apply this rule when creating custom HTTP endpoints, wrapping external APIs, or
57
57
  }
58
58
  ```
59
59
 
60
- 4. **Export the class to create an endpoint**: The export form controls the resulting URL. Choose the form that matches the URL shape you need.
60
+ 4. **Export the class to expose it as an endpoint**: The export form controls the resulting URL. Choose the form that matches the URL shape you need.
61
61
 
62
62
  | Export form | URL | Notes |
63
63
  | ------------------------------------------- | --------------- | --------------------------------------------------------------- |
@@ -68,12 +68,17 @@ Apply this rule when creating custom HTTP endpoints, wrapping external APIs, or
68
68
  | `static path = '/widget/:id'` (class field) | `/widget/:id` | Declare path on the class; overrides the export name. |
69
69
  | `server.resources.set('my-path', Foo);` | `/my-path/` | Programmatic registration for dynamic paths. |
70
70
 
71
- URL path matching is case-sensitive — `/Foo/` and `/foo/` are different endpoints.
71
+ 5. **Register programmatically when the path is dynamic**: Use `server.resources.set(` with a path string and the resource class.
72
72
 
73
- 5. **Declare path parameters with `static path`**: Use `:name` for a single segment and `*name` as a catch-all. Matched values are bound onto `target.<name>`.
73
+ ```javascript
74
+ server.resources.set('my-path', Foo);
75
+ ```
76
+
77
+ 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>`.
74
78
 
75
79
  ```javascript
76
80
  export class Widget extends Resource {
81
+ // GET /widget/10/action/jump -> target.id === '10', target.action === 'jump'
77
82
  static path = '/widget/:id/action/:action';
78
83
  static get(target) {
79
84
  return { id: target.id, action: target.action };
@@ -81,22 +86,11 @@ Apply this rule when creating custom HTTP endpoints, wrapping external APIs, or
81
86
  }
82
87
  ```
83
88
 
84
- A `static path` takes precedence over the export name. A leading `/` makes the path root-relative (top-level). A leading `./` or bare name resolves relative to the component directory.
85
-
86
- 6. **Register programmatically when the path is dynamic**: Use `server.resources.set(` when the path cannot be known at export time.
87
-
88
- ```javascript
89
- server.resources.set('my-path', Foo);
90
- ```
91
-
92
- 7. **Optionally source a table from a custom resource**: Use the resource as a caching layer for a local table.
93
- ```javascript
94
- tables.MyCache.sourcedFrom(MyExternalData);
95
- ```
89
+ 7. **Resolve path precedence correctly**: Exact and static paths always win over parameterized ones. Among parameterized routes, more specific paths win: a literal segment beats a `:param`, which beats a `*` wildcard, compared left to right.
96
90
 
97
91
  ## Examples
98
92
 
99
- ### External API wrapper with GET and PUT
93
+ **Wrapping an external API and using it as a cache source:**
100
94
 
101
95
  ```javascript
102
96
  import { tables, Resource } from 'harper';
@@ -119,19 +113,9 @@ export class MyExternalData extends Resource {
119
113
  tables.MyCache.sourcedFrom(MyExternalData);
120
114
  ```
121
115
 
122
- ### Path parameters with `static path`
116
+ **Catch-all wildcard path:**
123
117
 
124
118
  ```javascript
125
- import { Resource } from 'harper';
126
-
127
- export class Widget extends Resource {
128
- // GET /widget/10/action/jump -> target.id === '10', target.action === 'jump'
129
- static path = '/widget/:id/action/:action';
130
- static get(target) {
131
- return { id: target.id, action: target.action };
132
- }
133
- }
134
-
135
119
  export class Files extends Resource {
136
120
  // GET /files/a/b/c.txt -> target.rest === 'a/b/c.txt'
137
121
  static path = '/files/*rest';
@@ -141,25 +125,24 @@ export class Files extends Resource {
141
125
  }
142
126
  ```
143
127
 
144
- ### Programmatic registration
128
+ **Root-relative fixed route:**
145
129
 
146
130
  ```javascript
147
- import { Resource } from 'harper';
148
-
149
- export class Foo extends Resource {
131
+ export class AcmeChallenge extends Resource {
132
+ static path = '/.well-known/acme-challenge/:token';
150
133
  static get(target) {
151
- return { data: doSomething() };
134
+ return { token: target.token };
152
135
  }
153
136
  }
154
-
155
- server.resources.set('my-path', Foo);
156
137
  ```
157
138
 
158
139
  ## Notes
159
140
 
160
- - A bare `*` wildcard (no name) binds under `target.wildcard`. A wildcard must be the final segment of the path.
161
- - Resolution order: exact/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.
162
- - 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.
163
- - If a resource `extends` an existing table, avoid conflicting exports between the schema and the JavaScript implementation.
141
+ - URL path matching is case-sensitive — `/Foo/` and `/foo/` are different endpoints.
142
+ - A leading `/` in `static path` makes the path root-relative (top-level), independent of the file's location.
143
+ - A leading `./` or a bare name in `static path` resolves relative to the component directory.
144
+ - A bare `*` (no name) binds under `target.wildcard`. A wildcard must be the final segment of the path.
145
+ - `static path` takes precedence over the export name when both are present.
146
+ - 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.
147
+ - When a resource `extends` an existing table, avoid conflicting exports between the schema and the JavaScript implementation.
164
148
  - Link the `harper` package in your component directory to ensure correct typings: `npm link harper`. All installed components have `harper` automatically linked.
165
- - Harper runs as a single process — `tables`, `databases`, and other APIs are the same live, process-wide objects regardless of which component accesses them.