@stonyx/orm 0.2.1-beta.82 → 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 (150) 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 +58 -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 +57 -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 +178 -0
  24. package/dist/manage-record.d.ts +13 -0
  25. package/dist/manage-record.js +113 -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 +411 -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 +453 -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 +473 -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 +35 -0
  70. package/dist/serializer.d.ts +17 -0
  71. package/dist/serializer.js +130 -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 +44 -0
  81. package/dist/timescale/timescale-db.js +81 -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 +165 -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.js → belongs-to.ts} +36 -17
  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 +91 -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} +64 -34
  104. package/src/{manage-record.js → manage-record.ts} +41 -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} +533 -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} +165 -95
  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} +199 -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 +48 -0
  127. package/src/{serializer.js → serializer.ts} +44 -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 +107 -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} +53 -28
  144. package/src/view.ts +22 -0
  145. package/src/has-many.js +0 -68
  146. package/src/postgres/connection.js +0 -30
  147. package/src/relationships.js +0 -43
  148. package/src/transforms.js +0 -20
  149. package/src/utils.js +0 -12
  150. 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,33 @@ 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 schema: ModelSchema = {
239
+ table: viewSchema.viewName,
240
+ idType: 'number',
241
+ columns: viewSchema.columns || {},
242
+ foreignKeys: (viewSchema.foreignKeys || {}) as Record<string, ForeignKeyDef>,
243
+ relationships: { belongsTo: {}, hasMany: {} },
244
+ vectorColumns: {},
245
+ memory: false,
246
+ };
162
247
  const { sql, values } = this.deps.buildSelect(schema.table);
163
248
 
