stonyx 0.2.3-beta.9 → 0.2.3-beta.90

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 (67) hide show
  1. package/README.md +16 -5
  2. package/config/{environment.js → environment copy.ts } +16 -12
  3. package/dist/cli/help.d.ts +8 -0
  4. package/dist/cli/help.js +43 -0
  5. package/dist/cli/load-commands.d.ts +10 -0
  6. package/dist/cli/load-commands.js +49 -0
  7. package/dist/cli/new.d.ts +26 -0
  8. package/dist/cli/new.js +293 -0
  9. package/dist/cli/serve.d.ts +6 -0
  10. package/dist/cli/serve.js +49 -0
  11. package/dist/cli/test-setup.d.ts +1 -0
  12. package/dist/cli/test-setup.js +30 -0
  13. package/dist/cli/test.d.ts +4 -0
  14. package/dist/cli/test.js +23 -0
  15. package/dist/cli.d.ts +2 -0
  16. package/dist/cli.js +53 -0
  17. package/dist/exports/config.d.ts +2 -0
  18. package/dist/exports/config.js +2 -0
  19. package/dist/exports/log.d.ts +2 -0
  20. package/{src → dist}/exports/log.js +1 -2
  21. package/dist/exports/test-helpers.d.ts +5 -0
  22. package/dist/exports/test-helpers.js +7 -0
  23. package/dist/lifecycle.d.ts +7 -0
  24. package/dist/lifecycle.js +18 -0
  25. package/dist/main.d.ts +22 -0
  26. package/dist/main.js +98 -0
  27. package/dist/modules.d.ts +5 -0
  28. package/dist/modules.js +93 -0
  29. package/dist/util/import-config.d.ts +1 -0
  30. package/dist/util/import-config.js +10 -0
  31. package/dist/util/resolve-entry-point.d.ts +1 -0
  32. package/dist/util/resolve-entry-point.js +14 -0
  33. package/dist/util/resolve-module-name.d.ts +14 -0
  34. package/dist/util/resolve-module-name.js +33 -0
  35. package/package.json +41 -12
  36. package/scripts/postinstall.js +24 -2
  37. package/.claude/index.md +0 -96
  38. package/.github/workflows/ci.yml +0 -16
  39. package/.github/workflows/publish.yml +0 -51
  40. package/docs/api.md +0 -89
  41. package/docs/cli.md +0 -78
  42. package/docs/configuration.md +0 -72
  43. package/docs/conventions/discord-conventions.md +0 -62
  44. package/docs/conventions/framework-modules.md +0 -127
  45. package/docs/conventions/index.md +0 -24
  46. package/docs/conventions/orm-conventions.md +0 -158
  47. package/docs/conventions/project-structure.md +0 -105
  48. package/docs/conventions/rest-conventions.md +0 -96
  49. package/docs/conventions/testing-conventions.md +0 -56
  50. package/docs/developing-modules.md +0 -108
  51. package/docs/index.md +0 -15
  52. package/docs/lifecycle.md +0 -47
  53. package/docs/logging.md +0 -52
  54. package/docs/modules.md +0 -78
  55. package/docs/testing.md +0 -64
  56. package/src/cli/help.js +0 -48
  57. package/src/cli/load-commands.js +0 -56
  58. package/src/cli/new.js +0 -237
  59. package/src/cli/serve.js +0 -38
  60. package/src/cli/test-setup.js +0 -8
  61. package/src/cli/test.js +0 -27
  62. package/src/cli.js +0 -63
  63. package/src/exports/config.js +0 -3
  64. package/src/exports/test-helpers.js +0 -8
  65. package/src/lifecycle.js +0 -17
  66. package/src/main.js +0 -101
  67. package/src/modules.js +0 -112
