@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
@@ -1,6 +1,21 @@
1
+ interface QueryResult {
2
+ sql: string;
3
+ values: unknown[];
4
+ }
5
+
6
+ interface VectorSearchOptions {
7
+ limit?: number;
8
+ where?: Record<string, unknown>;
9
+ }
10
+
11
+ interface HybridSearchOptions {
12
+ limit?: number;
13
+ where?: Record<string, unknown>;
14
+ }
15
+
1
16
  const SAFE_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_-]*$/;
2
17
 
3
- export function validateIdentifier(name, context = 'identifier') {
18
+ export function validateIdentifier(name: string, context: string = 'identifier'): string {
4
19
  if (!name || typeof name !== 'string' || !SAFE_IDENTIFIER.test(name)) {
5
20
  throw new Error(`Invalid SQL ${context}: "${name}". Identifiers must match ${SAFE_IDENTIFIER}`);
6
21
  }
@@ -8,7 +23,7 @@ export function validateIdentifier(name, context = 'identifier') {
8
23
  return name;
9
24
  }
10
25
 
11
- export function buildInsert(table, data) {
26
+ export function buildInsert(table: string, data: Record<string, unknown>): QueryResult {
12
27
  validateIdentifier(table, 'table name');
13
28
 
14
29
  const keys = Object.keys(data);
@@ -22,21 +37,21 @@ export function buildInsert(table, data) {
22
37
  return { sql, values };
23
38
  }
24
39
 
25
- export function buildUpdate(table, id, data) {
40
+ export function buildUpdate(table: string, id: unknown, data: Record<string, unknown>): QueryResult {
26
41
  validateIdentifier(table, 'table name');
27
42
 
28
43
  const keys = Object.keys(data);
29
44
  keys.forEach(k => validateIdentifier(k, 'column name'));
30
45
 
31
46
  const setClauses = keys.map((k, i) => `"${k}" = $${i + 1}`);
32
- const values = [...keys.map(k => data[k]), id];
47
+ const values: unknown[] = [...keys.map(k => data[k]), id];
33
48
 
34
49
  const sql = `UPDATE "${table}" SET ${setClauses.join(', ')} WHERE "id" = $${keys.length + 1}`;
35
50
 
36
51
  return { sql, values };
37
52
  }
38
53
 
39
- export function buildDelete(table, id) {
54
+ export function buildDelete(table: string, id: unknown): QueryResult {
40
55
  validateIdentifier(table, 'table name');
41
56
 
42
57
  return {
@@ -45,7 +60,7 @@ export function buildDelete(table, id) {
45
60
  };
46
61
  }
47
62
 
48
- export function buildSelect(table, conditions) {
63
+ export function buildSelect(table: string, conditions?: Record<string, unknown>): QueryResult {
49
64
  validateIdentifier(table, 'table name');
50
65
 
51
66
  if (!conditions || Object.keys(conditions).length === 0) {
@@ -65,20 +80,13 @@ export function buildSelect(table, conditions) {
65
80
 
66
81
  /**
67
82
  * Build a vector similarity search query using cosine distance (<=>).
68
- * @param {string} table - Table name
69
- * @param {string} vectorColumn - Name of the vector column
70
- * @param {number[]} queryVector - The query vector
71
- * @param {Object} [options]
72
- * @param {number} [options.limit=10] - Number of results to return
73
- * @param {Object} [options.where] - Additional WHERE conditions
74
- * @returns {{ sql: string, values: any[] }}
75
83
  */
76
- export function buildVectorSearch(table, vectorColumn, queryVector, options = {}) {
84
+ export function buildVectorSearch(table: string, vectorColumn: string, queryVector: number[], options: VectorSearchOptions = {}): QueryResult {
77
85
  validateIdentifier(table, 'table name');
78
86
  validateIdentifier(vectorColumn, 'column name');
79
87
 
80
88
  const { limit = 10, where } = options;
81
- const values = [];
89
+ const values: unknown[] = [];
82
90
  let paramIndex = 1;
83
91
 
84
92
  // Vector parameter as a formatted string for pgvector
@@ -86,7 +94,7 @@ export function buildVectorSearch(table, vectorColumn, queryVector, options = {}
86
94
  values.push(vectorStr);
87
95
  const vectorParam = `$${paramIndex++}`;
88
96
 
89
- let whereClauses = [];
97
+ const whereClauses: string[] = [];
90
98
  if (where) {
91
99
  for (const [k, v] of Object.entries(where)) {
92
100
  validateIdentifier(k, 'column name');
@@ -106,23 +114,14 @@ export function buildVectorSearch(table, vectorColumn, queryVector, options = {}
106
114
  /**
107
115
  * Build a hybrid search query combining vector similarity with text filtering.
108
116
  * Uses cosine distance for vector ranking and ILIKE for text matching.
109
- * @param {string} table - Table name
110
- * @param {string} vectorColumn - Vector column name
111
- * @param {number[]} queryVector - The query vector
112
- * @param {string} textColumn - Column to search text in
113
- * @param {string} textQuery - Text to search for
114
- * @param {Object} [options]
115
- * @param {number} [options.limit=10]
116
- * @param {Object} [options.where] - Additional WHERE conditions
117
- * @returns {{ sql: string, values: any[] }}
118
117
  */
119
- export function buildHybridSearch(table, vectorColumn, queryVector, textColumn, textQuery, options = {}) {
118
+ export function buildHybridSearch(table: string, vectorColumn: string, queryVector: number[], textColumn: string, textQuery: string, options: HybridSearchOptions = {}): QueryResult {
120
119
  validateIdentifier(table, 'table name');
121
120
  validateIdentifier(vectorColumn, 'column name');
122
121
  validateIdentifier(textColumn, 'column name');
123
122
 
124
123
  const { limit = 10, where } = options;
125
- const values = [];
124
+ const values: unknown[] = [];
126
125
  let paramIndex = 1;
127
126
 
128
127
  const vectorStr = `[${queryVector.join(',')}]`;
@@ -132,7 +131,7 @@ export function buildHybridSearch(table, vectorColumn, queryVector, textColumn,
132
131
  values.push(`%${textQuery}%`);
133
132
  const textParam = `$${paramIndex++}`;
134
133
 
135
- let whereClauses = [`"${textColumn}" ILIKE ${textParam}`];
134
+ const whereClauses: string[] = [`"${textColumn}" ILIKE ${textParam}`];
136
135
  if (where) {
137
136
  for (const [k, v] of Object.entries(where)) {
138
137
  validateIdentifier(k, 'column name');
@@ -4,39 +4,69 @@ import { camelCaseToKebabCase } from '@stonyx/utils/string';
4
4
  import { getPluralName } from '../plural-registry.js';
5
5
  import { dbKey } from '../db.js';
6
6
  import { AggregateProperty } from '../aggregates.js';
7
+ import type { ForeignKeyDef, ModelSchema, ViewSchema } from '../types/orm-types.js';
8
+ import ModelProperty from '../model-property.js';
7
9
 
8
- function getRelationshipInfo(property) {
10
+ interface RelationshipInfo {
11
+ type: 'belongsTo' | 'hasMany';
12
+ modelName: string | null;
13
+ }
14
+
15
+ interface ViewSnapshotEntry {
16
+ viewName: string;
17
+ source: string;
18
+ groupBy?: string;
19
+ columns: Record<string, string>;
20
+ foreignKeys: Record<string, ForeignKeyDef>;
21
+ isView: boolean;
22
+ viewQuery: string;
23
+ }
24
+
25
+ interface ModelSnapshotEntry {
26
+ table: string;
27
+ idType: string;
28
+ columns: Record<string, string>;
29
+ foreignKeys: Record<string, ForeignKeyDef>;
30
+ vectorColumns?: Record<string, number>;
31
+ }
32
+
33
+ interface JoinDef {
34
+ table: string;
35
+ condition: string;
36
+ }
37
+
38
+ function getRelationshipInfo(property: unknown): RelationshipInfo | null {
9
39
  if (typeof property !== 'function') return null;
10
- const fnStr = property.toString();
11
- const modelName = property.__relatedModelName || null;
40
+ const relType = (property as { __relationshipType?: string }).__relationshipType;
41
+ const modelName = (property as { __relatedModelName?: string }).__relatedModelName || null;
12
42
 
13
- if (fnStr.includes(`getRelationships('belongsTo',`)) return { type: 'belongsTo', modelName };
14
- if (fnStr.includes(`getRelationships('hasMany',`)) return { type: 'hasMany', modelName };
43
+ if (relType === 'belongsTo') return { type: 'belongsTo', modelName };
44
+ if (relType === 'hasMany') return { type: 'hasMany', modelName };
15
45
 
16
46
  return null;
17
47
  }
18
48
 
19
- function sanitizeTableName(name) {
49
+ function sanitizeTableName(name: string): string {
20
50
  return name.replace(/[-/]/g, '_');
21
51
  }
22
52
 
23
- export function introspectModels() {
24
- const { models } = Orm.instance;
25
- const schemas = {};
53
+ export function introspectModels(): Record<string, ModelSchema> {
54
+ const { models } = Orm.instance as { models: Record<string, unknown> };
55
+ const schemas: Record<string, ModelSchema> = {};
26
56
 
27
57
  for (const [modelKey, modelClass] of Object.entries(models)) {
28
58
  const name = camelCaseToKebabCase(modelKey.slice(0, -5));
29
59
 
30
60
  if (name === dbKey) continue;
31
61
 
32
- const model = new modelClass(modelKey);
33
- const columns = {};
34
- const foreignKeys = {};
35
- const relationships = { belongsTo: {}, hasMany: {} };
36
- const vectorColumns = {};
62
+ const model = new (modelClass as new (key: string) => Record<string, unknown>)(modelKey);
63
+ const columns: Record<string, string> = {};
64
+ const foreignKeys: Record<string, ForeignKeyDef> = {};
65
+ const relationships: { belongsTo: Record<string, string | null>; hasMany: Record<string, string | null> } = { belongsTo: {}, hasMany: {} };
66
+ const vectorColumns: Record<string, number> = {};
37
67
  let idType = 'number';
38
68
 
39
- const transforms = Orm.instance.transforms;
69
+ const transforms = (Orm.instance as { transforms: Record<string, unknown> }).transforms;
40
70
 
41
71
  for (const [key, property] of Object.entries(model)) {
42
72
  if (key.startsWith('__')) continue;
@@ -47,15 +77,16 @@ export function introspectModels() {
47
77
  relationships.belongsTo[key] = relInfo.modelName;
48
78
  } else if (relInfo?.type === 'hasMany') {
49
79
  relationships.hasMany[key] = relInfo.modelName;
50
- } else if (property?.constructor?.name === 'ModelProperty') {
80
+ } else if (property instanceof ModelProperty) {
81
+ const prop = property as { type: string; dimensions?: number };
51
82
  if (key === 'id') {
52
- idType = property.type;
53
- } else if (property.type === 'vector') {
54
- const dimensions = property.dimensions || 1536;
83
+ idType = prop.type;
84
+ } else if (prop.type === 'vector') {
85
+ const dimensions = prop.dimensions || 1536;
55
86
  columns[key] = getVectorType(dimensions);
56
87
  vectorColumns[key] = dimensions;
57
88
  } else {
58
- columns[key] = getPgType(property.type, transforms[property.type]);
89
+ columns[key] = getPgType(prop.type, transforms[prop.type] as undefined);
59
90
  }
60
91
  }
61
92
  }
@@ -64,7 +95,7 @@ export function introspectModels() {
64
95
  for (const [relName, targetModelName] of Object.entries(relationships.belongsTo)) {
65
96
  const fkColumn = `${relName}_id`;
66
97
  foreignKeys[fkColumn] = {
67
- references: sanitizeTableName(getPluralName(targetModelName)),
98
+ references: sanitizeTableName(getPluralName(targetModelName!)),
68
99
  column: 'id',
69
100
  };
70
101
  }
@@ -76,17 +107,17 @@ export function introspectModels() {
76
107
  foreignKeys,
77
108
  relationships,
78
109
  vectorColumns,
79
- memory: modelClass.memory === true,
110
+ memory: (modelClass as { memory?: boolean }).memory === true,
80
111
  };
81
112
  }
82
113
 
83
114
  return schemas;
84
115
  }
85
116
 
86
- export function buildTableDDL(name, schema, allSchemas = {}) {
117
+ export function buildTableDDL(name: string, schema: ModelSchema, allSchemas: Record<string, ModelSchema> = {}): string {
87
118
  const { idType, columns, foreignKeys } = schema;
88
119
  const table = sanitizeTableName(schema.table);
89
- const lines = [];
120
+ const lines: string[] = [];
90
121
 
91
122
  // Primary key
92
123
  if (idType === 'string') {
@@ -121,13 +152,10 @@ export function buildTableDDL(name, schema, allSchemas = {}) {
121
152
 
122
153
  /**
123
154
  * Build HNSW index DDL for vector columns on a model.
124
- * @param {string} name - Model name
125
- * @param {Object} schema - Model schema with vectorColumns
126
- * @returns {string[]} Array of CREATE INDEX statements
127
155
  */
128
- export function buildVectorIndexDDL(name, schema) {
156
+ export function buildVectorIndexDDL(name: string, schema: ModelSchema): string[] {
129
157
  const table = sanitizeTableName(schema.table);
130
- const statements = [];
158
+ const statements: string[] = [];
131
159
 
132
160
  for (const [col] of Object.entries(schema.vectorColumns || {})) {
133
161
  statements.push(
@@ -138,7 +166,7 @@ export function buildVectorIndexDDL(name, schema) {
138
166
  return statements;
139
167
  }
140
168
 
141
- function getReferencedIdType(tableName, allSchemas) {
169
+ function getReferencedIdType(tableName: string, allSchemas: Record<string, ModelSchema>): string {
142
170
  for (const schema of Object.values(allSchemas)) {
143
171
  if (schema.table === tableName) {
144
172
  return schema.idType === 'string' ? 'VARCHAR(255)' : 'INTEGER';
@@ -148,11 +176,11 @@ function getReferencedIdType(tableName, allSchemas) {
148
176
  return 'INTEGER';
149
177
  }
150
178
 
151
- export function getTopologicalOrder(schemas) {
152
- const visited = new Set();
153
- const order = [];
179
+ export function getTopologicalOrder(schemas: Record<string, ModelSchema>): string[] {
180
+ const visited = new Set<string>();
181
+ const order: string[] = [];
154
182
 
155
- function visit(name) {
183
+ function visit(name: string): void {
156
184
  if (visited.has(name)) return;
157
185
  visited.add(name);
158
186
 
@@ -161,7 +189,7 @@ export function getTopologicalOrder(schemas) {
161
189
 
162
190
  // Visit dependencies (belongsTo targets) first
163
191
  for (const targetModelName of Object.values(schema.relationships.belongsTo)) {
164
- visit(targetModelName);
192
+ visit(targetModelName!);
165
193
  }
166
194
 
167
195
  order.push(name);
@@ -174,23 +202,23 @@ export function getTopologicalOrder(schemas) {
174
202
  return order;
175
203
  }
176
204
 
177
- export function introspectViews() {
178
- const orm = Orm.instance;
205
+ export function introspectViews(): Record<string, ViewSchema> {
206
+ const orm = Orm.instance as { views?: Record<string, unknown> };
179
207
  if (!orm.views) return {};
180
208
 
181
- const schemas = {};
209
+ const schemas: Record<string, ViewSchema> = {};
182
210
 
183
211
  for (const [viewKey, viewClass] of Object.entries(orm.views)) {
184
212
  const name = camelCaseToKebabCase(viewKey.slice(0, -4)); // Remove 'View' suffix
185
213
 
186
- const source = viewClass.source;
214
+ const source = (viewClass as { source?: string }).source;
187
215
  if (!source) continue;
188
216
 
189
- const model = new viewClass(name);
190
- const columns = {};
191
- const foreignKeys = {};
192
- const aggregates = {};
193
- const relationships = { belongsTo: {}, hasMany: {} };
217
+ const model = new (viewClass as new (name: string) => Record<string, unknown>)(name);
218
+ const columns: Record<string, string> = {};
219
+ const foreignKeys: Record<string, ForeignKeyDef> = {};
220
+ const aggregates: Record<string, AggregateProperty> = {};
221
+ const relationships: { belongsTo: Record<string, string | null>; hasMany: Record<string, string | null> } = { belongsTo: {}, hasMany: {} };
194
222
 
195
223
  for (const [key, property] of Object.entries(model)) {
196
224
  if (key.startsWith('__')) continue;
@@ -207,34 +235,35 @@ export function introspectViews() {
207
235
  relationships.belongsTo[key] = relInfo.modelName;
208
236
  const fkColumn = `${key}_id`;
209
237
  foreignKeys[fkColumn] = {
210
- references: sanitizeTableName(getPluralName(relInfo.modelName)),
238
+ references: sanitizeTableName(getPluralName(relInfo.modelName!)),
211
239
  column: 'id',
212
240
  };
213
241
  } else if (relInfo?.type === 'hasMany') {
214
242
  relationships.hasMany[key] = relInfo.modelName;
215
- } else if (property?.constructor?.name === 'ModelProperty') {
216
- const transforms = Orm.instance.transforms;
217
- columns[key] = getPgType(property.type, transforms[property.type]);
243
+ } else if (property instanceof ModelProperty) {
244
+ const transforms = (Orm.instance as { transforms: Record<string, unknown> }).transforms;
245
+ const prop = property as { type: string };
246
+ columns[key] = getPgType(prop.type, transforms[prop.type] as undefined);
218
247
  }
219
248
  }
220
249
 
221
250
  schemas[name] = {
222
251
  viewName: sanitizeTableName(getPluralName(name)),
223
252
  source,
224
- groupBy: viewClass.groupBy || undefined,
253
+ groupBy: (viewClass as { groupBy?: string }).groupBy || undefined,
225
254
  columns,
226
255
  foreignKeys,
227
256
  aggregates,
228
257
  relationships,
229
258
  isView: true,
230
- memory: viewClass.memory !== false ? false : false, // Views default to memory:false
259
+ memory: false, // Views default to memory:false
231
260
  };
232
261
  }
233
262
 
234
263
  return schemas;
235
264
  }
236
265
 
237
- export function buildViewDDL(name, viewSchema, modelSchemas = {}) {
266
+ export function buildViewDDL(name: string, viewSchema: ViewSchema, modelSchemas: Record<string, ModelSchema> = {}): string {
238
267
  if (!viewSchema.source) {
239
268
  throw new Error(`View '${name}' must define a source model`);
240
269
  }
@@ -245,8 +274,8 @@ export function buildViewDDL(name, viewSchema, modelSchemas = {}) {
245
274
  ? sourceSchema.table
246
275
  : getPluralName(sourceModelName));
247
276
 
248
- const selectColumns = [];
249
- const joins = [];
277
+ const selectColumns: string[] = [];
278
+ const joins: JoinDef[] = [];
250
279
  const hasAggregates = Object.keys(viewSchema.aggregates || {}).length > 0;
251
280
  const groupByField = viewSchema.groupBy;
252
281
 
@@ -260,7 +289,7 @@ export function buildViewDDL(name, viewSchema, modelSchemas = {}) {
260
289
  // Aggregate columns
261
290
  for (const [key, aggProp] of Object.entries(viewSchema.aggregates || {})) {
262
291
  // Use pgFunction if available, fall back to mysqlFunction
263
- const fn = aggProp.pgFunction || aggProp.mysqlFunction;
292
+ const fn = (aggProp as AggregateProperty & { pgFunction?: string }).pgFunction || aggProp.mysqlFunction;
264
293
 
265
294
  if (aggProp.relationship === undefined) {
266
295
  // Field-level aggregate (groupBy views)
@@ -317,8 +346,8 @@ export function buildViewDDL(name, viewSchema, modelSchemas = {}) {
317
346
  return sql;
318
347
  }
319
348
 
320
- export function viewSchemasToSnapshot(viewSchemas) {
321
- const snapshot = {};
349
+ export function viewSchemasToSnapshot(viewSchemas: Record<string, ViewSchema>): Record<string, ViewSnapshotEntry> {
350
+ const snapshot: Record<string, ViewSnapshotEntry> = {};
322
351
 
323
352
  for (const [name, schema] of Object.entries(viewSchemas)) {
324
353
  snapshot[name] = {
@@ -335,8 +364,8 @@ export function viewSchemasToSnapshot(viewSchemas) {
335
364
  return snapshot;
336
365
  }
337
366
 
338
- export function schemasToSnapshot(schemas) {
339
- const snapshot = {};
367
+ export function schemasToSnapshot(schemas: Record<string, ModelSchema>): Record<string, ModelSnapshotEntry> {
368
+ const snapshot: Record<string, ModelSnapshotEntry> = {};
340
369
 
341
370
  for (const [name, schema] of Object.entries(schemas)) {
342
371
  snapshot[name] = {
@@ -1,4 +1,10 @@
1
- const typeMap = {
1
+ interface TransformFn {
2
+ pgType?: string;
3
+ mysqlType?: string;
4
+ (...args: unknown[]): unknown;
5
+ }
6
+
7
+ const typeMap: Record<string, string> = {
2
8
  string: 'VARCHAR(255)',
3
9
  number: 'INTEGER',
4
10
  float: 'REAL',
@@ -24,7 +30,7 @@ const typeMap = {
24
30
  * - Otherwise, defaults to JSONB. Values are JSON-stringified on write and
25
31
  * JSON-parsed on read by PostgreSQL natively.
26
32
  */
27
- export function getPgType(attrType, transformFn) {
33
+ export function getPgType(attrType: string, transformFn?: TransformFn): string {
28
34
  if (typeMap[attrType]) return typeMap[attrType];
29
35
  if (transformFn?.pgType) return transformFn.pgType;
30
36
  if (transformFn?.mysqlType) return mysqlTypeToPg(transformFn.mysqlType);
@@ -34,14 +40,12 @@ export function getPgType(attrType, transformFn) {
34
40
 
35
41
  /**
36
42
  * Returns a vector column type for the given dimensions.
37
- * @param {number} dimensions - Vector dimensionality (e.g. 768, 1536)
38
- * @returns {string}
39
43
  */
40
- export function getVectorType(dimensions) {
44
+ export function getVectorType(dimensions: number): string {
41
45
  return `vector(${dimensions})`;
42
46
  }
43
47
 
44
- function mysqlTypeToPg(mysqlType) {
48
+ function mysqlTypeToPg(mysqlType: string): string {
45
49
  const upper = mysqlType.toUpperCase();
46
50
  if (upper === 'TINYINT(1)') return 'BOOLEAN';
47
51
  if (upper === 'INT' || upper === 'INT AUTO_INCREMENT') return 'INTEGER';
@@ -1,33 +1,72 @@
1
- import { store } from './index.js';
1
+ import { store } from '@stonyx/orm';
2
2
  import { getComputedProperties } from "./serializer.js";
3
3
  import { camelCaseToKebabCase } from '@stonyx/utils/string';
4
4
  import { getPluralName } from './plural-registry.js';
5
+ import type Serializer from './serializer.js';
6
+
7
+ interface ToJSONOptions {
8
+ fields?: Set<string>;
9
+ baseUrl?: string;
10
+ }
11
+
12
+ interface SerializeOptions {
13
+ update?: boolean;
14
+ serialize?: boolean;
15
+ transform?: boolean;
16
+ [key: string]: unknown;
17
+ }
18
+
19
+ interface UnloadOptions {
20
+ [key: string]: unknown;
21
+ }
22
+
23
+ interface RelationshipLinks {
24
+ self: string;
25
+ related: string;
26
+ }
27
+
28
+ interface RelationshipEntry {
29
+ data: { type: string; id: unknown } | { type: string; id: unknown }[] | null;
30
+ links?: RelationshipLinks;
31
+ }
32
+
33
+ interface JSONAPIResult {
34
+ attributes: { [key: string]: unknown };
35
+ relationships: { [key: string]: RelationshipEntry };
36
+ id: unknown;
37
+ type: string;
38
+ links?: { self: string };
39
+ }
40
+
5
41
  export default class Record {
6
42
  /** @private */
7
- __data = {};
43
+ __data: { [key: string]: unknown } = {};
8
44
  /** @private */
9
- __relationships = {};
45
+ __relationships: { [key: string]: unknown } = {};
10
46
  /** @private */
11
47
  __serialized = false;
48
+ /** @private */
49
+ __model: { __name: string; [key: string]: unknown };
50
+ /** @private */
51
+ __serializer: Serializer;
52
+
53
+ [key: string]: unknown;
12
54
 
13
- constructor(model, serializer) {
14
- /** @private */
55
+ constructor(model: { __name: string; [key: string]: unknown }, serializer: Serializer) {
15
56
  this.__model = model;
16
- /** @private */
17
57
  this.__serializer = serializer;
18
-
19
58
  }
20
59
 
21
- serialize(rawData, options={}) {
22
- const { __data:data } = this;
23
-
60
+ serialize(rawData?: unknown, options: SerializeOptions = {}): { [key: string]: unknown } {
61
+ const { __data: data } = this;
62
+
24
63
  if (this.__serialized && !options.update) {
25
- const relatedIds = {};
64
+ const relatedIds: { [key: string]: unknown } = {};
26
65
 
27
- for (const [ key, childRecord ] of Object.entries(this.__relationships)) {
28
- relatedIds[key] = Array.isArray(childRecord)
29
- ? childRecord.map(r => r.id)
30
- : childRecord?.id ?? null;
66
+ for (const [key, childRecord] of Object.entries(this.__relationships)) {
67
+ relatedIds[key] = Array.isArray(childRecord)
68
+ ? childRecord.map((r: Record) => r.id)
69
+ : (childRecord as Record)?.id ?? null;
31
70
  }
32
71
 
33
72
  return { ...data, ...relatedIds };
@@ -40,32 +79,32 @@ export default class Record {
40
79
  }
41
80
 
42
81
  // Similar to serialize, but preserves top level relationship records
43
- format() {
82
+ format(): { [key: string]: unknown } {
44
83
  if (!this.__serialized) throw new Error('Record must be serialized before being converted to JSON');
45
-
46
- const { __data:data } = this;
47
- const records = {};
48
-
49
- for (const [ key, childRecord ] of Object.entries(this.__relationships)) {
50
- records[key] = Array.isArray(childRecord)
51
- ? childRecord.map(r => r.serialize())
52
- : childRecord?.serialize() ?? null;
84
+
85
+ const { __data: data } = this;
86
+ const records: { [key: string]: unknown } = {};
87
+
88
+ for (const [key, childRecord] of Object.entries(this.__relationships)) {
89
+ records[key] = Array.isArray(childRecord)
90
+ ? childRecord.map((r: Record) => r.serialize())
91
+ : (childRecord as Record)?.serialize() ?? null;
53
92
  }
54
93
 
55
94
  return { ...data, ...records };
56
95
  }
57
96
 
58
97
  // Formats record for JSON API output
59
- toJSON(options = {}) {
98
+ toJSON(options: ToJSONOptions = {}): JSONAPIResult {
60
99
  if (!this.__serialized) throw new Error('Record must be serialized before being converted to JSON');
61
100
 
62
101
  const { fields, baseUrl } = options;
63
- const { __data:data } = this;
102
+ const { __data: data } = this;
64
103
  const modelName = this.__model.__name;
65
104
  const pluralizedModelName = getPluralName(modelName);
66
105
  const recordId = data.id;
67
- const relationships = {};
68
- const attributes = {};
106
+ const relationships: { [key: string]: RelationshipEntry } = {};
107
+ const attributes: { [key: string]: unknown } = {};
69
108
 
70
109
  for (const [key, value] of Object.entries(data)) {
71
110
  if (key === 'id') continue;
@@ -75,15 +114,15 @@ export default class Record {
75
114
 
76
115
  for (const [key, getter] of getComputedProperties(this.__model)) {
77
116
  if (fields && !fields.has(key)) continue;
78
- attributes[key] = getter.call(this);
117
+ attributes[key] = (getter as () => unknown).call(this);
79
118
  }
80
119
 
81
120
  for (const [key, childRecord] of Object.entries(this.__relationships)) {
82
121
  if (fields && !fields.has(key)) continue;
83
122
 
84
123
  const relationshipData = Array.isArray(childRecord)
85
- ? childRecord.map(r => ({ type: r.__model.__name, id: r.id }))
86
- : childRecord ? { type: childRecord.__model.__name, id: childRecord.id } : null;
124
+ ? childRecord.map((r: Record) => ({ type: r.__model.__name, id: r.id }))
125
+ : childRecord ? { type: (childRecord as Record).__model.__name, id: (childRecord as Record).id } : null;
87
126
 
88
127
  // Dasherize the key for URL paths (e.g., accessLinks -> access-links)
89
128
  const dasherizedKey = camelCaseToKebabCase(key);
@@ -99,7 +138,7 @@ export default class Record {
99
138
  }
100
139
  }
101
140
 
102
- const result = {
141
+ const result: JSONAPIResult = {
103
142
  attributes,
104
143
  relationships,
105
144
  id: recordId,
@@ -116,11 +155,11 @@ export default class Record {
116
155
  return result;
117
156
  }
118
157
 
119
- unload(options={}) {
158
+ unload(options: UnloadOptions = {}): void {
120
159
  store.unloadRecord(this.__model.__name, this.id, options);
121
160
  }
122
161
 
123
- clean() {
162
+ clean(): void {
124
163
  try {
125
164
  for (const key of Object.keys(this)) {
126
165
  delete this[key];