@stonyx/orm 0.2.1-beta.80 → 0.2.1-beta.82
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/package.json +7 -2
- package/src/main.js +14 -9
- package/src/manage-record.js +3 -3
- package/src/mysql/mysql-db.js +2 -2
- package/src/orm-request.js +3 -3
- package/src/postgres/connection.js +30 -0
- package/src/postgres/migration-generator.js +302 -0
- package/src/postgres/migration-runner.js +109 -0
- package/src/postgres/postgres-db.js +524 -0
- package/src/postgres/query-builder.js +149 -0
- package/src/postgres/schema-introspector.js +354 -0
- package/src/postgres/type-map.js +53 -0
- package/src/store.js +17 -17
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import Orm from '@stonyx/orm';
|
|
2
|
+
import { getPgType, getVectorType } from './type-map.js';
|
|
3
|
+
import { camelCaseToKebabCase } from '@stonyx/utils/string';
|
|
4
|
+
import { getPluralName } from '../plural-registry.js';
|
|
5
|
+
import { dbKey } from '../db.js';
|
|
6
|
+
import { AggregateProperty } from '../aggregates.js';
|
|
7
|
+
|
|
8
|
+
function getRelationshipInfo(property) {
|
|
9
|
+
if (typeof property !== 'function') return null;
|
|
10
|
+
const fnStr = property.toString();
|
|
11
|
+
const modelName = property.__relatedModelName || null;
|
|
12
|
+
|
|
13
|
+
if (fnStr.includes(`getRelationships('belongsTo',`)) return { type: 'belongsTo', modelName };
|
|
14
|
+
if (fnStr.includes(`getRelationships('hasMany',`)) return { type: 'hasMany', modelName };
|
|
15
|
+
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function sanitizeTableName(name) {
|
|
20
|
+
return name.replace(/[-/]/g, '_');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function introspectModels() {
|
|
24
|
+
const { models } = Orm.instance;
|
|
25
|
+
const schemas = {};
|
|
26
|
+
|
|
27
|
+
for (const [modelKey, modelClass] of Object.entries(models)) {
|
|
28
|
+
const name = camelCaseToKebabCase(modelKey.slice(0, -5));
|
|
29
|
+
|
|
30
|
+
if (name === dbKey) continue;
|
|
31
|
+
|
|
32
|
+
const model = new modelClass(modelKey);
|
|
33
|
+
const columns = {};
|
|
34
|
+
const foreignKeys = {};
|
|
35
|
+
const relationships = { belongsTo: {}, hasMany: {} };
|
|
36
|
+
const vectorColumns = {};
|
|
37
|
+
let idType = 'number';
|
|
38
|
+
|
|
39
|
+
const transforms = Orm.instance.transforms;
|
|
40
|
+
|
|
41
|
+
for (const [key, property] of Object.entries(model)) {
|
|
42
|
+
if (key.startsWith('__')) continue;
|
|
43
|
+
|
|
44
|
+
const relInfo = getRelationshipInfo(property);
|
|
45
|
+
|
|
46
|
+
if (relInfo?.type === 'belongsTo') {
|
|
47
|
+
relationships.belongsTo[key] = relInfo.modelName;
|
|
48
|
+
} else if (relInfo?.type === 'hasMany') {
|
|
49
|
+
relationships.hasMany[key] = relInfo.modelName;
|
|
50
|
+
} else if (property?.constructor?.name === 'ModelProperty') {
|
|
51
|
+
if (key === 'id') {
|
|
52
|
+
idType = property.type;
|
|
53
|
+
} else if (property.type === 'vector') {
|
|
54
|
+
const dimensions = property.dimensions || 1536;
|
|
55
|
+
columns[key] = getVectorType(dimensions);
|
|
56
|
+
vectorColumns[key] = dimensions;
|
|
57
|
+
} else {
|
|
58
|
+
columns[key] = getPgType(property.type, transforms[property.type]);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Build foreign keys from belongsTo relationships
|
|
64
|
+
for (const [relName, targetModelName] of Object.entries(relationships.belongsTo)) {
|
|
65
|
+
const fkColumn = `${relName}_id`;
|
|
66
|
+
foreignKeys[fkColumn] = {
|
|
67
|
+
references: sanitizeTableName(getPluralName(targetModelName)),
|
|
68
|
+
column: 'id',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
schemas[name] = {
|
|
73
|
+
table: sanitizeTableName(getPluralName(name)),
|
|
74
|
+
idType,
|
|
75
|
+
columns,
|
|
76
|
+
foreignKeys,
|
|
77
|
+
relationships,
|
|
78
|
+
vectorColumns,
|
|
79
|
+
memory: modelClass.memory === true,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return schemas;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function buildTableDDL(name, schema, allSchemas = {}) {
|
|
87
|
+
const { idType, columns, foreignKeys } = schema;
|
|
88
|
+
const table = sanitizeTableName(schema.table);
|
|
89
|
+
const lines = [];
|
|
90
|
+
|
|
91
|
+
// Primary key
|
|
92
|
+
if (idType === 'string') {
|
|
93
|
+
lines.push(' "id" VARCHAR(255) PRIMARY KEY');
|
|
94
|
+
} else {
|
|
95
|
+
lines.push(' "id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Attribute columns
|
|
99
|
+
for (const [col, pgType] of Object.entries(columns)) {
|
|
100
|
+
lines.push(` "${col}" ${pgType}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Foreign key columns
|
|
104
|
+
for (const [fkCol, fkDef] of Object.entries(foreignKeys)) {
|
|
105
|
+
const refIdType = getReferencedIdType(fkDef.references, allSchemas);
|
|
106
|
+
lines.push(` "${fkCol}" ${refIdType}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Timestamps
|
|
110
|
+
lines.push(' "created_at" TIMESTAMPTZ DEFAULT NOW()');
|
|
111
|
+
lines.push(' "updated_at" TIMESTAMPTZ DEFAULT NOW()');
|
|
112
|
+
|
|
113
|
+
// Foreign key constraints
|
|
114
|
+
for (const [fkCol, fkDef] of Object.entries(foreignKeys)) {
|
|
115
|
+
const refTable = sanitizeTableName(fkDef.references);
|
|
116
|
+
lines.push(` FOREIGN KEY ("${fkCol}") REFERENCES "${refTable}"("${fkDef.column}") ON DELETE SET NULL`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return `CREATE TABLE IF NOT EXISTS "${table}" (\n${lines.join(',\n')}\n)`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Build HNSW index DDL for vector columns on a model.
|
|
124
|
+
* @param {string} name - Model name
|
|
125
|
+
* @param {Object} schema - Model schema with vectorColumns
|
|
126
|
+
* @returns {string[]} Array of CREATE INDEX statements
|
|
127
|
+
*/
|
|
128
|
+
export function buildVectorIndexDDL(name, schema) {
|
|
129
|
+
const table = sanitizeTableName(schema.table);
|
|
130
|
+
const statements = [];
|
|
131
|
+
|
|
132
|
+
for (const [col] of Object.entries(schema.vectorColumns || {})) {
|
|
133
|
+
statements.push(
|
|
134
|
+
`CREATE INDEX IF NOT EXISTS "idx_${table}_${col}_hnsw" ON "${table}" USING hnsw ("${col}" vector_cosine_ops) WITH (m = 16, ef_construction = 200)`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return statements;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function getReferencedIdType(tableName, allSchemas) {
|
|
142
|
+
for (const schema of Object.values(allSchemas)) {
|
|
143
|
+
if (schema.table === tableName) {
|
|
144
|
+
return schema.idType === 'string' ? 'VARCHAR(255)' : 'INTEGER';
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return 'INTEGER';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function getTopologicalOrder(schemas) {
|
|
152
|
+
const visited = new Set();
|
|
153
|
+
const order = [];
|
|
154
|
+
|
|
155
|
+
function visit(name) {
|
|
156
|
+
if (visited.has(name)) return;
|
|
157
|
+
visited.add(name);
|
|
158
|
+
|
|
159
|
+
const schema = schemas[name];
|
|
160
|
+
if (!schema) return;
|
|
161
|
+
|
|
162
|
+
// Visit dependencies (belongsTo targets) first
|
|
163
|
+
for (const targetModelName of Object.values(schema.relationships.belongsTo)) {
|
|
164
|
+
visit(targetModelName);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
order.push(name);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
for (const name of Object.keys(schemas)) {
|
|
171
|
+
visit(name);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return order;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function introspectViews() {
|
|
178
|
+
const orm = Orm.instance;
|
|
179
|
+
if (!orm.views) return {};
|
|
180
|
+
|
|
181
|
+
const schemas = {};
|
|
182
|
+
|
|
183
|
+
for (const [viewKey, viewClass] of Object.entries(orm.views)) {
|
|
184
|
+
const name = camelCaseToKebabCase(viewKey.slice(0, -4)); // Remove 'View' suffix
|
|
185
|
+
|
|
186
|
+
const source = viewClass.source;
|
|
187
|
+
if (!source) continue;
|
|
188
|
+
|
|
189
|
+
const model = new viewClass(name);
|
|
190
|
+
const columns = {};
|
|
191
|
+
const foreignKeys = {};
|
|
192
|
+
const aggregates = {};
|
|
193
|
+
const relationships = { belongsTo: {}, hasMany: {} };
|
|
194
|
+
|
|
195
|
+
for (const [key, property] of Object.entries(model)) {
|
|
196
|
+
if (key.startsWith('__')) continue;
|
|
197
|
+
if (key === 'id') continue;
|
|
198
|
+
|
|
199
|
+
if (property instanceof AggregateProperty) {
|
|
200
|
+
aggregates[key] = property;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const relInfo = getRelationshipInfo(property);
|
|
205
|
+
|
|
206
|
+
if (relInfo?.type === 'belongsTo') {
|
|
207
|
+
relationships.belongsTo[key] = relInfo.modelName;
|
|
208
|
+
const fkColumn = `${key}_id`;
|
|
209
|
+
foreignKeys[fkColumn] = {
|
|
210
|
+
references: sanitizeTableName(getPluralName(relInfo.modelName)),
|
|
211
|
+
column: 'id',
|
|
212
|
+
};
|
|
213
|
+
} else if (relInfo?.type === 'hasMany') {
|
|
214
|
+
relationships.hasMany[key] = relInfo.modelName;
|
|
215
|
+
} else if (property?.constructor?.name === 'ModelProperty') {
|
|
216
|
+
const transforms = Orm.instance.transforms;
|
|
217
|
+
columns[key] = getPgType(property.type, transforms[property.type]);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
schemas[name] = {
|
|
222
|
+
viewName: sanitizeTableName(getPluralName(name)),
|
|
223
|
+
source,
|
|
224
|
+
groupBy: viewClass.groupBy || undefined,
|
|
225
|
+
columns,
|
|
226
|
+
foreignKeys,
|
|
227
|
+
aggregates,
|
|
228
|
+
relationships,
|
|
229
|
+
isView: true,
|
|
230
|
+
memory: viewClass.memory !== false ? false : false, // Views default to memory:false
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return schemas;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function buildViewDDL(name, viewSchema, modelSchemas = {}) {
|
|
238
|
+
if (!viewSchema.source) {
|
|
239
|
+
throw new Error(`View '${name}' must define a source model`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const sourceModelName = viewSchema.source;
|
|
243
|
+
const sourceSchema = modelSchemas[sourceModelName];
|
|
244
|
+
const sourceTable = sanitizeTableName(sourceSchema
|
|
245
|
+
? sourceSchema.table
|
|
246
|
+
: getPluralName(sourceModelName));
|
|
247
|
+
|
|
248
|
+
const selectColumns = [];
|
|
249
|
+
const joins = [];
|
|
250
|
+
const hasAggregates = Object.keys(viewSchema.aggregates || {}).length > 0;
|
|
251
|
+
const groupByField = viewSchema.groupBy;
|
|
252
|
+
|
|
253
|
+
// ID column: groupBy field or source table PK
|
|
254
|
+
if (groupByField) {
|
|
255
|
+
selectColumns.push(`"${sourceTable}"."${groupByField}" AS "id"`);
|
|
256
|
+
} else {
|
|
257
|
+
selectColumns.push(`"${sourceTable}"."id" AS "id"`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Aggregate columns
|
|
261
|
+
for (const [key, aggProp] of Object.entries(viewSchema.aggregates || {})) {
|
|
262
|
+
// Use pgFunction if available, fall back to mysqlFunction
|
|
263
|
+
const fn = aggProp.pgFunction || aggProp.mysqlFunction;
|
|
264
|
+
|
|
265
|
+
if (aggProp.relationship === undefined) {
|
|
266
|
+
// Field-level aggregate (groupBy views)
|
|
267
|
+
if (aggProp.aggregateType === 'count') {
|
|
268
|
+
selectColumns.push(`COUNT(*) AS "${key}"`);
|
|
269
|
+
} else {
|
|
270
|
+
selectColumns.push(`${fn}("${sourceTable}"."${aggProp.field}") AS "${key}"`);
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
// Relationship aggregate
|
|
274
|
+
const relName = aggProp.relationship;
|
|
275
|
+
const relTable = sanitizeTableName(getPluralName(relName));
|
|
276
|
+
|
|
277
|
+
if (aggProp.aggregateType === 'count') {
|
|
278
|
+
selectColumns.push(`${fn}("${relTable}"."id") AS "${key}"`);
|
|
279
|
+
} else {
|
|
280
|
+
const field = aggProp.field;
|
|
281
|
+
selectColumns.push(`${fn}("${relTable}"."${field}") AS "${key}"`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Add LEFT JOIN for the relationship if not already added
|
|
285
|
+
const joinKey = `${relTable}`;
|
|
286
|
+
if (!joins.find(j => j.table === joinKey)) {
|
|
287
|
+
const fkColumn = `${sourceModelName}_id`;
|
|
288
|
+
joins.push({
|
|
289
|
+
table: relTable,
|
|
290
|
+
condition: `"${relTable}"."${fkColumn}" = "${sourceTable}"."id"`
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Regular columns
|
|
297
|
+
for (const [key] of Object.entries(viewSchema.columns || {})) {
|
|
298
|
+
selectColumns.push(`"${sourceTable}"."${key}" AS "${key}"`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Build JOIN clauses
|
|
302
|
+
const joinClauses = joins.map(j =>
|
|
303
|
+
`LEFT JOIN "${j.table}" ON ${j.condition}`
|
|
304
|
+
).join('\n ');
|
|
305
|
+
|
|
306
|
+
// Build GROUP BY
|
|
307
|
+
let groupBy = '';
|
|
308
|
+
if (groupByField) {
|
|
309
|
+
groupBy = `\nGROUP BY "${sourceTable}"."${groupByField}"`;
|
|
310
|
+
} else if (hasAggregates) {
|
|
311
|
+
groupBy = `\nGROUP BY "${sourceTable}"."id"`;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const viewName = sanitizeTableName(viewSchema.viewName);
|
|
315
|
+
const sql = `CREATE OR REPLACE VIEW "${viewName}" AS\nSELECT\n ${selectColumns.join(',\n ')}\nFROM "${sourceTable}"${joinClauses ? '\n ' + joinClauses : ''}${groupBy}`;
|
|
316
|
+
|
|
317
|
+
return sql;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function viewSchemasToSnapshot(viewSchemas) {
|
|
321
|
+
const snapshot = {};
|
|
322
|
+
|
|
323
|
+
for (const [name, schema] of Object.entries(viewSchemas)) {
|
|
324
|
+
snapshot[name] = {
|
|
325
|
+
viewName: schema.viewName,
|
|
326
|
+
source: schema.source,
|
|
327
|
+
...(schema.groupBy ? { groupBy: schema.groupBy } : {}),
|
|
328
|
+
columns: { ...schema.columns },
|
|
329
|
+
foreignKeys: { ...schema.foreignKeys },
|
|
330
|
+
isView: true,
|
|
331
|
+
viewQuery: buildViewDDL(name, schema),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return snapshot;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export function schemasToSnapshot(schemas) {
|
|
339
|
+
const snapshot = {};
|
|
340
|
+
|
|
341
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
342
|
+
snapshot[name] = {
|
|
343
|
+
table: schema.table,
|
|
344
|
+
idType: schema.idType,
|
|
345
|
+
columns: { ...schema.columns },
|
|
346
|
+
foreignKeys: { ...schema.foreignKeys },
|
|
347
|
+
...(schema.vectorColumns && Object.keys(schema.vectorColumns).length > 0
|
|
348
|
+
? { vectorColumns: { ...schema.vectorColumns } }
|
|
349
|
+
: {}),
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return snapshot;
|
|
354
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const typeMap = {
|
|
2
|
+
string: 'VARCHAR(255)',
|
|
3
|
+
number: 'INTEGER',
|
|
4
|
+
float: 'REAL',
|
|
5
|
+
boolean: 'BOOLEAN',
|
|
6
|
+
date: 'TIMESTAMPTZ',
|
|
7
|
+
timestamp: 'BIGINT',
|
|
8
|
+
passthrough: 'TEXT',
|
|
9
|
+
trim: 'VARCHAR(255)',
|
|
10
|
+
uppercase: 'VARCHAR(255)',
|
|
11
|
+
ceil: 'INTEGER',
|
|
12
|
+
floor: 'INTEGER',
|
|
13
|
+
round: 'INTEGER',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Resolves a Stonyx ORM attribute type to a PostgreSQL column type.
|
|
18
|
+
*
|
|
19
|
+
* For built-in types, returns the mapped PostgreSQL type directly.
|
|
20
|
+
*
|
|
21
|
+
* For custom transforms (e.g. an `animal` transform that maps strings to ints):
|
|
22
|
+
* - If the transform function exports a `pgType` property, that value is used.
|
|
23
|
+
* - Otherwise, if `mysqlType` is defined, it is mapped to a PG equivalent.
|
|
24
|
+
* - Otherwise, defaults to JSONB. Values are JSON-stringified on write and
|
|
25
|
+
* JSON-parsed on read by PostgreSQL natively.
|
|
26
|
+
*/
|
|
27
|
+
export function getPgType(attrType, transformFn) {
|
|
28
|
+
if (typeMap[attrType]) return typeMap[attrType];
|
|
29
|
+
if (transformFn?.pgType) return transformFn.pgType;
|
|
30
|
+
if (transformFn?.mysqlType) return mysqlTypeToPg(transformFn.mysqlType);
|
|
31
|
+
|
|
32
|
+
return 'JSONB';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns a vector column type for the given dimensions.
|
|
37
|
+
* @param {number} dimensions - Vector dimensionality (e.g. 768, 1536)
|
|
38
|
+
* @returns {string}
|
|
39
|
+
*/
|
|
40
|
+
export function getVectorType(dimensions) {
|
|
41
|
+
return `vector(${dimensions})`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function mysqlTypeToPg(mysqlType) {
|
|
45
|
+
const upper = mysqlType.toUpperCase();
|
|
46
|
+
if (upper === 'TINYINT(1)') return 'BOOLEAN';
|
|
47
|
+
if (upper === 'INT' || upper === 'INT AUTO_INCREMENT') return 'INTEGER';
|
|
48
|
+
if (upper === 'DATETIME') return 'TIMESTAMPTZ';
|
|
49
|
+
if (upper === 'JSON') return 'JSONB';
|
|
50
|
+
return mysqlType;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export default typeMap;
|
package/src/store.js
CHANGED
|
@@ -22,15 +22,15 @@ export default class Store {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
* Async authoritative read. Always queries
|
|
25
|
+
* Async authoritative read. Always queries the SQL database for memory: false models.
|
|
26
26
|
* For memory: true models, returns from store (already loaded on boot).
|
|
27
27
|
* @param {string} modelName - The model name
|
|
28
28
|
* @param {string|number} id - The record ID
|
|
29
29
|
* @returns {Promise<Record|undefined>}
|
|
30
30
|
*/
|
|
31
31
|
async find(modelName, id) {
|
|
32
|
-
// For views in non-
|
|
33
|
-
if (Orm.instance?.isView?.(modelName) && !this.
|
|
32
|
+
// For views in non-SQL mode, use view resolver
|
|
33
|
+
if (Orm.instance?.isView?.(modelName) && !this._sqlDb) {
|
|
34
34
|
const resolver = new ViewResolver(modelName);
|
|
35
35
|
return resolver.resolveOne(id);
|
|
36
36
|
}
|
|
@@ -40,12 +40,12 @@ export default class Store {
|
|
|
40
40
|
return this.get(modelName, id);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
// For memory: false models, always query
|
|
44
|
-
if (this.
|
|
45
|
-
return this.
|
|
43
|
+
// For memory: false models, always query the SQL database
|
|
44
|
+
if (this._sqlDb) {
|
|
45
|
+
return this._sqlDb.findRecord(modelName, id);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
// Fallback to store (JSON mode or no
|
|
48
|
+
// Fallback to store (JSON mode or no SQL adapter)
|
|
49
49
|
return this.get(modelName, id);
|
|
50
50
|
}
|
|
51
51
|
|
|
@@ -57,8 +57,8 @@ export default class Store {
|
|
|
57
57
|
* @returns {Promise<Record[]>}
|
|
58
58
|
*/
|
|
59
59
|
async findAll(modelName, conditions) {
|
|
60
|
-
// For views in non-
|
|
61
|
-
if (Orm.instance?.isView?.(modelName) && !this.
|
|
60
|
+
// For views in non-SQL mode, use view resolver
|
|
61
|
+
if (Orm.instance?.isView?.(modelName) && !this._sqlDb) {
|
|
62
62
|
const resolver = new ViewResolver(modelName);
|
|
63
63
|
const records = await resolver.resolveAll();
|
|
64
64
|
|
|
@@ -75,9 +75,9 @@ export default class Store {
|
|
|
75
75
|
return modelStore ? Array.from(modelStore.values()) : [];
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
// For memory: false models (or filtered queries), always query
|
|
79
|
-
if (this.
|
|
80
|
-
return this.
|
|
78
|
+
// For memory: false models (or filtered queries), always query the SQL database
|
|
79
|
+
if (this._sqlDb) {
|
|
80
|
+
return this._sqlDb.findAll(modelName, conditions);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
// Fallback to store (JSON mode) — apply conditions in-memory if provided
|
|
@@ -101,8 +101,8 @@ export default class Store {
|
|
|
101
101
|
* @returns {Promise<Record[]>}
|
|
102
102
|
*/
|
|
103
103
|
async query(modelName, conditions = {}) {
|
|
104
|
-
if (this.
|
|
105
|
-
return this.
|
|
104
|
+
if (this._sqlDb) {
|
|
105
|
+
return this._sqlDb.findAll(modelName, conditions);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
// Fallback: filter in-memory store
|
|
@@ -125,10 +125,10 @@ export default class Store {
|
|
|
125
125
|
_memoryResolver = null;
|
|
126
126
|
|
|
127
127
|
/**
|
|
128
|
-
* Set by Orm during init — reference to the
|
|
129
|
-
* @type {
|
|
128
|
+
* Set by Orm during init — reference to the SQL adapter instance for on-demand queries.
|
|
129
|
+
* @type {object|null}
|
|
130
130
|
*/
|
|
131
|
-
|
|
131
|
+
_sqlDb = null;
|
|
132
132
|
|
|
133
133
|
/**
|
|
134
134
|
* Check if a model is configured for in-memory storage.
|