164
249
  try {
165
- const result = await this.pool.query(sql, values);
250
+ const result = await this.requirePool().query(sql, values);
166
251
 
167
252
  for (const row of result.rows) {
168
253
  const rawData = this._rowToRawData(row, schema);
169
254
  this.deps.createRecord(viewName, rawData, { isDbRecord: true, serialize: false, transform: false });
170
255
  }
171
256
  } catch (error) {
172
- if (error.code === '42P01') {
173
- this.deps.log.db(`View '${viewSchema.viewName}' does not exist yet. Skipping load for '${viewName}'.`);
257
+ if (isDbError(error) && error.code === '42P01') {
258
+ this.deps.log.db!(`View '${viewSchema.viewName}' does not exist yet. Skipping load for '${viewName}'.`);
174
259
  continue;
175
260
  }
176
261
  throw error;
@@ -181,27 +266,32 @@ export default class PostgresDB {
181
266
  /**
182
267
  * @deprecated Use loadMemoryRecords() instead. Kept for backward compatibility.
183
268
  */
184
- async loadAllRecords() {
269
+ async loadAllRecords(): Promise<void> {
185
270
  return this.loadMemoryRecords();
186
271
  }
187
272
 
188
273
  /**
189
274
  * Find a single record by ID from PostgreSQL.
190
275
  * 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
276
  */
195
- async findRecord(modelName, id) {
277
+ async findRecord(modelName: string, id: string | number): Promise<OrmRecord | undefined> {
196
278
  const schemas = this.deps.introspectModels();
197
- let schema = schemas[modelName];
279
+ let schema: ModelSchema | undefined = schemas[modelName];
198
280
 
199
281
  // Check views if not found in models
200
282
  if (!schema) {
201
283
  const viewSchemas = this.deps.introspectViews();
202
284
  const viewSchema = viewSchemas[modelName];
203
285
  if (viewSchema) {
204
- schema = { table: viewSchema.viewName, columns: viewSchema.columns || {}, foreignKeys: viewSchema.foreignKeys || {} };
286
+ schema = {
287
+ table: viewSchema.viewName,
288
+ idType: 'number',
289
+ columns: viewSchema.columns || {},
290
+ foreignKeys: (viewSchema.foreignKeys || {}) as Record<string, ForeignKeyDef>,
291
+ relationships: { belongsTo: {}, hasMany: {} },
292
+ vectorColumns: {},
293
+ memory: false,
294
+ };
205
295
  }
206
296
  }
207
297
 
@@ -210,38 +300,43 @@ export default class PostgresDB {
210
300
  const { sql, values } = this.deps.buildSelect(schema.table, { id });
211
301
 
212
302
  try {
213
- const result = await this.pool.query(sql, values);
303
+ const result = await this.requirePool().query(sql, values);
214
304
 
215
305
  if (result.rows.length === 0) return undefined;
216
306
 
217
307
  const rawData = this._rowToRawData(result.rows[0], schema);
218
- const record = this.deps.createRecord(modelName, rawData, { isDbRecord: true, serialize: false, transform: false });
308
+ const record = this.deps.createRecord(modelName, rawData, { isDbRecord: true, serialize: false, transform: false }) as unknown as OrmRecord;
219
309
 
220
310
  this._evictIfNotMemory(modelName, record);
221
311
 
222
312
  return record;
223
313
  } catch (error) {
224
- if (error.code === '42P01') return undefined;
314
+ if (isDbError(error) && error.code === '42P01') return undefined;
225
315
  throw error;
226
316
  }
227
317
  }
228
318
 
229
319
  /**
230
320
  * 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
321
  */
235
- async findAll(modelName, conditions) {
322
+ async findAll(modelName: string, conditions?: Record<string, unknown>): Promise<OrmRecord[]> {
236
323
  const schemas = this.deps.introspectModels();
237
- let schema = schemas[modelName];
324
+ let schema: ModelSchema | undefined = schemas[modelName];
238
325
 
239
326
  // Check views if not found in models
240
327
  if (!schema) {
241
328
  const viewSchemas = this.deps.introspectViews();
242
329
  const viewSchema = viewSchemas[modelName];
243
330
  if (viewSchema) {
244
- schema = { table: viewSchema.viewName, columns: viewSchema.columns || {}, foreignKeys: viewSchema.foreignKeys || {} };
331
+ schema = {
332
+ table: viewSchema.viewName,
333
+ idType: 'number',
334
+ columns: viewSchema.columns || {},
335
+ foreignKeys: (viewSchema.foreignKeys || {}) as Record<string, ForeignKeyDef>,
336
+ relationships: { belongsTo: {}, hasMany: {} },
337
+ vectorColumns: {},
338
+ memory: false,
339
+ };
245
340
  }
246
341
  }
247
342
 
@@ -250,11 +345,11 @@ export default class PostgresDB {
250
345
  const { sql, values } = this.deps.buildSelect(schema.table, conditions);
251
346
 
252
347
  try {
253
- const result = await this.pool.query(sql, values);
348
+ const result = await this.requirePool().query(sql, values);
254
349
 
255
350
  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 });
351
+ const rawData = this._rowToRawData(row, schema!);
352
+ return this.deps.createRecord(modelName, rawData, { isDbRecord: true, serialize: false, transform: false }) as unknown as OrmRecord;
258
353
  });
259
354
 
260
355
  for (const record of records) {
@@ -263,22 +358,15 @@ export default class PostgresDB {
263
358
 
264
359
  return records;
265
360
  } catch (error) {
266
- if (error.code === '42P01') return [];
361
+ if (isDbError(error) && error.code === '42P01') return [];
267
362
  throw error;
268
363
  }
269
364
  }
270
365
 
271
366
  /**
272
367
  * 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
368
  */
281
- async vectorSearch(modelName, vectorColumn, queryVector, options = {}) {
369
+ async vectorSearch(modelName: string, vectorColumn: string, queryVector: number[], options: VectorSearchOptions = {}): Promise<SearchResult[]> {
282
370
  const schemas = this.deps.introspectModels();
283
371
  const schema = schemas[modelName];
284
372
  if (!schema) return [];
@@ -286,33 +374,26 @@ export default class PostgresDB {
286
374
  const { sql, values } = this.deps.buildVectorSearch(schema.table, vectorColumn, queryVector, options);
287
375
 
288
376
  try {
289
- const result = await this.pool.query(sql, values);
377
+ const result = await this.requirePool().query(sql, values);
290
378
 
291
379
  return result.rows.map(row => {
292
- const distance = row.distance;
380
+ const distance = row.distance as number;
293
381
  delete row.distance;
294
382
  const rawData = this._rowToRawData(row, schema);
295
- const record = this.deps.createRecord(modelName, rawData, { isDbRecord: true, serialize: false, transform: false });
383
+ const record = this.deps.createRecord(modelName, rawData, { isDbRecord: true, serialize: false, transform: false }) as unknown as OrmRecord;
296
384
  this._evictIfNotMemory(modelName, record);
297
385
  return { record, distance };
298
386
  });
299
387
  } catch (error) {
300
- if (error.code === '42P01') return [];
388
+ if (isDbError(error) && error.code === '42P01') return [];
301
389
  throw error;
302
390
  }
303
391
  }
304
392
 
305
393
  /**
306
394
  * 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
395
  */
315
- async hybridSearch(modelName, vectorColumn, queryVector, textColumn, textQuery, options = {}) {
396
+ async hybridSearch(modelName: string, vectorColumn: string, queryVector: number[], textColumn: string, textQuery: string, options: VectorSearchOptions = {}): Promise<SearchResult[]> {
316
397
  const schemas = this.deps.introspectModels();
317
398
  const schema = schemas[modelName];
318
399
  if (!schema) return [];
@@ -320,40 +401,44 @@ export default class PostgresDB {
320
401
  const { sql, values } = this.deps.buildHybridSearch(schema.table, vectorColumn, queryVector, textColumn, textQuery, options);
321
402
 
322
403
  try {
323
- const result = await this.pool.query(sql, values);
404
+ const result = await this.requirePool().query(sql, values);
324
405
 
325
406
  return result.rows.map(row => {
326
- const distance = row.distance;
407
+ const distance = row.distance as number;
327
408
  delete row.distance;
328
409
  const rawData = this._rowToRawData(row, schema);
329
- const record = this.deps.createRecord(modelName, rawData, { isDbRecord: true, serialize: false, transform: false });
410
+ const record = this.deps.createRecord(modelName, rawData, { isDbRecord: true, serialize: false, transform: false }) as unknown as OrmRecord;
330
411
  this._evictIfNotMemory(modelName, record);
331
412
  return { record, distance };
332
413
  });
333
414
  } catch (error) {
334
- if (error.code === '42P01') return [];
415
+ if (isDbError(error) && error.code === '42P01') return [];
335
416
  throw error;
336
417
  }
337
418
  }
338
419
 
339
420
  /**
340
421
  * 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.
422
+ * The record object itself survives -- the caller retains the reference.
342
423
  * @private
343
424
  */
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);
425
+ private _evictIfNotMemory(modelName: string, record: OrmRecord): void {
426
+ const storeRef = this.deps.store as unknown as {
427
+ _memoryResolver?: (name: string) => boolean;
428
+ get?: (name: string) => Map<unknown, unknown> | undefined;
429
+ data?: { get(name: string): Map<unknown, unknown> | undefined };
430
+ };
431
+
432
+ if (storeRef._memoryResolver && !storeRef._memoryResolver(modelName)) {
433
+ const modelStore = storeRef.get?.(modelName) ?? storeRef.data?.get(modelName);
349
434
  if (modelStore) modelStore.delete(record.id);
350
435
  }
351
436
  }
352
437
 
353
- _rowToRawData(row, schema) {
354
- const rawData = { ...row };
438
+ private _rowToRawData(row: Record<string, unknown>, schema: ModelSchema): Record<string, unknown> {
439
+ const rawData: Record<string, unknown> = { ...row };
355
440
 
356
- // PostgreSQL returns native booleans and parsed JSONB no manual conversion needed.
441
+ // PostgreSQL returns native booleans and parsed JSONB -- no manual conversion needed.
357
442
  // Only FK remapping and timestamp stripping required.
358
443
 
359
444
  // Map FK columns back to relationship keys
@@ -366,17 +451,17 @@ export default class PostgresDB {
366
451
  }
367
452
  }
368
453
 
369
- // Remove timestamp columns managed by PostgreSQL
454
+ // Remove timestamp columns -- managed by PostgreSQL
370
455
  delete rawData.created_at;
371
456
  delete rawData.updated_at;
372
457
 
373
458
  return rawData;
374
459
  }
375
460
 
376
- async persist(operation, modelName, context, response) {
377
- // Views are read-only no-op for all write operations
461
+ async persist(operation: string, modelName: string, context: PersistContext, response: PersistResponse): Promise<void> {
462
+ // Views are read-only -- no-op for all write operations
378
463
  const Orm = (await import('@stonyx/orm')).default;
379
- if (Orm.instance?.isView?.(modelName)) return;
464
+ if ((Orm.instance as { isView?: (name: string) => boolean })?.isView?.(modelName)) return;
380
465
 
381
466
  switch (operation) {
382
467
  case 'create':
@@ -388,14 +473,17 @@ export default class PostgresDB {
388
473
  }
389
474
  }
390
475
 
391
- async _persistCreate(modelName, context, response) {
476
+ private async _persistCreate(modelName: string, _context: PersistContext, response: PersistResponse): Promise<void> {
392
477
  const schemas = this.deps.introspectModels();
393
478
  const schema = schemas[modelName];
394
479
 
395
480
  if (!schema) return;
396
481
 
397
482
  const recordId = response?.data?.id;
398
- const record = recordId != null ? this.deps.store.get(modelName, isNaN(recordId) ? recordId : parseInt(recordId)) : null;
483
+ const storeRef = this.deps.store as unknown as {
484
+ get(name: string, id: unknown): OrmRecord | null;
485
+ };
486
+ const record = recordId != null ? storeRef.get(modelName, isNaN(recordId as number) ? recordId : parseInt(recordId as string)) : null;
399
487
 
400
488
  if (!record) return;
401
489
 
@@ -410,13 +498,13 @@ export default class PostgresDB {
410
498
 
411
499
  const { sql, values } = this.deps.buildInsert(schema.table, insertData);
412
500
 
413
- const result = await this.pool.query(sql, values);
501
+ const result = await this.requirePool().query(sql, values);
414
502
 
415
503
  // Re-key the record in the store if PostgreSQL generated the ID (via RETURNING)
416
504
  if (isPendingId && result.rows.length > 0) {
417
505
  const pendingId = record.id;
418
506
  const realId = result.rows[0].id;
419
- const modelStore = this.deps.store.get(modelName);
507
+ const modelStore = (this.deps.store as unknown as { get(name: string): Map<unknown, unknown> }).get(modelName);
420
508
 
421
509
  modelStore.delete(pendingId);
422
510
  record.__data.id = realId;
@@ -432,7 +520,7 @@ export default class PostgresDB {
432
520
  }
433
521
  }
434
522
 
435
- async _persistUpdate(modelName, context, response) {
523
+ private async _persistUpdate(modelName: string, context: PersistContext, _response: PersistResponse): Promise<void> {
436
524
  const schemas = this.deps.introspectModels();
437
525
  const schema = schemas[modelName];
438
526
 
@@ -446,7 +534,7 @@ export default class PostgresDB {
446
534
  const currentData = record.__data;
447
535
 
448
536
  // Build a diff of changed columns
449
- const changedData = {};
537
+ const changedData: Record<string, unknown> = {};
450
538
 
451
539
  for (const [col] of Object.entries(schema.columns)) {
452
540
  if (currentData[col] !== oldState[col]) {
@@ -457,7 +545,7 @@ export default class PostgresDB {
457
545
  // Check FK changes too
458
546
  for (const fkCol of Object.keys(schema.foreignKeys)) {
459
547
  const relName = fkCol.replace(/_id$/, '');
460
- const currentFkValue = record.__relationships[relName]?.id ?? null;
548
+ const currentFkValue = (record.__relationships[relName] as { id: unknown } | undefined)?.id ?? null;
461
549
  const oldFkValue = oldState[relName] ?? null;
462
550
 
463
551
  if (currentFkValue !== oldFkValue) {
@@ -467,14 +555,14 @@ export default class PostgresDB {
467
555
 
468
556
  if (Object.keys(changedData).length === 0) return;
469
557
 
470
- // PostgreSQL doesn't have ON UPDATE CURRENT_TIMESTAMP set updated_at manually
558
+ // PostgreSQL doesn't have ON UPDATE CURRENT_TIMESTAMP -- set updated_at manually
471
559
  changedData.updated_at = new Date();
472
560
 
473
561
  const { sql, values } = this.deps.buildUpdate(schema.table, id, changedData);
474
- await this.pool.query(sql, values);
562
+ await this.requirePool().query(sql, values);
475
563
  }
476
564
 
477
- async _persistDelete(modelName, context) {
565
+ private async _persistDelete(modelName: string, context: PersistContext): Promise<void> {
478
566
  const schemas = this.deps.introspectModels();
479
567
  const schema = schemas[modelName];
480
568
 
@@ -484,11 +572,11 @@ export default class PostgresDB {
484
572
  if (id == null) return;
485
573
 
486
574
  const { sql, values } = this.deps.buildDelete(schema.table, id);
487
- await this.pool.query(sql, values);
575
+ await this.requirePool().query(sql, values);
488
576
  }
489
577
 
490
- _recordToRow(record, schema) {
491
- const row = {};
578
+ private _recordToRow(record: OrmRecord, schema: ModelSchema): Record<string, unknown> {
579
+ const row: Record<string, unknown> = {};
492
580
  const data = record.__data;
493
581
 
494
582
  // ID
@@ -512,7 +600,7 @@ export default class PostgresDB {
512
600
  const related = record.__relationships[relName];
513
601
 
514
602
  if (related) {
515
- row[fkCol] = related.id;
603
+ row[fkCol] = (related as { id: unknown }).id;
516
604
  } else if (data[relName] !== undefined) {
517
605
  // Raw FK value (e.g., from create payload)
518
606
  row[fkCol] = data[relName];