@stonyx/orm 0.2.1-beta.84 → 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/belongs-to.js +2 -1
- package/dist/has-many.js +3 -2
- package/dist/main.js +2 -1
- package/dist/manage-record.js +3 -2
- package/dist/mysql/mysql-db.js +8 -4
- package/dist/orm-request.js +6 -4
- package/dist/postgres/postgres-db.js +6 -3
- package/dist/relationships.js +5 -1
- package/dist/serializer.js +8 -2
- package/dist/store.js +1 -1
- package/dist/timescale/timescale-db.d.ts +1 -0
- package/dist/timescale/timescale-db.js +8 -5
- package/dist/view-resolver.js +4 -0
- package/package.json +1 -1
- package/src/belongs-to.ts +2 -1
- package/src/has-many.ts +4 -3
- package/src/main.ts +2 -1
- package/src/manage-record.ts +2 -1
- package/src/mysql/mysql-db.ts +9 -5
- package/src/orm-request.ts +6 -4
- package/src/postgres/postgres-db.ts +7 -4
- package/src/relationships.ts +8 -3
- package/src/serializer.ts +10 -2
- package/src/store.ts +1 -1
- package/src/timescale/timescale-db.ts +17 -5
- package/src/view-resolver.ts +6 -4
package/dist/belongs-to.js
CHANGED
|
@@ -42,7 +42,8 @@ export default function belongsTo(modelName) {
|
|
|
42
42
|
}
|
|
43
43
|
relationship.set(relationshipId, output || {});
|
|
44
44
|
// Populate hasMany side if the relationship is defined
|
|
45
|
-
const
|
|
45
|
+
const outputRecord = typeof output === 'object' && output !== null && 'id' in output ? output : undefined;
|
|
46
|
+
const otherSide = outputRecord ? hasManyRelationships.get(modelName)?.get(sourceModelName)?.get(outputRecord.id) : undefined;
|
|
46
47
|
if (otherSide) {
|
|
47
48
|
otherSide.push(sourceRecord);
|
|
48
49
|
// Remove pending queue if it was just fulfilled
|
package/dist/has-many.js
CHANGED
|
@@ -36,8 +36,9 @@ export default function hasMany(modelName) {
|
|
|
36
36
|
record = createRecord(modelName, elementData, options);
|
|
37
37
|
}
|
|
38
38
|
// Populate belongTo side if the relationship is defined
|
|
39
|
-
const
|
|
40
|
-
|
|
39
|
+
const recordWithId = typeof record === 'object' && record !== null && 'id' in record ? record : undefined;
|
|
40
|
+
const otherSide = recordWithId ? getBelongsToRegistry()
|
|
41
|
+
.get(modelName)?.get(sourceModelName)?.get(recordWithId.id) : undefined;
|
|
41
42
|
if (otherSide)
|
|
42
43
|
Object.assign(otherSide, sourceRecord);
|
|
43
44
|
return record;
|
package/dist/main.js
CHANGED
|
@@ -66,7 +66,8 @@ export default class Orm {
|
|
|
66
66
|
Orm.store.set(name, new Map());
|
|
67
67
|
registerPluralName(name, exported);
|
|
68
68
|
}
|
|
69
|
-
|
|
69
|
+
const collection = this[pluralize(lowerCaseType)];
|
|
70
|
+
return collection[alias] = exported;
|
|
70
71
|
}, { ignoreAccessFailure: true, rawName: true, recursive: true, recursiveNaming: true });
|
|
71
72
|
});
|
|
72
73
|
// Wait for imports before db & rest server setup
|
package/dist/manage-record.js
CHANGED
|
@@ -22,8 +22,9 @@ export function createRecord(modelName, rawData = {}, userOptions = {}) {
|
|
|
22
22
|
if (!modelStore)
|
|
23
23
|
throw new Error(`Model store for '${modelName}' is not registered. Ensure the model is defined before creating records.`);
|
|
24
24
|
assignRecordId(modelName, rawData);
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
const existingRecord = modelStore.get(rawData.id);
|
|
26
|
+
if (existingRecord)
|
|
27
|
+
return existingRecord;
|
|
27
28
|
const recordClasses = orm.getRecordClasses(modelName);
|
|
28
29
|
const modelClass = recordClasses.modelClass;
|
|
29
30
|
const serializerClass = recordClasses.serializerClass;
|
package/dist/mysql/mysql-db.js
CHANGED
|
@@ -123,7 +123,8 @@ export default class MysqlDB {
|
|
|
123
123
|
const schema = schemas[modelName];
|
|
124
124
|
const { sql, values } = this.deps.buildSelect(schema.table);
|
|
125
125
|
try {
|
|
126
|
-
const
|
|
126
|
+
const result = await this.requirePool().execute(sql, values);
|
|
127
|
+
const rows = result[0];
|
|
127
128
|
for (const row of rows) {
|
|
128
129
|
const rawData = this._rowToRawData(row, schema);
|
|
129
130
|
this.deps.createRecord(modelName, rawData, { isDbRecord: true, serialize: false, transform: false });
|
|
@@ -149,7 +150,8 @@ export default class MysqlDB {
|
|
|
149
150
|
const schema = { table: viewSchema.viewName, columns: viewSchema.columns || {}, foreignKeys: viewSchema.foreignKeys || {} };
|
|
150
151
|
const { sql, values } = this.deps.buildSelect(schema.table);
|
|
151
152
|
try {
|
|
152
|
-
const
|
|
153
|
+
const result = await this.requirePool().execute(sql, values);
|
|
154
|
+
const rows = result[0];
|
|
153
155
|
for (const row of rows) {
|
|
154
156
|
const rawData = this._rowToRawData(row, schema);
|
|
155
157
|
this.deps.createRecord(viewName, rawData, { isDbRecord: true, serialize: false, transform: false });
|
|
@@ -189,7 +191,8 @@ export default class MysqlDB {
|
|
|
189
191
|
return undefined;
|
|
190
192
|
const { sql, values } = this.deps.buildSelect(schema.table, { id });
|
|
191
193
|
try {
|
|
192
|
-
const
|
|
194
|
+
const result = await this.requirePool().execute(sql, values);
|
|
195
|
+
const rows = result[0];
|
|
193
196
|
if (rows.length === 0)
|
|
194
197
|
return undefined;
|
|
195
198
|
const rawData = this._rowToRawData(rows[0], schema);
|
|
@@ -223,7 +226,8 @@ export default class MysqlDB {
|
|
|
223
226
|
return [];
|
|
224
227
|
const { sql, values } = this.deps.buildSelect(schema.table, conditions);
|
|
225
228
|
try {
|
|
226
|
-
const
|
|
229
|
+
const result = await this.requirePool().execute(sql, values);
|
|
230
|
+
const rows = result[0];
|
|
227
231
|
const records = rows.map(row => {
|
|
228
232
|
const rawData = this._rowToRawData(row, schema);
|
|
229
233
|
return this.deps.createRecord(modelName, rawData, { isDbRecord: true, serialize: false, transform: false });
|
package/dist/orm-request.js
CHANGED
|
@@ -99,12 +99,14 @@ function traverseIncludePath(currentRecords, includePath, depth, seen, included)
|
|
|
99
99
|
const type = relatedRecord.__model.__name;
|
|
100
100
|
const id = relatedRecord.id;
|
|
101
101
|
// Initialize Set for this type if needed
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
let seenIds = seen.get(type);
|
|
103
|
+
if (!seenIds) {
|
|
104
|
+
seenIds = new Set();
|
|
105
|
+
seen.set(type, seenIds);
|
|
104
106
|
}
|
|
105
107
|
// Check if we've already seen this type+id combination
|
|
106
|
-
if (!
|
|
107
|
-
|
|
108
|
+
if (!seenIds.has(id)) {
|
|
109
|
+
seenIds.add(id);
|
|
108
110
|
included.push(relatedRecord);
|
|
109
111
|
nextRecords.push(relatedRecord); // Prepare for next depth level
|
|
110
112
|
}
|
|
@@ -150,9 +150,10 @@ export default class PostgresDB {
|
|
|
150
150
|
this.deps.log.db(`Skipping memory load for view '${viewName}' (memory: false)`);
|
|
151
151
|
continue;
|
|
152
152
|
}
|
|
153
|
+
const sourceIdType = schemas[viewSchema.source]?.idType || 'number';
|
|
153
154
|
const schema = {
|
|
154
155
|
table: viewSchema.viewName,
|
|
155
|
-
idType:
|
|
156
|
+
idType: sourceIdType,
|
|
156
157
|
columns: viewSchema.columns || {},
|
|
157
158
|
foreignKeys: (viewSchema.foreignKeys || {}),
|
|
158
159
|
relationships: { belongsTo: {}, hasMany: {} },
|
|
@@ -194,9 +195,10 @@ export default class PostgresDB {
|
|
|
194
195
|
const viewSchemas = this.deps.introspectViews();
|
|
195
196
|
const viewSchema = viewSchemas[modelName];
|
|
196
197
|
if (viewSchema) {
|
|
198
|
+
const sourceIdType = schemas[viewSchema.source]?.idType || 'number';
|
|
197
199
|
schema = {
|
|
198
200
|
table: viewSchema.viewName,
|
|
199
|
-
idType:
|
|
201
|
+
idType: sourceIdType,
|
|
200
202
|
columns: viewSchema.columns || {},
|
|
201
203
|
foreignKeys: (viewSchema.foreignKeys || {}),
|
|
202
204
|
relationships: { belongsTo: {}, hasMany: {} },
|
|
@@ -234,9 +236,10 @@ export default class PostgresDB {
|
|
|
234
236
|
const viewSchemas = this.deps.introspectViews();
|
|
235
237
|
const viewSchema = viewSchemas[modelName];
|
|
236
238
|
if (viewSchema) {
|
|
239
|
+
const sourceIdType = schemas[viewSchema.source]?.idType || 'number';
|
|
237
240
|
schema = {
|
|
238
241
|
table: viewSchema.viewName,
|
|
239
|
-
idType:
|
|
242
|
+
idType: sourceIdType,
|
|
240
243
|
columns: viewSchema.columns || {},
|
|
241
244
|
foreignKeys: (viewSchema.foreignKeys || {}),
|
|
242
245
|
relationships: { belongsTo: {}, hasMany: {} },
|
package/dist/relationships.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { relationships } from '@stonyx/orm';
|
|
2
2
|
// TODO: Refactor mapping to remove a level of iteration
|
|
3
3
|
export function getRelationships(type, sourceModel, targetModel, relationshipId) {
|
|
4
|
-
|
|
4
|
+
let allRelationships = relationships.get(type);
|
|
5
|
+
if (!allRelationships) {
|
|
6
|
+
allRelationships = new Map();
|
|
7
|
+
relationships.set(type, allRelationships);
|
|
8
|
+
}
|
|
5
9
|
// create relationship map for this type of it doesn't already exist
|
|
6
10
|
if (!allRelationships.has(sourceModel))
|
|
7
11
|
allRelationships.set(sourceModel, new Map());
|
package/dist/serializer.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import config from 'stonyx/config';
|
|
2
2
|
import { get, makeArray } from '@stonyx/utils/object';
|
|
3
3
|
const RESERVED_KEYS = ['__name'];
|
|
4
|
+
function isAggregateProperty(v) {
|
|
5
|
+
return typeof v === 'object' && v !== null && v.__kind === 'AggregateProperty';
|
|
6
|
+
}
|
|
7
|
+
function isModelProperty(v) {
|
|
8
|
+
return typeof v === 'object' && v !== null && v.__kind === 'ModelProperty';
|
|
9
|
+
}
|
|
4
10
|
function searchQuery(query, array, key) {
|
|
5
11
|
const result = makeArray(array).find((item) => {
|
|
6
12
|
for (const [prop, value] of Object.entries(query)) {
|
|
@@ -78,13 +84,13 @@ export default class Serializer {
|
|
|
78
84
|
continue;
|
|
79
85
|
}
|
|
80
86
|
// Aggregate property handling — use the rawData value, not the aggregate descriptor
|
|
81
|
-
if (handler
|
|
87
|
+
if (isAggregateProperty(handler)) {
|
|
82
88
|
parsedData[key] = data;
|
|
83
89
|
rec[key] = data;
|
|
84
90
|
continue;
|
|
85
91
|
}
|
|
86
92
|
// Direct assignment handling
|
|
87
|
-
if (handler
|
|
93
|
+
if (!isModelProperty(handler)) {
|
|
88
94
|
parsedData[key] = handler;
|
|
89
95
|
rec[key] = handler;
|
|
90
96
|
continue;
|
package/dist/store.js
CHANGED
|
@@ -133,7 +133,7 @@ export default class Store {
|
|
|
133
133
|
this._nullifyBelongsToReferences(modelName, recordId, visited);
|
|
134
134
|
this._cleanupRelationshipRegistries(modelName, recordId);
|
|
135
135
|
recordToUnload.clean();
|
|
136
|
-
this.data.get(modelName)
|
|
136
|
+
this.data.get(modelName)?.delete(recordId);
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
unloadAllRecords(model, options = {}) {
|
|
@@ -19,6 +19,7 @@ export default class TimescaleDB extends PostgresDB {
|
|
|
19
19
|
static extensions: string[];
|
|
20
20
|
static configKey: string;
|
|
21
21
|
constructor(deps?: Record<string, unknown>);
|
|
22
|
+
private get tsDeps();
|
|
22
23
|
/**
|
|
23
24
|
* Convert a table to a TimescaleDB hypertable.
|
|
24
25
|
* Should be called after the table is created (e.g. after initial migration).
|
|
@@ -14,6 +14,9 @@ export default class TimescaleDB extends PostgresDB {
|
|
|
14
14
|
buildEnableCompression,
|
|
15
15
|
});
|
|
16
16
|
}
|
|
17
|
+
get tsDeps() {
|
|
18
|
+
return this.deps;
|
|
19
|
+
}
|
|
17
20
|
/**
|
|
18
21
|
* Convert a table to a TimescaleDB hypertable.
|
|
19
22
|
* Should be called after the table is created (e.g. after initial migration).
|
|
@@ -23,7 +26,7 @@ export default class TimescaleDB extends PostgresDB {
|
|
|
23
26
|
const schema = schemas[modelName];
|
|
24
27
|
if (!schema)
|
|
25
28
|
throw new Error(`Model '${modelName}' not found`);
|
|
26
|
-
const { sql } = this.
|
|
29
|
+
const { sql } = this.tsDeps.buildCreateHypertable(schema.table, timeColumn, options);
|
|
27
30
|
await this.requirePool().query(sql);
|
|
28
31
|
}
|
|
29
32
|
/**
|
|
@@ -34,7 +37,7 @@ export default class TimescaleDB extends PostgresDB {
|
|
|
34
37
|
const schema = schemas[modelName];
|
|
35
38
|
if (!schema)
|
|
36
39
|
return [];
|
|
37
|
-
const { sql, values } = this.
|
|
40
|
+
const { sql, values } = this.tsDeps.buildTimeBucket(schema.table, timeColumn, bucketSize, options);
|
|
38
41
|
try {
|
|
39
42
|
const result = await this.requirePool().query(sql, values);
|
|
40
43
|
return result.rows;
|
|
@@ -53,7 +56,7 @@ export default class TimescaleDB extends PostgresDB {
|
|
|
53
56
|
const schema = schemas[modelName];
|
|
54
57
|
if (!schema)
|
|
55
58
|
throw new Error(`Model '${modelName}' not found`);
|
|
56
|
-
const { sql } = this.
|
|
59
|
+
const { sql } = this.tsDeps.buildContinuousAggregate(viewName, schema.table, timeColumn, bucketSize, aggregates, options);
|
|
57
60
|
await this.requirePool().query(sql);
|
|
58
61
|
}
|
|
59
62
|
/**
|
|
@@ -64,7 +67,7 @@ export default class TimescaleDB extends PostgresDB {
|
|
|
64
67
|
const schema = schemas[modelName];
|
|
65
68
|
if (!schema)
|
|
66
69
|
throw new Error(`Model '${modelName}' not found`);
|
|
67
|
-
const { sql } = this.
|
|
70
|
+
const { sql } = this.tsDeps.buildEnableCompression(schema.table, options.segmentBy, options.orderBy);
|
|
68
71
|
await this.requirePool().query(sql);
|
|
69
72
|
}
|
|
70
73
|
/**
|
|
@@ -75,7 +78,7 @@ export default class TimescaleDB extends PostgresDB {
|
|
|
75
78
|
const schema = schemas[modelName];
|
|
76
79
|
if (!schema)
|
|
77
80
|
throw new Error(`Model '${modelName}' not found`);
|
|
78
|
-
const { sql } = this.
|
|
81
|
+
const { sql } = this.tsDeps.buildCompressionPolicy(schema.table, compressAfter);
|
|
79
82
|
await this.requirePool().query(sql);
|
|
80
83
|
}
|
|
81
84
|
}
|
package/dist/view-resolver.js
CHANGED
|
@@ -49,6 +49,8 @@ export default class ViewResolver {
|
|
|
49
49
|
const rawData = { id: sourceRecord.id };
|
|
50
50
|
// Compute aggregate fields from source record's relationships
|
|
51
51
|
for (const [key, aggProp] of Object.entries(aggregateFields)) {
|
|
52
|
+
if (!aggProp.relationship)
|
|
53
|
+
continue;
|
|
52
54
|
const relatedRecords = sourceRecord.__relationships?.[aggProp.relationship]
|
|
53
55
|
|| sourceRecord[aggProp.relationship];
|
|
54
56
|
const relArray = Array.isArray(relatedRecords) ? relatedRecords : [];
|
|
@@ -112,6 +114,8 @@ export default class ViewResolver {
|
|
|
112
114
|
}
|
|
113
115
|
else {
|
|
114
116
|
// Relationship aggregate — flatten related records across all group members
|
|
117
|
+
if (!aggProp.relationship)
|
|
118
|
+
continue;
|
|
115
119
|
const allRelated = [];
|
|
116
120
|
for (const record of groupRecords) {
|
|
117
121
|
const relatedRecords = record.__relationships?.[aggProp.relationship]
|
package/package.json
CHANGED
package/src/belongs-to.ts
CHANGED
|
@@ -69,7 +69,8 @@ export default function belongsTo(modelName: string): RelationshipHandler {
|
|
|
69
69
|
relationship.set(relationshipId, output || {});
|
|
70
70
|
|
|
71
71
|
// Populate hasMany side if the relationship is defined
|
|
72
|
-
const
|
|
72
|
+
const outputRecord = typeof output === 'object' && output !== null && 'id' in output ? output as SourceRecord : undefined;
|
|
73
|
+
const otherSide = outputRecord ? hasManyRelationships.get(modelName)?.get(sourceModelName)?.get(outputRecord.id) as unknown[] | undefined : undefined;
|
|
73
74
|
|
|
74
75
|
if (otherSide) {
|
|
75
76
|
otherSide.push(sourceRecord);
|
package/src/has-many.ts
CHANGED
|
@@ -44,7 +44,7 @@ export default function hasMany(modelName: string): RelationshipHandler {
|
|
|
44
44
|
const modelStore = store.get(modelName);
|
|
45
45
|
const pendingRelationshipQueue: PendingItem[] = [];
|
|
46
46
|
|
|
47
|
-
const output: unknown[] = !rawData ? [] :
|
|
47
|
+
const output: unknown[] = !rawData ? [] : makeArray(rawData).map((elementData: unknown) => {
|
|
48
48
|
let record: unknown;
|
|
49
49
|
|
|
50
50
|
if (typeof elementData !== 'object') {
|
|
@@ -66,8 +66,9 @@ export default function hasMany(modelName: string): RelationshipHandler {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// Populate belongTo side if the relationship is defined
|
|
69
|
-
const
|
|
70
|
-
|
|
69
|
+
const recordWithId = typeof record === 'object' && record !== null && 'id' in record ? record as SourceRecord : undefined;
|
|
70
|
+
const otherSide = recordWithId ? getBelongsToRegistry()
|
|
71
|
+
.get(modelName)?.get(sourceModelName)?.get(recordWithId.id) : undefined;
|
|
71
72
|
|
|
72
73
|
if (otherSide) Object.assign(otherSide, sourceRecord);
|
|
73
74
|
|
package/src/main.ts
CHANGED
|
@@ -98,7 +98,8 @@ export default class Orm {
|
|
|
98
98
|
registerPluralName(name, exported as { pluralName?: string });
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
const collection = this[pluralize(lowerCaseType) as keyof this] as Record<string, unknown>;
|
|
102
|
+
return collection[alias] = exported;
|
|
102
103
|
}, { ignoreAccessFailure: true, rawName: true, recursive: true, recursiveNaming: true });
|
|
103
104
|
});
|
|
104
105
|
|
package/src/manage-record.ts
CHANGED
|
@@ -43,7 +43,8 @@ export function createRecord(modelName: string, rawData: { [key: string]: unknow
|
|
|
43
43
|
if (!modelStore) throw new Error(`Model store for '${modelName}' is not registered. Ensure the model is defined before creating records.`);
|
|
44
44
|
|
|
45
45
|
assignRecordId(modelName, rawData);
|
|
46
|
-
|
|
46
|
+
const existingRecord = modelStore.get(rawData.id as number | string);
|
|
47
|
+
if (existingRecord) return existingRecord as OrmRecord;
|
|
47
48
|
|
|
48
49
|
const recordClasses = orm.getRecordClasses(modelName);
|
|
49
50
|
const modelClass = recordClasses.modelClass as (new (name: string) => { __name: string; [key: string]: unknown }) | undefined;
|
package/src/mysql/mysql-db.ts
CHANGED
|
@@ -72,7 +72,7 @@ const defaultDeps: MysqlDBDeps = {
|
|
|
72
72
|
introspectModels, introspectViews, getTopologicalOrder, schemasToSnapshot,
|
|
73
73
|
loadLatestSnapshot, detectSchemaDrift,
|
|
74
74
|
buildInsert, buildUpdate, buildDelete, buildSelect,
|
|
75
|
-
createRecord, store: store as
|
|
75
|
+
createRecord, store: store as OrmStore, confirm, readFile, getPluralName,
|
|
76
76
|
config, log, path
|
|
77
77
|
};
|
|
78
78
|
|
|
@@ -199,7 +199,8 @@ export default class MysqlDB {
|
|
|
199
199
|
const { sql, values } = this.deps.buildSelect(schema.table);
|
|
200
200
|
|
|
201
201
|
try {
|
|
202
|
-
const
|
|
202
|
+
const result = await this.requirePool().execute(sql, values);
|
|
203
|
+
const rows = result[0] as Record<string, unknown>[];
|
|
203
204
|
|
|
204
205
|
for (const row of rows) {
|
|
205
206
|
const rawData = this._rowToRawData(row, schema);
|
|
@@ -230,7 +231,8 @@ export default class MysqlDB {
|
|
|
230
231
|
const { sql, values } = this.deps.buildSelect(schema.table);
|
|
231
232
|
|
|
232
233
|
try {
|
|
233
|
-
const
|
|
234
|
+
const result = await this.requirePool().execute(sql, values);
|
|
235
|
+
const rows = result[0] as Record<string, unknown>[];
|
|
234
236
|
|
|
235
237
|
for (const row of rows) {
|
|
236
238
|
const rawData = this._rowToRawData(row, schema);
|
|
@@ -275,7 +277,8 @@ export default class MysqlDB {
|
|
|
275
277
|
const { sql, values } = this.deps.buildSelect(schema.table, { id });
|
|
276
278
|
|
|
277
279
|
try {
|
|
278
|
-
const
|
|
280
|
+
const result = await this.requirePool().execute(sql, values);
|
|
281
|
+
const rows = result[0] as Record<string, unknown>[];
|
|
279
282
|
|
|
280
283
|
if (rows.length === 0) return undefined;
|
|
281
284
|
|
|
@@ -314,7 +317,8 @@ export default class MysqlDB {
|
|
|
314
317
|
const { sql, values } = this.deps.buildSelect(schema.table, conditions);
|
|
315
318
|
|
|
316
319
|
try {
|
|
317
|
-
const
|
|
320
|
+
const result = await this.requirePool().execute(sql, values);
|
|
321
|
+
const rows = result[0] as Record<string, unknown>[];
|
|
318
322
|
|
|
319
323
|
const records = rows.map(row => {
|
|
320
324
|
const rawData = this._rowToRawData(row, schema!);
|
package/src/orm-request.ts
CHANGED
|
@@ -167,13 +167,15 @@ function traverseIncludePath(
|
|
|
167
167
|
const id = relatedRecord.id as string | number;
|
|
168
168
|
|
|
169
169
|
// Initialize Set for this type if needed
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
let seenIds = seen.get(type);
|
|
171
|
+
if (!seenIds) {
|
|
172
|
+
seenIds = new Set();
|
|
173
|
+
seen.set(type, seenIds);
|
|
172
174
|
}
|
|
173
175
|
|
|
174
176
|
// Check if we've already seen this type+id combination
|
|
175
|
-
if (!
|
|
176
|
-
|
|
177
|
+
if (!seenIds.has(id)) {
|
|
178
|
+
seenIds.add(id);
|
|
177
179
|
included.push(relatedRecord);
|
|
178
180
|
nextRecords.push(relatedRecord); // Prepare for next depth level
|
|
179
181
|
} else if (depth < includePath.length - 1) {
|
|
@@ -235,9 +235,10 @@ export default class PostgresDB {
|
|
|
235
235
|
continue;
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
+
const sourceIdType = schemas[viewSchema.source]?.idType || 'number';
|
|
238
239
|
const schema: ModelSchema = {
|
|
239
240
|
table: viewSchema.viewName,
|
|
240
|
-
idType:
|
|
241
|
+
idType: sourceIdType,
|
|
241
242
|
columns: viewSchema.columns || {},
|
|
242
243
|
foreignKeys: (viewSchema.foreignKeys || {}) as Record<string, ForeignKeyDef>,
|
|
243
244
|
relationships: { belongsTo: {}, hasMany: {} },
|
|
@@ -283,9 +284,10 @@ export default class PostgresDB {
|
|
|
283
284
|
const viewSchemas = this.deps.introspectViews();
|
|
284
285
|
const viewSchema = viewSchemas[modelName];
|
|
285
286
|
if (viewSchema) {
|
|
287
|
+
const sourceIdType = schemas[viewSchema.source]?.idType || 'number';
|
|
286
288
|
schema = {
|
|
287
289
|
table: viewSchema.viewName,
|
|
288
|
-
idType:
|
|
290
|
+
idType: sourceIdType,
|
|
289
291
|
columns: viewSchema.columns || {},
|
|
290
292
|
foreignKeys: (viewSchema.foreignKeys || {}) as Record<string, ForeignKeyDef>,
|
|
291
293
|
relationships: { belongsTo: {}, hasMany: {} },
|
|
@@ -328,9 +330,10 @@ export default class PostgresDB {
|
|
|
328
330
|
const viewSchemas = this.deps.introspectViews();
|
|
329
331
|
const viewSchema = viewSchemas[modelName];
|
|
330
332
|
if (viewSchema) {
|
|
333
|
+
const sourceIdType = schemas[viewSchema.source]?.idType || 'number';
|
|
331
334
|
schema = {
|
|
332
335
|
table: viewSchema.viewName,
|
|
333
|
-
idType:
|
|
336
|
+
idType: sourceIdType,
|
|
334
337
|
columns: viewSchema.columns || {},
|
|
335
338
|
foreignKeys: (viewSchema.foreignKeys || {}) as Record<string, ForeignKeyDef>,
|
|
336
339
|
relationships: { belongsTo: {}, hasMany: {} },
|
|
@@ -423,7 +426,7 @@ export default class PostgresDB {
|
|
|
423
426
|
* @private
|
|
424
427
|
*/
|
|
425
428
|
private _evictIfNotMemory(modelName: string, record: OrmRecord): void {
|
|
426
|
-
const storeRef = this.deps.store as
|
|
429
|
+
const storeRef = this.deps.store as {
|
|
427
430
|
_memoryResolver?: (name: string) => boolean;
|
|
428
431
|
get?: (name: string) => Map<unknown, unknown> | undefined;
|
|
429
432
|
data?: { get(name: string): Map<unknown, unknown> | undefined };
|
package/src/relationships.ts
CHANGED
|
@@ -3,12 +3,17 @@ import type { HasManyMap, BelongsToMap, GlobalMap, PendingMap, PendingBelongsToM
|
|
|
3
3
|
|
|
4
4
|
// TODO: Refactor mapping to remove a level of iteration
|
|
5
5
|
export function getRelationships(type: string, sourceModel: string, targetModel: string, relationshipId?: string): Map<unknown, unknown> | undefined {
|
|
6
|
-
|
|
6
|
+
let allRelationships = relationships.get(type) as Map<string, Map<string, Map<unknown, unknown>>> | undefined;
|
|
7
|
+
|
|
8
|
+
if (!allRelationships) {
|
|
9
|
+
allRelationships = new Map();
|
|
10
|
+
relationships.set(type, allRelationships);
|
|
11
|
+
}
|
|
7
12
|
|
|
8
13
|
// create relationship map for this type of it doesn't already exist
|
|
9
|
-
if (!allRelationships
|
|
14
|
+
if (!allRelationships.has(sourceModel)) allRelationships.set(sourceModel, new Map());
|
|
10
15
|
|
|
11
|
-
const modelRelationship = allRelationships
|
|
16
|
+
const modelRelationship = allRelationships.get(sourceModel)!;
|
|
12
17
|
|
|
13
18
|
if (!modelRelationship.has(targetModel)) modelRelationship.set(targetModel, new Map());
|
|
14
19
|
|
package/src/serializer.ts
CHANGED
|
@@ -5,6 +5,14 @@ import type ModelProperty from './model-property.js';
|
|
|
5
5
|
|
|
6
6
|
const RESERVED_KEYS = ['__name'];
|
|
7
7
|
|
|
8
|
+
function isAggregateProperty(v: unknown): v is AggregateProperty {
|
|
9
|
+
return typeof v === 'object' && v !== null && (v as { __kind?: string }).__kind === 'AggregateProperty';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function isModelProperty(v: unknown): v is ModelProperty {
|
|
13
|
+
return typeof v === 'object' && v !== null && (v as { __kind?: string }).__kind === 'ModelProperty';
|
|
14
|
+
}
|
|
15
|
+
|
|
8
16
|
function searchQuery(query: Record<string, unknown>, array: unknown, key?: string): unknown {
|
|
9
17
|
const result = makeArray(array).find((item: unknown) => {
|
|
10
18
|
for (const [prop, value] of Object.entries(query)) {
|
|
@@ -93,14 +101,14 @@ export default class Serializer {
|
|
|
93
101
|
}
|
|
94
102
|
|
|
95
103
|
// Aggregate property handling — use the rawData value, not the aggregate descriptor
|
|
96
|
-
if ((handler
|
|
104
|
+
if (isAggregateProperty(handler)) {
|
|
97
105
|
parsedData[key] = data;
|
|
98
106
|
rec[key] = data;
|
|
99
107
|
continue;
|
|
100
108
|
}
|
|
101
109
|
|
|
102
110
|
// Direct assignment handling
|
|
103
|
-
if ((handler
|
|
111
|
+
if (!isModelProperty(handler)) {
|
|
104
112
|
parsedData[key] = handler;
|
|
105
113
|
rec[key] = handler;
|
|
106
114
|
continue;
|
package/src/store.ts
CHANGED
|
@@ -204,7 +204,7 @@ export default class Store {
|
|
|
204
204
|
this._cleanupRelationshipRegistries(modelName, recordId);
|
|
205
205
|
recordToUnload.clean();
|
|
206
206
|
|
|
207
|
-
this.data.get(modelName)
|
|
207
|
+
this.data.get(modelName)?.delete(recordId as string | number);
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
210
|
|
|
@@ -22,6 +22,14 @@ interface CompressionOptions {
|
|
|
22
22
|
orderBy?: string;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
interface TimescaleDeps {
|
|
26
|
+
buildCreateHypertable: typeof buildCreateHypertable;
|
|
27
|
+
buildTimeBucket: typeof buildTimeBucket;
|
|
28
|
+
buildContinuousAggregate: typeof buildContinuousAggregate;
|
|
29
|
+
buildEnableCompression: typeof buildEnableCompression;
|
|
30
|
+
buildCompressionPolicy: typeof buildCompressionPolicy;
|
|
31
|
+
}
|
|
32
|
+
|
|
25
33
|
export default class TimescaleDB extends PostgresDB {
|
|
26
34
|
static override extensions: string[] = ['timescaledb'];
|
|
27
35
|
static override configKey: string = 'timescale';
|
|
@@ -37,6 +45,10 @@ export default class TimescaleDB extends PostgresDB {
|
|
|
37
45
|
});
|
|
38
46
|
}
|
|
39
47
|
|
|
48
|
+
private get tsDeps(): TimescaleDeps {
|
|
49
|
+
return this.deps as unknown as TimescaleDeps;
|
|
50
|
+
}
|
|
51
|
+
|
|
40
52
|
/**
|
|
41
53
|
* Convert a table to a TimescaleDB hypertable.
|
|
42
54
|
* Should be called after the table is created (e.g. after initial migration).
|
|
@@ -46,7 +58,7 @@ export default class TimescaleDB extends PostgresDB {
|
|
|
46
58
|
const schema = schemas[modelName];
|
|
47
59
|
if (!schema) throw new Error(`Model '${modelName}' not found`);
|
|
48
60
|
|
|
49
|
-
const { sql } =
|
|
61
|
+
const { sql } = this.tsDeps.buildCreateHypertable(schema.table, timeColumn, options);
|
|
50
62
|
await this.requirePool().query(sql);
|
|
51
63
|
}
|
|
52
64
|
|
|
@@ -58,7 +70,7 @@ export default class TimescaleDB extends PostgresDB {
|
|
|
58
70
|
const schema = schemas[modelName];
|
|
59
71
|
if (!schema) return [];
|
|
60
72
|
|
|
61
|
-
const { sql, values } =
|
|
73
|
+
const { sql, values } = this.tsDeps.buildTimeBucket(schema.table, timeColumn, bucketSize, options);
|
|
62
74
|
|
|
63
75
|
try {
|
|
64
76
|
const result = await this.requirePool().query(sql, values);
|
|
@@ -77,7 +89,7 @@ export default class TimescaleDB extends PostgresDB {
|
|
|
77
89
|
const schema = schemas[modelName];
|
|
78
90
|
if (!schema) throw new Error(`Model '${modelName}' not found`);
|
|
79
91
|
|
|
80
|
-
const { sql } =
|
|
92
|
+
const { sql } = this.tsDeps.buildContinuousAggregate(viewName, schema.table, timeColumn, bucketSize, aggregates, options);
|
|
81
93
|
await this.requirePool().query(sql);
|
|
82
94
|
}
|
|
83
95
|
|
|
@@ -89,7 +101,7 @@ export default class TimescaleDB extends PostgresDB {
|
|
|
89
101
|
const schema = schemas[modelName];
|
|
90
102
|
if (!schema) throw new Error(`Model '${modelName}' not found`);
|
|
91
103
|
|
|
92
|
-
const { sql } =
|
|
104
|
+
const { sql } = this.tsDeps.buildEnableCompression(schema.table, options.segmentBy, options.orderBy);
|
|
93
105
|
await this.requirePool().query(sql);
|
|
94
106
|
}
|
|
95
107
|
|
|
@@ -101,7 +113,7 @@ export default class TimescaleDB extends PostgresDB {
|
|
|
101
113
|
const schema = schemas[modelName];
|
|
102
114
|
if (!schema) throw new Error(`Model '${modelName}' not found`);
|
|
103
115
|
|
|
104
|
-
const { sql } =
|
|
116
|
+
const { sql } = this.tsDeps.buildCompressionPolicy(schema.table, compressAfter);
|
|
105
117
|
await this.requirePool().query(sql);
|
|
106
118
|
}
|
|
107
119
|
}
|
package/src/view-resolver.ts
CHANGED
|
@@ -73,8 +73,9 @@ export default class ViewResolver {
|
|
|
73
73
|
|
|
74
74
|
// Compute aggregate fields from source record's relationships
|
|
75
75
|
for (const [key, aggProp] of Object.entries(aggregateFields)) {
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
if (!aggProp.relationship) continue;
|
|
77
|
+
const relatedRecords = sourceRecord.__relationships?.[aggProp.relationship]
|
|
78
|
+
|| sourceRecord[aggProp.relationship];
|
|
78
79
|
const relArray = Array.isArray(relatedRecords) ? relatedRecords : [];
|
|
79
80
|
rawData[key] = aggProp.compute(relArray);
|
|
80
81
|
}
|
|
@@ -151,10 +152,11 @@ export default class ViewResolver {
|
|
|
151
152
|
rawData[key] = aggProp.compute(groupRecords);
|
|
152
153
|
} else {
|
|
153
154
|
// Relationship aggregate — flatten related records across all group members
|
|
155
|
+
if (!aggProp.relationship) continue;
|
|
154
156
|
const allRelated: unknown[] = [];
|
|
155
157
|
for (const record of groupRecords) {
|
|
156
|
-
const relatedRecords = record.__relationships?.[aggProp.relationship
|
|
157
|
-
|| record[aggProp.relationship
|
|
158
|
+
const relatedRecords = record.__relationships?.[aggProp.relationship]
|
|
159
|
+
|| record[aggProp.relationship];
|
|
158
160
|
if (Array.isArray(relatedRecords)) {
|
|
159
161
|
allRelated.push(...relatedRecords);
|
|
160
162
|
}
|