stonyx 0.2.3-alpha.1 → 0.2.3-alpha.2

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.
@@ -0,0 +1,121 @@
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/oauth`
107
+
108
+ OAuth providers. Class ordering: constructor with `super()` → async methods → transform methods.
109
+
110
+ Key methods: `getAuthorizationUrl()`, `handleCallback()`, `getSession()`, `logout()`
111
+
112
+ ## `@abofs/code-conventions`
113
+
114
+ Shared lint and formatting config. Import and spread; never define local rules.
115
+
116
+ Exports:
117
+ - `@abofs/code-conventions/eslint` — ESLint config
118
+ - `@abofs/code-conventions/prettier` — Prettier config
119
+ - `@abofs/code-conventions/eslint-ember` — Ember-specific ESLint
120
+ - `@abofs/code-conventions/template-lint` — Template linting
121
+ - `@abofs/code-conventions/lint-staged` — Lint-staged config
@@ -0,0 +1,23 @@
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
+ - [Testing Conventions](./testing-conventions.md) — test organization, patterns, and tooling
@@ -0,0 +1,158 @@
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`.
@@ -0,0 +1,106 @@
1
+ # Project Structure
2
+
3
+ ## Entry Point
4
+
5
+ The application entry point is always `app.js` at the project root. This file exports a default class that initializes the application.
6
+
7
+ ```js
8
+ import log from 'stonyx/log';
9
+
10
+ export default class App {
11
+ constructor() {
12
+ if (App.instance) return App.instance;
13
+ App.instance = this;
14
+
15
+ this.ready = this.init();
16
+ }
17
+
18
+ async init() {
19
+ log.info('Initializing Application');
20
+ // Application setup here
21
+ log.info('Application has been initialized');
22
+ }
23
+ }
24
+ ```
25
+
26
+ ## File Layout
27
+
28
+ Flat file structure at root level — non-directory project files live at the project root.
29
+
30
+ ### Standard Directories
31
+
32
+ | Directory | Purpose | When to create |
33
+ |-----------|---------|----------------|
34
+ | `config/` | Configuration files | Always |
35
+ | `models/` | ORM model definitions | When using `@stonyx/orm` |
36
+ | `serializers/` | Data serializers | When using `@stonyx/orm` |
37
+ | `access/` | Access control definitions | When using `@stonyx/orm` |
38
+ | `transforms/` | Value transforms | When using `@stonyx/orm` |
39
+ | `hooks/` | Lifecycle hooks | When using `@stonyx/orm` |
40
+ | `requests/` | REST request handlers | When using `@stonyx/rest-server` |
41
+ | `crons/` | Scheduled tasks | When using `@stonyx/cron` |
42
+ | `clients/` | External API clients | When fetching external data |
43
+ | `resources/` | Business logic resources | As needed |
44
+ | `test/` | Tests | Always |
45
+ | `utils.js` or `utils/` | Project-specific reusable logic | As needed (never duplicate `@stonyx/utils`) |
46
+
47
+ ### Nested Model Directories
48
+
49
+ Use nested directories under `models/` for `belongsTo` child models:
50
+
51
+ ```
52
+ models/
53
+ character.js
54
+ character/
55
+ relationship.js
56
+ ```
57
+
58
+ ## Config Conventions
59
+
60
+ ### `config/environment.js`
61
+
62
+ Destructure env vars at the top, apply defaults with `??`, export a plain object:
63
+
64
+ ```js
65
+ const {
66
+ CORS_ORIGIN,
67
+ DB_FILE,
68
+ NODE_ENV,
69
+ REST_PORT,
70
+ } = process.env;
71
+
72
+ const environment = NODE_ENV ?? 'development';
73
+
74
+ export default {
75
+ orm: {
76
+ db: {
77
+ file: DB_FILE ?? 'db.json',
78
+ schema: './config/db-schema.js'
79
+ }
80
+ },
81
+ restServer: {
82
+ origin: CORS_ORIGIN ?? '*',
83
+ port: REST_PORT ?? 3000
84
+ }
85
+ }
86
+ ```
87
+
88
+ **Do NOT re-declare module defaults.** Only include config values that differ from the module's built-in defaults.
89
+
90
+ ### `config/db-schema.js`
91
+
92
+ Extends `Model` and uses `hasMany` to define each collection:
93
+
94
+ ```js
95
+ import { Model, hasMany } from '@stonyx/orm';
96
+
97
+ export default class DBModel extends Model {
98
+ owners = hasMany('owner');
99
+ animals = hasMany('animal');
100
+ }
101
+ ```
102
+
103
+ ## Reference Projects
104
+
105
+ - `smart-lock-backend/` — full example with REST, ORM, sockets, crons, hooks
106
+ - `nextgoal-backend/` — example with models, transforms, filters, bot commands
@@ -0,0 +1,96 @@
1
+ # REST Conventions
2
+
3
+ ## Request Classes
4
+
5
+ One request class per file in `requests/`. Each class extends `Request` from `@stonyx/rest-server`.
6
+
7
+ **Class ordering:** properties → `handlers` object → `auth()` hook → validation/middleware methods
8
+
9
+ ### Public Request (no auth)
10
+
11
+ ```js
12
+ import { Request } from '@stonyx/rest-server';
13
+
14
+ export default class PublicRequest extends Request {
15
+ testProp = 'stonyx';
16
+
17
+ handlers = {
18
+ get: {
19
+ '/': (_request, _state) => {
20
+ return { data: 'foo' };
21
+ },
22
+
23
+ '/url-params/:x/:y/:z': ({ params }, _state) => {
24
+ return params;
25
+ },
26
+
27
+ // Middleware chaining: [middlewareFn, handlerFn]
28
+ '/foo': [this.validationSuccessSample, (_request, state) => {
29
+ return { data: state };
30
+ }],
31
+
32
+ '/fail': [this.validationFailureSample, (_request, _state) => {
33
+ return { unreachable: 'response' };
34
+ }],
35
+ }
36
+ }
37
+
38
+ validationSuccessSample(_request, state) {
39
+ state.newProp = 'bar';
40
+ }
41
+
42
+ validationFailureSample() {
43
+ return 504; // returning a status code rejects the request
44
+ }
45
+ }
46
+ ```
47
+
48
+ ### Private Request (with auth)
49
+
50
+ ```js
51
+ import { Request } from '@stonyx/rest-server';
52
+
53
+ export default class PrivateRequest extends Request {
54
+ handlers = {
55
+ get: {
56
+ '/success': (_request, _state) => {
57
+ return { data: 'foo' };
58
+ },
59
+
60
+ '/failure': (_request, _state) => {
61
+ return { data: 'foo' };
62
+ }
63
+ }
64
+ }
65
+
66
+ auth(request, _state) {
67
+ if (request.path === '/failure') return 505;
68
+ }
69
+ }
70
+ ```
71
+
72
+ ## Key Patterns
73
+
74
+ ### `handlers` Object
75
+
76
+ Keyed by HTTP method (`get`, `post`, `put`, `delete`), each containing route-to-handler mappings.
77
+
78
+ Handler signature: `(request, state) => responseData`
79
+
80
+ ### Middleware Chaining
81
+
82
+ Use `[middlewareFn, handlerFn]` arrays to chain middleware before a handler. The middleware function receives `(request, state)` and can:
83
+ - Mutate `state` to pass data to the handler
84
+ - Return a status code number to reject the request early
85
+
86
+ ### `auth()` Hook
87
+
88
+ Runs before all handlers in the class. Return an error status code to reject the request. If `auth()` returns nothing (undefined), the request proceeds.
89
+
90
+ ### Route Parameters
91
+
92
+ Express-style route params (`:param`) are available via `request.params`.
93
+
94
+ ### Binding
95
+
96
+ Handler functions defined as arrow functions or class methods have access to `this` (the request class instance).
@@ -0,0 +1,56 @@
1
+ # Testing Conventions
2
+
3
+ ## Directory Structure
4
+
5
+ ```
6
+ test/
7
+ unit/
8
+ integration/
9
+ acceptance/
10
+ config/
11
+ environment.js # test config overrides
12
+ sample/ # fixtures and mocks (alternative: test/mocks/)
13
+ ```
14
+
15
+ ## File Naming
16
+
17
+ All test files use the `*-test.js` suffix: `animal-test.js`, `public-request-test.js`.
18
+
19
+ ## Test Framework
20
+
21
+ - **QUnit** — test framework
22
+ - **Sinon** — stubs, spies, and mocks
23
+
24
+ Run tests with `stonyx test` (alias: `stonyx t`), which:
25
+ 1. Sets `NODE_ENV=test`
26
+ 2. Loads test setup (bootstrap with test config overrides)
27
+ 3. Runs QUnit against `test/**/*-test.js`
28
+
29
+ ## Test Config Overrides
30
+
31
+ Place test-specific config at `test/config/environment.js`. Stonyx auto-merges these overrides when running in test mode.
32
+
33
+ ## Sample / Fixture Files
34
+
35
+ Each stonyx module's `test/sample/` directory contains reference implementations. Mirror this structure in your project tests:
36
+
37
+ - `test/sample/models/` — model fixtures
38
+ - `test/sample/serializers/` — serializer fixtures
39
+ - `test/sample/access/` — access control fixtures
40
+ - `test/sample/transforms/` — transform fixtures
41
+ - `test/sample/requests/` — request handler fixtures
42
+
43
+ The file ordering in tests should mirror the sample files from each module's `test/sample/` directory.
44
+
45
+ ## Running Tests
46
+
47
+ ```bash
48
+ # Run all tests
49
+ stonyx test
50
+
51
+ # Run specific test file
52
+ stonyx test test/unit/animal-test.js
53
+
54
+ # Run specific glob
55
+ stonyx test "test/integration/**/*-test.js"
56
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stonyx",
3
- "version": "0.2.3-alpha.1",
3
+ "version": "0.2.3-alpha.2",
4
4
  "description": "Base stonyx framework module",
5
5
  "main": "main.js",
6
6
  "type": "module",