Skip to content

Commit aff4acf

Browse files
expand operations section, more resource focus
1 parent 66fe4ba commit aff4acf

File tree

1 file changed

+265
-13
lines changed

1 file changed

+265
-13
lines changed

learn/developers/harper-application-and-plugin-development.mdx

Lines changed: 265 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -167,13 +167,13 @@ fetch('/', {
167167

168168
### First Operations API Request
169169

170-
There are many great operations to chose from, but to get started, lets try the `system_information` operation.
170+
There are many great operations to chose from, but to get started, lets try the `get_status` operation.
171171

172172
:::note
173173
All `operation` values will be in snake_case; all lowercase and underscores `_` in-place of spaces.
174174
:::
175175

176-
First, ensure Harper is running (refer to the previous guide if you need a quick refresher). Then, using your HTTP client of choice, create a POST request to your running Harper instance with `Content-Type: application/json` header, and a JSON body containing `{ "operation": "system_information" }`.
176+
First, ensure Harper is running (refer to the previous guide if you need a quick refresher). Then, using your HTTP client of choice, create a POST request to your running Harper instance with `Content-Type: application/json` header, and a JSON body containing `{ "operation": "get_status" }`.
177177

178178
> Fabric users, remember to replace `http://localhost` with your Fabric instance URL, and include an authorization header.
179179
@@ -184,8 +184,7 @@ First, ensure Harper is running (refer to the previous guide if you need a quick
184184
curl 'http://localhost:9925/' \
185185
-X POST \
186186
-H "Content-Type: application/json" \
187-
-H "Authorization: <authorizationValue>" \
188-
-d '{ "operation": "get_configuration" }'
187+
-d '{ "operation": "get_status" }'
189188
```
190189

191190
You may also include `| jq` here to render the output as JSON.
@@ -198,10 +197,9 @@ const response = await fetch('http://localhost:9925/', {
198197
method: 'POST',
199198
headers: {
200199
'Content-Type': 'application/json',
201-
'Authorization': authorizationValue,
202200
},
203201
body: JSON.stringify({
204-
"operation": "get_configuration"
202+
"operation": "get_status"
205203
}),
206204
});
207205
const data = await response.json();
@@ -212,9 +210,147 @@ console.log(data);
212210

213211
</Tabs>
214212

215-
You should see a JSON representation of the Harper instance's configuration with top-level properties such as `http`, `threads`, `authentication`, and `logging`.
213+
This operation returns a JSON object with three top-level properties: `restartRequired`, `systemStatus`, and `componentStatus`.
214+
215+
The `restartRequired` property is a mechanism for Harper plugins to indicate they require a restart for some changes to take effect (more on this later).
216+
217+
The other two properties are lists containing status objects corresponding to different parts of Harper. These should all read `"status": "healthy"` right now, and you may recognize some of the `"name"` and `"componentName"` fields as they correspond to Harper's built-in subsystems (such as `"http"`, `"threads"`, and `"authentication"`).
218+
219+
### More with Operations API
220+
221+
The Operations API is mainly intended to be used for system management purposes. It does have the ability to do data management (create/modify/query databases, tables, and records), Harper has released significantly more ergonomic and performant methods instead.
222+
223+
Harper keeps a reference of all operations in the Operations API reference documentation, but here a few more you can try immediately: `user_info`, `read_log`, and `describe_all`.
224+
225+
For `describe_all` to work, ensure that you are still running the Harper application you created in the previous guide. If you need to, checkout the [`02-rest-api`](https://github.com/HarperFast/create-your-first-application/tree/02-rest-api) branch of the `HarperFast/create-your-first-application` repository to ensure you have the necessary application files for this example.
226+
227+
You should see a JSON object with a top-level property `"data"`. This operation returns a map of all databases and tables. The `"data"` is the default database in Harper. Within that object, there should be a `"Dog"` key. This is the table you defined with `graphqlSchema` in the previous guide.
228+
229+
The entire JSON response should look something like this:
230+
231+
```json
232+
{
233+
"data": {
234+
"Dog": {
235+
"schema": "data",
236+
"name": "Dog",
237+
"hash_attribute": "id",
238+
"audit": true,
239+
"schema_defined": true,
240+
"attributes": [
241+
{
242+
"attribute": "id",
243+
"type": "ID",
244+
"is_primary_key": true
245+
},
246+
{
247+
"attribute": "name",
248+
"type": "String"
249+
},
250+
{
251+
"attribute": "breed",
252+
"type": "String"
253+
},
254+
{
255+
"attribute": "age",
256+
"type": "Int"
257+
}
258+
],
259+
"db_size": 212992,
260+
"sources": [],
261+
"record_count": 1,
262+
"table_size": 16384,
263+
"db_audit_size": 16384
264+
}
265+
}
266+
}
267+
```
268+
269+
Now lets keep drilling down in specificity by using the `describe_database` and then the `describe_table` operations. The difference this time is that these operations require additional properties.
270+
271+
For `describe_database`, you can specify `"database": "data"`. The entire request body would look something like this:
272+
273+
```json
274+
{
275+
"operation": "describe_database",
276+
"database": "data"
277+
}
278+
```
279+
280+
The response this time should omit the top-level `"data"` key, and instead be just an object containing `"Dog"` (the singular table defined in the `data` database so far).
281+
282+
And for `describe_table`, you would specify both `"database": "data"` and `"table": "Dog"`,
283+
284+
```json
285+
{
286+
"operation": "describe_database",
287+
"database": "data",
288+
"table": "Dog"
289+
}
290+
```
291+
292+
Now there is yet another way to get information about the `Dog` table; with the REST interface!
293+
294+
Create a `GET` request to `http://localhost:9926/Dog` and its important that you omit any trailing forward slash `/`, this request should return a slightly different JSON object describing the `Dog` table.
295+
296+
<Tabs>
297+
<TabItem value="curl">
298+
299+
```bash
300+
curl -s 'http://localhost:9926/Dog' | jq
301+
```
302+
303+
</TabItem>
304+
<TabItem value="fetch">
305+
306+
```typescript
307+
const response = await fetch('http://localhost:9926/Dog');
308+
const data = await response.json();
309+
console.log(data);
310+
```
311+
312+
</TabItem>
313+
314+
</Tabs>
315+
316+
Expected result:
317+
318+
```json
319+
{
320+
"records": "./",
321+
"name": "Dog",
322+
"database": "data",
323+
"auditSize": 3,
324+
"attributes": [
325+
{
326+
"type": "ID",
327+
"name": "id",
328+
"isPrimaryKey": true,
329+
"attribute": "id"
330+
},
331+
{
332+
"type": "String",
333+
"name": "name",
334+
"attribute": "name"
335+
},
336+
{
337+
"type": "String",
338+
"name": "breed",
339+
"attribute": "breed"
340+
},
341+
{
342+
"type": "Int",
343+
"name": "age",
344+
"attribute": "age"
345+
}
346+
]
347+
}
348+
```
349+
350+
All in all, the Operations API is fundamental tool for managing and introspecting your Harper instance. We'll cover more operations throughout the Learn guides.
216351

217352
{/*
353+
(This comment was from before when this example used get_configuration)
218354
I had an idea for a section here that dives into the configuration object. This would be an opportunity to like highlight different default features as well as
219355
default configuration values. It would also highlight like relevant things the user may want to tweak, such as logging level, threads debugging, etc. But I've
220356
decided that that content is better suited for specific sections. Like later in this guide it will introduce using the debugger, and will refer to modifying the
@@ -229,6 +365,10 @@ Harper configuration.
229365
230366
*/}
231367

368+
{/*
369+
370+
I had this section here because I originally wanted to guide local users through enforcing authorization, but per other conversations we switched to the other way. this is no longer necessary; users will get experience setting config options later.
371+
232372
### Modifying the Harper Configuration using the Operations API
233373
234374
Particularly relevant for local installation users, lets learn how to disable the `authentication.authorizeLocal` property so that you have the same experience as Fabric users do providing `Authorization` headers for all Operations API requests. Keep in mind, Learn guides will always specify an `Authorization` header for requests, but it is possible for local installation users to ignore it if they leave this option enabled.
@@ -271,7 +411,7 @@ console.log(data);
271411
272412
</TabItem>
273413
274-
</Tabs>
414+
</Tabs> */}
275415

276416
{/* I'm not sure we really need this section; a callout to the reference docs for _all_ Operations would be better IMO */}
277417

@@ -283,15 +423,127 @@ console.log(data);
283423
- Other essential operations for application development
284424
Include code examples for 1-2 operations. */}
285425

286-
### Operations API vs Application REST APIs
426+
## Expanding your Harper Application with custom Resources
427+
428+
If you're following along from getting started, you should have a basic Harper application running containing a `schema.graphql` and `config.yaml` files defining a simple `Dog` table and REST endpoint. Lets expand on this example while also exploring more of Harper's lifecycle and application development capabilities.
429+
430+
:::note
431+
If you want to ensure you're application code is at the right starting point, checkout the [`02-rest-api`](https://github.com/HarperFast/create-your-first-application/tree/02-rest-api) branch of the `HarperFast/create-your-first-application` repository.
432+
:::
433+
434+
Create a new file `resources.js` within your Harper application; here we are going to define custom Resources.
435+
436+
**Resources** are the mechanism for defining custom functionality in your Harper application. This gives you tremendous flexibility and control over how data is accessed and modified in Harper. The corresponding Resource API is a unified API for modeling different data sources within Harper as JavaScript classes. Generally, this is where the core business logic of your application lives. Database tables (the ones defined by `graphqlSchema` entries) are `Resource` classes, and so extending the function of a table is as simple as extending their class.
437+
438+
Resource classes have methods that correspond to standard HTTP/REST methods, like `get`, `post`, `patch`, and `put` to implement specific handling for any of these methods (for tables they all have default implementations). Furthermore, by simply `export` 'ing a resource class, Harper will generate REST API endpoints for it just like the `@export` directive did in `graphqlSchema`. The Resource API is quite powerful, and we'll dive into different aspects throughout future Learn guides, but for now lets start with a trivial example extending the existing `Dog` table that already exists in your application.
439+
440+
Inside of `resources.js` add the following code for defining a `DogWithHumanAge` custom resource:
441+
442+
```javascript
443+
// Fun fact, the 7:1 ratio is a misconception
444+
// https://www.akc.org/expert-advice/health/how-to-calculate-dog-years-to-human-years/
445+
function calculateHumanAge(dogAge) {
446+
if (dogAge === 1) {
447+
return 15;
448+
} else if (dogAge === 2) {
449+
return 24;
450+
} else {
451+
return 24 + 5 * dogAge - 2;
452+
}
453+
}
454+
455+
export class DogWithHumanAge extends tables.Dog {
456+
static loadAsInstance = false;
457+
async get(target) {
458+
const dogRecord = await super.get(target);
459+
460+
return {
461+
...dogRecord,
462+
humanAge: calculateHumanAge(dogRecord.age),
463+
}
464+
}
465+
}
466+
```
467+
468+
Then open `config.yaml` and add the `jsResource` plugin:
469+
470+
```yaml
471+
# Harper application configuration
472+
graphqlSchema:
473+
files: 'schema.graphql'
474+
jsResource:
475+
files: 'resources.js'
476+
rest: true
477+
```
478+
479+
Ensure Harper has restarted (automatically in `dev` mode or by manually starting/stopping it), and then prepare to query the new resource. In the getting started guide we created a singular dog record with an id of `001`. Create a `GET` request to `/DogWithHumanAge/001` and display the resulting JSON:
480+
481+
<Tabs groupId="http-client">
482+
<TabItem value="curl">
483+
484+
```bash
485+
curl -s 'http://localhost:9926/DogWithHumanAge/001' | jq
486+
```
487+
488+
</TabItem>
489+
<TabItem value="fetch">
490+
491+
```typescript
492+
const response = await fetch('http://localhost:9926/DogWithHumanAge/001');
493+
const dog = await response.json();
494+
console.log(dog);
495+
```
496+
497+
</TabItem>
498+
</Tabs>
499+
500+
The resulting JSON object should look similar to the original `Dog/001` entry, except this time there is a new property `humanAge`.
501+
502+
Notably, did you see how we were able to use the `001` id with the new resource immediately? And it was able to derive the underlying `Dog` record? Lets take a closer look at the custom resource implementation:
503+
504+
```javascript
505+
export class DogWithHumanAge extends tables.Dog {
506+
// ...
507+
}
508+
```
509+
510+
The `DogWithHumanAge` class extends from `tables.Dog`. The `tables` reference is a global added by Harper that is a map of all tables within the instance, such as the ones defined by `graphqlSchema`. The `export` keyword is used here to instruct Harper to automatically generated a REST API endpoint for the custom resource.
511+
512+
```javascript
513+
export class DogWithHumanAge extends tables.Dog {
514+
static loadAsInstance = false;
515+
async get (target) {
516+
// ...
517+
}
518+
}
519+
```
520+
521+
The `static loadAsInstance = false;` line established that the Resource instances will not be bound to a specific record. Instead instances represent the whole table, capturing the context and current transactional state.
522+
523+
```javascript
524+
export class DogWithHumanAge extends tables.Dog {
525+
static loadAsInstance = false;
526+
async get(target) {
527+
const dogRecord = await super.get(target);
528+
529+
return {
530+
...dogRecord,
531+
humanAge: calculateHumanAge(dogRecord.age),
532+
}
533+
}
534+
}
535+
```
536+
537+
Thus, the `super` part of `const dogRecord = await super.get(target);` refers to the original `tables.Dog`. By passing through the `target` object to `super.get(target)`, we are querying the original `Dog` table defined by `graphqlSchema`. The `dogRecord` instance returned here corresponds to whatever the request `/<id>` portion specified.
538+
539+
The rest of the `get()` method returns a new object with a copy of `dogRecord` and a newly computed `humanAge` field.
287540

288-
{/* AI Generated Outline: Clarify the distinction - Operations API (port 9925) for system management vs REST API (port 9926) for application data. This reinforces concepts from Getting Started. */}
541+
The `dogRecord` isn't just a plain JSON object; it has its own set of methods including comprehensive getters and setters. However, Harper does make all the defined properties available as enumerable properties so by using the `...` spread operator, you can easily copy all of the relevant properties of the `dogRecord` instance.
289542

290-
{/* AI is stupid. Lets use this section to show off more operations APIs like getting resources. compare to using REST interface from getting started. */}
543+
Now if you perhaps tried to use a query string selector, like `GET /DogWithHumanAge/?age=5`, this `get()` method implementation can't quite handle it yet; but this will be covered soon!
291544

292-
## Working with the Harper CLI
545+
For now, celebrate that you've successfully implemented your first custom resource!
293546

294-
{/* AI Generated Outline: Expand on CLI usage beyond the basic `harper` and `harper dev` commands already introduced in Getting Started. */}
295547

296548
### Essential CLI Commands for Application Development
297549

0 commit comments

Comments
 (0)