@stonyx/orm 0.2.1-beta.83 → 0.2.1-beta.84
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 +58 -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 +57 -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 +178 -0
- package/dist/manage-record.d.ts +13 -0
- package/dist/manage-record.js +113 -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 +411 -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 +453 -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 +473 -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 +35 -0
- package/dist/serializer.d.ts +17 -0
- package/dist/serializer.js +130 -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 +44 -0
- package/dist/timescale/timescale-db.js +81 -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 +165 -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.js → belongs-to.ts} +36 -17
- 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 +91 -0
- package/src/{hooks.js → hooks.ts} +23 -27
- package/src/{index.js → index.ts} +4 -4
- package/src/{main.js → main.ts} +59 -34
- package/src/{manage-record.js → manage-record.ts} +41 -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} +533 -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} +165 -95
- 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} +195 -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 +48 -0
- package/src/{serializer.js → serializer.ts} +44 -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 +107 -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} +53 -28
- package/src/view.ts +22 -0
- 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,25 @@ 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
|
-
return this[pluralize(lowerCaseType)][alias] = exported;
|
|
101
|
+
return (this as unknown as Record<string, Record<string, unknown>>)[pluralize(lowerCaseType)][alias] = exported;
|
|
77
102
|
}, { ignoreAccessFailure: true, rawName: true, recursive: true, recursiveNaming: true });
|
|
78
103
|
});
|
|
79
104
|
|
|
@@ -82,16 +107,16 @@ export default class Orm {
|
|
|
82
107
|
|
|
83
108
|
// Discover views from paths.view (separate from model/serializer/transform)
|
|
84
109
|
if (paths.view) {
|
|
85
|
-
await forEachFileImport(paths.view, (exported, { name }) => {
|
|
110
|
+
await forEachFileImport(paths.view, (exported: unknown, { name }: { name: string }) => {
|
|
86
111
|
const alias = `${kebabCaseToPascalCase(name)}View`;
|
|
87
112
|
Orm.store.set(name, new Map());
|
|
88
|
-
registerPluralName(name, exported);
|
|
113
|
+
registerPluralName(name, exported as { pluralName?: string });
|
|
89
114
|
this.views[alias] = exported;
|
|
90
115
|
}, { ignoreAccessFailure: true, rawName: true, recursive: true, recursiveNaming: true });
|
|
91
116
|
}
|
|
92
117
|
|
|
93
118
|
// Setup event names for hooks after models are loaded
|
|
94
|
-
const eventNames = [];
|
|
119
|
+
const eventNames: string[] = [];
|
|
95
120
|
const operations = ['list', 'get', 'create', 'update', 'delete'];
|
|
96
121
|
const viewOperations = ['list', 'get'];
|
|
97
122
|
const timings = ['before', 'after'];
|
|
@@ -111,17 +136,17 @@ export default class Orm {
|
|
|
111
136
|
|
|
112
137
|
if (config.orm.timescale) {
|
|
113
138
|
const { default: TimescaleDB } = await import('./timescale/timescale-db.js');
|
|
114
|
-
this.sqlDb = new TimescaleDB();
|
|
139
|
+
this.sqlDb = new TimescaleDB() as SqlDb;
|
|
115
140
|
this.db = this.sqlDb;
|
|
116
141
|
promises.push(this.sqlDb.init());
|
|
117
142
|
} else if (config.orm.postgres) {
|
|
118
143
|
const { default: PostgresDB } = await import('./postgres/postgres-db.js');
|
|
119
|
-
this.sqlDb = new PostgresDB();
|
|
144
|
+
this.sqlDb = new PostgresDB() as SqlDb;
|
|
120
145
|
this.db = this.sqlDb;
|
|
121
146
|
promises.push(this.sqlDb.init());
|
|
122
147
|
} else if (config.orm.mysql) {
|
|
123
148
|
const { default: MysqlDB } = await import('./mysql/mysql-db.js');
|
|
124
|
-
this.sqlDb = new MysqlDB();
|
|
149
|
+
this.sqlDb = new MysqlDB() as SqlDb;
|
|
125
150
|
this.db = this.sqlDb;
|
|
126
151
|
promises.push(this.sqlDb.init());
|
|
127
152
|
} else if (this.options.dbType !== 'none') {
|
|
@@ -136,9 +161,9 @@ export default class Orm {
|
|
|
136
161
|
}
|
|
137
162
|
|
|
138
163
|
// Wire up memory resolver so store.find() can check model memory flags
|
|
139
|
-
Orm.store._memoryResolver = (modelName) => {
|
|
164
|
+
Orm.store._memoryResolver = (modelName: string): boolean => {
|
|
140
165
|
const { modelClass } = this.getRecordClasses(modelName);
|
|
141
|
-
return modelClass?.memory === true;
|
|
166
|
+
return (modelClass as { memory?: boolean })?.memory === true;
|
|
142
167
|
};
|
|
143
168
|
|
|
144
169
|
// Wire up SQL adapter reference for on-demand queries from store.find()/findAll()
|
|
@@ -150,21 +175,21 @@ export default class Orm {
|
|
|
150
175
|
Orm.initialized = true;
|
|
151
176
|
}
|
|
152
177
|
|
|
153
|
-
async startup() {
|
|
178
|
+
async startup(): Promise<void> {
|
|
154
179
|
if (this.sqlDb) await this.sqlDb.startup();
|
|
155
180
|
}
|
|
156
181
|
|
|
157
|
-
async shutdown() {
|
|
182
|
+
async shutdown(): Promise<void> {
|
|
158
183
|
if (this.sqlDb) await this.sqlDb.shutdown();
|
|
159
184
|
}
|
|
160
185
|
|
|
161
|
-
static get db() {
|
|
186
|
+
static get db(): OrmDB | SqlDb {
|
|
162
187
|
if (!Orm.initialized) throw new Error('ORM has not been initialized yet');
|
|
163
188
|
|
|
164
|
-
return Orm.instance.db
|
|
189
|
+
return Orm.instance.db!;
|
|
165
190
|
}
|
|
166
191
|
|
|
167
|
-
getRecordClasses(modelName) {
|
|
192
|
+
getRecordClasses(modelName: string): { modelClass: unknown; serializerClass: unknown } {
|
|
168
193
|
const modelClassPrefix = kebabCaseToPascalCase(modelName);
|
|
169
194
|
|
|
170
195
|
// Check views first, then models
|
|
@@ -182,21 +207,21 @@ export default class Orm {
|
|
|
182
207
|
};
|
|
183
208
|
}
|
|
184
209
|
|
|
185
|
-
isView(modelName) {
|
|
210
|
+
isView(modelName: string): boolean {
|
|
186
211
|
const modelClassPrefix = kebabCaseToPascalCase(modelName);
|
|
187
212
|
return !!this.views[`${modelClassPrefix}View`];
|
|
188
213
|
}
|
|
189
214
|
|
|
190
215
|
// Queue warnings to avoid the same error from being logged in the same iteration
|
|
191
|
-
warn(message) {
|
|
216
|
+
warn(message: string): void {
|
|
192
217
|
this.warnings.add(message);
|
|
193
218
|
|
|
194
219
|
setTimeout(() => {
|
|
195
|
-
this.warnings.forEach(warning => log.warn(warning));
|
|
220
|
+
this.warnings.forEach(warning => log.warn!(warning));
|
|
196
221
|
this.warnings.clear();
|
|
197
222
|
}, 0);
|
|
198
223
|
}
|
|
199
224
|
}
|
|
200
225
|
|
|
201
226
|
export const store = Orm.store;
|
|
202
|
-
export const relationships = Orm.relationships;
|
|
227
|
+
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,26 @@ 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
|
-
if (modelStore.has(rawData.id)) return modelStore.get(rawData.id);
|
|
46
|
+
if (modelStore.has(rawData.id as number | string)) return modelStore.get(rawData.id as number | string)! as OrmRecord;
|
|
30
47
|
|
|
31
|
-
const
|
|
48
|
+
const recordClasses = orm.getRecordClasses(modelName);
|
|
49
|
+
const modelClass = recordClasses.modelClass as (new (name: string) => { __name: string; [key: string]: unknown }) | undefined;
|
|
50
|
+
const serializerClass = recordClasses.serializerClass as new (model: { [key: string]: unknown }) => Serializer;
|
|
32
51
|
|
|
33
52
|
if (!modelClass) throw new Error(`A model named '${modelName}' does not exist`);
|
|
34
53
|
|
|
35
54
|
const model = new modelClass(modelName);
|
|
36
55
|
const serializer = new serializerClass(model);
|
|
37
|
-
const record = new
|
|
56
|
+
const record = new OrmRecord(model, serializer);
|
|
38
57
|
|
|
39
58
|
record.serialize(rawData, options);
|
|
40
|
-
modelStore.set(record.id, record);
|
|
59
|
+
modelStore.set(record.id as number | string, record);
|
|
41
60
|
|
|
42
61
|
// populate global hasMany relationships
|
|
43
62
|
const globalHasMany = globalRelationships.get(modelName);
|
|
@@ -51,12 +70,12 @@ export function createRecord(modelName, rawData={}, userOptions={}) {
|
|
|
51
70
|
}
|
|
52
71
|
|
|
53
72
|
// Fulfill pending belongsTo relationships
|
|
54
|
-
const pendingBelongsToQueue =
|
|
55
|
-
const pendingBelongsTo = pendingBelongsToQueue.get(modelName)?.get(record.id);
|
|
73
|
+
const pendingBelongsToQueue = getPendingBelongsToRegistry();
|
|
74
|
+
const pendingBelongsTo = pendingBelongsToQueue.get(modelName)?.get(record.id) as PendingBelongsToEntry[] | undefined;
|
|
56
75
|
|
|
57
76
|
if (pendingBelongsTo) {
|
|
58
|
-
const belongsToReg =
|
|
59
|
-
const hasManyReg =
|
|
77
|
+
const belongsToReg = getBelongsToRegistry();
|
|
78
|
+
const hasManyReg = getHasManyRegistry();
|
|
60
79
|
|
|
61
80
|
for (const { sourceRecord, sourceModelName, relationshipKey, relationshipId } of pendingBelongsTo) {
|
|
62
81
|
// Update the belongsTo relationship on the source record
|
|
@@ -87,7 +106,7 @@ export function createRecord(modelName, rawData={}, userOptions={}) {
|
|
|
87
106
|
return record;
|
|
88
107
|
}
|
|
89
108
|
|
|
90
|
-
export function updateRecord(record, rawData, userOptions={}) {
|
|
109
|
+
export function updateRecord(record: OrmRecord, rawData: unknown, userOptions: CreateRecordOptions = {}): void {
|
|
91
110
|
if (!rawData) throw new Error('rawData must be passed in to updateRecord call');
|
|
92
111
|
|
|
93
112
|
// Guard: read-only views cannot be updated
|
|
@@ -96,7 +115,7 @@ export function updateRecord(record, rawData, userOptions={}) {
|
|
|
96
115
|
throw new Error(`Cannot update records for read-only view '${modelName}'`);
|
|
97
116
|
}
|
|
98
117
|
|
|
99
|
-
const options = { ...defaultOptions, ...userOptions, update:true };
|
|
118
|
+
const options = { ...defaultOptions, ...userOptions, update: true };
|
|
100
119
|
|
|
101
120
|
record.serialize(rawData, options);
|
|
102
121
|
}
|
|
@@ -107,7 +126,7 @@ export function updateRecord(record, rawData, userOptions={}) {
|
|
|
107
126
|
* In MySQL mode with numeric IDs, assigns a temporary pending ID.
|
|
108
127
|
* MySQL's AUTO_INCREMENT provides the real ID after INSERT.
|
|
109
128
|
*/
|
|
110
|
-
function assignRecordId(modelName, rawData) {
|
|
129
|
+
function assignRecordId(modelName: string, rawData: { [key: string]: unknown }): void {
|
|
111
130
|
if (rawData.id) return;
|
|
112
131
|
|
|
113
132
|
// In SQL mode with numeric IDs, defer to database auto-increment
|
|
@@ -117,15 +136,15 @@ function assignRecordId(modelName, rawData) {
|
|
|
117
136
|
return;
|
|
118
137
|
}
|
|
119
138
|
|
|
120
|
-
const modelStore = Array.from(store.get(modelName)
|
|
121
|
-
rawData.id = modelStore.length ? modelStore.at(-1)
|
|
139
|
+
const modelStore = Array.from(store.get(modelName)!.values()) as OrmRecord[];
|
|
140
|
+
rawData.id = modelStore.length ? (modelStore.at(-1)!.id as number) + 1 : 1;
|
|
122
141
|
}
|
|
123
142
|
|
|
124
|
-
function isStringIdModel(modelName) {
|
|
125
|
-
const
|
|
143
|
+
function isStringIdModel(modelName: string): boolean {
|
|
144
|
+
const modelClass = Orm.instance.getRecordClasses(modelName).modelClass as (new (name: string) => { [key: string]: unknown }) | undefined;
|
|
126
145
|
if (!modelClass) return false;
|
|
127
146
|
|
|
128
147
|
const model = new modelClass(modelName);
|
|
129
148
|
|
|
130
|
-
return model.id?.type === 'string';
|
|
149
|
+
return (model.id as { type?: string } | undefined)?.type === 'string';
|
|
131
150
|
}
|
|
@@ -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
|
}
|