@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.
Files changed (149) hide show
  1. package/dist/aggregates.d.ts +21 -0
  2. package/dist/aggregates.js +90 -0
  3. package/dist/attr.d.ts +2 -0
  4. package/dist/attr.js +22 -0
  5. package/dist/belongs-to.d.ts +11 -0
  6. package/dist/belongs-to.js +58 -0
  7. package/dist/cli.d.ts +22 -0
  8. package/dist/cli.js +148 -0
  9. package/dist/commands.d.ts +7 -0
  10. package/dist/commands.js +146 -0
  11. package/dist/db.d.ts +21 -0
  12. package/dist/db.js +174 -0
  13. package/dist/exports/db.d.ts +7 -0
  14. package/{src → dist}/exports/db.js +2 -4
  15. package/dist/has-many.d.ts +11 -0
  16. package/dist/has-many.js +57 -0
  17. package/dist/hooks.d.ts +47 -0
  18. package/dist/hooks.js +106 -0
  19. package/dist/index.d.ts +14 -0
  20. package/dist/index.js +34 -0
  21. package/dist/main.d.ts +46 -0
  22. package/dist/main.js +178 -0
  23. package/dist/manage-record.d.ts +13 -0
  24. package/dist/manage-record.js +113 -0
  25. package/dist/meta-request.d.ts +6 -0
  26. package/dist/meta-request.js +52 -0
  27. package/dist/migrate.d.ts +2 -0
  28. package/dist/migrate.js +57 -0
  29. package/dist/model-property.d.ts +9 -0
  30. package/dist/model-property.js +29 -0
  31. package/dist/model.d.ts +15 -0
  32. package/dist/model.js +18 -0
  33. package/dist/mysql/connection.d.ts +14 -0
  34. package/dist/mysql/connection.js +24 -0
  35. package/dist/mysql/migration-generator.d.ts +45 -0
  36. package/dist/mysql/migration-generator.js +245 -0
  37. package/dist/mysql/migration-runner.d.ts +12 -0
  38. package/dist/mysql/migration-runner.js +83 -0
  39. package/dist/mysql/mysql-db.d.ts +100 -0
  40. package/dist/mysql/mysql-db.js +411 -0
  41. package/dist/mysql/query-builder.d.ts +10 -0
  42. package/dist/mysql/query-builder.js +44 -0
  43. package/dist/mysql/schema-introspector.d.ts +19 -0
  44. package/dist/mysql/schema-introspector.js +286 -0
  45. package/dist/mysql/type-map.d.ts +21 -0
  46. package/dist/mysql/type-map.js +36 -0
  47. package/dist/orm-request.d.ts +38 -0
  48. package/dist/orm-request.js +453 -0
  49. package/dist/plural-registry.d.ts +4 -0
  50. package/{src → dist}/plural-registry.js +3 -6
  51. package/dist/postgres/connection.d.ts +15 -0
  52. package/dist/postgres/connection.js +30 -0
  53. package/dist/postgres/migration-generator.d.ts +45 -0
  54. package/dist/postgres/migration-generator.js +257 -0
  55. package/dist/postgres/migration-runner.d.ts +10 -0
  56. package/dist/postgres/migration-runner.js +82 -0
  57. package/dist/postgres/postgres-db.d.ts +119 -0
  58. package/dist/postgres/postgres-db.js +473 -0
  59. package/dist/postgres/query-builder.d.ts +27 -0
  60. package/dist/postgres/query-builder.js +98 -0
  61. package/dist/postgres/schema-introspector.d.ts +29 -0
  62. package/dist/postgres/schema-introspector.js +309 -0
  63. package/dist/postgres/type-map.d.ts +23 -0
  64. package/dist/postgres/type-map.js +53 -0
  65. package/dist/record.d.ts +75 -0
  66. package/dist/record.js +115 -0
  67. package/dist/relationships.d.ts +10 -0
  68. package/dist/relationships.js +35 -0
  69. package/dist/serializer.d.ts +17 -0
  70. package/dist/serializer.js +130 -0
  71. package/dist/setup-rest-server.d.ts +1 -0
  72. package/dist/setup-rest-server.js +54 -0
  73. package/dist/standalone-db.d.ts +58 -0
  74. package/dist/standalone-db.js +142 -0
  75. package/dist/store.d.ts +62 -0
  76. package/dist/store.js +271 -0
  77. package/dist/timescale/query-builder.d.ts +41 -0
  78. package/dist/timescale/query-builder.js +87 -0
  79. package/dist/timescale/timescale-db.d.ts +44 -0
  80. package/dist/timescale/timescale-db.js +81 -0
  81. package/dist/transforms.d.ts +2 -0
  82. package/dist/transforms.js +17 -0
  83. package/dist/types/orm-types.d.ts +142 -0
  84. package/dist/types/orm-types.js +1 -0
  85. package/dist/utils.d.ts +5 -0
  86. package/dist/utils.js +13 -0
  87. package/dist/view-resolver.d.ts +8 -0
  88. package/dist/view-resolver.js +165 -0
  89. package/dist/view.d.ts +11 -0
  90. package/dist/view.js +18 -0
  91. package/package.json +34 -11
  92. package/src/{aggregates.js → aggregates.ts} +27 -13
  93. package/src/{attr.js → attr.ts} +2 -2
  94. package/src/{belongs-to.js → belongs-to.ts} +36 -17
  95. package/src/{cli.js → cli.ts} +17 -11
  96. package/src/{commands.js → commands.ts} +179 -170
  97. package/src/{db.js → db.ts} +35 -26
  98. package/src/exports/db.ts +7 -0
  99. package/src/has-many.ts +91 -0
  100. package/src/{hooks.js → hooks.ts} +23 -27
  101. package/src/{index.js → index.ts} +4 -4
  102. package/src/{main.js → main.ts} +59 -34
  103. package/src/{manage-record.js → manage-record.ts} +41 -22
  104. package/src/{meta-request.js → meta-request.ts} +17 -14
  105. package/src/{migrate.js → migrate.ts} +9 -9
  106. package/src/{model-property.js → model-property.ts} +12 -6
  107. package/src/{model.js → model.ts} +5 -4
  108. package/src/mysql/{connection.js → connection.ts} +43 -28
  109. package/src/mysql/{migration-generator.js → migration-generator.ts} +332 -286
  110. package/src/mysql/{migration-runner.js → migration-runner.ts} +116 -110
  111. package/src/mysql/{mysql-db.js → mysql-db.ts} +533 -473
  112. package/src/mysql/{query-builder.js → query-builder.ts} +69 -64
  113. package/src/mysql/{schema-introspector.js → schema-introspector.ts} +355 -325
  114. package/src/mysql/{type-map.js → type-map.ts} +42 -37
  115. package/src/{orm-request.js → orm-request.ts} +165 -95
  116. package/src/plural-registry.ts +12 -0
  117. package/src/postgres/{connection.js → connection.ts} +14 -5
  118. package/src/postgres/{migration-generator.js → migration-generator.ts} +82 -38
  119. package/src/postgres/{migration-runner.js → migration-runner.ts} +11 -10
  120. package/src/postgres/{postgres-db.js → postgres-db.ts} +195 -114
  121. package/src/postgres/{query-builder.js → query-builder.ts} +27 -28
  122. package/src/postgres/{schema-introspector.js → schema-introspector.ts} +87 -58
  123. package/src/postgres/{type-map.js → type-map.ts} +10 -6
  124. package/src/{record.js → record.ts} +73 -34
  125. package/src/relationships.ts +48 -0
  126. package/src/{serializer.js → serializer.ts} +44 -36
  127. package/src/{setup-rest-server.js → setup-rest-server.ts} +18 -13
  128. package/src/{standalone-db.js → standalone-db.ts} +33 -24
  129. package/src/{store.js → store.ts} +90 -68
  130. package/src/timescale/{query-builder.js → query-builder.ts} +33 -38
  131. package/src/timescale/timescale-db.ts +107 -0
  132. package/src/transforms.ts +20 -0
  133. package/src/types/mysql2.d.ts +30 -0
  134. package/src/types/orm-types.ts +146 -0
  135. package/src/types/pg.d.ts +28 -0
  136. package/src/types/stonyx-cron.d.ts +5 -0
  137. package/src/types/stonyx-events.d.ts +4 -0
  138. package/src/types/stonyx-rest-server.d.ts +11 -0
  139. package/src/types/stonyx-utils.d.ts +33 -0
  140. package/src/types/stonyx.d.ts +21 -0
  141. package/src/utils.ts +16 -0
  142. package/src/{view-resolver.js → view-resolver.ts} +53 -28
  143. package/src/view.ts +22 -0
  144. package/src/has-many.js +0 -68
  145. package/src/relationships.js +0 -43
  146. package/src/timescale/timescale-db.js +0 -111
  147. package/src/transforms.js +0 -20
  148. package/src/utils.js +0 -12
  149. 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 {string} operation - Operation name: 'create', 'update', 'delete', 'get', or 'list'
