@stonyx/orm 0.3.2-alpha.74 → 0.3.2-alpha.76

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 CHANGED
@@ -951,7 +951,18 @@ per-record filter. An input you cannot identify must **deny**.
951
951
  `methodAccessMap[request.method]`: it would ask a different question on a
952
952
  write route than on a read route, which is the two-vocabularies failure
953
953
  `createLinkageFilter` exists to prevent. An unresolvable class
954
- (`getAccess()` → `undefined`) and a predicate that throws both **deny**. A
954
+ (`getAccess()` → `undefined`) and a predicate that throws both **deny**.
955
+
956
+ **What the two write surfaces cost before #235, measured rather than
957
+ described:** one HTTP verb defeated the filter on the same record. On
958
+ `dev @ 8dda5d6`, seconds apart, with no query string and no relationship
959
+ route, `GET /animals/1` returned `owner.data: null` while `PATCH /animals/1`
960
+ returned **200 naming angela**. Any caller who could read a record could also
961
+ write it and be handed the id the read withheld. That consequence is kept here
962
+ after the fix, and stated as a measurement, because **naming the two handlers
963
+ is not a substitute for it** — a reader who is told only that `POST` and
964
+ `PATCH` are now covered cannot tell what was wrong, and a reviewer cannot tell
965
+ whether the fix addressed it. A
955
966
  filtered-out relationship is **indistinguishable from a genuinely empty one** —
956
967
  an emptied `hasMany` is `data: []` and an emptied `belongsTo` is `data: null`,
957
968
  both **keeping their `links`**, which are built from the serialized record's
@@ -983,20 +994,30 @@ per-record filter. An input you cannot identify must **deny**.
983
994
  *permitted* related record, which is recorded in the release notes as a
984
995
  breaking change.
985
996
 
