@stonyx/orm 0.3.2-alpha.92 → 0.3.2-alpha.94

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.
Files changed (2) hide show
  1. package/README.md +65 -3
  2. package/package.json +7 -5
package/README.md CHANGED
@@ -318,16 +318,78 @@ await setupRestServer('/', './access');
318
318
  Access classes define models and provide custom filtering/authorization logic:
319
319
 
320
320
  ```js
321
- export default class GlobalAccess {
322
- models = ['owner', 'animal'];
321
+ export default class OwnerAccess {
322
+ models = ['owner'];
323
323
 
324
324
  access(request) {
325
- if (request.url.endsWith('/owner/angela')) return false;
325
+ // `access` runs after route matching, so `request.params` is populated and
326
+ // `id` has already been URL-decoded. Authorize on it.
327
+ const { id } = request.params;
328
+
329
+ // Returning false explicitly denies access to this record
330
+ if (id === 'angela') return false;
331
+
332
+ // No `id` means the collection route. Returning a function plugs it in to
333
+ // the response object as a filter
334
+ if (id === undefined) return record => record.id !== 'angela';
335
+
336
+ // Returning a list of operations allows full access to everything else
326
337
  return ['read', 'create', 'update', 'delete'];
327
338
  }
328
339
  }
329
340
  ```
330
341
 
342
+ **Do not authorize on the request URL.** `request.url` is rewritten relative to
343
+ the mount point, so inside the REST server it is `/angela`, not `/owners/angela`
344
+ — a suffix comparison against it never matches and the request falls through to
345
+ whatever the method returns next. `request.originalUrl` keeps the full path but
346
+ is still the raw text the client sent, so it varies with query strings
347
+ (`/owners?x=1`), trailing slashes (`/owners/angela/`), casing (`/OwNeRs/angela`,
348
+ which Express routes to the same handler) and percent-encoding
349
+ (`/owners/%61ngela`). Each of those is a plain address-bar request, and each one
350
+ slips past a URL predicate. `request.params.id` is identical for all of them.
351
+
352
+ **One access class per model when the rules are model-specific.** `access()`
353
+ receives only the request, and the request does not carry the model name in any
354
+ form that is safe to match on — `request.baseUrl` is the mount text as the
355
+ client spelled it (`/OWNERS`), not the model. A class may still list several
356
+ models in `models` when they share one rule.
357
+
358
+ The sample above is executed verbatim against a live server by
359
+ `test/integration/readme-access/`, which reads it out of this file: the request
360
+ `DELETE /owners/angela` is asserted to return 403 with the record intact, along
361
+ with each of the spellings named above.
362
+
363
+ ### Upgrading: behaviour changes
364
+
365
+ **Advertised `links.self` / `links.related` now carry the REST mount route.**
366
+ Consumer-visible for any deployment where `orm.restServer.route` is not the
367
+ default `'/'`.
368
+
369
+ Measured on this repo's mounted-route harness at `ORM_REST_ROUTE='/api'`, resource
370
+ `links.self` in the response to `GET /api/animals/1`:
371
+
372
+ | | published `links.self` | fetching that URL |
373
+ |---|---|---|
374
+ | before | `http://host/animals/1` | **404** |
375
+ | after | `http://host/api/animals/1` | **200** |
376
+
377
+ The ORM previously built links from the request origin alone, so at any non-default
378
+ mount every URL it advertised pointed at a route that did not exist
379
+ (abofs/stonyx-orm#254). Links are now built from the path the routes are actually
380
+ mounted at, and are followable verbatim.
381
+
382
+ **⚠️ Breaking if you carry a prepending workaround.** The usual workaround for #254
383
+ was for the client to prepend the mount to every link the ORM published. That now
384
+ double-prefixes: prepending `/api` to the new `http://host/api/animals/1` yields
385
+ `http://host/api/api/animals/1`, measured **404**. Remove the prepending. There is no
386
+ configuration flag that restores the old link shape.
387
+
388
+ **Unaffected.** Deployments on the default `ORM_REST_ROUTE='/'` see byte-identical
389
+ output — the prefix is empty, and this is pinned by a byte-identity test against a
390
+ golden fixture captured *before* the fix. Response structure, field names and the
391
+ public API are unchanged; only the URL value inside `links` changes.
392
+
331
393
  ### Include Parameter (Sideloading Relationships)
332
394
 
333
395
  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.
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "stonyx-async",
5
5
  "stonyx-module"
6
6
  ],