30
- * @param {string} model - Model name (e.g., 'user', 'animal')
31
- * @param {Function} handler - Middleware function (context) => any
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 {Function} Unsubscribe function
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).push(handler);
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 {string} operation - Operation name
58
- * @param {string} model - Model name
59
- * @param {Function} handler - Middleware function (context) => void
60
- * @returns {Function} Unsubscribe function
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).push(handler);
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 {string} operation - Operation name
105
- * @param {string} model - Model name
106
- * @param {string} [type] - 'before' or 'after' (if omitted, clears both)
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) 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
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
@@ -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
- const defaultOptions = {
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
- models = {};
39
- serializers = {};
40
- views = {};
41
- transforms = { ...baseTransforms };
42
- warnings = new Set();
43
-
44
- constructor(options={}) {
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
- const promises = ['Model', 'Serializer', 'Transform'].map(type => {
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, relationships } from '@stonyx/orm';
2
- import Record from './record.js';
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 = relationships.get('global');
24
- const pendingRelationships = relationships.get('pending');
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 { modelClass, serializerClass } = orm.getRecordClasses(modelName);
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 Record(model, serializer);
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 = relationships.get('pendingBelongsTo');
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 = relationships.get('belongsTo');
59
- const hasManyReg = relationships.get('hasMany');
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).values());
121
- rawData.id = modelStore.length ? modelStore.at(-1).id + 1 : 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 { modelClass } = Orm.instance.getRecordClasses(modelName);
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
- constructor() {
8
- super(...arguments);
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?.constructor?.name === 'ModelProperty') {
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.toString().includes(`getRelationships('belongsTo',`);
34
- const isHasMany = property.toString().includes(`getRelationships('hasMany',`);
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
- constructor(type='passthrough', defaultValue) {
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
- return this._value = newValue;
27
+ this._value = newValue;
28
+ return;
23
29
  }
24
30
 
25
31
  if (newValue === undefined) return;
@@ -1,4 +1,4 @@
1
- import { attr } from '@stonyx/orm';
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
  }