@@ -1,51 +0,0 @@
1
- name: Publish to NPM
2
-
3
- on:
4
- repository_dispatch:
5
- types: [cascade-publish]
6
- workflow_dispatch:
7
- inputs:
8
- version-type:
9
- description: 'Version type'
10
- required: true
11
- type: choice
12
- options:
13
- - patch
14
- - minor
15
- - major
16
- custom-version:
17
- description: 'Custom version (optional, overrides version-type)'
18
- required: false
19
- type: string
20
- pull_request:
21
- types: [opened, synchronize, reopened]
22
- branches: [main]
23
- push:
24
- branches: [main]
25
-
26
- concurrency:
27
- group: ${{ github.event_name == 'repository_dispatch' && 'cascade-update' || format('publish-{0}', github.ref) }}
28
- cancel-in-progress: false
29
-
30
- permissions:
31
- contents: write
32
- id-token: write
33
- pull-requests: write
34
-
35
- jobs:
36
- publish:
37
- if: "!contains(github.event.head_commit.message, '[skip ci]')"
38
- uses: abofs/stonyx-workflows/.github/workflows/npm-publish.yml@main
39
- with:
40
- version-type: ${{ github.event.inputs.version-type }}
41
- custom-version: ${{ github.event.inputs.custom-version }}
42
- cascade-source: ${{ github.event.client_payload.source_package || '' }}
43
- secrets: inherit
44
-
45
- cascade:
46
- needs: publish
47
- uses: abofs/stonyx-workflows/.github/workflows/cascade.yml@main
48
- with:
49
- package-name: ${{ needs.publish.outputs.package-name }}
50
- published-version: ${{ needs.publish.outputs.published-version }}
51
- secrets: inherit
package/docs/api.md DELETED
@@ -1,89 +0,0 @@
1
- # API Reference
2
-
3
- Public exports available from the `stonyx` package.
4
-
5
- ## Exports Map
6
-
7
- | Import Path | Export | Description |
8
- |-------------|--------|-------------|
9
- | `stonyx` | `default` (Stonyx class) | Main framework class (singleton) |
10
- | `stonyx` | `waitForModule(name)` | Wait for an async module to be ready |
11
- | `stonyx/config` | `default` (config object) | Live reference to Stonyx configuration |
12
- | `stonyx/log` | `default` (Chronicle instance) | Live reference to Chronicle logger |
13
- | `stonyx/lifecycle` | `runStartupHooks(modules)` | Run startup hooks on a module array |
14
- | `stonyx/lifecycle` | `runShutdownHooks(modules)` | Run shutdown hooks in reverse order |
15
- | `stonyx/test-helpers` | `setupIntegrationTests(hooks)` | QUnit hook for integration test setup |
16
-
17
- ## Stonyx Class
18
-
19
- ```js
20
- import Stonyx from 'stonyx';
21
- ```
22
-
23
- ### Constructor
24
-
25
- ```js
26
- new Stonyx(config, rootPath)
27
- ```
28
-
29
- - **config** — Full environment configuration object
30
- - **rootPath** — Absolute path to the project root
31
-
32
- Returns the existing instance if one already exists (singleton pattern).
33
-
34
- ### Static Properties
35
-
36
- | Property | Type | Description |
37
- |----------|------|-------------|
38
- | `Stonyx.instance` | `Stonyx` | The singleton instance |
39
- | `Stonyx.ready` | `Promise` | Resolves when all modules are initialized |
40
- | `Stonyx.initialized` | `boolean` | Whether Stonyx has started initialization |
41
-
42
- ### Static Getters
43
-
44
- | Getter | Returns | Throws |
45
- |--------|---------|--------|
46
- | `Stonyx.config` | Config object | If not initialized |
47
- | `Stonyx.log` | Chronicle instance | If not initialized |
48
-
49
- ### Instance Properties
50
-
51
- | Property | Type | Description |
52
- |----------|------|-------------|
53
- | `instance.config` | `object` | Merged environment configuration |
54
- | `instance.chronicle` | `Chronicle` | Logger instance |
55
- | `instance.modules` | `Array` | Loaded module instances |
56
-
57
- ## waitForModule
58
-
59
- ```js
60
- import { waitForModule } from 'stonyx';
61
-
62
- await waitForModule('rest-server');
63
- ```
64
-
65
- Waits for a specific `@stonyx/*` module to complete initialization. Pass the module name **without** the `@stonyx/` prefix.
66
-
67
- Throws if the module is not registered in project dependencies.
68
-
69
- ## Lifecycle Functions
70
-
71
- ```js
72
- import { runStartupHooks, runShutdownHooks } from 'stonyx/lifecycle';
73
- ```
74
-
75
- ### runStartupHooks(modules)
76
-
77
- Calls `startup()` on each module in array order. Skips modules without a `startup` method.
78
-
79
- ### runShutdownHooks(modules)
80
-
81
- Calls `shutdown()` on each module in **reverse** array order. Errors are caught and logged — one failing hook does not prevent others from running.
82
-
83
- ## setupIntegrationTests
84
-
85
- ```js
86
- import { setupIntegrationTests } from 'stonyx/test-helpers';
87
- ```
88
-
89
- Registers a QUnit `before` hook that waits for `Stonyx.ready`. Use within a `module()` block to ensure Stonyx is fully initialized before tests run.
package/docs/cli.md DELETED
@@ -1,78 +0,0 @@
1
- # CLI
2
-
3
- Stonyx includes a CLI that handles bootstrapping, module initialization, and application execution.
4
-
5
- ## Usage
6
-
7
- ```bash
8
- stonyx <command> [...args]
9
- ```
10
-
11
- ## Built-in Commands
12
-
13
- | Command | Alias | Description |
14
- |---------|-------|-------------|
15
- | `serve` | `s` | Bootstrap Stonyx and run the app |
16
- | `test` | `t` | Bootstrap Stonyx in test mode and run tests |
17
- | `help` | `h` | Show available commands |
18
-
19
- ### serve
20
-
21
- Bootstraps Stonyx (loads config, initializes modules, runs lifecycle hooks), then imports your application entry point.
22
-
23
- ```bash
24
- stonyx serve # Runs app.js by default
25
- stonyx serve --entry custom.js # Runs a custom entry file
26
- ```
27
-
28
- The serve command also registers `SIGTERM` and `SIGINT` handlers that run [shutdown hooks](lifecycle.md) before exiting.
29
-
30
- ### test
31
-
32
- Runs your test suite using [QUnit](https://qunitjs.com/) with automatic Stonyx bootstrapping. Sets `NODE_ENV=test` and applies any [test config overrides](configuration.md#test-environment-overrides).
33
-
34
- ```bash
35
- stonyx test # Runs test/**/*-test.js by default
36
- stonyx test "test/unit/**/*.js" # Custom test glob
37
- ```
38
-
39
- ### help
40
-
41
- Displays all available commands, including any [module commands](#module-commands).
42
-
43
- ```bash
44
- stonyx help
45
- ```
46
-
47
- ## Module Commands
48
-
49
- Stonyx modules can register custom CLI commands by exporting a `./commands` entry in their `package.json`. These are automatically discovered and available through the CLI.
50
-
51
- ```json
52
- {
53
- "exports": {
54
- "./commands": "./src/commands.js"
55
- }
56
- }
57
- ```
58
-
59
- The commands file should export an object mapping command names to definitions:
60
-
61
- ```js
62
- export default {
63
- 'db:migrate': {
64
- description: 'Run database migrations',
65
- bootstrap: true,
66
- run: async ({ args, cwd }) => { /* ... */ }
67
- }
68
- };
69
- ```
70
-
71
- - **`bootstrap: true`** — Stonyx will be fully initialized before the command runs
72
- - **`bootstrap: false`** — The command runs without Stonyx initialization
73
-
74
- Module commands appear under "Module commands" in `stonyx help` output. If two modules register the same command name, the first one loaded wins and a warning is printed.
75
-
76
- ## Environment Variables
77
-
78
- The CLI automatically loads `.env` files via `process.loadEnvFile()` before executing any command.
@@ -1,72 +0,0 @@
1
- # Configuration
2
-
3
- Stonyx uses a centralized configuration file that all modules read from at startup.
4
-
5
- ## Environment Config
6
-
7
- Your project's configuration lives at `config/environment.js`:
8
-
9
- ```js
10
- const { DEBUG, NODE_ENV } = process.env;
11
-
12
- const environment = NODE_ENV ?? 'development';
13
-
14
- export default {
15
- environment,
16
- debug: DEBUG ?? environment === 'development',
17
-
18
- // Module-specific configuration
19
- restServer: { logColor: 'yellow', port: 3000 },
20
- cron: { log: true },
21
- };
22
- ```
23
-
24
- This file is auto-generated on `npm install` via the postinstall script if it doesn't already exist.
25
-
26
- > **Note:** `config/environment.js` is gitignored by default so each environment can have its own settings.
27
-
28
- ## Module Configuration
29
-
30
- Each Stonyx module reads its configuration from a top-level key matching its camelCase name. For example, `@stonyx/rest-server` reads from `config.restServer`.
31
-
32
- Async modules ship with their own default config at `config/environment.js` inside the module package. Your project config is merged on top of these defaults — you only need to specify overrides.
33
-
34
- ## Logging Configuration
35
-
36
- Any config key with a `logColor` property automatically creates a [Chronicle](logging.md) log type:
37
-
38
- ```js
39
- export default {
40
- myService: {
41
- logColor: 'purple', // Required — enables log creation
42
- logMethod: 'highlight', // Optional — custom method name (defaults to key name)
43
- logTimestamp: true, // Optional — include timestamps
44
- },
45
- };
46
- ```
47
-
48
- This works for both module configs and custom service configs. See [Logging](logging.md) for details.
49
-
50
- ## Test Environment Overrides
51
-
52
- When `NODE_ENV=test`, Stonyx automatically looks for `test/config/environment.js` in your project root:
53
-
54
- ```js
55
- // test/config/environment.js
56
- export default {
57
- debug: false,
58
- restServer: { port: 0 },
59
- };
60
- ```
61
-
62
- These overrides are deep-merged into the main config using in-place mutation, so any existing references (like `stonyx/config` exports) stay valid.
63
-
64
- ## Accessing Config at Runtime
65
-
66
- ```js
67
- import config from 'stonyx/config';
68
-
69
- console.log(config.environment); // 'development', 'test', etc.
70
- ```
71
-
72
- The `stonyx/config` export is a live reference to the Stonyx instance config. It will throw if accessed before Stonyx is initialized.
@@ -1,62 +0,0 @@
1
- # Discord Conventions
2
-
3
- Conventions for projects using `@stonyx/discord`.
4
-
5
- ## Command Files
6
-
7
- - One class per file in the configured command directory (default: `discord-commands/`)
8
- - Each file default-exports a class extending `Command` from `@stonyx/discord`
9
- - Class must define a `data` property (a `SlashCommandBuilder` instance) and an `async execute(interaction)` method
10
- - Class ordering: static properties → `data` → `execute()`
11
- - Filenames use kebab-case; `forEachFileImport` converts to camelCase for internal registration
12
- - Permission checks belong in the consuming application, not in the module
13
-
14
- ```js
15
- import { Command } from '@stonyx/discord';
16
- import { SlashCommandBuilder } from 'discord.js';
17
-
18
- export default class PingCommand extends Command {
19
- data = new SlashCommandBuilder()
20
- .setName('ping')
21
- .setDescription('Replies with Pong!');
22
-
23
- async execute(interaction) {
24
- await interaction.reply('Pong!');
25
- }
26
- }
27
- ```
28
-
29
- ## Event Handler Files
30
-
31
- - One class per file in the configured event directory (default: `discord-events/`)
32
- - Each file default-exports a class extending `EventHandler` from `@stonyx/discord`
33
- - Class must set `static event` to a valid Discord.js event name (e.g., `'messageCreate'`, `'voiceStateUpdate'`)
34
- - Class must implement a `handle(...args)` method matching the Discord.js event signature
35
- - Class ordering: static properties → `handle()`
36
-
37
- ```js
38
- import { EventHandler } from '@stonyx/discord';
39
-
40
- export default class WelcomeHandler extends EventHandler {
41
- static event = 'guildMemberAdd';
42
-
43
- handle(member) {
44
- // Welcome new member
45
- }
46
- }
47
- ```
48
-
49
- ## Configuration
50
-
51
- - Config namespace: `config.discord`
52
- - Environment variables prefixed with `DISCORD_`
53
- - Logging: `log.discord()` — configured via `logColor: '#7289da'` and `logMethod: 'discord'`
54
- - Intent auto-derivation: the module computes required gateway intents from discovered event handlers; use `additionalIntents` in config for edge cases
55
-
56
- ## Module Keywords
57
-
58
- ```json
59
- "keywords": ["stonyx-async", "stonyx-module"]
60
- ```
61
-
62
- `stonyx-async` indicates the module's `init()` is async and must be awaited by the framework.
@@ -1,127 +0,0 @@
1
- # Framework Modules
2
-
3
- When to use which `@stonyx/*` module. Always check these before reaching for Node built-ins or npm packages.
4
-
5
- ## `stonyx/log`
6
-
7
- All logging goes through `stonyx/log`. Never use `console.log`, `console.warn`, or `console.error`.
8
-
9
- ```js
10
- import log from 'stonyx/log';
11
-
12
- log.info('Server started');
13
- log.error('Connection failed', error);
14
- ```
15
-
16
- Custom log types can be configured in `config/environment.js`:
17
-
18
- ```js
19
- export default {
20
- socket: { logColor: 'magenta' }
21
- }
22
- ```
23
-
24
- ## `@stonyx/utils`
25
-
26
- Utility library organized by domain. Always check here before using Node built-ins or adding npm dependencies.
27
-
28
- ### File I/O (`@stonyx/utils/file`)
29
-
30
- **`fs` is explicitly prohibited.** Use these instead:
31
-
32
- - `createFile(filePath, data, options?)` — write a new file (creates parent dirs)
33
- - `updateFile(filePath, data, options?)` — atomic update via swap file
34
- - `copyFile(sourcePath, targetPath, options?)` — copy with optional overwrite
35
- - `readFile(filePath, options?)` — read file, supports `{ json: true }` and `{ missingFileCallback }`
36
- - `deleteFile(filePath, options?)` — delete file, supports `{ ignoreAccessFailure: true }`
37
- - `createDirectory(dir)` — recursive mkdir
38
- - `deleteDirectory(dir)` — recursive rm
39
- - `forEachFileImport(dir, callback, options?)` — iterate and dynamically import all `.js` files in a directory
40
- - `fileExists(filePath)` — check existence
41
-
42
- ### Object Manipulation (`@stonyx/utils/object`)
43
-
44
- - `deepCopy(obj)` — deep clone via JSON
45
- - `objToJson(obj, format?)` — stringify with formatting
46
- - `makeArray(obj)` — wrap in array if not already
47
- - `mergeObject(obj1, obj2, options?)` — deep merge objects, supports `{ ignoreNewKeys: true }`
48
- - `get(obj, path)` — dot-path property access (e.g., `get(obj, 'a.b.c')`)
49
- - `getOrSet(map, key, defaultValue)` — get from Map or set default (supports factory functions)
50
-
51
- ### String Transforms (`@stonyx/utils/string`)
52
-
53
- - `kebabCaseToCamelCase(str)` — `'my-thing'` → `'myThing'`
54
- - `kebabCaseToPascalCase(str)` — `'my-thing'` → `'MyThing'`
55
- - `camelCaseToKebabCase(str)` — `'myThing'` → `'my-thing'`
56
- - `generateRandomString(length?)` — alphanumeric random string (default 8 chars)
57
- - `pluralize(str)` — basic pluralization
58
-
59
- ### Date / Timestamp (`@stonyx/utils/date`)
60
-
61
- - `getTimestamp(dateObject?)` — Unix timestamp in seconds (current time if no arg)
62
-
63
- ### Promises (`@stonyx/utils/promise`)
64
-
65
- - `sleep(seconds)` — async delay
66
-
67
- ### Interactive Prompts (`@stonyx/utils/prompt`)
68
-
69
- - `confirm(question)` — yes/no prompt, returns boolean
70
- - `prompt(question)` — free-text input, returns string
71
-
72
- File issues on `stonyx-utils` for any gaps rather than adding workarounds or npm dependencies.
73
-
74
- ## `@stonyx/events`
75
-
76
- All pub/sub event handling. Never create custom event emitters.
77
-
78
- - `setup(eventNames)` — register valid event names
79
- - `subscribe(event, callback)` — listen for events
80
- - `once(event, callback)` — single-fire listener
81
- - `unsubscribe(event, callback)` — remove listener
82
- - `emit(event, ...args)` — fire event
83
- - `clear(event)` / `reset()` — cleanup
84
-
85
- ## `@stonyx/cron`
86
-
87
- All scheduled and interval tasks. Never use raw `setInterval` or `setTimeout` for recurring work.
88
-
89
- - `register(key, callback, interval, runOnInit?)` — schedule a recurring job
90
- - `unregister(key)` — cancel a job
91
-
92
- Configurable via `config/environment.js`:
93
-
94
- ```js
95
- export default {
96
- cron: { log: true }
97
- }
98
- ```
99
-
100
- ## `@stonyx/sockets`
101
-
102
- WebSocket handlers. Class ordering: static properties → `server()` method → `client()` method.
103
-
104
- Exports: `SocketServer`, `SocketClient`, `Handler`, `Sockets`
105
-
106
- ## `@stonyx/discord`
107
-
108
- Discord bot with command and event handler auto-discovery. Class ordering: static properties → `data` / `static event` → `execute()` / `handle()`.
109
-
110
- Exports: `DiscordBot`, `Command`, `EventHandler`, `Discord`, `chunkMessage`
111
-
112
- ## `@stonyx/oauth`
113
-
114
- OAuth providers. Class ordering: constructor with `super()` → async methods → transform methods.
115
-
116
- Key methods: `getAuthorizationUrl()`, `handleCallback()`, `getSession()`, `logout()`
117
-
118
- ## `@abofs/code-conventions`
119
-
120
- Shared lint and formatting config. Import and spread; never define local rules.
121
-
122
- Exports:
123
- - `@abofs/code-conventions/eslint` — ESLint config
124
- - `@abofs/code-conventions/prettier` — Prettier config
125
- - `@abofs/code-conventions/eslint-ember` — Ember-specific ESLint
126
- - `@abofs/code-conventions/template-lint` — Template linting
127
- - `@abofs/code-conventions/lint-staged` — Lint-staged config
@@ -1,24 +0,0 @@
1
- # Stonyx Framework Conventions
2
-
3
- Universal rules that apply to every Stonyx project. Section-specific conventions are linked below.
4
-
5
- ## Universal Rules
6
-
7
- - **One class per file** — each file exports a single default class (or function for transforms)
8
- - **Constants**: shared across files → dedicated constants file; single-use → top of the consuming file
9
- - **No `console.log` / `.warn` / `.error`** — use `log` from `stonyx/log` for all logging
10
- - **Check `@stonyx/utils` first** — before reaching for Node built-ins or npm packages, check if `@stonyx/utils` already provides what you need (file I/O, object manipulation, string transforms, date/timestamp, promises, prompts). File issues on stonyx-utils for gaps rather than working around them
11
- - **`fs` is prohibited** — use file utilities from `@stonyx/utils/file` instead
12
- - **Always LTS Node or higher** — version specified in `.nvmrc`
13
- - **Always pnpm** — never npm or yarn
14
- - **Lint config**: import from `@abofs/code-conventions`; never define local lint rules
15
- - **ES Modules** — all projects use `"type": "module"` and ESM imports
16
-
17
- ## Section Conventions
18
-
19
- - [Project Structure](./project-structure.md) — directory layout, file organization, config conventions
20
- - [Framework Modules](./framework-modules.md) — when to use which `@stonyx/*` module
21
- - [ORM Conventions](./orm-conventions.md) — models, serializers, access control, transforms, hooks
22
- - [REST Conventions](./rest-conventions.md) — REST server request classes and handlers
23
- - [Discord Conventions](./discord-conventions.md) — Discord bot commands and event handlers
24
- - [Testing Conventions](./testing-conventions.md) — test organization, patterns, and tooling
@@ -1,158 +0,0 @@
1
- # ORM Conventions
2
-
3
- ## Models
4
-
5
- Models define the data structure with attributes and relationships. One model per file in `models/`.
6
-
7
- **Property ordering:** `attr()` → `belongsTo()` / `hasMany()` → computed getters
8
-
9
- ```js
10
- import { Model, attr, belongsTo, hasMany } from '@stonyx/orm';
11
- import { ANIMALS } from '../constants.js';
12
-
13
- export default class AnimalModel extends Model {
14
- type = attr('animal');
15
- age = attr('number');
16
- size = attr('string');
17
-
18
- owner = belongsTo('owner');
19
- traits = hasMany('trait');
20
-
21
- get tag() {
22
- const { owner, size } = this;
23
-
24
- if (!owner) {
25
- return `Unowned ${size} ${ANIMALS[this.type]}`;
26
- }
27
-
28
- return `${owner.id}'s ${size} ${ANIMALS[this.type]}`;
29
- }
30
- }
31
- ```
32
-
33
- ### Property Flattening
34
-
35
- Every property must resolve to a final data type — no passthrough objects. If an API returns a nested object, create a sub-model with `belongsTo` rather than storing the raw object.
36
-
37
- ### Nested Models
38
-
39
- Use nested directories for `belongsTo` children:
40
-
41
- ```
42
- models/
43
- character.js
44
- character/
45
- relationship.js
46
- ```
47
-
48
- ## Serializers
49
-
50
- Serializers map raw external data to the model shape. One serializer per model in `serializers/`.
51
-
52
- The serializer has a single `map` property. It is responsible for mapping only, not fetching.
53
-
54
- ```js
55
- import { Serializer } from '@stonyx/orm';
56
-
57
- const COLOR_TRAIT_MAP = {
58
- 'black': 2,
59
- 'white': 3,
60
- }
61
-
62
- export default class AnimalSerializer extends Serializer {
63
- map = {
64
- age: 'details.age',
65
- size: 'details.c',
66
- color: 'details.x',
67
- owner: 'details.location.owner',
68
-
69
- // Array value: [sourcePath, customHandler]
70
- traits: ['details', ({ x:color }) => {
71
- const traits = [{ id: 1, type: 'habitat', value: 'farm', category: 'physical' }];
72
-
73
- const id = COLOR_TRAIT_MAP[color];
74
- if (id) traits.push({ id, type: 'color', value: color, category: 'appearance' });
75
-
76
- return traits;
77
- }]
78
- }
79
- }
80
- ```
81
-
82
- ### Clients vs Serializers
83
-
84
- - **Clients** fetch, decrypt, and provide raw data from external sources
85
- - **Serializers** map that raw data to the model shape
86
-
87
- Clients handle I/O; serializers handle structure. Never mix the two.
88
-
89
- ## Access Control
90
-
91
- Access classes define which models they govern and implement an `access()` method. One access file per logical access boundary in `access/`.
92
-
93
- **Structure:** `models` property → `access()` method
94
-
95
- ```js
96
- export default class GlobalAccess {
97
- models = ['owner', 'animal', 'trait', 'category', 'phone-number'];
98
-
99
- access(request) {
100
- const { originalUrl: url } = request;
101
-
102
- // Return false to deny
103
- if (url.endsWith('/owners/angela')) return false;
104
-
105
- // Return a filter function for conditional access
106
- if (url.endsWith('/owners')) return record => record.id !== 'angela';
107
- if (url.endsWith('/animals')) return record => record.owner !== 'angela';
108
-
109
- // Return permission array for full access
110
- return ['read', 'create', 'update', 'delete'];
111
- }
112
- }
113
- ```
114
-
115
- The `models` property accepts an array of model names, or `'*'` to match all models.
116
-
117
- `access()` return values:
118
- - `false` — deny access
119
- - `function` — filter function applied to response records
120
- - `string[]` — allowed operations (e.g., `['read', 'create', 'update', 'delete']`)
121
-
122
- ## Transforms
123
-
124
- Transforms are default-exported functions (not classes) that convert values. One transform per model in `transforms/`.
125
-
126
- ```js
127
- import { ANIMALS } from '../constants.js';
128
-
129
- const codeEnumMap = {}
130
-
131
- for (let i = 0; i < ANIMALS.length; i++) codeEnumMap[ANIMALS[i]] = i;
132
-
133
- export default function(value) {
134
- return codeEnumMap[value] || 0;
135
- }
136
- ```
137
-
138
- ## Hooks
139
-
140
- Use `beforeHook` / `afterHook` from `@stonyx/orm/hooks`. Place hook files in `hooks/`.
141
-
142
- ## DB Schema
143
-
144
- The database schema extends `Model` and uses `hasMany` for each collection:
145
-
146
- ```js
147
- import { Model, hasMany } from '@stonyx/orm';
148
-
149
- export default class DBModel extends Model {
150
- owners = hasMany('owner');
151
- animals = hasMany('animal');
152
- traits = hasMany('trait');
153
- categories = hasMany('category');
154
- phoneNumbers = hasMany('phone-number');
155
- }
156
- ```
157
-
158
- Located at `config/db-schema.js`, referenced from `config/environment.js`.