@stonyx/orm 0.2.1-alpha.34 → 0.2.1-alpha.36

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 (151) hide show
  1. package/config/environment.js +17 -0
  2. package/dist/aggregates.d.ts +21 -0
  3. package/dist/aggregates.js +90 -0
  4. package/dist/attr.d.ts +2 -0
  5. package/dist/attr.js +22 -0
  6. package/dist/belongs-to.d.ts +11 -0
  7. package/dist/belongs-to.js +59 -0
  8. package/dist/cli.d.ts +22 -0
  9. package/dist/cli.js +148 -0
  10. package/dist/commands.d.ts +7 -0
  11. package/dist/commands.js +146 -0
  12. package/dist/db.d.ts +21 -0
  13. package/dist/db.js +174 -0
  14. package/dist/exports/db.d.ts +7 -0
  15. package/{src → dist}/exports/db.js +2 -4
  16. package/dist/has-many.d.ts +11 -0
  17. package/dist/has-many.js +58 -0
  18. package/dist/hooks.d.ts +47 -0
  19. package/dist/hooks.js +106 -0
  20. package/dist/index.d.ts +14 -0
  21. package/dist/index.js +34 -0
  22. package/dist/main.d.ts +46 -0
  23. package/dist/main.js +179 -0
  24. package/dist/manage-record.d.ts +13 -0
  25. package/dist/manage-record.js +114 -0
  26. package/dist/meta-request.d.ts +6 -0
  27. package/dist/meta-request.js +52 -0
  28. package/dist/migrate.d.ts +2 -0
  29. package/dist/migrate.js +57 -0
  30. package/dist/model-property.d.ts +9 -0
  31. package/dist/model-property.js +29 -0
  32. package/dist/model.d.ts +15 -0
  33. package/dist/model.js +18 -0
  34. package/dist/mysql/connection.d.ts +14 -0
  35. package/dist/mysql/connection.js +24 -0
  36. package/dist/mysql/migration-generator.d.ts +45 -0
  37. package/dist/mysql/migration-generator.js +245 -0
  38. package/dist/mysql/migration-runner.d.ts +12 -0
  39. package/dist/mysql/migration-runner.js +83 -0
  40. package/dist/mysql/mysql-db.d.ts +100 -0
  41. package/dist/mysql/mysql-db.js +415 -0
  42. package/dist/mysql/query-builder.d.ts +10 -0
  43. package/dist/mysql/query-builder.js +44 -0
  44. package/dist/mysql/schema-introspector.d.ts +19 -0
  45. package/dist/mysql/schema-introspector.js +286 -0
  46. package/dist/mysql/type-map.d.ts +21 -0
  47. package/dist/mysql/type-map.js +36 -0
  48. package/dist/orm-request.d.ts +38 -0
  49. package/dist/orm-request.js +455 -0
  50. package/dist/plural-registry.d.ts +4 -0
  51. package/{src → dist}/plural-registry.js +3 -6
  52. package/dist/postgres/connection.d.ts +15 -0
  53. package/dist/postgres/connection.js +30 -0
  54. package/dist/postgres/migration-generator.d.ts +45 -0
  55. package/dist/postgres/migration-generator.js +257 -0
  56. package/dist/postgres/migration-runner.d.ts +10 -0
  57. package/dist/postgres/migration-runner.js +82 -0
  58. package/dist/postgres/postgres-db.d.ts +119 -0
  59. package/dist/postgres/postgres-db.js +476 -0
  60. package/dist/postgres/query-builder.d.ts +27 -0
  61. package/dist/postgres/query-builder.js +98 -0
  62. package/dist/postgres/schema-introspector.d.ts +29 -0
  63. package/dist/postgres/schema-introspector.js +309 -0
  64. package/dist/postgres/type-map.d.ts +23 -0
  65. package/dist/postgres/type-map.js +53 -0
  66. package/dist/record.d.ts +75 -0
  67. package/dist/record.js +115 -0
  68. package/dist/relationships.d.ts +10 -0
  69. package/dist/relationships.js +39 -0
  70. package/dist/serializer.d.ts +17 -0
  71. package/dist/serializer.js +136 -0
  72. package/dist/setup-rest-server.d.ts +1 -0
  73. package/dist/setup-rest-server.js +54 -0
  74. package/dist/standalone-db.d.ts +58 -0
  75. package/dist/standalone-db.js +142 -0
  76. package/dist/store.d.ts +62 -0
  77. package/dist/store.js +271 -0
  78. package/dist/timescale/query-builder.d.ts +41 -0
  79. package/dist/timescale/query-builder.js +87 -0
  80. package/dist/timescale/timescale-db.d.ts +45 -0
  81. package/dist/timescale/timescale-db.js +84 -0
  82. package/dist/transforms.d.ts +2 -0
  83. package/dist/transforms.js +17 -0
  84. package/dist/types/orm-types.d.ts +142 -0
  85. package/dist/types/orm-types.js +1 -0
  86. package/dist/utils.d.ts +5 -0
  87. package/dist/utils.js +13 -0
  88. package/dist/view-resolver.d.ts +8 -0
  89. package/dist/view-resolver.js +169 -0
  90. package/dist/view.d.ts +11 -0
  91. package/dist/view.js +18 -0
  92. package/package.json +34 -11
  93. package/src/{aggregates.js → aggregates.ts} +27 -13
  94. package/src/{attr.js → attr.ts} +2 -2
  95. package/src/belongs-to.ts +90 -0
  96. package/src/{cli.js → cli.ts} +17 -11
  97. package/src/{commands.js → commands.ts} +179 -170
  98. package/src/{db.js → db.ts} +35 -26
  99. package/src/exports/db.ts +7 -0
  100. package/src/has-many.ts +92 -0
  101. package/src/{hooks.js → hooks.ts} +23 -27
  102. package/src/{index.js → index.ts} +4 -4
  103. package/src/{main.js → main.ts} +65 -34
  104. package/src/{manage-record.js → manage-record.ts} +42 -22
  105. package/src/{meta-request.js → meta-request.ts} +17 -14
  106. package/src/{migrate.js → migrate.ts} +9 -9
  107. package/src/{model-property.js → model-property.ts} +12 -6
  108. package/src/{model.js → model.ts} +5 -4
  109. package/src/mysql/{connection.js → connection.ts} +43 -28
  110. package/src/mysql/{migration-generator.js → migration-generator.ts} +332 -286
  111. package/src/mysql/{migration-runner.js → migration-runner.ts} +116 -110
  112. package/src/mysql/{mysql-db.js → mysql-db.ts} +537 -473
  113. package/src/mysql/{query-builder.js → query-builder.ts} +69 -64
  114. package/src/mysql/{schema-introspector.js → schema-introspector.ts} +355 -325
  115. package/src/mysql/{type-map.js → type-map.ts} +42 -37
  116. package/src/{orm-request.js → orm-request.ts} +169 -97
  117. package/src/plural-registry.ts +12 -0
  118. package/src/postgres/connection.ts +46 -0
  119. package/src/postgres/{migration-generator.js → migration-generator.ts} +82 -38
  120. package/src/postgres/{migration-runner.js → migration-runner.ts} +11 -10
  121. package/src/postgres/{postgres-db.js → postgres-db.ts} +202 -111
  122. package/src/postgres/{query-builder.js → query-builder.ts} +27 -28
  123. package/src/postgres/{schema-introspector.js → schema-introspector.ts} +87 -58
  124. package/src/postgres/{type-map.js → type-map.ts} +10 -6
  125. package/src/{record.js → record.ts} +73 -34
  126. package/src/relationships.ts +53 -0
  127. package/src/{serializer.js → serializer.ts} +52 -36
  128. package/src/{setup-rest-server.js → setup-rest-server.ts} +18 -13
  129. package/src/{standalone-db.js → standalone-db.ts} +33 -24
  130. package/src/{store.js → store.ts} +90 -68
  131. package/src/timescale/query-builder.ts +137 -0
  132. package/src/timescale/timescale-db.ts +119 -0
  133. package/src/transforms.ts +20 -0
  134. package/src/types/mysql2.d.ts +30 -0
  135. package/src/types/orm-types.ts +146 -0
  136. package/src/types/pg.d.ts +28 -0
  137. package/src/types/stonyx-cron.d.ts +5 -0
  138. package/src/types/stonyx-events.d.ts +4 -0
  139. package/src/types/stonyx-rest-server.d.ts +11 -0
  140. package/src/types/stonyx-utils.d.ts +33 -0
  141. package/src/types/stonyx.d.ts +21 -0
  142. package/src/utils.ts +16 -0
  143. package/src/{view-resolver.js → view-resolver.ts} +51 -24
  144. package/src/view.ts +22 -0
  145. package/src/belongs-to.js +0 -70
  146. package/src/has-many.js +0 -68
  147. package/src/postgres/connection.js +0 -30
  148. package/src/relationships.js +0 -43
  149. package/src/transforms.js +0 -20
  150. package/src/utils.js +0 -12
  151. package/src/view.js +0 -21
