@stonyx/orm 0.2.1-beta.83 → 0.2.1-beta.85
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/dist/aggregates.d.ts +21 -0
- package/dist/aggregates.js +90 -0
- package/dist/attr.d.ts +2 -0
- package/dist/attr.js +22 -0
- package/dist/belongs-to.d.ts +11 -0
- package/dist/belongs-to.js +59 -0
- package/dist/cli.d.ts +22 -0
- package/dist/cli.js +148 -0
- package/dist/commands.d.ts +7 -0
- package/dist/commands.js +146 -0
- package/dist/db.d.ts +21 -0
- package/dist/db.js +174 -0
- package/dist/exports/db.d.ts +7 -0
- package/{src → dist}/exports/db.js +2 -4
- package/dist/has-many.d.ts +11 -0
- package/dist/has-many.js +58 -0
- package/dist/hooks.d.ts +47 -0
- package/dist/hooks.js +106 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +34 -0
- package/dist/main.d.ts +46 -0
- package/dist/main.js +179 -0
- package/dist/manage-record.d.ts +13 -0
- package/dist/manage-record.js +114 -0
- package/dist/meta-request.d.ts +6 -0
- package/dist/meta-request.js +52 -0
- package/dist/migrate.d.ts +2 -0
- package/dist/migrate.js +57 -0
- package/dist/model-property.d.ts +9 -0
- package/dist/model-property.js +29 -0
- package/dist/model.d.ts +15 -0
- package/dist/model.js +18 -0
- package/dist/mysql/connection.d.ts +14 -0
- package/dist/mysql/connection.js +24 -0
- package/dist/mysql/migration-generator.d.ts +45 -0
- package/dist/mysql/migration-generator.js +245 -0
- package/dist/mysql/migration-runner.d.ts +12 -0
- package/dist/mysql/migration-runner.js +83 -0
- package/dist/mysql/mysql-db.d.ts +100 -0
- package/dist/mysql/mysql-db.js +415 -0
- package/dist/mysql/query-builder.d.ts +10 -0
- package/dist/mysql/query-builder.js +44 -0
- package/dist/mysql/schema-introspector.d.ts +19 -0
- package/dist/mysql/schema-introspector.js +286 -0
- package/dist/mysql/type-map.d.ts +21 -0
- package/dist/mysql/type-map.js +36 -0
- package/dist/orm-request.d.ts +38 -0
- package/dist/orm-request.js +455 -0
- package/dist/plural-registry.d.ts +4 -0
- package/{src → dist}/plural-registry.js +3 -6
- package/dist/postgres/connection.d.ts +15 -0
- package/dist/postgres/connection.js +30 -0
- package/dist/postgres/migration-generator.d.ts +45 -0
- package/dist/postgres/migration-generator.js +257 -0
- package/dist/postgres/migration-runner.d.ts +10 -0
- package/dist/postgres/migration-runner.js +82 -0
- package/dist/postgres/postgres-db.d.ts +119 -0
- package/dist/postgres/postgres-db.js +476 -0
- package/dist/postgres/query-builder.d.ts +27 -0
- package/dist/postgres/query-builder.js +98 -0
- package/dist/postgres/schema-introspector.d.ts +29 -0
- package/dist/postgres/schema-introspector.js +309 -0
- package/dist/postgres/type-map.d.ts +23 -0
- package/dist/postgres/type-map.js +53 -0
- package/dist/record.d.ts +75 -0
- package/dist/record.js +115 -0
- package/dist/relationships.d.ts +10 -0
- package/dist/relationships.js +39 -0
- package/dist/serializer.d.ts +17 -0
- package/dist/serializer.js +136 -0
- package/dist/setup-rest-server.d.ts +1 -0
- package/dist/setup-rest-server.js +54 -0
- package/dist/standalone-db.d.ts +58 -0
- package/dist/standalone-db.js +142 -0
- package/dist/store.d.ts +62 -0
- package/dist/store.js +271 -0
- package/dist/timescale/query-builder.d.ts +41 -0
- package/dist/timescale/query-builder.js +87 -0
- package/dist/timescale/timescale-db.d.ts +45 -0
- package/dist/timescale/timescale-db.js +84 -0
- package/dist/transforms.d.ts +2 -0
- package/dist/transforms.js +17 -0
- package/dist/types/orm-types.d.ts +142 -0
- package/dist/types/orm-types.js +1 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +13 -0
- package/dist/view-resolver.d.ts +8 -0
- package/dist/view-resolver.js +169 -0
- package/dist/view.d.ts +11 -0
- package/dist/view.js +18 -0
- package/package.json +34 -11
- package/src/{aggregates.js → aggregates.ts} +27 -13
- package/src/{attr.js → attr.ts} +2 -2
- package/src/belongs-to.ts +90 -0
- package/src/{cli.js → cli.ts} +17 -11
- package/src/{commands.js → commands.ts} +179 -170
- package/src/{db.js → db.ts} +35 -26
- package/src/exports/db.ts +7 -0
- package/src/has-many.ts +92 -0
- package/src/{hooks.js → hooks.ts} +23 -27
- package/src/{index.js → index.ts} +4 -4
- package/src/{main.js → main.ts} +60 -34
- package/src/{manage-record.js → manage-record.ts} +42 -22
- package/src/{meta-request.js → meta-request.ts} +17 -14
- package/src/{migrate.js → migrate.ts} +9 -9
- package/src/{model-property.js → model-property.ts} +12 -6
- package/src/{model.js → model.ts} +5 -4
- package/src/mysql/{connection.js → connection.ts} +43 -28
- package/src/mysql/{migration-generator.js → migration-generator.ts} +332 -286
- package/src/mysql/{migration-runner.js → migration-runner.ts} +116 -110
- package/src/mysql/{mysql-db.js → mysql-db.ts} +537 -473
- package/src/mysql/{query-builder.js → query-builder.ts} +69 -64
- package/src/mysql/{schema-introspector.js → schema-introspector.ts} +355 -325
- package/src/mysql/{type-map.js → type-map.ts} +42 -37
- package/src/{orm-request.js → orm-request.ts} +169 -97
- package/src/plural-registry.ts +12 -0
- package/src/postgres/{connection.js → connection.ts} +14 -5
- package/src/postgres/{migration-generator.js → migration-generator.ts} +82 -38
- package/src/postgres/{migration-runner.js → migration-runner.ts} +11 -10
- package/src/postgres/{postgres-db.js → postgres-db.ts} +198 -114
- package/src/postgres/{query-builder.js → query-builder.ts} +27 -28
- package/src/postgres/{schema-introspector.js → schema-introspector.ts} +87 -58
- package/src/postgres/{type-map.js → type-map.ts} +10 -6
- package/src/{record.js → record.ts} +73 -34
- package/src/relationships.ts +53 -0
- package/src/{serializer.js → serializer.ts} +52 -36
- package/src/{setup-rest-server.js → setup-rest-server.ts} +18 -13
- package/src/{standalone-db.js → standalone-db.ts} +33 -24
- package/src/{store.js → store.ts} +90 -68
- package/src/timescale/{query-builder.js → query-builder.ts} +33 -38
- package/src/timescale/timescale-db.ts +119 -0
- package/src/transforms.ts +20 -0
- package/src/types/mysql2.d.ts +30 -0
- package/src/types/orm-types.ts +146 -0
- package/src/types/pg.d.ts +28 -0
- package/src/types/stonyx-cron.d.ts +5 -0
- package/src/types/stonyx-events.d.ts +4 -0
- package/src/types/stonyx-rest-server.d.ts +11 -0
- package/src/types/stonyx-utils.d.ts +33 -0
- package/src/types/stonyx.d.ts +21 -0
- package/src/utils.ts +16 -0
- package/src/{view-resolver.js → view-resolver.ts} +51 -24
- package/src/view.ts +22 -0
- package/src/belongs-to.js +0 -70
- package/src/has-many.js +0 -68
- package/src/relationships.js +0 -43
- package/src/timescale/timescale-db.js +0 -111
- package/src/transforms.js +0 -20
- package/src/utils.js +0 -12
- package/src/view.js +0 -21
|
@@ -19,26 +19,28 @@
|
|
|
19
19
|
* Unlike event-based hooks, middleware hooks run sequentially and can halt operations.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
type HookHandler = (context: Record<string, unknown>) => unknown | Promise<unknown>;
|
|
23
|
+
|
|
22
24
|
// Map of "operation:model" -> handler[]
|
|
23
|
-
const beforeHooks = new Map();
|
|
24
|
-
const afterHooks = new Map();
|
|
25
|
+
const beforeHooks: Map<string, HookHandler[]> = new Map();
|
|
26
|
+
const afterHooks: Map<string, HookHandler[]> = new Map();
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* Register a before hook middleware that runs before the operation executes.
|
|
28
30
|
*
|
|
29
|
-
* @param
|
|
30
|
-
* @param
|
|
31
|
-
* @param
|
|
31
|
+
* @param operation - Operation name: 'create', 'update', 'delete', 'get', or 'list'
|
|
32
|
+
* @param model - Model name (e.g., 'user', 'animal')
|
|
33
|
+
* @param handler - Middleware function (context) => any
|
|
32
34
|
* - Return undefined to continue to next hook/handler
|
|
33
35
|
* - Return any value to halt operation (integer = HTTP status, object = response body)
|
|
34
|
-
* @returns
|
|
36
|
+
* @returns Unsubscribe function
|
|
35
37
|
*/
|
|
36
|
-
export function beforeHook(operation, model, handler) {
|
|
38
|
+
export function beforeHook(operation: string, model: string, handler: HookHandler): () => void {
|
|
37
39
|
const key = `${operation}:${model}`;
|
|
38
40
|
if (!beforeHooks.has(key)) {
|
|
39
41
|
beforeHooks.set(key, []);
|
|
40
42
|
}
|
|
41
|
-
beforeHooks.get(key)
|
|
43
|
+
beforeHooks.get(key)!.push(handler);
|
|
42
44
|
|
|
43
45
|
// Return unsubscribe function
|
|
44
46
|
return () => {
|
|
@@ -54,17 +56,17 @@ export function beforeHook(operation, model, handler) {
|
|
|
54
56
|
* Register an after hook middleware that runs after the operation completes.
|
|
55
57
|
* After hooks cannot halt operations (they run after completion).
|
|
56
58
|
*
|
|
57
|
-
* @param
|
|
58
|
-
* @param
|
|
59
|
-
* @param
|
|
60
|
-
* @returns
|
|
59
|
+
* @param operation - Operation name
|
|
60
|
+
* @param model - Model name
|
|
61
|
+
* @param handler - Middleware function (context) => void
|
|
62
|
+
* @returns Unsubscribe function
|
|
61
63
|
*/
|
|
62
|
-
export function afterHook(operation, model, handler) {
|
|
64
|
+
export function afterHook(operation: string, model: string, handler: HookHandler): () => void {
|
|
63
65
|
const key = `${operation}:${model}`;
|
|
64
66
|
if (!afterHooks.has(key)) {
|
|
65
67
|
afterHooks.set(key, []);
|
|
66
68
|
}
|
|
67
|
-
afterHooks.get(key)
|
|
69
|
+
afterHooks.get(key)!.push(handler);
|
|
68
70
|
|
|
69
71
|
// Return unsubscribe function
|
|
70
72
|
return () => {
|
|
@@ -78,22 +80,16 @@ export function afterHook(operation, model, handler) {
|
|
|
78
80
|
|
|
79
81
|
/**
|
|
80
82
|
* Get all before hooks for an operation:model combination.
|
|
81
|
-
* @param {string} operation
|
|
82
|
-
* @param {string} model
|
|
83
|
-
* @returns {Function[]}
|
|
84
83
|
*/
|
|
85
|
-
export function getBeforeHooks(operation, model) {
|
|
84
|
+
export function getBeforeHooks(operation: string, model: string): HookHandler[] {
|
|
86
85
|
const key = `${operation}:${model}`;
|
|
87
86
|
return beforeHooks.get(key) || [];
|
|
88
87
|
}
|
|
89
88
|
|
|
90
89
|
/**
|
|
91
90
|
* Get all after hooks for an operation:model combination.
|
|
92
|
-
* @param {string} operation
|
|
93
|
-
* @param {string} model
|
|
94
|
-
* @returns {Function[]}
|
|
95
91
|
*/
|
|
96
|
-
export function getAfterHooks(operation, model) {
|
|
92
|
+
export function getAfterHooks(operation: string, model: string): HookHandler[] {
|
|
97
93
|
const key = `${operation}:${model}`;
|
|
98
94
|
return afterHooks.get(key) || [];
|
|
99
95
|
}
|
|
@@ -101,11 +97,11 @@ export function getAfterHooks(operation, model) {
|
|
|
101
97
|
/**
|
|
102
98
|
* Clear registered hooks for a specific operation:model.
|
|
103
99
|
*
|
|
104
|
-
* @param
|
|
105
|
-
* @param
|
|
106
|
-
* @param
|
|
100
|
+
* @param operation - Operation name
|
|
101
|
+
* @param model - Model name
|
|
102
|
+
* @param type - 'before' or 'after' (if omitted, clears both)
|
|
107
103
|
*/
|
|
108
|
-
export function clearHook(operation, model, type) {
|
|
104
|
+
export function clearHook(operation: string, model: string, type?: 'before' | 'after'): void {
|
|
109
105
|
const key = `${operation}:${model}`;
|
|
110
106
|
if (!type || type === 'before') {
|
|
111
107
|
beforeHooks.set(key, []);
|
|
@@ -118,7 +114,7 @@ export function clearHook(operation, model, type) {
|
|
|
118
114
|
/**
|
|
119
115
|
* Clear all hooks (useful for testing).
|
|
120
116
|
*/
|
|
121
|
-
export function clearAllHooks() {
|
|
117
|
+
export function clearAllHooks(): void {
|
|
122
118
|
beforeHooks.clear();
|
|
123
119
|
afterHooks.clear();
|
|
124
120
|
}
|
|
@@ -32,7 +32,7 @@ export { count, avg, sum, min, max }; // aggregate helpers
|
|
|
32
32
|
export { beforeHook, afterHook, clearHook, clearAllHooks } from './hooks.js'; // middleware hooks
|
|
33
33
|
|
|
34
34
|
// Store API:
|
|
35
|
-
// store.get(model, id)
|
|
36
|
-
// store.find(model, id)
|
|
37
|
-
// store.findAll(model)
|
|
38
|
-
// store.query(model, conditions)
|
|
35
|
+
// store.get(model, id) -- sync, memory-only
|
|
36
|
+
// store.find(model, id) -- async, MySQL for memory:false models
|
|
37
|
+
// store.findAll(model) -- async, all records
|
|
38
|
+
// store.query(model, conditions) -- async, always hits MySQL
|
package/src/{main.js → main.ts}
RENAMED
|
@@ -26,22 +26,46 @@ import Store from './store.js';
|
|
|
26
26
|
import Serializer from './serializer.js';
|
|
27
27
|
import { setup } from '@stonyx/events';
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
interface OrmOptions {
|
|
30
|
+
dbType?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface SqlDb {
|
|
34
|
+
init(): Promise<unknown>;
|
|
35
|
+
startup(): Promise<void>;
|
|
36
|
+
shutdown(): Promise<void>;
|
|
37
|
+
persist(operation: string, model: string, context: unknown, response: unknown): Promise<void>;
|
|
38
|
+
findRecord(modelName: string, id: unknown): Promise<unknown>;
|
|
39
|
+
findAll(modelName: string, conditions?: Record<string, unknown>): Promise<unknown[]>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface OrmDB {
|
|
43
|
+
record: unknown;
|
|
44
|
+
save(): Promise<void>;
|
|
45
|
+
init(): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const defaultOptions: OrmOptions = {
|
|
30
49
|
dbType: 'json'
|
|
31
50
|
}
|
|
32
51
|
|
|
33
52
|
export default class Orm {
|
|
34
|
-
static initialized = false;
|
|
35
|
-
static relationships = new Map();
|
|
36
|
-
static store = new Store();
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
53
|
+
static initialized: boolean = false;
|
|
54
|
+
static relationships: Map<string, Map<string, unknown>> = new Map();
|
|
55
|
+
static store: Store = new Store();
|
|
56
|
+
static instance: Orm;
|
|
57
|
+
static ready: unknown[];
|
|
58
|
+
|
|
59
|
+
models: Record<string, unknown> = {};
|
|
60
|
+
serializers: Record<string, unknown> = {};
|
|
61
|
+
views: Record<string, unknown> = {};
|
|
62
|
+
transforms: Record<string, (value: unknown) => unknown> = { ...baseTransforms };
|
|
63
|
+
warnings: Set<string> = new Set();
|
|
64
|
+
options!: OrmOptions;
|
|
65
|
+
sqlDb?: SqlDb;
|
|
66
|
+
db?: OrmDB | SqlDb;
|
|
67
|
+
|
|
68
|
+
constructor(options: OrmOptions = {}) {
|
|
45
69
|
if (Orm.instance) return Orm.instance;
|
|
46
70
|
|
|
47
71
|
const { relationships } = Orm;
|
|
@@ -56,24 +80,26 @@ export default class Orm {
|
|
|
56
80
|
Orm.instance = this;
|
|
57
81
|
}
|
|
58
82
|
|
|
59
|
-
async init() {
|
|
83
|
+
async init(): Promise<void> {
|
|
60
84
|
const { paths, restServer } = config.orm;
|
|
61
|
-
|
|
85
|
+
|
|
86
|
+
const promises: Promise<unknown>[] = ['Model', 'Serializer', 'Transform'].map(type => {
|
|
62
87
|
const lowerCaseType = type.toLowerCase();
|
|
63
88
|
const path = paths[lowerCaseType];
|
|
64
89
|
|
|
65
90
|
if (!path) throw new Error(`Configuration Error: ORM path for "${type}" must be defined.`);
|
|
66
91
|
|
|
67
|
-
return forEachFileImport(path, (exported, { name }) => {
|
|
92
|
+
return forEachFileImport(path, (exported: unknown, { name }: { name: string }) => {
|
|
68
93
|
// Transforms keep their original name, everything else gets converted to PascalCase with the type suffix
|
|
69
94
|
const alias = type === 'Transform' ? name : `${kebabCaseToPascalCase(name)}${type}`;
|
|
70
95
|
|
|
71
96
|
if (type === 'Model') {
|
|
72
97
|
Orm.store.set(name, new Map());
|
|
73
|
-
registerPluralName(name, exported);
|
|
98
|
+
registerPluralName(name, exported as { pluralName?: string });
|
|
74
99
|
}
|
|
75
100
|
|
|
76
|
-
|
|
101
|
+
const collection = this[pluralize(lowerCaseType) as keyof this] as Record<string, unknown>;
|
|
102
|
+
return collection[alias] = exported;
|
|
77
103
|
}, { ignoreAccessFailure: true, rawName: true, recursive: true, recursiveNaming: true });
|
|
78
104
|
});
|
|
79
105
|
|
|
@@ -82,16 +108,16 @@ export default class Orm {
|
|
|
82
108
|
|
|
83
109
|
// Discover views from paths.view (separate from model/serializer/transform)
|
|
84
110
|
if (paths.view) {
|
|
85
|
-
await forEachFileImport(paths.view, (exported, { name }) => {
|
|
111
|
+
await forEachFileImport(paths.view, (exported: unknown, { name }: { name: string }) => {
|
|
86
112
|
const alias = `${kebabCaseToPascalCase(name)}View`;
|
|
87
113
|
Orm.store.set(name, new Map());
|
|
88
|
-
registerPluralName(name, exported);
|
|
114
|
+
registerPluralName(name, exported as { pluralName?: string });
|
|
89
115
|
this.views[alias] = exported;
|
|
90
116
|
}, { ignoreAccessFailure: true, rawName: true, recursive: true, recursiveNaming: true });
|
|
91
117
|
}
|
|
92
118
|
|
|
93
119
|
// Setup event names for hooks after models are loaded
|
|
94
|
-
const eventNames = [];
|
|
120
|
+
const eventNames: string[] = [];
|
|
95
121
|
const operations = ['list', 'get', 'create', 'update', 'delete'];
|
|
96
122
|
const viewOperations = ['list', 'get'];
|
|
97
123
|
const timings = ['before', 'after'];
|
|
@@ -111,17 +137,17 @@ export default class Orm {
|
|
|
111
137
|
|
|
112
138
|
if (config.orm.timescale) {
|
|
113
139
|
const { default: TimescaleDB } = await import('./timescale/timescale-db.js');
|
|
114
|
-
this.sqlDb = new TimescaleDB();
|
|
140
|
+
this.sqlDb = new TimescaleDB() as SqlDb;
|
|
115
141
|
this.db = this.sqlDb;
|
|
116
142
|
promises.push(this.sqlDb.init());
|
|
117
143
|
} else if (config.orm.postgres) {
|
|
118
144
|
const { default: PostgresDB } = await import('./postgres/postgres-db.js');
|
|
119
|
-
this.sqlDb = new PostgresDB();
|
|
145
|
+
this.sqlDb = new PostgresDB() as SqlDb;
|
|
120
146
|
this.db = this.sqlDb;
|
|
121
147
|
promises.push(this.sqlDb.init());
|
|
122
148
|
} else if (config.orm.mysql) {
|
|
123
149
|
const { default: MysqlDB } = await import('./mysql/mysql-db.js');
|
|
124
|
-
this.sqlDb = new MysqlDB();
|
|
150
|
+
this.sqlDb = new MysqlDB() as SqlDb;
|
|
125
151
|
this.db = this.sqlDb;
|
|
126
152
|
promises.push(this.sqlDb.init());
|
|
127
153
|
} else if (this.options.dbType !== 'none') {
|
|
@@ -136,9 +162,9 @@ export default class Orm {
|
|
|
136
162
|
}
|
|
137
163
|
|
|
138
164
|
// Wire up memory resolver so store.find() can check model memory flags
|
|
139
|
-
Orm.store._memoryResolver = (modelName) => {
|
|
165
|
+
Orm.store._memoryResolver = (modelName: string): boolean => {
|
|
140
166
|
const { modelClass } = this.getRecordClasses(modelName);
|
|
141
|
-
return modelClass?.memory === true;
|
|
167
|
+
return (modelClass as { memory?: boolean })?.memory === true;
|
|
142
168
|
};
|
|
143
169
|
|
|
144
170
|
// Wire up SQL adapter reference for on-demand queries from store.find()/findAll()
|
|
@@ -150,21 +176,21 @@ export default class Orm {
|
|
|
150
176
|
Orm.initialized = true;
|
|
151
177
|
}
|
|
152
178
|
|
|
153
|
-
async startup() {
|
|
179
|
+
async startup(): Promise<void> {
|
|
154
180
|
if (this.sqlDb) await this.sqlDb.startup();
|
|
155
181
|
}
|
|
156
182
|
|
|
157
|
-
async shutdown() {
|
|
183
|
+
async shutdown(): Promise<void> {
|
|
158
184
|
if (this.sqlDb) await this.sqlDb.shutdown();
|
|
159
185
|
}
|
|
160
186
|
|
|
161
|
-
static get db() {
|
|
187
|
+
static get db(): OrmDB | SqlDb {
|
|
162
188
|
if (!Orm.initialized) throw new Error('ORM has not been initialized yet');
|
|
163
189
|
|
|
164
|
-
return Orm.instance.db
|
|
190
|
+
return Orm.instance.db!;
|
|
165
191
|
}
|
|
166
192
|
|
|
167
|
-
getRecordClasses(modelName) {
|
|
193
|
+
getRecordClasses(modelName: string): { modelClass: unknown; serializerClass: unknown } {
|
|
168
194
|
const modelClassPrefix = kebabCaseToPascalCase(modelName);
|
|
169
195
|
|
|
170
196
|
// Check views first, then models
|
|
@@ -182,21 +208,21 @@ export default class Orm {
|
|
|
182
208
|
};
|
|
183
209
|
}
|
|
184
210
|
|
|
185
|
-
isView(modelName) {
|
|
211
|
+
isView(modelName: string): boolean {
|
|
186
212
|
const modelClassPrefix = kebabCaseToPascalCase(modelName);
|
|
187
213
|
return !!this.views[`${modelClassPrefix}View`];
|
|
188
214
|
}
|
|
189
215
|
|
|
190
216
|
// Queue warnings to avoid the same error from being logged in the same iteration
|
|
191
|
-
warn(message) {
|
|
217
|
+
warn(message: string): void {
|
|
192
218
|
this.warnings.add(message);
|
|
193
219
|
|
|
194
220
|
setTimeout(() => {
|
|
195
|
-
this.warnings.forEach(warning => log.warn(warning));
|
|
221
|
+
this.warnings.forEach(warning => log.warn!(warning));
|
|
196
222
|
this.warnings.clear();
|
|
197
223
|
}, 0);
|
|
198
224
|
}
|
|
199
225
|
}
|
|
200
226
|
|
|
201
227
|
export const store = Orm.store;
|
|
202
|
-
export const relationships = Orm.relationships;
|
|
228
|
+
export const relationships = Orm.relationships;
|
|
@@ -1,13 +1,30 @@
|
|
|
1
|
-
import Orm, { store
|
|
2
|
-
import
|
|
1
|
+
import Orm, { store } from '@stonyx/orm';
|
|
2
|
+
import OrmRecord from './record.js';
|
|
3
|
+
import { getGlobalRegistry, getPendingRegistry, getPendingBelongsToRegistry, getBelongsToRegistry, getHasManyRegistry } from './relationships.js';
|
|
4
|
+
import type Serializer from './serializer.js';
|
|
5
|
+
|
|
6
|
+
interface CreateRecordOptions {
|
|
7
|
+
isDbRecord?: boolean;
|
|
8
|
+
serialize?: boolean;
|
|
9
|
+
transform?: boolean;
|
|
10
|
+
update?: boolean;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface PendingBelongsToEntry {
|
|
15
|
+
sourceRecord: OrmRecord;
|
|
16
|
+
sourceModelName: string;
|
|
17
|
+
relationshipKey: string;
|
|
18
|
+
relationshipId: unknown;
|
|
19
|
+
}
|
|
3
20
|
|
|
4
|
-
const defaultOptions = {
|
|
21
|
+
const defaultOptions: CreateRecordOptions = {
|
|
5
22
|
isDbRecord: false,
|
|
6
23
|
serialize: true,
|
|
7
24
|
transform: true
|
|
8
25
|
};
|
|
9
26
|
|
|
10
|
-
export function createRecord(modelName, rawData={}, userOptions={}) {
|
|
27
|
+
export function createRecord(modelName: string, rawData: { [key: string]: unknown } = {}, userOptions: CreateRecordOptions = {}): OrmRecord {
|
|
11
28
|
const orm = Orm.instance;
|
|
12
29
|
const { initialized } = Orm;
|
|
13
30
|
const options = { ...defaultOptions, ...userOptions };
|
|
@@ -20,24 +37,27 @@ export function createRecord(modelName, rawData={}, userOptions={}) {
|
|
|
20
37
|
}
|
|
21
38
|
|
|
22
39
|
const modelStore = store.get(modelName);
|
|
23
|
-
const globalRelationships =
|
|
24
|
-
const pendingRelationships =
|
|
40
|
+
const globalRelationships = getGlobalRegistry();
|
|
41
|
+
const pendingRelationships = getPendingRegistry();
|
|
25
42
|
|
|
26
43
|
if (!modelStore) throw new Error(`Model store for '${modelName}' is not registered. Ensure the model is defined before creating records.`);
|
|
27
44
|
|
|
28
45
|
assignRecordId(modelName, rawData);
|
|
29
|
-
|
|
46
|
+
const existingRecord = modelStore.get(rawData.id as number | string);
|
|
47
|
+
if (existingRecord) return existingRecord as OrmRecord;
|
|
30
48
|
|
|
31
|
-
const
|
|
49
|
+
const recordClasses = orm.getRecordClasses(modelName);
|
|
50
|
+
const modelClass = recordClasses.modelClass as (new (name: string) => { __name: string; [key: string]: unknown }) | undefined;
|
|
51
|
+
const serializerClass = recordClasses.serializerClass as new (model: { [key: string]: unknown }) => Serializer;
|
|
32
52
|
|
|
33
53
|
if (!modelClass) throw new Error(`A model named '${modelName}' does not exist`);
|
|
34
54
|
|
|
35
55
|
const model = new modelClass(modelName);
|
|
36
56
|
const serializer = new serializerClass(model);
|
|
37
|
-
const record = new
|
|
57
|
+
const record = new OrmRecord(model, serializer);
|
|
38
58
|
|
|
39
59
|
record.serialize(rawData, options);
|
|
40
|
-
modelStore.set(record.id, record);
|
|
60
|
+
modelStore.set(record.id as number | string, record);
|
|
41
61
|
|
|
42
62
|
// populate global hasMany relationships
|
|
43
63
|
const globalHasMany = globalRelationships.get(modelName);
|
|
@@ -51,12 +71,12 @@ export function createRecord(modelName, rawData={}, userOptions={}) {
|
|
|
51
71
|
}
|
|
52
72
|
|
|
53
73
|
// Fulfill pending belongsTo relationships
|
|
54
|
-
const pendingBelongsToQueue =
|
|
55
|
-
const pendingBelongsTo = pendingBelongsToQueue.get(modelName)?.get(record.id);
|
|
74
|
+
const pendingBelongsToQueue = getPendingBelongsToRegistry();
|
|
75
|
+
const pendingBelongsTo = pendingBelongsToQueue.get(modelName)?.get(record.id) as PendingBelongsToEntry[] | undefined;
|
|
56
76
|
|
|
57
77
|
if (pendingBelongsTo) {
|
|
58
|
-
const belongsToReg =
|
|
59
|
-
const hasManyReg =
|
|
78
|
+
const belongsToReg = getBelongsToRegistry();
|
|
79
|
+
const hasManyReg = getHasManyRegistry();
|
|
60
80
|
|
|
61
81
|
for (const { sourceRecord, sourceModelName, relationshipKey, relationshipId } of pendingBelongsTo) {
|
|
62
82
|
// Update the belongsTo relationship on the source record
|
|
@@ -87,7 +107,7 @@ export function createRecord(modelName, rawData={}, userOptions={}) {
|
|
|
87
107
|
return record;
|
|
88
108
|
}
|
|
89
109
|
|
|
90
|
-
export function updateRecord(record, rawData, userOptions={}) {
|
|
110
|
+
export function updateRecord(record: OrmRecord, rawData: unknown, userOptions: CreateRecordOptions = {}): void {
|
|
91
111
|
if (!rawData) throw new Error('rawData must be passed in to updateRecord call');
|
|
92
112
|
|
|
93
113
|
// Guard: read-only views cannot be updated
|
|
@@ -96,7 +116,7 @@ export function updateRecord(record, rawData, userOptions={}) {
|
|
|
96
116
|
throw new Error(`Cannot update records for read-only view '${modelName}'`);
|
|
97
117
|
}
|
|
98
118
|
|
|
99
|
-
const options = { ...defaultOptions, ...userOptions, update:true };
|
|
119
|
+
const options = { ...defaultOptions, ...userOptions, update: true };
|
|
100
120
|
|
|
101
121
|
record.serialize(rawData, options);
|
|
102
122
|
}
|
|
@@ -107,7 +127,7 @@ export function updateRecord(record, rawData, userOptions={}) {
|
|
|
107
127
|
* In MySQL mode with numeric IDs, assigns a temporary pending ID.
|
|
108
128
|
* MySQL's AUTO_INCREMENT provides the real ID after INSERT.
|
|
109
129
|
*/
|
|
110
|
-
function assignRecordId(modelName, rawData) {
|
|
130
|
+
function assignRecordId(modelName: string, rawData: { [key: string]: unknown }): void {
|
|
111
131
|
if (rawData.id) return;
|
|
112
132
|
|
|
113
133
|
// In SQL mode with numeric IDs, defer to database auto-increment
|
|
@@ -117,15 +137,15 @@ function assignRecordId(modelName, rawData) {
|
|
|
117
137
|
return;
|
|
118
138
|
}
|
|
119
139
|
|
|
120
|
-
const modelStore = Array.from(store.get(modelName)
|
|
121
|
-
rawData.id = modelStore.length ? modelStore.at(-1)
|
|
140
|
+
const modelStore = Array.from(store.get(modelName)!.values()) as OrmRecord[];
|
|
141
|
+
rawData.id = modelStore.length ? (modelStore.at(-1)!.id as number) + 1 : 1;
|
|
122
142
|
}
|
|
123
143
|
|
|
124
|
-
function isStringIdModel(modelName) {
|
|
125
|
-
const
|
|
144
|
+
function isStringIdModel(modelName: string): boolean {
|
|
145
|
+
const modelClass = Orm.instance.getRecordClasses(modelName).modelClass as (new (name: string) => { [key: string]: unknown }) | undefined;
|
|
126
146
|
if (!modelClass) return false;
|
|
127
147
|
|
|
128
148
|
const model = new modelClass(modelName);
|
|
129
149
|
|
|
130
|
-
return model.id?.type === 'string';
|
|
150
|
+
return (model.id as { type?: string } | undefined)?.type === 'string';
|
|
131
151
|
}
|
|
@@ -1,37 +1,40 @@
|
|
|
1
1
|
import { Request } from '@stonyx/rest-server';
|
|
2
|
+
import ModelProperty from './model-property.js';
|
|
2
3
|
import Orm from '@stonyx/orm';
|
|
3
4
|
import config from 'stonyx/config';
|
|
4
5
|
import { dbKey } from './db.js';
|
|
5
6
|
|
|
6
7
|
export default class MetaRequest extends Request {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
handlers: Record<string, unknown>;
|
|
9
|
+
|
|
10
|
+
constructor(...args: unknown[]) {
|
|
11
|
+
super(...args);
|
|
12
|
+
|
|
10
13
|
this.handlers = {
|
|
11
14
|
get: {
|
|
12
15
|
'/meta': () => {
|
|
13
16
|
try {
|
|
14
|
-
const { models } = Orm.instance;
|
|
15
|
-
const metadata = {};
|
|
17
|
+
const { models } = Orm.instance as Orm;
|
|
18
|
+
const metadata: Record<string, Record<string, unknown>> = {};
|
|
16
19
|
|
|
17
20
|
for (const [modelName, modelClass] of Object.entries(models)) {
|
|
18
21
|
const name = modelName.slice(0, -5).toLowerCase();
|
|
19
22
|
|
|
20
23
|
if (name === dbKey) continue;
|
|
21
24
|
|
|
22
|
-
const model = new modelClass(modelName);
|
|
23
|
-
const properties = {};
|
|
25
|
+
const model = new (modelClass as new (name: string) => Record<string, unknown>)(modelName);
|
|
26
|
+
const properties: Record<string, unknown> = {};
|
|
24
27
|
|
|
25
28
|
// Get regular properties and relationships
|
|
26
29
|
for (const [key, property] of Object.entries(model)) {
|
|
27
30
|
// Skip internal properties
|
|
28
31
|
if (key.startsWith('__')) continue;
|
|
29
32
|
|
|
30
|
-
if (property
|
|
31
|
-
properties[key] = { type: property.type };
|
|
33
|
+
if (property instanceof ModelProperty) {
|
|
34
|
+
properties[key] = { type: (property as { type: string }).type };
|
|
32
35
|
} else if (typeof property === 'function') {
|
|
33
|
-
const isBelongsTo = property
|
|
34
|
-
const isHasMany = property
|
|
36
|
+
const isBelongsTo = (property as { __relationshipType?: string }).__relationshipType === 'belongsTo';
|
|
37
|
+
const isHasMany = (property as { __relationshipType?: string }).__relationshipType === 'hasMany';
|
|
35
38
|
|
|
36
39
|
if (isBelongsTo || isHasMany) properties[key] = { [isBelongsTo ? 'belongsTo' : 'hasMany']: name };
|
|
37
40
|
}
|
|
@@ -40,16 +43,16 @@ export default class MetaRequest extends Request {
|
|
|
40
43
|
metadata[name] = properties;
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
return metadata;
|
|
46
|
+
return metadata;
|
|
44
47
|
} catch (error) {
|
|
45
|
-
return { error: error.message };
|
|
48
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
46
49
|
}
|
|
47
50
|
},
|
|
48
51
|
},
|
|
49
52
|
}
|
|
50
53
|
}
|
|
51
54
|
|
|
52
|
-
auth() {
|
|
55
|
+
auth(): number | undefined {
|
|
53
56
|
if (!config.orm.restServer.metaRoute) return 403;
|
|
54
57
|
}
|
|
55
58
|
}
|
|
@@ -4,10 +4,10 @@ import { createFile, createDirectory, readFile, updateFile, deleteDirectory } fr
|
|
|
4
4
|
import { dbKey } from './db.js';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
|
|
7
|
-
function getCollectionKeys() {
|
|
8
|
-
const SchemaClass = Orm.instance.models[`${dbKey}Model`]
|
|
7
|
+
function getCollectionKeys(): string[] {
|
|
8
|
+
const SchemaClass = (Orm.instance as Orm).models[`${dbKey}Model`] as new () => Record<string, unknown>;
|
|
9
9
|
const instance = new SchemaClass();
|
|
10
|
-
const keys = [];
|
|
10
|
+
const keys: string[] = [];
|
|
11
11
|
|
|
12
12
|
for (const key of Object.keys(instance)) {
|
|
13
13
|
if (key === '__name' || key === 'id') continue;
|
|
@@ -17,7 +17,7 @@ function getCollectionKeys() {
|
|
|
17
17
|
return keys;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
function getDirPath() {
|
|
20
|
+
function getDirPath(): string {
|
|
21
21
|
const { rootPath } = config;
|
|
22
22
|
const { file, directory } = config.orm.db;
|
|
23
23
|
const dbDir = path.dirname(path.resolve(`${rootPath}/${file}`));
|
|
@@ -25,7 +25,7 @@ function getDirPath() {
|
|
|
25
25
|
return path.join(dbDir, directory);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export async function fileToDirectory() {
|
|
28
|
+
export async function fileToDirectory(): Promise<void> {
|
|
29
29
|
const { rootPath } = config;
|
|
30
30
|
const { file } = config.orm.db;
|
|
31
31
|
const dbFilePath = path.resolve(`${rootPath}/${file}`);
|
|
@@ -33,7 +33,7 @@ export async function fileToDirectory() {
|
|
|
33
33
|
const dirPath = getDirPath();
|
|
34
34
|
|
|
35
35
|
// Read full data from db.json
|
|
36
|
-
const data = await readFile(dbFilePath, { json: true })
|
|
36
|
+
const data = await readFile(dbFilePath, { json: true }) as Record<string, unknown[]>;
|
|
37
37
|
|
|
38
38
|
// Create directory and write each collection
|
|
39
39
|
await createDirectory(dirPath);
|
|
@@ -43,13 +43,13 @@ export async function fileToDirectory() {
|
|
|
43
43
|
));
|
|
44
44
|
|
|
45
45
|
// Overwrite db.json with empty-array skeleton
|
|
46
|
-
const skeleton = {};
|
|
46
|
+
const skeleton: Record<string, unknown[]> = {};
|
|
47
47
|
for (const key of collectionKeys) skeleton[key] = [];
|
|
48
48
|
|
|
49
49
|
await updateFile(dbFilePath, skeleton, { json: true });
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
export async function directoryToFile() {
|
|
52
|
+
export async function directoryToFile(): Promise<void> {
|
|
53
53
|
const { rootPath } = config;
|
|
54
54
|
const { file } = config.orm.db;
|
|
55
55
|
const dbFilePath = path.resolve(`${rootPath}/${file}`);
|
|
@@ -57,7 +57,7 @@ export async function directoryToFile() {
|
|
|
57
57
|
const dirPath = getDirPath();
|
|
58
58
|
|
|
59
59
|
// Read each collection from the directory
|
|
60
|
-
const assembled = {};
|
|
60
|
+
const assembled: Record<string, unknown> = {};
|
|
61
61
|
|
|
62
62
|
await Promise.all(collectionKeys.map(async key => {
|
|
63
63
|
const filePath = path.join(dirPath, `${key}.json`);
|
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
import Orm from '@stonyx/orm';
|
|
2
2
|
|
|
3
|
-
function validType(type) {
|
|
3
|
+
function validType(type: string): boolean {
|
|
4
4
|
return Object.keys(Orm.instance.transforms).includes(type);
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export default class ModelProperty {
|
|
8
|
-
|
|
8
|
+
readonly __kind = 'ModelProperty' as const;
|
|
9
|
+
type: string;
|
|
10
|
+
private _value: unknown;
|
|
11
|
+
ignoreFirstTransform?: boolean;
|
|
12
|
+
|
|
13
|
+
constructor(type: string = 'passthrough', defaultValue?: unknown) {
|
|
9
14
|
if (!validType(type)) throw new Error(`Invalid model property type: ${type}`);
|
|
10
|
-
|
|
15
|
+
|
|
11
16
|
this.type = type;
|
|
12
17
|
this.value = defaultValue;
|
|
13
18
|
}
|
|
14
19
|
|
|
15
|
-
get value() {
|
|
20
|
+
get value(): unknown {
|
|
16
21
|
return this._value;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
|
-
set value(newValue) {
|
|
24
|
+
set value(newValue: unknown) {
|
|
20
25
|
if (this.ignoreFirstTransform) {
|
|
21
26
|
delete this.ignoreFirstTransform;
|
|
22
|
-
|
|
27
|
+
this._value = newValue;
|
|
28
|
+
return;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
if (newValue === undefined) return;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import attr from './attr.js';
|
|
2
2
|
|
|
3
3
|
export default class Model {
|
|
4
4
|
/**
|
|
@@ -9,12 +9,13 @@ export default class Model {
|
|
|
9
9
|
*
|
|
10
10
|
* Override in subclass: static memory = true;
|
|
11
11
|
*/
|
|
12
|
-
static memory = false;
|
|
13
|
-
static pluralName = undefined;
|
|
12
|
+
static memory: boolean = false;
|
|
13
|
+
static pluralName: string | undefined = undefined;
|
|
14
14
|
|
|
15
15
|
id = attr('number');
|
|
16
|
+
__name: string;
|
|
16
17
|
|
|
17
|
-
constructor(name) {
|
|
18
|
+
constructor(name: string) {
|
|
18
19
|
this.__name = name;
|
|
19
20
|
}
|
|
20
21
|
}
|