Skip to content

Commit e323358

Browse files
committed
More updates to use updated Resource API
1 parent 204ce9b commit e323358

File tree

3 files changed

+59
-41
lines changed

3 files changed

+59
-41
lines changed

docs/developers/applications/README.md

Lines changed: 39 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,22 @@ 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+
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. Harper will maintain the current context, ensuring that we are accessing the data atomically, in a consistent snapshot across tables. This provides automatic 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:
273277

274278
```javascript
275279
//resource.js:
276280
const { Dog, Breed } = tables; // get the Breed table too
277281
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);
282+
static loadAsInstance = false;
283+
async get(target) {
284+
// get the Dog record
285+
const record = await super.get(target);
286+
// get the Breed record
287+
let breedDescription = await Breed.get(record.breed);
288+
return {
289+
...record,
290+
breedDescription
291+
};
282292
}
283293
}
284294
```
@@ -289,9 +299,12 @@ Here we have focused on customizing how we retrieve data, but we may also want t
289299

290300
```javascript
291301
export class CustomDog extends Dog {
292-
async post(data) {
293-
if (data.action === 'add-trick')
294-
this.tricks.push(data.trick);
302+
static loadAsInstance = false;
303+
async post(target, data) {
304+
if (data.action === 'add-trick') {
305+
const record = this.update(target);
306+
record.tricks.push(data.trick);
307+
}
295308
}
296309
}
297310
```
@@ -300,12 +313,22 @@ And a POST request to /CustomDog/ would call this `post` method. The Resource cl
300313

301314
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).
302315

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:
316+
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:
304317

305318
```javascript
306319
export class CustomDog extends Dog {
307-
allowUpdate(user) {
308-
return this.owner === user.username;
320+
static loadAsInstance = false;
321+
async post(target, data) {
322+
if (data.action === 'add-trick') {
323+
const context = this.getContext();
324+
if (record.owner !== context.user?.username) {
325+
throw new Error('Can not update this record');
326+
}
327+
// if we now want to skip the default permission checks, we can turn that off:
328+
target.checkPermissions = false;
329+
const record = this.update(target);
330+
record.tricks.push(data.trick);
331+
}
309332
}
310333
}
311334
```
@@ -329,8 +352,8 @@ We can also directly implement the Resource class and use it to create new data
329352
```javascript
330353
const { Breed } = tables; // our Breed table
331354
class BreedSource extends Resource { // define a data source
332-
async get() {
333-
return (await fetch(`http://best-dog-site.com/${this.getId()}`)).json();
355+
async get(target) {
356+
return (await fetch(`http://best-dog-site.com/${target}`)).json();
334357
}
335358
}
336359
// define that our breed table is a cache of data from the data source above, with a specified expiration

docs/technical-details/reference/resource-migration.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@ The updated Resource API is enabled on a per-class basis, by setting static `loa
1111
* A `target` property of `checkPermission` indicates that a method should check the permission before of request before proceeding. The default instance methods provide the default authorization behavior.
1212
* This supplants the need for `allowRead`, `allowUpdate`, `allowCreate`, and `allowDelete` methods, which shouldn't need to be used (and don't provide the id of the target record).
1313
* Any data from a POST, PUT, and PATCH request will be available in the second argument. This reverses the order of the arguments to `put`, `post`, and `patch` compared to the legacy Resource API.
14-
* Context is tracked using asynchronous context tracking, and will automatically be available to calls to other other resources.
15-
* The method will return a `Updatable` object (instead of a Resource instance), which provides properties mapped to a record, but these properties can be updated and changes will be saved when the transaction is committed.
16-
* The `update` methods will return an `Updatable` object (instead of a Resource instance), which provides properties mapped to a record, but these properties can be updated and changes will be saved when the transaction is committed.
14+
* Context is tracked using asynchronous context tracking, and will automatically be available to calls to other resources. This can be disabled by setting `static explicitContext = true`, which can improve performance.
15+
* The `update` method will return an `Updatable` object (instead of a Resource instance), which provides properties mapped to a record, but these properties can be updated and changes will be saved when the transaction is committed.
1716

1817
Here are examples of how to convert/upgrade to the non-instance binding Resource API:
1918
Previous code with a `get` method:
@@ -45,9 +44,9 @@ export class MyData extends tables.MyData {
4544
// we can retrieve another record from this table directly with this.get/super.get or with tables.MyData.get
4645
record = await super.get(idWithQuery);
4746
} else {
48-
record = await super.get(target); // we can just directly use the query as well
47+
record = await super.get(target); // we can just directly use the target as well
4948
}
50-
// the record itself is frozen, but we can copy/assign to a new record with additional properties if we want
49+
// the record itself is frozen, but we can copy/assign to a new object with additional properties if we want
5150
return { ...record, newProperty: 'value' };
5251
}
5352
}
@@ -74,6 +73,7 @@ export class MyData extends tables.MyData {
7473
// to perform/call authorization explicitly in direct get, put, post methods rather than using allow* methods.
7574
if (!this.getContext().user) throw new Error('Unauthorized');
7675
target.checkPermissions = false; // authorization complete, no need to further check permissions below
76+
// target.checkPermissions is set to true or left in place, this default get method will perform the default permissions checks
7777
return super.get(target); // we can just directly use the query as well
7878
}
7979
}
@@ -98,14 +98,13 @@ export class MyData extends tables.MyData {
9898
```
9999
Updated code:
100100
```javascript
101-
102101
export class MyData extends tables.MyData {
103102
static loadAsInstance = false; // opt in to updated behavior
104103
// IMPORTANT: arguments are reversed:
105104
async post(target, data) {
106105
let record = await this.get(data.id);
107106
if (record) { // update a property
108-
const updatable = await this.update(data.id);
107+
const updatable = await this.update(data.id); // we can alternately pass a target to update
109108
updatable.someProperty = 'value';
110109
// or
111110
this.patch(data.id, { someProperty: 'value' }, this);

0 commit comments

Comments
 (0)