@@ -3,15 +3,70 @@ import { ensureMigrationsTable, getAppliedMigrations, getMigrationFiles, applyMi
3
3
  import { introspectModels, introspectViews, getTopologicalOrder, schemasToSnapshot } from './schema-introspector.js';
4
4
  import { loadLatestSnapshot, detectSchemaDrift } from './migration-generator.js';
5
5
  import { buildInsert, buildUpdate, buildDelete, buildSelect, buildVectorSearch, buildHybridSearch } from './query-builder.js';
6
- import { createRecord, store } from '@stonyx/orm';
6
+ import { store } from '@stonyx/orm';
7
+ import { createRecord } from '../manage-record.js';
7
8
  import { confirm } from '@stonyx/utils/prompt';
8
9
  import { readFile } from '@stonyx/utils/file';
9
10
  import { getPluralName } from '../plural-registry.js';
11
+ import { isDbError } from '../utils.js';
10
12
  import config from 'stonyx/config';
11
13
  import log from 'stonyx/log';
12
14
  import path from 'path';
15
+ import type { Pool } from 'pg';
16
+ import type { ForeignKeyDef, ModelSchema, OrmRecord } from '../types/orm-types.js';
13
17
 
14
- const defaultDeps = {
18
+ interface PersistContext {
19
+ record?: OrmRecord;
20
+ recordId?: unknown;
21
+ oldState?: Record<string, unknown>;
22
+ }
23
+
24
+ interface PersistResponse {
25
+ data?: { id: unknown };
26
+ }
27
+
28
+ interface SearchResult {
29
+ record: OrmRecord;
30
+ distance: number;
31
+ }
32
+
33
+ interface VectorSearchOptions {
34
+ limit?: number;
35
+ where?: Record<string, unknown>;
36
+ }
37
+
38
+ interface PostgresDeps {
39
+ getPool: typeof getPool;
40
+ closePool: typeof closePool;
41
+ ensureMigrationsTable: typeof ensureMigrationsTable;
42
+ getAppliedMigrations: typeof getAppliedMigrations;
43
+ getMigrationFiles: typeof getMigrationFiles;
44
+ applyMigration: typeof applyMigration;
45
+ parseMigrationFile: typeof parseMigrationFile;
46
+ introspectModels: typeof introspectModels;
47
+ introspectViews: typeof introspectViews;
48
+ getTopologicalOrder: typeof getTopologicalOrder;
49
+ schemasToSnapshot: typeof schemasToSnapshot;
50
+ loadLatestSnapshot: typeof loadLatestSnapshot;
51
+ detectSchemaDrift: typeof detectSchemaDrift;
52
+ buildInsert: typeof buildInsert;
53
+ buildUpdate: typeof buildUpdate;
54
+ buildDelete: typeof buildDelete;
55
+ buildSelect: typeof buildSelect;
56
+ buildVectorSearch: typeof buildVectorSearch;
57
+ buildHybridSearch: typeof buildHybridSearch;
58
+ createRecord: typeof createRecord;
59
+ store: typeof store;
60
+ confirm: typeof confirm;
61
+ readFile: typeof readFile;
62
+ getPluralName: typeof getPluralName;
63
+ config: typeof config;
64
+ log: typeof log;
65
+ path: typeof path;
66
+ [key: string]: unknown;
67
+ }
68
+
69
+ const defaultDeps: PostgresDeps = {
15
70
  getPool, closePool, ensureMigrationsTable, getAppliedMigrations,
16
71
  getMigrationFiles, applyMigration, parseMigrationFile,
17
72
  introspectModels, introspectViews, getTopologicalOrder, schemasToSnapshot,
@@ -21,47 +76,69 @@ const defaultDeps = {
21
76
  };
22
77
 
23
78
  export default class PostgresDB {
24
- constructor(deps = {}) {
25
- if (PostgresDB.instance) return PostgresDB.instance;
26
- PostgresDB.instance = this;
79
+ /** PostgreSQL extensions to enable on pool init. Subclasses can override. */
80
+ static extensions: string[] = ['vector'];
81
+
82
+ /** Config key under config.orm for this adapter. Subclasses can override. */
83
+ static configKey: string = 'postgres';
84
+
85
+ /** Singleton instance, set by subclass constructor name. */
86
+ static instance: PostgresDB | undefined;
87
+
88
+ deps!: PostgresDeps;
89
+ pool!: Pool | null;
90
+ pgConfig!: Record<string, unknown>;
91
+
92
+ constructor(deps: Partial<PostgresDeps> = {}) {
93
+ const Ctor = this.constructor as typeof PostgresDB;
94
+ if (Ctor.instance) return Ctor.instance;
95
+ Ctor.instance = this;
27
96
 
28
- this.deps = { ...defaultDeps, ...deps };
97
+ this.deps = { ...defaultDeps, ...deps } as PostgresDeps;
29
98
  this.pool = null;
30
- this.pgConfig = this.deps.config.orm.postgres;
99
+ this.pgConfig = this.deps.config.orm[Ctor.configKey] as Record<string, unknown>;
31
100
  }
32
101
 
33
- async init() {
34
- this.pool = await this.deps.getPool(this.pgConfig);
35
- await this.deps.ensureMigrationsTable(this.pool, this.pgConfig.migrationsTable);
102
+ protected requirePool(): Pool {
103
+ if (!this.pool) throw new Error('PostgresDB pool not initialized — call init() first');
104
+ return this.pool;
105
+ }
106
+
107
+ async init(): Promise<void> {
108
+ this.pool = await this.deps.getPool(
109
+ this.pgConfig as unknown as Parameters<typeof getPool>[0],
110
+ (this.constructor as typeof PostgresDB).extensions
111
+ );
112
+ await this.deps.ensureMigrationsTable(this.pool, this.pgConfig.migrationsTable as string | undefined);
36
113
  await this.loadMemoryRecords();
37
114
  }
38
115
 
39
- async startup() {
40
- const migrationsPath = this.deps.path.resolve(this.deps.config.rootPath, this.pgConfig.migrationsDir);
116
+ async startup(): Promise<void> {
117
+ const migrationsPath = this.deps.path.resolve(this.deps.config.rootPath, this.pgConfig.migrationsDir as string);
41
118
 
42
119
  // Check for pending migrations
43
- const applied = await this.deps.getAppliedMigrations(this.pool, this.pgConfig.migrationsTable);
120
+ const applied = await this.deps.getAppliedMigrations(this.requirePool(), this.pgConfig.migrationsTable as string | undefined);
44
121
  const files = await this.deps.getMigrationFiles(migrationsPath);
45
122
  const pending = files.filter(f => !applied.includes(f));
46
123
 
47
124
  if (pending.length > 0) {
48
- this.deps.log.db(`${pending.length} pending migration(s) found.`);
125
+ this.deps.log.db!(`${pending.length} pending migration(s) found.`);
49
126
 
50
127
  const shouldApply = await this.deps.confirm(`${pending.length} pending migration(s) found. Apply now?`);
51
128
 
52
129
  if (shouldApply) {
53
130
  for (const filename of pending) {
54
- const content = await this.deps.readFile(this.deps.path.join(migrationsPath, filename));
131
+ const content = await this.deps.readFile(this.deps.path.join(migrationsPath, filename)) as string;
55
132
  const { up } = this.deps.parseMigrationFile(content);
56
133
 
57
- await this.deps.applyMigration(this.pool, filename, up, this.pgConfig.migrationsTable);
58
- this.deps.log.db(`Applied migration: ${filename}`);
134
+ await this.deps.applyMigration(this.requirePool(), filename, up, this.pgConfig.migrationsTable as string | undefined);
135
+ this.deps.log.db!(`Applied migration: ${filename}`);
59
136
  }
60
137
 
61
138
  // Reload records after applying migrations
62
139
  await this.loadMemoryRecords();
63
140
  } else {
64
- this.deps.log.warn('Skipping pending migrations. Schema may be outdated.');
141
+ this.deps.log.warn!('Skipping pending migrations. Schema may be outdated.');
65
142
  }
66
143
  } else if (files.length === 0) {
67
144
  const schemas = this.deps.introspectModels();
@@ -78,52 +155,52 @@ export default class PostgresDB {
78
155
 
79
156
  if (result) {
80
157
  const { up } = this.deps.parseMigrationFile(result.content);
81
- await this.deps.applyMigration(this.pool, result.filename, up, this.pgConfig.migrationsTable);
82
- this.deps.log.db(`Applied migration: ${result.filename}`);
158
+ await this.deps.applyMigration(this.requirePool(), result.filename, up, this.pgConfig.migrationsTable as string | undefined);
159
+ this.deps.log.db!(`Applied migration: ${result.filename}`);
83
160
  await this.loadMemoryRecords();
84
161
  }
85
162
  } else {
86
- this.deps.log.warn('Skipping initial migration. Tables may not exist.');
163
+ this.deps.log.warn!('Skipping initial migration. Tables may not exist.');
87
164
  }
88
165
  }
89
166
  }
90
167
 
91
168
  // Check for schema drift
92
169
  const schemas = this.deps.introspectModels();
93
- const snapshot = await this.deps.loadLatestSnapshot(this.deps.path.resolve(this.deps.config.rootPath, this.pgConfig.migrationsDir));
170
+ const snapshot = await this.deps.loadLatestSnapshot(this.deps.path.resolve(this.deps.config.rootPath, this.pgConfig.migrationsDir as string));
94
171
 
95
172
  if (Object.keys(snapshot).length > 0) {
96
- const drift = this.deps.detectSchemaDrift(schemas, snapshot);
173
+ const drift = this.deps.detectSchemaDrift(schemas, snapshot as Parameters<typeof detectSchemaDrift>[1]);
97
174
 
98
175
  if (drift.hasChanges) {
99
- this.deps.log.warn('Schema drift detected: models have changed since the last migration.');
100
- this.deps.log.warn('Run `stonyx db:generate-migration` to create a new migration.');
176
+ this.deps.log.warn!('Schema drift detected: models have changed since the last migration.');
177
+ this.deps.log.warn!('Run `stonyx db:generate-migration` to create a new migration.');
101
178
  }
102
179
  }
103
180
  }
104
181
 
105
- async shutdown() {
182
+ async shutdown(): Promise<void> {
106
183
  await this.deps.closePool();
107
184
  this.pool = null;
108
185
  }
109
186
 
110
- async save() {
187
+ async save(): Promise<void> {
111
188
  // No-op: PostgreSQL persists data immediately via persist()
112
189
  }
113
190
 
114
191
  /**
115
192
  * Loads only models with memory: true into the in-memory store on startup.
116
- * Models with memory: false are skipped accessed on-demand via find()/findAll().
193
+ * Models with memory: false are skipped -- accessed on-demand via find()/findAll().
117
194
  */
118
- async loadMemoryRecords() {
195
+ async loadMemoryRecords(): Promise<void> {
119
196
  const schemas = this.deps.introspectModels();
120
197
  const order = this.deps.getTopologicalOrder(schemas);
121
198
  const Orm = (await import('@stonyx/orm')).default;
122
199
 
123
200
  for (const modelName of order) {
124
- const { modelClass } = Orm.instance.getRecordClasses(modelName);
201
+ const { modelClass } = Orm.instance.getRecordClasses(modelName) as { modelClass: { memory?: boolean } };
125
202
  if (modelClass?.memory === false) {
126
- this.deps.log.db(`Skipping memory load for '${modelName}' (memory: false)`);
203
+ this.deps.log.db!(`Skipping memory load for '${modelName}' (memory: false)`);
127
204
  continue;
128
205
  }
129
206
 
@@ -131,7 +208,7 @@ export default class PostgresDB {
131
208
  const { sql, values } = this.deps.buildSelect(schema.table);
132
209
 
133
210
  try {
134
- const result = await this.pool.query(sql, values);
211
+ const result = await this.requirePool().query(sql, values);
135
212
 
136
213
  for (const row of result.rows) {
137
214
  const rawData = this._rowToRawData(row, schema);
@@ -139,8 +216,8 @@ export default class PostgresDB {
139
216
  }
140
217
  } catch (error) {
141
218
  // 42P01 = undefined_table (PG equivalent of ER_NO_SUCH_TABLE)
142
- if (error.code === '42P01') {
143
- this.deps.log.db(`Table '${schema.table}' does not exist yet. Skipping load for '${modelName}'.`);
219
+ if (isDbError(error) && error.code === '42P01') {
220
+ this.deps.log.db!(`Table '${schema.table}' does not exist yet. Skipping load for '${modelName}'.`);
144
221
  continue;
145
222
  }
146
223
 
@@ -152,25 +229,34 @@ export default class PostgresDB {
152
229
  const viewSchemas = this.deps.introspectViews();
153
230
 
154
231
  for (const [viewName, viewSchema] of Object.entries(viewSchemas)) {
155
- const { modelClass: viewClass } = Orm.instance.getRecordClasses(viewName);
232
+ const { modelClass: viewClass } = Orm.instance.getRecordClasses(viewName) as { modelClass: { memory?: boolean } };
156
233
  if (viewClass?.memory !== true) {
157
- this.deps.log.db(`Skipping memory load for view '${viewName}' (memory: false)`);
234
+ this.deps.log.db!(`Skipping memory load for view '${viewName}' (memory: false)`);
158
235
  continue;
159
236
  }
160
237
 
161
- const schema = { table: viewSchema.viewName, columns: viewSchema.columns || {}, foreignKeys: viewSchema.foreignKeys || {} };
238
+ const sourceIdType = schemas[viewSchema.source]?.idType || 'number';
239
+ const schema: ModelSchema = {
240
+ table: viewSchema.viewName,
241
+ idType: sourceIdType,
242
+ columns: viewSchema.columns || {},
243
+ foreignKeys: (viewSchema.foreignKeys || {}) as Record<string, ForeignKeyDef>,
244
+ relationships: { belongsTo: {}, hasMany: {} },
245
+ vectorColumns: {},
246
+ memory: false,
247
+ };
162
248
  const { sql, values } = this.deps.buildSelect(schema.table);
163
249
 
164
250
  try {
165
- const result = await this.pool.query(sql, values);
251
+ const result = await this.requirePool().query(sql, values);
166
252
 
167
253
  for (const row of result.rows) {
168
254
  const rawData = this._rowToRawData(row, schema);
169
255
  this.deps.createRecord(viewName, rawData, { isDbRecord: true, serialize: false, transform: false });
170
256
  }
171
257
  } catch (error) {
172
- if (error.code === '42P01') {
173
- this.deps.log.db(`View '${viewSchema.viewName}' does not exist yet. Skipping load for '${viewName}'.`);
258
+ if (isDbError(error) && error.code === '42P01') {
259
+ this.deps.log.db!(`View '${viewSchema.viewName}' does not exist yet. Skipping load for '${viewName}'.`);
174
260
  continue;
175
261
  }
176
262
  throw error;
@@ -181,27 +267,33 @@ export default class PostgresDB {
181
267
  /**
182
268
  * @deprecated Use loadMemoryRecords() instead. Kept for backward compatibility.
183
269
  */
184
- async loadAllRecords() {
270
+ async loadAllRecords(): Promise<void> {
185
271
  return this.loadMemoryRecords();
186
272
  }
187
273
 
188
274
  /**
189
275
  * Find a single record by ID from PostgreSQL.
190
276
  * Does NOT cache the result in the store for memory: false models.
191
- * @param {string} modelName
192
- * @param {string|number} id
193
- * @returns {Promise<Record|undefined>}
194
277
  */
195
- async findRecord(modelName, id) {
278
+ async findRecord(modelName: string, id: string | number): Promise<OrmRecord | undefined> {
196
279
  const schemas = this.deps.introspectModels();
197
- let schema = schemas[modelName];
280
+ let schema: ModelSchema | undefined = schemas[modelName];
198
281
 
199
282
  // Check views if not found in models
200
283
  if (!schema) {
201
284
  const viewSchemas = this.deps.introspectViews();
202
285
  const viewSchema = viewSchemas[modelName];
203
286
  if (viewSchema) {
204
- schema = { table: viewSchema.viewName, columns: viewSchema.columns || {}, foreignKeys: viewSchema.foreignKeys || {} };
287
+ const sourceIdType = schemas[viewSchema.source]?.idType || 'number';
288
+ schema = {
289
+ table: viewSchema.viewName,
290
+ idType: sourceIdType,
291
+ columns: viewSchema.columns || {},
292
+ foreignKeys: (viewSchema.foreignKeys || {}) as Record<string, ForeignKeyDef>,
293
+ relationships: { belongsTo: {}, hasMany: {} },
294
+ vectorColumns: {},
295
+ memory: false,
296
+ };
205
297
  }
206
298
  }
207
299
 
@@ -210,38 +302,44 @@ export default class PostgresDB {
210
302
  const { sql, values } = this.deps.buildSelect(schema.table, { id });
211
303
 
212
304
  try {
213
- const result = await this.pool.query(sql, values);
305
+ const result = await this.requirePool().query(sql, values);
214
306
 
215
307
  if (result.rows.length === 0) return undefined;
216
308
 
217
309
  const rawData = this._rowToRawData(result.rows[0], schema);
218
- const record = this.deps.createRecord(modelName, rawData, { isDbRecord: true, serialize: false, transform: false });
310
+ const record = this.deps.createRecord(modelName, rawData, { isDbRecord: true, serialize: false, transform: false }) as unknown as OrmRecord;
219
311
 
220
312
  this._evictIfNotMemory(modelName, record);
221
313
 
222
314
  return record;
223
315
  } catch (error) {
224
- if (error.code === '42P01') return undefined;
316
+ if (isDbError(error) && error.code === '42P01') return undefined;
225
317
  throw error;
226
318
  }
227
319
  }
228
320
 
229
321
  /**
230
322
  * Find all records of a model from PostgreSQL, with optional conditions.
231
- * @param {string} modelName
232
- * @param {Object} [conditions] - Optional WHERE conditions (key-value pairs)
233
- * @returns {Promise<Record[]>}
234
323
  */
235
- async findAll(modelName, conditions) {
324
+ async findAll(modelName: string, conditions?: Record<string, unknown>): Promise<OrmRecord[]> {
236
325
  const schemas = this.deps.introspectModels();
237
- let schema = schemas[modelName];
326
+ let schema: ModelSchema | undefined = schemas[modelName];
238
327
 
239
328
  // Check views if not found in models
240
329
  if (!schema) {
241
330
  const viewSchemas = this.deps.introspectViews();
242
331
  const viewSchema = viewSchemas[modelName];
243
332
  if (viewSchema) {
244
- schema = { table: viewSchema.viewName, columns: viewSchema.columns || {}, foreignKeys: viewSchema.foreignKeys || {} };
333
+ const sourceIdType = schemas[viewSchema.source]?.idType || 'number';
334
+ schema = {
335
+ table: viewSchema.viewName,
336
+ idType: sourceIdType,
337
+ columns: viewSchema.columns || {},
338
+ foreignKeys: (viewSchema.foreignKeys || {}) as Record<string, ForeignKeyDef>,
339
+ relationships: { belongsTo: {}, hasMany: {} },
340
+ vectorColumns: {},
341
+ memory: false,
342
+ };
245
343
  }
246
344
  }
247
345
 
@@ -250,11 +348,11 @@ export default class PostgresDB {
250
348
  const { sql, values } = this.deps.buildSelect(schema.table, conditions);
251
349
 
252
350
  try {
253
- const result = await this.pool.query(sql, values);
351
+ const result = await this.requirePool().query(sql, values);
254
352
 
255
353
  const records = result.rows.map(row => {
256
- const rawData = this._rowToRawData(row, schema);
257
- return this.deps.createRecord(modelName, rawData, { isDbRecord: true, serialize: false, transform: false });
354
+ const rawData = this._rowToRawData(row, schema!);
355
+ return this.deps.createRecord(modelName, rawData, { isDbRecord: true, serialize: false, transform: false }) as unknown as OrmRecord;
258
356
  });
259
357
 
260
358
  for (const record of records) {
@@ -263,22 +361,15 @@ export default class PostgresDB {
263
361
 
264
362
  return records;
265
363
  } catch (error) {
266
- if (error.code === '42P01') return [];
364
+ if (isDbError(error) && error.code === '42P01') return [];
267
365
  throw error;
268
366
  }
269
367
  }
270
368
 
271
369
  /**
272
370
  * Perform a vector similarity search using cosine distance.
273
- * @param {string} modelName
274
- * @param {string} vectorColumn - Name of the vector column
275
- * @param {number[]} queryVector - The query vector
276
- * @param {Object} [options]
277
- * @param {number} [options.limit=10]
278
- * @param {Object} [options.where] - Additional conditions
279
- * @returns {Promise<{ record: Record, distance: number }[]>}
280
371
  */
281
- async vectorSearch(modelName, vectorColumn, queryVector, options = {}) {
372
+ async vectorSearch(modelName: string, vectorColumn: string, queryVector: number[], options: VectorSearchOptions = {}): Promise<SearchResult[]> {
282
373
  const schemas = this.deps.introspectModels();
283
374
  const schema = schemas[modelName];
284
375
  if (!schema) return [];
@@ -286,33 +377,26 @@ export default class PostgresDB {
286
377
  const { sql, values } = this.deps.buildVectorSearch(schema.table, vectorColumn, queryVector, options);
287
378
 
288
379
  try {
289
- const result = await this.pool.query(sql, values);
380
+ const result = await this.requirePool().query(sql, values);
290
381
 
291
382
  return result.rows.map(row => {
292
- const distance = row.distance;
383
+ const distance = row.distance as number;
293
384
  delete row.distance;
294
385
  const rawData = this._rowToRawData(row, schema);
295
- const record = this.deps.createRecord(modelName, rawData, { isDbRecord: true, serialize: false, transform: false });
386
+ const record = this.deps.createRecord(modelName, rawData, { isDbRecord: true, serialize: false, transform: false }) as unknown as OrmRecord;
296
387
  this._evictIfNotMemory(modelName, record);
297
388
  return { record, distance };
298
389
  });
299
390
  } catch (error) {
300
- if (error.code === '42P01') return [];
391
+ if (isDbError(error) && error.code === '42P01') return [];
301
392
  throw error;
302
393
  }
303
394
  }
304
395
 
305
396
  /**
306
397
  * Perform a hybrid search combining vector similarity with text filtering.
307
- * @param {string} modelName
308
- * @param {string} vectorColumn
309
- * @param {number[]} queryVector
310
- * @param {string} textColumn
311
- * @param {string} textQuery
312
- * @param {Object} [options]
313
- * @returns {Promise<{ record: Record, distance: number }[]>}
314
398
  */
315
- async hybridSearch(modelName, vectorColumn, queryVector, textColumn, textQuery, options = {}) {
399
+ async hybridSearch(modelName: string, vectorColumn: string, queryVector: number[], textColumn: string, textQuery: string, options: VectorSearchOptions = {}): Promise<SearchResult[]> {
316
400
  const schemas = this.deps.introspectModels();
317
401
  const schema = schemas[modelName];
318
402
  if (!schema) return [];
@@ -320,40 +404,44 @@ export default class PostgresDB {
320
404
  const { sql, values } = this.deps.buildHybridSearch(schema.table, vectorColumn, queryVector, textColumn, textQuery, options);
321
405
 
322
406
  try {
323
- const result = await this.pool.query(sql, values);
407
+ const result = await this.requirePool().query(sql, values);
324
408
 
325
409
  return result.rows.map(row => {
326
- const distance = row.distance;
410
+ const distance = row.distance as number;
327
411
  delete row.distance;
328
412
  const rawData = this._rowToRawData(row, schema);
329
- const record = this.deps.createRecord(modelName, rawData, { isDbRecord: true, serialize: false, transform: false });
413
+ const record = this.deps.createRecord(modelName, rawData, { isDbRecord: true, serialize: false, transform: false }) as unknown as OrmRecord;
330
414
  this._evictIfNotMemory(modelName, record);
331
415
  return { record, distance };
332
416
  });
333
417
  } catch (error) {
334
- if (error.code === '42P01') return [];
418
+ if (isDbError(error) && error.code === '42P01') return [];
335
419
  throw error;
336
420
  }
337
421
  }
338
422
 
339
423
  /**
340
424
  * Remove a record from the in-memory store if its model has memory: false.
341
- * The record object itself survives the caller retains the reference.
425
+ * The record object itself survives -- the caller retains the reference.
342
426
  * @private
343
427
  */
344
- _evictIfNotMemory(modelName, record) {
345
- const store = this.deps.store;
346
-
347
- if (store._memoryResolver && !store._memoryResolver(modelName)) {
348
- const modelStore = store.get?.(modelName) ?? store.data?.get(modelName);
428
+ private _evictIfNotMemory(modelName: string, record: OrmRecord): void {
429
+ const storeRef = this.deps.store as {
430
+ _memoryResolver?: (name: string) => boolean;
431
+ get?: (name: string) => Map<unknown, unknown> | undefined;
432
+ data?: { get(name: string): Map<unknown, unknown> | undefined };
433
+ };
434
+
435
+ if (storeRef._memoryResolver && !storeRef._memoryResolver(modelName)) {
436
+ const modelStore = storeRef.get?.(modelName) ?? storeRef.data?.get(modelName);
349
437
  if (modelStore) modelStore.delete(record.id);
350
438
  }
351
439
  }
352
440
 
353
- _rowToRawData(row, schema) {
354
- const rawData = { ...row };
441
+ private _rowToRawData(row: Record<string, unknown>, schema: ModelSchema): Record<string, unknown> {
442
+ const rawData: Record<string, unknown> = { ...row };
355
443
 
356
- // PostgreSQL returns native booleans and parsed JSONB no manual conversion needed.
444
+ // PostgreSQL returns native booleans and parsed JSONB -- no manual conversion needed.
357
445
  // Only FK remapping and timestamp stripping required.
358
446
 
359
447
  // Map FK columns back to relationship keys
@@ -366,17 +454,17 @@ export default class PostgresDB {
366
454
  }
367
455
  }
368
456
 
369
- // Remove timestamp columns managed by PostgreSQL
457
+ // Remove timestamp columns -- managed by PostgreSQL
370
458
  delete rawData.created_at;
371
459
  delete rawData.updated_at;
372
460
 
373
461
  return rawData;
374
462
  }
375
463
 
376
- async persist(operation, modelName, context, response) {
377
- // Views are read-only no-op for all write operations
464
+ async persist(operation: string, modelName: string, context: PersistContext, response: PersistResponse): Promise<void> {
465
+ // Views are read-only -- no-op for all write operations
378
466
  const Orm = (await import('@stonyx/orm')).default;
379
- if (Orm.instance?.isView?.(modelName)) return;
467
+ if ((Orm.instance as { isView?: (name: string) => boolean })?.isView?.(modelName)) return;
380
468
 
381
469
  switch (operation) {
382
470
  case 'create':
@@ -388,14 +476,17 @@ export default class PostgresDB {
388
476
  }
389
477
  }
390
478
 
391
- async _persistCreate(modelName, context, response) {
479
+ private async _persistCreate(modelName: string, _context: PersistContext, response: PersistResponse): Promise<void> {
392
480
  const schemas = this.deps.introspectModels();
393
481
  const schema = schemas[modelName];
394
482
 
395
483
  if (!schema) return;
396
484
 
397
485
  const recordId = response?.data?.id;
398
- const record = recordId != null ? this.deps.store.get(modelName, isNaN(recordId) ? recordId : parseInt(recordId)) : null;
486
+ const storeRef = this.deps.store as unknown as {
487
+ get(name: string, id: unknown): OrmRecord | null;
488
+ };
489
+ const record = recordId != null ? storeRef.get(modelName, isNaN(recordId as number) ? recordId : parseInt(recordId as string)) : null;
399
490
 
400
491
  if (!record) return;
401
492
 
@@ -410,13 +501,13 @@ export default class PostgresDB {
410
501
 
411
502
  const { sql, values } = this.deps.buildInsert(schema.table, insertData);
412
503
 
413
- const result = await this.pool.query(sql, values);
504
+ const result = await this.requirePool().query(sql, values);
414
505
 
415
506
  // Re-key the record in the store if PostgreSQL generated the ID (via RETURNING)
416
507
  if (isPendingId && result.rows.length > 0) {
417
508
  const pendingId = record.id;
418
509
  const realId = result.rows[0].id;
419
- const modelStore = this.deps.store.get(modelName);
510
+ const modelStore = (this.deps.store as unknown as { get(name: string): Map<unknown, unknown> }).get(modelName);
420
511
 
421
512
  modelStore.delete(pendingId);
422
513
  record.__data.id = realId;
@@ -432,7 +523,7 @@ export default class PostgresDB {
432
523
  }
433
524
  }
434
525
 
435
- async _persistUpdate(modelName, context, response) {
526
+ private async _persistUpdate(modelName: string, context: PersistContext, _response: PersistResponse): Promise<void> {
436
527
  const schemas = this.deps.introspectModels();
437
528
  const schema = schemas[modelName];
438
529
 
@@ -446,7 +537,7 @@ export default class PostgresDB {
446
537
  const currentData = record.__data;
447
538
 
448
539
  // Build a diff of changed columns
449
- const changedData = {};
540
+ const changedData: Record<string, unknown> = {};
450
541
 
451
542
  for (const [col] of Object.entries(schema.columns)) {
452
543
  if (currentData[col] !== oldState[col]) {
@@ -457,7 +548,7 @@ export default class PostgresDB {
457
548
  // Check FK changes too
458
549
  for (const fkCol of Object.keys(schema.foreignKeys)) {
459
550
  const relName = fkCol.replace(/_id$/, '');
460
- const currentFkValue = record.__relationships[relName]?.id ?? null;
551
+ const currentFkValue = (record.__relationships[relName] as { id: unknown } | undefined)?.id ?? null;
461
552
  const oldFkValue = oldState[relName] ?? null;
462
553
 
463
554
  if (currentFkValue !== oldFkValue) {
@@ -467,14 +558,14 @@ export default class PostgresDB {
467
558
 
468
559
  if (Object.keys(changedData).length === 0) return;
469
560
 
470
- // PostgreSQL doesn't have ON UPDATE CURRENT_TIMESTAMP set updated_at manually
561
+ // PostgreSQL doesn't have ON UPDATE CURRENT_TIMESTAMP -- set updated_at manually
471
562
  changedData.updated_at = new Date();
472
563
 
473
564
  const { sql, values } = this.deps.buildUpdate(schema.table, id, changedData);
474
- await this.pool.query(sql, values);
565
+ await this.requirePool().query(sql, values);
475
566
  }
476
567
 
477
- async _persistDelete(modelName, context) {
568
+ private async _persistDelete(modelName: string, context: PersistContext): Promise<void> {
478
569
  const schemas = this.deps.introspectModels();
479
570
  const schema = schemas[modelName];
480
571
 
@@ -484,11 +575,11 @@ export default class PostgresDB {
484
575
  if (id == null) return;
485
576
 
486
577
  const { sql, values } = this.deps.buildDelete(schema.table, id);
487
- await this.pool.query(sql, values);
578
+ await this.requirePool().query(sql, values);
488
579
  }
489
580
 
490
- _recordToRow(record, schema) {
491
- const row = {};
581
+ private _recordToRow(record: OrmRecord, schema: ModelSchema): Record<string, unknown> {
582
+ const row: Record<string, unknown> = {};
492
583
  const data = record.__data;
493
584
 
494
585
  // ID
@@ -512,7 +603,7 @@ export default class PostgresDB {
512
603
  const related = record.__relationships[relName];
513
604
 
514
605
  if (related) {
515
- row[fkCol] = related.id;
606
+ row[fkCol] = (related as { id: unknown }).id;
516
607
  } else if (data[relName] !== undefined) {
517
608
  // Raw FK value (e.g., from create payload)
518
609
  row[fkCol] = data[relName];