7
- "version": "0.3.2-alpha.92",
7
+ "version": "0.3.2-alpha.94",
8
8
  "description": "",
9
9
  "main": "dist/index.js",
10
10
  "type": "module",
@@ -61,8 +61,8 @@
61
61
  },
62
62
  "homepage": "https://github.com/abofs/stonyx-orm#readme",
63
63
  "dependencies": {
64
- "@stonyx/cron": "0.2.1-beta.95",
65
- "@stonyx/events": "0.1.1-beta.52",
64
+ "@stonyx/cron": "0.2.1-beta.98",
65
+ "@stonyx/events": "0.1.1-beta.54",
66
66
  "@stonyx/utils": "0.2.3-beta.26",
67
67
  "stonyx": "0.2.3-beta.81"
68
68
  },
@@ -91,7 +91,7 @@
91
91
  }
92
92
  },
93
93
  "devDependencies": {
94
- "@stonyx/rest-server": "0.2.1-beta.98",
94
+ "@stonyx/rest-server": "0.2.1-beta.100",
95
95
  "@types/node": "^25.6.0",
96
96
  "mysql2": "^3.20.0",
97
97
  "pg": "^8.20.0",
@@ -103,8 +103,10 @@
103
103
  "scripts": {
104
104
  "build": "tsc",
105
105
  "build:test": "tsc -p tsconfig.test.json",
106
- "test": "pnpm build && NODE_ENV=test node --import tsx/esm --import ./test/setup.ts node_modules/qunit/bin/qunit.js 'test/**/*-test.ts' && ORM_TEST_ROUTE=/ pnpm test:mounted && ORM_TEST_ROUTE=/api pnpm test:mounted && ORM_TEST_ROUTE=api pnpm test:mounted && ORM_TEST_ROUTE=/api/v1 pnpm test:mounted && ORM_TEST_ROUTE=/api/ pnpm test:mounted",
106
+ "test": "pnpm build && NODE_ENV=test node --import tsx/esm --import ./test/setup.ts node_modules/qunit/bin/qunit.js 'test/**/*-test.ts' && ORM_TEST_ROUTE=/ pnpm test:mounted && ORM_TEST_ROUTE=/api pnpm test:mounted && ORM_TEST_ROUTE=api pnpm test:mounted && ORM_TEST_ROUTE=/api/v1 pnpm test:mounted && ORM_TEST_ROUTE=/api/ pnpm test:mounted && pnpm test:readme && pnpm test:reference",
107
107
  "test:mounted": "node --import tsx/esm --import ./test/integration/mounted-route/setup.ts node_modules/qunit/bin/qunit.js 'test/integration/mounted-route/links-mounted.ts' 'test/zz-exit-test.ts'",
108
+ "test:readme": "node --import tsx/esm --import ./test/integration/readme-access/setup.ts node_modules/qunit/bin/qunit.js 'test/integration/readme-access/readme-sample.ts' 'test/zz-exit-test.ts'",
109
+ "test:reference": "node --import tsx/esm --import ./test/integration/reference-access/setup.ts node_modules/qunit/bin/qunit.js 'test/integration/reference-access/reference-sample.ts' 'test/zz-exit-test.ts'",
108
110
  "test:dynamodb": "pnpm build && node --import tsx/esm --import ./test/integration/dynamodb/setup.ts node_modules/qunit/bin/qunit.js 'test/integration/dynamodb/**/*-test.ts'"
109
111
  }
110
112
  }