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