986
- **Not yet covered, and each one still publishes ids the surfaces above
987
- withhold:**
988
-
989
- - **`GET /:models/:id/relationships/{relationship}`**, whose *primary data* is
990
- linkage, so filtering it is a **membership** decision —
991
- [#232](https://github.com/abofs/stonyx-orm/issues/232), the filed child of
992
- [#196](https://github.com/abofs/stonyx-orm/issues/196). This route builds
993
- its `{type, id}` objects by hand and never calls `toJSON`, so it does not
994
- see a `linkage` option no matter who supplies one. Measured:
995
- `GET /animals/1/relationships/owner` answers
996
- `{"type":"owner","id":"angela"}` while `GET /owners/angela` is `404`.
997
- - **Whether a related resource appears in `included` at all**
998
- [#233](https://github.com/abofs/stonyx-orm/issues/233). #235 filters what a
999
- record *already in* `included` may **name**; a hidden record is still a
997
+ **Not yet covered by #235. Each still publishes ids the surfaces above
998
+ withhold, except where its own owning issue has since closed it — the first
999
+ entry names an issue that is in flight as this is written:**
1000
+
1001
+ - **`GET /:models/:id/relationships/{relationship}`, and its state is #232's
1002
+ to report rather than this entry's.**
1003
+ [#232](https://github.com/abofs/stonyx-orm/issues/232) owns the
1004
+ relationships-linkage route. Its *primary data* is linkage,
1005
+ so filtering it is a **membership** decision which is why it is the filed
1006
+ child of [#196](https://github.com/abofs/stonyx-orm/issues/196) and not of
1007
+ #234. The route builds its `{type, id}` objects by hand and never calls
1008
+ `toJSON`, so the `linkage` **option** never reaches it; whatever that route
1009
+ filters, it filters itself. Measured **on `dev @ 8dda5d6`**, the commit
1010
+ #235 branched from: `GET /animals/1/relationships/owner` answered
1011
+ `{"type":"owner","id":"angela"}` while `GET /owners/angela` was `404`. That
1012
+ measurement is pinned to a commit on purpose, so that it does not quietly
1013
+ become a false claim about `dev`. **PR
1014
+ [#247](https://github.com/abofs/stonyx-orm/pull/247) is in flight against
1015
+ this entry**; if it has landed, this route is covered and the bullet #247
1016
+ adds above supersedes this one.
1017
+ - **Whether a related resource appears in `included` at all.**
1018
+ [#233](https://github.com/abofs/stonyx-orm/issues/233) owns whether a
1019
+ related resource appears in `included`. #235 filters what a record
1020
+ *already in* `included` may **name**; a hidden record is still a
1000
1021
  **member** of that array. The two are different questions and neither closes
1001
1022
  the other: after #235, `GET /animals/1?include=owner,owner.pets` returns
1002
1023
  `owner.data: null` on every permitted animal it sideloads **and still
@@ -1105,11 +1126,29 @@ read all of these.
1105
1126
 
1106
1127
  #### `Record.toJSON()` does not filter relationship linkage unless you pass a verdict
1107
1128
 
1108
- **The framework owns this on four surfaces. You own it everywhere else.**
1109
-
1110
- `GET /:models`, `GET /:models/:id` and both `GET /:models/:id/{relationship}`
1111
- shapes resolve a linkage verdict and pass it to `toJSON()` for you. Any other
1112
- path to a document `JSON.stringify(record)`, `res.json(record)`,
1129
+ **The framework resolves a verdict for you on every request-bound surface that
1130
+ serializes a record through `toJSON()`. You own it everywhere else.**
1131
+
1132
+ Those surfaces are `GET /:models`, `GET /:models/:id`, both shapes of
1133
+ `GET /:models/:id/{relationship}`, the `POST /:models` and `PATCH /:models/:id`
1134
+ **response documents**, and every record inside an `?include=` **`included`**
1135
+ array ([#234](https://github.com/abofs/stonyx-orm/issues/234) for the four
1136
+ reads, [#235](https://github.com/abofs/stonyx-orm/issues/235) for the two
1137
+ writes and `included`). Each resolves a linkage verdict and passes it to
1138
+ `toJSON()` for you.
1139
+
1140
+ **`GET /:models/:id/relationships/{relationship}` is not on that list, and its
1141
+ state is not this section's to report.** It builds its `{ type, id }` objects by
1142
+ hand instead of calling `toJSON()`, so the `linkage` **option** never reaches it
1143
+ — whatever that route filters, it filters itself. And because its linkage *is*
1144
+ its primary data, filtering it is a **membership** decision rather than a
1145
+ linkage one. Membership on both relationship route families is owned by
1146
+ [#232](https://github.com/abofs/stonyx-orm/issues/232) (PR
1147
+ [#247](https://github.com/abofs/stonyx-orm/pull/247), in flight as this is
1148
+ written); read that issue for its state rather than inferring it here, because
1149
+ this section describes only what `toJSON()` filters.
1150
+
1151
+ Any other path to a document — `JSON.stringify(record)`, `res.json(record)`,
1113
1152
  `console.log(record)`, a custom route, a queue payload, a websocket frame —
1114
1153
  calls `toJSON()` with no verdict, and **the no-verdict document names every
1115
1154
  related id, including records hidden on every one of their own surfaces**
@@ -1333,27 +1333,46 @@ export default class OrmRequest extends Request {
1333
1333
  };
1334
1334
  // Relationship linkage route: GET /:id/relationships/{relationship}
1335
1335
  //
1336
- // NO `linkage` FILTER HERE, AND IT IS NOT AN OVERSIGHT --
1337
- // abofs/stonyx-orm#232 OWNS THIS ROUTE. The three sites that carry the
1338
- // filter (`buildResponse`'s `included`, the related-resource branch
1339
- // above, and the two write handlers) all call `record.toJSON()`, which is
1340
- // where the `linkage` option is applied. This branch builds its
1341
- // `{ type, id }` objects BY HAND and never calls `toJSON` at all, so it
1342
- // cannot see a filter no matter who passes one.
1336
+ // NO `linkage` FILTER FROM abofs/stonyx-orm#235, AND THAT IS A SCOPE
1337
+ // BOUNDARY RATHER THAN AN OVERSIGHT -- abofs/stonyx-orm#232 OWNS THIS
1338
+ // ROUTE, and PR #247 is IN FLIGHT against it in this same sprint. If you
1339
+ // are reading this after #247 landed, the filtering below is #232's and
1340
+ // this note records why it was never #235's to add.
1343
1341
  //
1344
- // It is also a DIFFERENT QUESTION. Everywhere else, linkage is metadata
1345
- // ABOUT a document. Here the linkage IS the primary data, so dropping an
1346
- // entry is a MEMBERSHIP decision about what this route serves -- the same
1347
- // class as abofs/stonyx-orm#233 and #196, not the class #234/#235 close.
1348
- // That is why it is absent from #224 §2a's seven-site inventory.
1342
+ // The three sites #235 does own (`buildResponse`'s `included`, the
1343
+ // related-resource branch above, and the two write handlers) all reach
1344
+ // the filter through `record.toJSON()`, which is where the `linkage`
1345
+ // OPTION is applied. This branch builds its `{ type, id }` objects BY
1346
+ // HAND and never calls `toJSON` at all, so the `linkage` option cannot
1347
+ // reach it -- whatever this route filters, it has to filter itself, which
1348
+ // is precisely why doing so is a separate change with a separate owner.
1349
1349
  //
1350
- // MEASURED, so the next person does not re-derive it: this route answers
1351
- // `GET /animals/1/relationships/owner` with
1352
- // `{"type":"owner","id":"angela"}` while `GET /owners/angela` is 404.
1353
- // Wiring the filter in here takes the suite to 993/2 and turns the
1350
+ // It is also a DIFFERENT QUESTION. Everywhere #235 touches, linkage is
1351
+ // metadata ABOUT a document. Here the linkage IS the primary data, so
1352
+ // dropping an entry is a MEMBERSHIP decision about what this route
1353
+ // serves -- the same class as abofs/stonyx-orm#233 and #196, not the
1354
+ // class #234/#235 close. That is why it is absent from #224 §2a's
1355
+ // seven-site inventory.
1356
+ //
1357
+ // MEASURED, so the next person does not re-derive it. Against this
1358
+ // branch's baseline of 1011/0, wiring `createLinkageFilter` into the
1359
+ // belongsTo branch below takes the suite to 1009/2, reddening
1360
+ // `[GUARD] #235 X2` and the
1354
1361
  // `GET /animals/:id/relationships/owner returns relationship linkage`
1355
- // test red -- which is #232's own reproduction, not a regression.
1356
- // Pinned unchanged by `[GUARD] #235 X2` in test/integration/orm-test.ts.
1362
+ // test -- the latter is #232's own reproduction, not a regression.
1363
+ //
1364
+ // THE BASELINE IS QUOTED WITH THE RESULT BECAUSE AN EARLIER REVISION OF
1365
+ // THIS COMMENT SAID 993/2 AND SHIPPED IT. This file lands in consumers'
1366
+ // `node_modules`, so a wrong number here is a wrong number in the
1367
+ // published package. 993+2 = 995 is the DEV baseline, carried over from
1368
+ // a branch on which `[GUARD] #235 X2` does not exist. A pass/fail pair
1369
+ // with no baseline beside it cannot be checked by reading, which is how
1370
+ // it survived three artifacts and a review; the qualitative claim was
1371
+ // right the whole time and only the count was wrong.
1372
+ //
1373
+ // `[GUARD] #235 X2` in test/integration/orm-test.ts pins the OWNERSHIP
1374
+ // BOUNDARY here rather than this route's current answer, so that it
1375
+ // survives #247 landing. Read its comment before changing it.
1357
1376
  routes[`/:id/relationships/${dasherizedName}`] = async (request, { filter } = {}) => {
1358
1377
  const record = await store.find(model, getId(request.params));
1359
1378
  if (!record)
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "stonyx-async",
5
5
  "stonyx-module"
6
6
  ],
7
- "version": "0.3.2-alpha.74",
7
+ "version": "0.3.2-alpha.76",
8
8
  "description": "",
9
9
  "main": "dist/index.js",
10
10
  "type": "module",
@@ -1454,27 +1454,46 @@ export default class OrmRequest extends Request {
1454
1454
 
1455
1455
  // Relationship linkage route: GET /:id/relationships/{relationship}
1456
1456
  //
1457
- // NO `linkage` FILTER HERE, AND IT IS NOT AN OVERSIGHT --
1458
- // abofs/stonyx-orm#232 OWNS THIS ROUTE. The three sites that carry the
1459
- // filter (`buildResponse`'s `included`, the related-resource branch
1460
- // above, and the two write handlers) all call `record.toJSON()`, which is
1461
- // where the `linkage` option is applied. This branch builds its
1462
- // `{ type, id }` objects BY HAND and never calls `toJSON` at all, so it
1463
- // cannot see a filter no matter who passes one.
1457
+ // NO `linkage` FILTER FROM abofs/stonyx-orm#235, AND THAT IS A SCOPE
1458
+ // BOUNDARY RATHER THAN AN OVERSIGHT -- abofs/stonyx-orm#232 OWNS THIS
1459
+ // ROUTE, and PR #247 is IN FLIGHT against it in this same sprint. If you
1460
+ // are reading this after #247 landed, the filtering below is #232's and
1461
+ // this note records why it was never #235's to add.
1464
1462
  //
1465
- // It is also a DIFFERENT QUESTION. Everywhere else, linkage is metadata
1466
- // ABOUT a document. Here the linkage IS the primary data, so dropping an
1467
- // entry is a MEMBERSHIP decision about what this route serves -- the same
1468
- // class as abofs/stonyx-orm#233 and #196, not the class #234/#235 close.
1469
- // That is why it is absent from #224 §2a's seven-site inventory.
1463
+ // The three sites #235 does own (`buildResponse`'s `included`, the
1464
+ // related-resource branch above, and the two write handlers) all reach
1465
+ // the filter through `record.toJSON()`, which is where the `linkage`
1466
+ // OPTION is applied. This branch builds its `{ type, id }` objects BY
1467
+ // HAND and never calls `toJSON` at all, so the `linkage` option cannot
1468
+ // reach it -- whatever this route filters, it has to filter itself, which
1469
+ // is precisely why doing so is a separate change with a separate owner.
1470
1470
  //
1471
- // MEASURED, so the next person does not re-derive it: this route answers
1472
- // `GET /animals/1/relationships/owner` with
1473
- // `{"type":"owner","id":"angela"}` while `GET /owners/angela` is 404.
1474
- // Wiring the filter in here takes the suite to 993/2 and turns the
1471
+ // It is also a DIFFERENT QUESTION. Everywhere #235 touches, linkage is
1472
+ // metadata ABOUT a document. Here the linkage IS the primary data, so
1473
+ // dropping an entry is a MEMBERSHIP decision about what this route
1474
+ // serves -- the same class as abofs/stonyx-orm#233 and #196, not the
1475
+ // class #234/#235 close. That is why it is absent from #224 §2a's
1476
+ // seven-site inventory.
1477
+ //
1478
+ // MEASURED, so the next person does not re-derive it. Against this
1479
+ // branch's baseline of 1011/0, wiring `createLinkageFilter` into the
1480
+ // belongsTo branch below takes the suite to 1009/2, reddening
1481
+ // `[GUARD] #235 X2` and the
1475
1482
  // `GET /animals/:id/relationships/owner returns relationship linkage`
1476
- // test red -- which is #232's own reproduction, not a regression.
1477
- // Pinned unchanged by `[GUARD] #235 X2` in test/integration/orm-test.ts.
1483
+ // test -- the latter is #232's own reproduction, not a regression.
1484
+ //
1485
+ // THE BASELINE IS QUOTED WITH THE RESULT BECAUSE AN EARLIER REVISION OF
1486
+ // THIS COMMENT SAID 993/2 AND SHIPPED IT. This file lands in consumers'
1487
+ // `node_modules`, so a wrong number here is a wrong number in the
1488
+ // published package. 993+2 = 995 is the DEV baseline, carried over from
1489
+ // a branch on which `[GUARD] #235 X2` does not exist. A pass/fail pair
1490
+ // with no baseline beside it cannot be checked by reading, which is how
1491
+ // it survived three artifacts and a review; the qualitative claim was
1492
+ // right the whole time and only the count was wrong.
1493
+ //
1494
+ // `[GUARD] #235 X2` in test/integration/orm-test.ts pins the OWNERSHIP
1495
+ // BOUNDARY here rather than this route's current answer, so that it
1496
+ // survives #247 landing. Read its comment before changing it.
1478
1497
  routes[`/:id/relationships/${dasherizedName}`] = async (request: OrmRequest$, { filter }: { [key: string]: unknown } = {}) => {
1479
1498
  const record = await store.find(model, getId(request.params)) as OrmRecord | undefined;
1480
1499
  if (!record) return 404;