@stonyx/orm 0.3.2-alpha.119 → 0.3.2-alpha.120

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 +22 -9
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -9,7 +9,7 @@ A lightweight ORM for Stonyx projects, featuring model definitions, serializers,
9
9
 
10
10
  ## Highlights
11
11
 
12
- - **Automatic Loading**: Models, serializers, transforms, and access classes are auto-registered from their configured directories. Models, serializers, transforms and views are also loaded from subdirectories and named after their path (`models/admin/user-role.ts` registers `admin/user-role`); access classes are loaded only from the top level of `paths.access`, and one in a subdirectory is silently never loaded.
12
+ - **Automatic Loading**: Models, serializers, transforms, views and access classes are auto-registered from their configured directories. Models, serializers, transforms and views are also loaded from subdirectories and named after their path (`models/admin/user-role.ts` registers `admin/user-role`); access classes are loaded only from the top level of `paths.access`, and one in a subdirectory is silently never loaded.
13
13
  - **Models**: Define attributes with type-safe proxies (`attr`) and relationships (`hasMany`, `belongsTo`).
14
14
  - **Serializers**: Map raw data into model-friendly structures, including nested properties.
15
15
  - **Transforms**: Apply custom transformations on data values automatically.
@@ -313,7 +313,7 @@ In directory mode, each collection is stored as `{directory}/{collection}.json`
313
313
 
314
314
  ### MySQL Mode
315
315
 
316
- Set the `MYSQL_HOST` environment variable to enable MySQL persistence. The ORM loads all records into memory on startup and persists CRUD operations to MySQL automatically. Supports schema-aware migration generation, apply, rollback, and drift detection.
316
+ Set the `MYSQL_HOST` environment variable to enable MySQL persistence. At startup the ORM loads into memory only the records of models declared `static memory = true`; every other model (the default is `memory = false`) is read from MySQL on demand by `store.find()` / `store.findAll()`, and the synchronous `store.get()` does not see it (see [docs/internals.md](https://github.com/abofs/stonyx-orm/blob/dev/docs/internals.md#store-reads-and-the-memory-flag)). A record read that way resolves a `belongsTo` only when the related record is already in memory; otherwise the relationship is `null`. CRUD operations are persisted to MySQL automatically. Supports schema-aware migration generation, apply, rollback, and drift detection.
317
317
 
318
318
  | Command | Description |
319
319
  |---------|-------------|
@@ -442,6 +442,11 @@ unrecognised spelling falls through to whatever your method returns next, so
442
442
  prefer one class per model. A class may still list several models in `models`
443
443
  when they share one rule.
444
444
 
445
+ `access()` covers a model's own routes only: records of that model served
446
+ through another model's `?include=`, `/:id/<relationship>` or
447
+ `/:id/relationships/<relationship>` routes are never
448
+ checked against it. See [docs/security.md](https://github.com/abofs/stonyx-orm/blob/dev/docs/security.md).
449
+
445
450
  **Numeric ids: authorize on `request.recordId`, not on `request.params.id`.**
446
451
  `params.id` is raw text from the client. When it looks numeric the ORM coerces
447
452
  it *before* it resolves the record, so `7`, `007`, `7.0`, `7.9`, `7e0`, `0x7`,
@@ -639,6 +644,7 @@ GET /animals/1
639
644
  #### Limitations
640
645
 
641
646
  - Only available on GET endpoints (not POST/PATCH)
647
+ - Included records are authorized only by the requested model's `access()`, not by their own model's, and `include` has no built-in cap on depth or on the number of paths; a long `include` blocks the server while it is walked. [docs/security.md](https://github.com/abofs/stonyx-orm/blob/dev/docs/security.md) has the measurements and an `access()` check that caps both.
642
648
 
643
649
  ## Lifecycle Hooks
644
650
 
@@ -737,8 +743,11 @@ beforeHook('create', 'animal', (context) => {
737
743
  // resolved the record by. `context.params.id` is the raw client text and is a
738
744
  // different value for every alias of the same id, so a lookup keyed on it
739
745
  // finds nothing on a numeric-id model (abofs/stonyx-orm#270).
740
- beforeHook('delete', 'animal', (context) => {
741
- const animal = store.get('animal', context.request.recordId);
746
+ // `store.find` rather than `store.get`: on MySQL/PostgreSQL a model without
747
+ // `static memory = true` is not in memory, and `get` returns undefined.
748
+ // Before hooks are awaited, so an async hook works here.
749
+ beforeHook('delete', 'animal', async (context) => {
750
+ const animal = await store.find('animal', context.request.recordId);
742
751
  if (animal.protected) {
743
752
  return { errors: [{ detail: 'Cannot delete protected animals' }] };
744
753
  }
@@ -769,8 +778,9 @@ beforeHook('create', 'owner', (context) => {
769
778
  // Send notification after animal is adopted.
770
779
  // `oldState` holds attributes only, and a belongsTo is the related Record, not
771
780
  // its id: capture the previous owner's id before the update, compare ids after.
772
- beforeHook('update', 'animal', (context) => {
773
- context.state.previousOwnerId = store.get('animal', context.request.recordId)?.owner?.id;
781
+ // On SQL the owner resolves only when it is in memory (see MySQL Mode).
782
+ beforeHook('update', 'animal', async (context) => {
783
+ context.state.previousOwnerId = (await store.find('animal', context.request.recordId))?.owner?.id;
774
784
  });
775
785
 
776
786
  afterHook('update', 'animal', async (context) => {
@@ -839,10 +849,13 @@ afterHook('delete', 'animal', async (context) => {
839
849
  // same value `access()` is handed. Authorizing on `context.params.id` instead
840
850
  // looks up the raw client text: on a numeric-id model that lookup returns
841
851
  // `undefined` for EVERY spelling, `animal.owner` throws, and the check never
842
- // runs. Measured; abofs/stonyx-orm#270.
843
- beforeHook('delete', 'animal', (context) => {
852
+ // runs. Measured; abofs/stonyx-orm#270. `store.find`, not `store.get`: on SQL a
853
+ // model without `static memory = true` is not in memory. On SQL `animal.owner`
854
+ // resolves only when the owner is in memory too, and is `null` otherwise, so this
855
+ // check then refuses the owner as well (see MySQL Mode).
856
+ beforeHook('delete', 'animal', async (context) => {
844
857
  const user = context.state.currentUser;
845
- const animal = store.get('animal', context.request.recordId);
858
+ const animal = await store.find('animal', context.request.recordId);
846
859
 
847
860
  // `animal.owner` is the related Record, so compare its id. Refuse an unowned
848
861
  // animal outright: `animal.owner?.id !== user.id` lets a user with no id
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "stonyx-async",
5
5
  "stonyx-module"
6
6
  ],
7
- "version": "0.3.2-alpha.119",
7
+ "version": "0.3.2-alpha.120",
8
8
  "description": "",
9
9
  "main": "dist/index.js",
10
10
  "type": "module",