@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.
- package/config/environment.js +17 -0
- package/dist/aggregates.d.ts +21 -0
- package/dist/aggregates.js +90 -0
- package/dist/attr.d.ts +2 -0
- package/dist/attr.js +22 -0
- package/dist/belongs-to.d.ts +11 -0
- package/dist/belongs-to.js +58 -0
- package/dist/cli.d.ts +22 -0
- package/dist/cli.js +148 -0
- package/dist/commands.d.ts +7 -0
- package/dist/commands.js +146 -0
- package/dist/db.d.ts +21 -0
- package/dist/db.js +174 -0
- package/dist/exports/db.d.ts +7 -0
- package/{src → dist}/exports/db.js +2 -4
- package/dist/has-many.d.ts +11 -0
- package/dist/has-many.js +57 -0
- package/dist/hooks.d.ts +47 -0
- package/dist/hooks.js +106 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +34 -0
- package/dist/main.d.ts +46 -0
- package/dist/main.js +178 -0
- package/dist/manage-record.d.ts +13 -0
- package/dist/manage-record.js +113 -0
- package/dist/meta-request.d.ts +6 -0
- package/dist/meta-request.js +52 -0
- package/dist/migrate.d.ts +2 -0
- package/dist/migrate.js +57 -0
- package/dist/model-property.d.ts +9 -0
- package/dist/model-property.js +29 -0
- package/dist/model.d.ts +15 -0
- package/dist/model.js +18 -0
- package/dist/mysql/connection.d.ts +14 -0
- package/dist/mysql/connection.js +24 -0
- package/dist/mysql/migration-generator.d.ts +45 -0
- package/dist/mysql/migration-generator.js +245 -0
- package/dist/mysql/migration-runner.d.ts +12 -0
- package/dist/mysql/migration-runner.js +83 -0
- package/dist/mysql/mysql-db.d.ts +100 -0
- package/dist/mysql/mysql-db.js +411 -0
- package/dist/mysql/query-builder.d.ts +10 -0
- package/dist/mysql/query-builder.js +44 -0
- package/dist/mysql/schema-introspector.d.ts +19 -0
- package/dist/mysql/schema-introspector.js +286 -0
- package/dist/mysql/type-map.d.ts +21 -0
- package/dist/mysql/type-map.js +36 -0
- package/dist/orm-request.d.ts +38 -0
- package/dist/orm-request.js +453 -0
- package/dist/plural-registry.d.ts +4 -0
- package/{src → dist}/plural-registry.js +3 -6
- package/dist/postgres/connection.d.ts +15 -0
- package/dist/postgres/connection.js +30 -0
- package/dist/postgres/migration-generator.d.ts +45 -0
- package/dist/postgres/migration-generator.js +257 -0
- package/dist/postgres/migration-runner.d.ts +10 -0
- package/dist/postgres/migration-runner.js +82 -0
- package/dist/postgres/postgres-db.d.ts +119 -0
- package/dist/postgres/postgres-db.js +473 -0
- package/dist/postgres/query-builder.d.ts +27 -0
- package/dist/postgres/query-builder.js +98 -0
- package/dist/postgres/schema-introspector.d.ts +29 -0
- package/dist/postgres/schema-introspector.js +309 -0
- package/dist/postgres/type-map.d.ts +23 -0
- package/dist/postgres/type-map.js +53 -0
- package/dist/record.d.ts +75 -0
- package/dist/record.js +115 -0
- package/dist/relationships.d.ts +10 -0
- package/dist/relationships.js +35 -0
- package/dist/serializer.d.ts +17 -0
- package/dist/serializer.js +130 -0
- package/dist/setup-rest-server.d.ts +1 -0
- package/dist/setup-rest-server.js +54 -0
- package/dist/standalone-db.d.ts +58 -0
- package/dist/standalone-db.js +142 -0
- package/dist/store.d.ts +62 -0
- package/dist/store.js +271 -0
- package/dist/timescale/query-builder.d.ts +41 -0
- package/dist/timescale/query-builder.js +87 -0
- package/dist/timescale/timescale-db.d.ts +44 -0
- package/dist/timescale/timescale-db.js +81 -0
- package/dist/transforms.d.ts +2 -0
- package/dist/transforms.js +17 -0
- package/dist/types/orm-types.d.ts +142 -0
- package/dist/types/orm-types.js +1 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +13 -0
- package/dist/view-resolver.d.ts +8 -0
- package/dist/view-resolver.js +165 -0
- package/dist/view.d.ts +11 -0
- package/dist/view.js +18 -0
- package/package.json +34 -11
- package/src/{aggregates.js → aggregates.ts} +27 -13
- package/src/{attr.js → attr.ts} +2 -2
- package/src/{belongs-to.js → belongs-to.ts} +36 -17
- package/src/{cli.js → cli.ts} +17 -11
- package/src/{commands.js → commands.ts} +179 -170
- package/src/{db.js → db.ts} +35 -26
- package/src/exports/db.ts +7 -0
- package/src/has-many.ts +91 -0
- package/src/{hooks.js → hooks.ts} +23 -27
- package/src/{index.js → index.ts} +4 -4
- package/src/{main.js → main.ts} +64 -34
- package/src/{manage-record.js → manage-record.ts} +41 -22
- package/src/{meta-request.js → meta-request.ts} +17 -14
- package/src/{migrate.js → migrate.ts} +9 -9
- package/src/{model-property.js → model-property.ts} +12 -6
- package/src/{model.js → model.ts} +5 -4
- package/src/mysql/{connection.js → connection.ts} +43 -28
- package/src/mysql/{migration-generator.js → migration-generator.ts} +332 -286
- package/src/mysql/{migration-runner.js → migration-runner.ts} +116 -110
- package/src/mysql/{mysql-db.js → mysql-db.ts} +533 -473
- package/src/mysql/{query-builder.js → query-builder.ts} +69 -64
- package/src/mysql/{schema-introspector.js → schema-introspector.ts} +355 -325
- package/src/mysql/{type-map.js → type-map.ts} +42 -37
- package/src/{orm-request.js → orm-request.ts} +165 -95
- package/src/plural-registry.ts +12 -0
- package/src/postgres/connection.ts +46 -0
- package/src/postgres/{migration-generator.js → migration-generator.ts} +82 -38
- package/src/postgres/{migration-runner.js → migration-runner.ts} +11 -10
- package/src/postgres/{postgres-db.js → postgres-db.ts} +199 -111
- package/src/postgres/{query-builder.js → query-builder.ts} +27 -28
- package/src/postgres/{schema-introspector.js → schema-introspector.ts} +87 -58
- package/src/postgres/{type-map.js → type-map.ts} +10 -6
- package/src/{record.js → record.ts} +73 -34
- package/src/relationships.ts +48 -0
- package/src/{serializer.js → serializer.ts} +44 -36
- package/src/{setup-rest-server.js → setup-rest-server.ts} +18 -13
- package/src/{standalone-db.js → standalone-db.ts} +33 -24
- package/src/{store.js → store.ts} +90 -68
- package/src/timescale/query-builder.ts +137 -0
- package/src/timescale/timescale-db.ts +107 -0
- package/src/transforms.ts +20 -0
- package/src/types/mysql2.d.ts +30 -0
- package/src/types/orm-types.ts +146 -0
- package/src/types/pg.d.ts +28 -0
- package/src/types/stonyx-cron.d.ts +5 -0
- package/src/types/stonyx-events.d.ts +4 -0
- package/src/types/stonyx-rest-server.d.ts +11 -0
- package/src/types/stonyx-utils.d.ts +33 -0
- package/src/types/stonyx.d.ts +21 -0
- package/src/utils.ts +16 -0
- package/src/{view-resolver.js → view-resolver.ts} +53 -28
- package/src/view.ts +22 -0
- package/src/has-many.js +0 -68
- package/src/postgres/connection.js +0 -30
- package/src/relationships.js +0 -43
- package/src/transforms.js +0 -20
- package/src/utils.js +0 -12
- 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 {
|
|
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
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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.
|
|
99
|
+
this.pgConfig = this.deps.config.orm[Ctor.configKey] as Record<string, unknown>;
|
|
31
100
|
}
|
|
32
101
|
|
|
33
|
-
|
|
34
|
-
this.pool
|
|
35
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
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 = {
|
|
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.
|
|
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 = {
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
422
|
+
* The record object itself survives -- the caller retains the reference.
|
|
342
423
|
* @private
|
|
343
424
|
*/
|
|
344
|
-
_evictIfNotMemory(modelName, record) {
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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.
|
|
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,
|
|
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
|
|
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.
|
|
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.
|
|
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];
|