Skip to content

Commit 929e61b

Browse files
authored
Merge pull request #175 from HarperDB/resource-api-update
Documenting the updated resource API
2 parents 933c600 + 26754a9 commit 929e61b

File tree

4 files changed

+1029
-182
lines changed

4 files changed

+1029
-182
lines changed

docs/developers/applications/README.md

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -249,9 +249,13 @@ To define custom (JavaScript) resources as endpoints, we need to create a `resou
249249
const { Dog } = tables; // get the Dog table from the Harper provided set of tables (in the default database)
250250
251251
export class DogWithHumanAge extends Dog {
252-
get(query) {
253-
this.humanAge = 15 + this.age * 5; // silly calculation of human age equivalent
254-
return super.get(query);
252+
static loadAsInstance = false;
253+
async get(target) {
254+
const record = await super.get(target);
255+
return {
256+
...record, // include all properties from the record
257+
humanAge: 15 + record.age * 5 // silly calculation of human age equivalent
258+
};
255259
}
256260
}
257261
```
@@ -269,16 +273,28 @@ type Breed @table {
269273
}
270274
```
271275

272-
And next we will use this table in our `get()` method. We will call the new table's (static) `get()` method to retrieve a breed by id. To do this correctly, we access the table using our current context by passing in `this` as the second argument. This is important because it ensures that we are accessing the data atomically, in a consistent snapshot across tables. This provides automatically tracking of most recently updated timestamps across resources for caching purposes. This allows for sharing of contextual metadata (like user who requested the data), and ensure transactional atomicity for any writes (not needed in this get operation, but important for other operations). The resource methods are automatically wrapped with a transaction (will commit/finish when the method completes), and this allows us to fully utilize multiple resources in our current transaction. With our own snapshot of the database for the Dog and Breed table we can then access data like this:
276+
We use the new table's (static) `get()` method to retrieve a breed by id. Harper will maintain the current context, ensuring that we are accessing the data atomically, in a consistent snapshot across tables. This provides:
277+
278+
1. Automatic tracking of most recently updated timestamps across resources for caching purposes
279+
2. Sharing of contextual metadata (like user who requested the data)
280+
3. Transactional atomicity for any writes (not needed in this get operation, but important for other operations)
281+
282+
The resource methods are automatically wrapped with a transaction and will automatically commit the changes when the method finishes. This allows us to fully utilize multiple resources in our current transaction. With our own snapshot of the database for the Dog and Breed table we can then access data like this:
273283

274284
```javascript
275285
//resource.js:
276286
const { Dog, Breed } = tables; // get the Breed table too
277287
export class DogWithBreed extends Dog {
278-
async get(query) {
279-
let breedDescription = await Breed.get(this.breed, this);
280-
this.breedDescription = breedDescription;
281-
return super.get(query);
288+
static loadAsInstance = false;
289+
async get(target) {
290+
// get the Dog record
291+
const record = await super.get(target);
292+
// get the Breed record
293+
let breedDescription = await Breed.get(record.breed);
294+
return {
295+
...record,
296+
breedDescription
297+
};
282298
}
283299
}
284300
```
@@ -289,9 +305,12 @@ Here we have focused on customizing how we retrieve data, but we may also want t
289305

290306
```javascript
291307
export class CustomDog extends Dog {
292-
async post(data) {
293-
if (data.action === 'add-trick')
294-
this.tricks.push(data.trick);
308+
static loadAsInstance = false;
309+
async post(target, data) {
310+
if (data.action === 'add-trick') {
311+
const record = this.update(target);
312+
record.tricks.push(data.trick);
313+
}
295314
}
296315
}
297316
```
@@ -300,12 +319,23 @@ And a POST request to /CustomDog/ would call this `post` method. The Resource cl
300319

301320
The `post` method automatically marks the current instance as being update. However, you can also explicitly specify that you are changing a resource by calling the `update()` method. If you want to modify a resource instance that you retrieved through a `get()` call (like `Breed.get()` call above), you can call its `update()` method to ensure changes are saved (and will be committed in the current transaction).
302321

303-
We can also define custom authorization capabilities. For example, we might want to specify that only the owner of a dog can make updates to a dog. We could add logic to our `post` method or `put` method to do this, but we may want to separate the logic so these methods can be called separately without authorization checks. The [Resource API](../../technical-details/reference/resource.md) defines `allowRead`, `allowUpdate`, `allowCreate`, and `allowDelete`, or to easily configure individual capabilities. For example, we might do this:
322+
We can also define custom authorization capabilities. For example, we might want to specify that only the owner of a dog can make updates to a dog. We could add logic to our `post()` method or `put()` method to do this. For example, we might do this:
304323

305324
```javascript
306325
export class CustomDog extends Dog {
307-
allowUpdate(user) {
308-
return this.owner === user.username;
326+
static loadAsInstance = false;
327+
async post(target, data) {
328+
if (data.action === 'add-trick') {
329+
const context = this.getContext();
330+
// if we want to skip the default permission checks, we can turn off checkPermissions:
331+
target.checkPermissions = false;
332+
const record = this.update(target);
333+
// and do our own/custom permission check:
334+
if (record.owner !== context.user?.username) {
335+
throw new Error('Can not update this record');
336+
}
337+
record.tricks.push(data.trick);
338+
}
309339
}
310340
}
311341
```
@@ -329,8 +359,8 @@ We can also directly implement the Resource class and use it to create new data
329359
```javascript
330360
const { Breed } = tables; // our Breed table
331361
class BreedSource extends Resource { // define a data source
332-
async get() {
333-
return (await fetch(`http://best-dog-site.com/${this.getId()}`)).json();
362+
async get(target) {
363+
return (await fetch(`http://best-dog-site.com/${target}`)).json();
334364
}
335365
}
336366
// define that our breed table is a cache of data from the data source above, with a specified expiration

0 commit comments

Comments
 (0)