@stonyx/orm 0.2.1-alpha.0 → 0.2.1-alpha.10
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/.claude/code-style-rules.md +44 -0
- package/.claude/hooks.md +250 -0
- package/.claude/index.md +281 -0
- package/.claude/usage-patterns.md +234 -0
- package/.github/workflows/ci.yml +5 -25
- package/.github/workflows/publish.yml +24 -116
- package/README.md +440 -15
- package/config/environment.js +26 -5
- package/improvements.md +139 -0
- package/package.json +19 -8
- package/project-structure.md +343 -0
- package/src/commands.js +170 -0
- package/src/db.js +132 -6
- package/src/hooks.js +124 -0
- package/src/index.js +8 -1
- package/src/main.js +47 -3
- package/src/manage-record.js +19 -4
- package/src/migrate.js +72 -0
- package/src/model.js +11 -0
- package/src/mysql/connection.js +28 -0
- package/src/mysql/migration-generator.js +188 -0
- package/src/mysql/migration-runner.js +110 -0
- package/src/mysql/mysql-db.js +422 -0
- package/src/mysql/query-builder.js +64 -0
- package/src/mysql/schema-introspector.js +160 -0
- package/src/mysql/type-map.js +37 -0
- package/src/orm-request.js +355 -41
- package/src/plural-registry.js +12 -0
- package/src/record.js +47 -12
- package/src/serializer.js +2 -2
- package/src/setup-rest-server.js +4 -1
- package/src/store.js +105 -0
- package/src/utils.js +12 -0
- package/test-events-setup.js +41 -0
- package/test-hooks-manual.js +54 -0
- package/test-hooks-with-logging.js +52 -0
- package/.claude/project-structure.md +0 -578
- package/stonyx-bootstrap.cjs +0 -30
package/src/store.js
CHANGED
|
@@ -9,12 +9,117 @@ export default class Store {
|
|
|
9
9
|
this.data = new Map();
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Synchronous memory-only access.
|
|
14
|
+
* Returns the record if it exists in the in-memory store, undefined otherwise.
|
|
15
|
+
* Does NOT query the database. For memory:false models, use find() instead.
|
|
16
|
+
*/
|
|
12
17
|
get(key, id) {
|
|
13
18
|
if (!id) return this.data.get(key);
|
|
14
19
|
|
|
15
20
|
return this.data.get(key)?.get(id);
|
|
16
21
|
}
|
|
17
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Async authoritative read. Always queries MySQL for memory: false models.
|
|
25
|
+
* For memory: true models, returns from store (already loaded on boot).
|
|
26
|
+
* @param {string} modelName - The model name
|
|
27
|
+
* @param {string|number} id - The record ID
|
|
28
|
+
* @returns {Promise<Record|undefined>}
|
|
29
|
+
*/
|
|
30
|
+
async find(modelName, id) {
|
|
31
|
+
// For memory: true models, the store is authoritative
|
|
32
|
+
if (this._isMemoryModel(modelName)) {
|
|
33
|
+
return this.get(modelName, id);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// For memory: false models, always query MySQL
|
|
37
|
+
if (this._mysqlDb) {
|
|
38
|
+
return this._mysqlDb.findRecord(modelName, id);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Fallback to store (JSON mode or no MySQL)
|
|
42
|
+
return this.get(modelName, id);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Async read for all records of a model. Always queries MySQL for memory: false models.
|
|
47
|
+
* For memory: true models, returns from store.
|
|
48
|
+
* @param {string} modelName - The model name
|
|
49
|
+
* @param {Object} [conditions] - Optional WHERE conditions
|
|
50
|
+
* @returns {Promise<Record[]>}
|
|
51
|
+
*/
|
|
52
|
+
async findAll(modelName, conditions) {
|
|
53
|
+
// For memory: true models without conditions, return from store
|
|
54
|
+
if (this._isMemoryModel(modelName) && !conditions) {
|
|
55
|
+
const modelStore = this.get(modelName);
|
|
56
|
+
return modelStore ? Array.from(modelStore.values()) : [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// For memory: false models (or filtered queries), always query MySQL
|
|
60
|
+
if (this._mysqlDb) {
|
|
61
|
+
return this._mysqlDb.findAll(modelName, conditions);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Fallback to store (JSON mode) — apply conditions in-memory if provided
|
|
65
|
+
const modelStore = this.get(modelName);
|
|
66
|
+
if (!modelStore) return [];
|
|
67
|
+
|
|
68
|
+
const records = Array.from(modelStore.values());
|
|
69
|
+
|
|
70
|
+
if (!conditions || Object.keys(conditions).length === 0) return records;
|
|
71
|
+
|
|
72
|
+
return records.filter(record =>
|
|
73
|
+
Object.entries(conditions).every(([key, value]) => record.__data[key] === value)
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Async query — always hits MySQL, never reads from memory cache.
|
|
79
|
+
* Use for complex queries, aggregations, or when you need guaranteed freshness.
|
|
80
|
+
* @param {string} modelName - The model name
|
|
81
|
+
* @param {Object} conditions - WHERE conditions
|
|
82
|
+
* @returns {Promise<Record[]>}
|
|
83
|
+
*/
|
|
84
|
+
async query(modelName, conditions = {}) {
|
|
85
|
+
if (this._mysqlDb) {
|
|
86
|
+
return this._mysqlDb.findAll(modelName, conditions);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Fallback: filter in-memory store
|
|
90
|
+
const modelStore = this.get(modelName);
|
|
91
|
+
if (!modelStore) return [];
|
|
92
|
+
|
|
93
|
+
const records = Array.from(modelStore.values());
|
|
94
|
+
|
|
95
|
+
if (Object.keys(conditions).length === 0) return records;
|
|
96
|
+
|
|
97
|
+
return records.filter(record =>
|
|
98
|
+
Object.entries(conditions).every(([key, value]) => record.__data[key] === value)
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Set by Orm during init — resolves memory flag for a model name.
|
|
104
|
+
* @type {Function|null}
|
|
105
|
+
*/
|
|
106
|
+
_memoryResolver = null;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Set by Orm during init — reference to the MysqlDB instance for on-demand queries.
|
|
110
|
+
* @type {MysqlDB|null}
|
|
111
|
+
*/
|
|
112
|
+
_mysqlDb = null;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Check if a model is configured for in-memory storage.
|
|
116
|
+
* @private
|
|
117
|
+
*/
|
|
118
|
+
_isMemoryModel(modelName) {
|
|
119
|
+
if (this._memoryResolver) return this._memoryResolver(modelName);
|
|
120
|
+
return true; // default to memory if resolver not set yet
|
|
121
|
+
}
|
|
122
|
+
|
|
18
123
|
set(key, value) {
|
|
19
124
|
this.data.set(key, value);
|
|
20
125
|
}
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { pluralize as basePluralize } from '@stonyx/utils/string';
|
|
2
|
+
|
|
3
|
+
// Wrapper to handle dasherized model names (e.g., "access-link" → "access-links")
|
|
4
|
+
export function pluralize(word) {
|
|
5
|
+
if (word.includes('-')) {
|
|
6
|
+
const parts = word.split('-');
|
|
7
|
+
const pluralizedLast = basePluralize(parts.pop());
|
|
8
|
+
return [...parts, pluralizedLast].join('-');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return basePluralize(word);
|
|
12
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Debug script to verify event setup
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import Stonyx from 'stonyx';
|
|
6
|
+
import config from './config/environment.js';
|
|
7
|
+
import Orm from './src/main.js';
|
|
8
|
+
import { subscribe } from '@stonyx/events';
|
|
9
|
+
|
|
10
|
+
// Override paths for tests
|
|
11
|
+
Object.assign(config.paths, {
|
|
12
|
+
access: './test/sample/access',
|
|
13
|
+
model: './test/sample/models',
|
|
14
|
+
serializer: './test/sample/serializers',
|
|
15
|
+
transform: './test/sample/transforms'
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Override db settings for tests
|
|
19
|
+
Object.assign(config.db, {
|
|
20
|
+
file: './test/sample/db.json',
|
|
21
|
+
schema: './test/sample/db-schema.js'
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
new Stonyx(config, import.meta.dirname);
|
|
25
|
+
|
|
26
|
+
const orm = new Orm();
|
|
27
|
+
await orm.init();
|
|
28
|
+
|
|
29
|
+
console.log('ORM initialized');
|
|
30
|
+
console.log('Store keys:', Array.from(Orm.store.data.keys()));
|
|
31
|
+
|
|
32
|
+
// Try subscribing to an event
|
|
33
|
+
try {
|
|
34
|
+
const unsubscribe = subscribe('before:create:animal', (context) => {
|
|
35
|
+
console.log('Hook called!', context);
|
|
36
|
+
});
|
|
37
|
+
console.log('✓ Successfully subscribed to before:create:animal');
|
|
38
|
+
unsubscribe();
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error('✗ Failed to subscribe:', error.message);
|
|
41
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manual test script for hooks functionality
|
|
3
|
+
* Run with: node test-hooks-manual.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { setup, subscribe, emit } from '@stonyx/events';
|
|
7
|
+
|
|
8
|
+
console.log('Testing hooks system...\n');
|
|
9
|
+
|
|
10
|
+
// Setup events
|
|
11
|
+
const eventNames = ['before:create:animal', 'after:create:animal'];
|
|
12
|
+
setup(eventNames);
|
|
13
|
+
|
|
14
|
+
let beforeCalled = false;
|
|
15
|
+
let afterCalled = false;
|
|
16
|
+
let contextReceived = null;
|
|
17
|
+
|
|
18
|
+
// Subscribe to hooks
|
|
19
|
+
const unsubscribe1 = subscribe('before:create:animal', async (context) => {
|
|
20
|
+
console.log('✓ before:create:animal hook called');
|
|
21
|
+
console.log(' Context:', JSON.stringify(context, null, 2));
|
|
22
|
+
beforeCalled = true;
|
|
23
|
+
contextReceived = context;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const unsubscribe2 = subscribe('after:create:animal', async (context) => {
|
|
27
|
+
console.log('✓ after:create:animal hook called');
|
|
28
|
+
console.log(' Context:', JSON.stringify(context, null, 2));
|
|
29
|
+
afterCalled = true;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Simulate hook execution
|
|
33
|
+
const testContext = {
|
|
34
|
+
model: 'animal',
|
|
35
|
+
operation: 'create',
|
|
36
|
+
body: { data: { type: 'animals', attributes: { name: 'Test' } } }
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
console.log('Emitting before:create:animal...');
|
|
40
|
+
await emit('before:create:animal', testContext);
|
|
41
|
+
|
|
42
|
+
console.log('\nEmitting after:create:animal...');
|
|
43
|
+
await emit('after:create:animal', { ...testContext, record: { id: 1, name: 'Test' } });
|
|
44
|
+
|
|
45
|
+
console.log('\n--- Test Results ---');
|
|
46
|
+
console.log('Before hook called:', beforeCalled ? '✓ PASS' : '✗ FAIL');
|
|
47
|
+
console.log('After hook called:', afterCalled ? '✓ PASS' : '✗ FAIL');
|
|
48
|
+
console.log('Context passed correctly:', contextReceived?.model === 'animal' ? '✓ PASS' : '✗ FAIL');
|
|
49
|
+
|
|
50
|
+
// Cleanup
|
|
51
|
+
unsubscribe1();
|
|
52
|
+
unsubscribe2();
|
|
53
|
+
|
|
54
|
+
console.log('\n✓ Hooks system working correctly!');
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test to verify hooks wrapper is being called
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { emit } from '@stonyx/events';
|
|
6
|
+
|
|
7
|
+
// Simulate the _withHooks wrapper
|
|
8
|
+
function _withHooks(operation, handler, model) {
|
|
9
|
+
console.log(`Creating wrapper for ${operation} on ${model}`);
|
|
10
|
+
|
|
11
|
+
return async (request, state) => {
|
|
12
|
+
console.log(`Wrapper called for ${operation} on ${model}`);
|
|
13
|
+
|
|
14
|
+
const context = {
|
|
15
|
+
model,
|
|
16
|
+
operation,
|
|
17
|
+
request,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
console.log(`About to emit before:${operation}:${model}`);
|
|
21
|
+
await emit(`before:${operation}:${model}`, context);
|
|
22
|
+
console.log(`Emitted before hook`);
|
|
23
|
+
|
|
24
|
+
const response = await handler(request, state);
|
|
25
|
+
console.log(`Handler completed`);
|
|
26
|
+
|
|
27
|
+
context.response = response;
|
|
28
|
+
await emit(`after:${operation}:${model}`, context);
|
|
29
|
+
console.log(`Emitted after hook`);
|
|
30
|
+
|
|
31
|
+
return response;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Simulate a handler
|
|
36
|
+
const createHandler = ({ body }) => {
|
|
37
|
+
console.log('Original handler called');
|
|
38
|
+
return { data: { id: 1, ...body } };
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Create wrapped handler
|
|
42
|
+
const wrappedHandler = _withHooks('create', createHandler, 'animal');
|
|
43
|
+
|
|
44
|
+
// Simulate a request
|
|
45
|
+
const mockRequest = {
|
|
46
|
+
body: { name: 'Test' }
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
console.log('\n=== Testing wrapper ===');
|
|
50
|
+
const result = await wrappedHandler(mockRequest, {});
|
|
51
|
+
console.log('Result:', result);
|
|
52
|
+
console.log('\n✓ Wrapper test completed');
|