@stonyx/orm 0.3.2-alpha.81 → 0.3.2-alpha.83
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/README.md +99 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1325,6 +1325,32 @@ or an `errors` member for the withheld case makes the route an **existence
|
|
|
1325
1325
|
oracle** — see [Filter functions](#filter-functions) for the rule and for the
|
|
1326
1326
|
measurement that closed it on this route.
|
|
1327
1327
|
|
|
1328
|
+
**A per-record deny for a *related* resource cannot be expressed, and nothing
|
|
1329
|
+
tells you so at the point you would write it.** `createLinkageFilter` resolves
|
|
1330
|
+
the related model's access class by **type**: `context.recordId` is `null`, and
|
|
1331
|
+
`request.params` names a record of a **different model** — the one the route is
|
|
1332
|
+
addressed to. So `access()` is handed the model, the operation and the request,
|
|
1333
|
+
and **a rule that has to know *which* related record it is being asked about
|
|
1334
|
+
cannot be written**. Model-level denies (`return false` for a model) work.
|
|
1335
|
+
Request-level denies (a header, a tenant, the method) work. The per-record
|
|
1336
|
+
**filter** shape works too — `access()` may return a function, and that function
|
|
1337
|
+
receives the whole record, id included. What does not work is branching on the
|
|
1338
|
+
record's identity *before* returning, because `access()` is not told it.
|
|
1339
|
+
|
|
1340
|
+
This is a fixed property of the mechanism rather than a defect awaiting a fix.
|
|
1341
|
+
The verdict is resolved **once per type** and cached before any record has been
|
|
1342
|
+
examined, so seeding `recordId` from a record would let the first member of a
|
|
1343
|
+
`hasMany` decide the context for all of them. The rule the framework holds to is
|
|
1344
|
+
**`recordId` may name a record only where the route addresses exactly one record
|
|
1345
|
+
of the model being asked about** — true for `GET /owners/{id}`, false for
|
|
1346
|
+
linkage, and false for a `hasMany` related-resource route. The consumer-facing
|
|
1347
|
+
consequence is the part to check: a predicate that branches on `recordId` sees
|
|
1348
|
+
`null` here and takes whichever branch `null` takes, with no warning, and if
|
|
1349
|
+
that branch grants then it **grants**. Express the rule as a returned filter
|
|
1350
|
+
function instead. Stated again, with the same label, under
|
|
1351
|
+
[Known limitations](#known-limitations)
|
|
1352
|
+
([#232](https://github.com/abofs/stonyx-orm/issues/232)).
|
|
1353
|
+
|
|
1328
1354
|
Do this:
|
|
1329
1355
|
|
|
1330
1356
|
```js
|
|
@@ -1442,10 +1468,31 @@ they are recorded here.
|
|
|
1442
1468
|
return 404. Only affects function-style `access` users, for whom the old
|
|
1443
1469
|
behaviour was the bypass.
|
|
1444
1470
|
|
|
1445
|
-
"Seven surfaces" means the seven endpoints of **the filtered model
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
is
|
|
1471
|
+
"Seven surfaces" means the seven endpoints of **the filtered model** —
|
|
1472
|
+
`GET /:models`, `GET /:models/:id`, `GET /:models/:id/{relationship}`,
|
|
1473
|
+
`GET /:models/:id/relationships/{relationship}`, `POST /:models`,
|
|
1474
|
+
`PATCH /:models/:id` and `DELETE /:models/:id`. That count is still seven and
|
|
1475
|
+
is still the model's own endpoints, but **it is no longer the whole
|
|
1476
|
+
population**: the boundary moved outward rather than the number changing, and
|
|
1477
|
+
this sentence used to be read as saying a filtered model's predicate is
|
|
1478
|
+
consulted nowhere else. It now is, in two further places, both from
|
|
1479
|
+
*another* model's routes —
|
|
1480
|
+
|
|
1481
|
+
- on **both relationship route families**, where the related record is the
|
|
1482
|
+
primary data and the filtered model's own class decides whether it is
|
|
1483
|
+
served at all (breaking change 9 below,
|
|
1484
|
+
[#232](https://github.com/abofs/stonyx-orm/issues/232)); and
|
|
1485
|
+
- on the `relationships.*.data` **linkage** of every request-bound surface
|
|
1486
|
+
that serializes a record through `toJSON()`
|
|
1487
|
+
([#234](https://github.com/abofs/stonyx-orm/issues/234),
|
|
1488
|
+
[#235](https://github.com/abofs/stonyx-orm/issues/235)), which decides
|
|
1489
|
+
which ids another model's document may name.
|
|
1490
|
+
|
|
1491
|
+
A **write** to another collection can still reach one of its records through
|
|
1492
|
+
a relationship — [#207](https://github.com/abofs/stonyx-orm/issues/207),
|
|
1493
|
+
which is **not** closed here. That is the half of the old sentence that
|
|
1494
|
+
survives, and it is a read/write asymmetry now rather than a blanket
|
|
1495
|
+
statement about cross-model reach.
|
|
1449
1496
|
5. **A predicate that throws is treated as a denial** rather than propagating to
|
|
1450
1497
|
Express's default 500 handler. So is an `access()` that throws.
|
|
1451
1498
|
6. **`access()` returning a bare string is one permission, not full access.**
|
|
@@ -1520,6 +1567,54 @@ they are recorded here.
|
|
|
1520
1567
|
`stonyx/log`. Previously this case threw out of the handler and express
|
|
1521
1568
|
answered `500` with a stack trace.
|
|
1522
1569
|
|
|
1570
|
+
9. **Both relationship route families now resolve the *related* model's own
|
|
1571
|
+
access class, so a related record that is hidden on its own routes is no
|
|
1572
|
+
longer served through another model's.**
|
|
1573
|
+
[#232](https://github.com/abofs/stonyx-orm/issues/232). Affects
|
|
1574
|
+
function-style `access` users with relationships between filtered models. The
|
|
1575
|
+
old behaviour was a bypass at **zero query parameters** and with no
|
|
1576
|
+
`include=`: measured on `dev @ 8dda5d6`, `GET /animals/1/owner` returned
|
|
1577
|
+
angela's full document and `GET /animals/1/relationships/owner` returned
|
|
1578
|
+
`{"type":"owner","id":"angela"}`, while `GET /owners/angela` answered `404`.
|
|
1579
|
+
The severe case is a model claimed by **no** access class — `getAccess()`
|
|
1580
|
+
returns `undefined`, no route is mounted for it at all, and it was still
|
|
1581
|
+
readable as a related resource.
|
|
1582
|
+
|
|
1583
|
+
**The shapes, on both families.** A denied `hasMany` member is **dropped from
|
|
1584
|
+
the array**: `200`, `links` intact, no `errors` member. A denied `belongsTo`
|
|
1585
|
+
target answers **`200` with `data: null`**, byte-identical to a target that
|
|
1586
|
+
genuinely does not exist — same status, same bytes modulo the parent id the
|
|
1587
|
+
caller put in the URL. `404` on these routes is now reserved for the
|
|
1588
|
+
**parent**.
|
|
1589
|
+
|
|
1590
|
+
**`data: null` and not `404`, deliberately.** The 404 spelling was an
|
|
1591
|
+
existence oracle and was measured as one on this branch: unauthenticated, no
|
|
1592
|
+
query string, one request each, against `tag` — the model with no route
|
|
1593
|
+
mounted at all — `GET /traits/1/tag` (absent) answered `200`
|
|
1594
|
+
`application/json` at 68 bytes while `GET /traits/2/tag` (denied) answered
|
|
1595
|
+
`404` `text/plain` at 9 bytes, and the document surface reported both as
|
|
1596
|
+
`{"data":null}`. It also brings these two routes into line with this module's
|
|
1597
|
+
rule that every status on a record route is identical for filtered-out and
|
|
1598
|
+
does-not-exist (see [Filter functions](#filter-functions)), which the `404`
|
|
1599
|
+
spelling was the one exception to.
|
|
1600
|
+
|
|
1601
|
+
**What to check before you upgrade.** If a consumer reaches a related record
|
|
1602
|
+
through `GET /:models/:id/{relationship}` that it cannot reach on that
|
|
1603
|
+
record's own collection route, it was relying on the bypass and will now get
|
|
1604
|
+
`data: null` or a shorter array. And the related model's class is resolved
|
|
1605
|
+
through the same `Orm.instance.getAccess` path as the linkage filter, so it
|
|
1606
|
+
inherits the same arity limit: a **single-argument** predicate answers about
|
|
1607
|
+
the collection the request was *addressed to*, not the one it was asked
|
|
1608
|
+
about, and that is the direction that **grants**. See
|
|
1609
|
+
[Known limitations](#known-limitations) and
|
|
1610
|
+
[#221](https://github.com/abofs/stonyx-orm/issues/221).
|
|
1611
|
+
|
|
1612
|
+
**Not closed here:** whether a related resource appears in `included` at all
|
|
1613
|
+
([#233](https://github.com/abofs/stonyx-orm/issues/233)), and the
|
|
1614
|
+
re-parenting write ([#207](https://github.com/abofs/stonyx-orm/issues/207)).
|
|
1615
|
+
A **per-record** deny for a related resource is not expressible on these
|
|
1616
|
+
routes at all — see [Consumer Contracts](#consumer-contracts).
|
|
1617
|
+
|
|
1523
1618
|
### Include Parameter (Sideloading Relationships)
|
|
1524
1619
|
|
|
1525
1620
|
The ORM supports JSON API-compliant relationship sideloading via the `include` query parameter. This reduces the need for multiple API requests by embedding related records in a single response.
|