@stonyx/orm 0.2.1-beta.83 → 0.2.1-beta.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/dist/aggregates.d.ts +21 -0
  2. package/dist/aggregates.js +90 -0
  3. package/dist/attr.d.ts +2 -0
  4. package/dist/attr.js +22 -0
  5. package/dist/belongs-to.d.ts +11 -0
  6. package/dist/belongs-to.js +58 -0
  7. package/dist/cli.d.ts +22 -0
  8. package/dist/cli.js +148 -0
  9. package/dist/commands.d.ts +7 -0
  10. package/dist/commands.js +146 -0
  11. package/dist/db.d.ts +21 -0
  12. package/dist/db.js +174 -0
  13. package/dist/exports/db.d.ts +7 -0
  14. package/{src → dist}/exports/db.js +2 -4
  15. package/dist/has-many.d.ts +11 -0
  16. package/dist/has-many.js +57 -0
  17. package/dist/hooks.d.ts +47 -0
  18. package/dist/hooks.js +106 -0
  19. package/dist/index.d.ts +14 -0
  20. package/dist/index.js +34 -0
  21. package/dist/main.d.ts +46 -0
  22. package/dist/main.js +178 -0
  23. package/dist/manage-record.d.ts +13 -0
  24. package/dist/manage-record.js +113 -0
  25. package/dist/meta-request.d.ts +6 -0
  26. package/dist/meta-request.js +52 -0
  27. package/dist/migrate.d.ts +2 -0
  28. package/dist/migrate.js +57 -0
  29. package/dist/model-property.d.ts +9 -0
  30. package/dist/model-property.js +29 -0
  31. package/dist/model.d.ts +15 -0
  32. package/dist/model.js +18 -0
  33. package/dist/mysql/connection.d.ts +14 -0
  34. package/dist/mysql/connection.js +24 -0
  35. package/dist/mysql/migration-generator.d.ts +45 -0
  36. package/dist/mysql/migration-generator.js +245 -0
  37. package/dist/mysql/migration-runner.d.ts +12 -0
  38. package/dist/mysql/migration-runner.js +83 -0
  39. package/dist/mysql/mysql-db.d.ts +100 -0
  40. package/dist/mysql/mysql-db.js +411 -0
  41. package/dist/mysql/query-builder.d.ts +10 -0
  42. package/dist/mysql/query-builder.js +44 -0
  43. package/dist/mysql/schema-introspector.d.ts +19 -0
  44. package/dist/mysql/schema-introspector.js +286 -0
  45. package/dist/mysql/type-map.d.ts +21 -0
  46. package/dist/mysql/type-map.js +36 -0
  47. package/dist/orm-request.d.ts +38 -0
  48. package/dist/orm-request.js +453 -0
  49. package/dist/plural-registry.d.ts +4 -0
  50. package/{src → dist}/plural-registry.js +3 -6
  51. package/dist/postgres/connection.d.ts +15 -0
  52. package/dist/postgres/connection.js +30 -0
  53. package/dist/postgres/migration-generator.d.ts +45 -0
  54. package/dist/postgres/migration-generator.js +257 -0
  55. package/dist/postgres/migration-runner.d.ts +10 -0
  56. package/dist/postgres/migration-runner.js +82 -0
  57. package/dist/postgres/postgres-db.d.ts +119 -0
  58. package/dist/postgres/postgres-db.js +473 -0
  59. package/dist/postgres/query-builder.d.ts +27 -0
  60. package/dist/postgres/query-builder.js +98 -0
  61. package/dist/postgres/schema-introspector.d.ts +29 -0
  62. package/dist/postgres/schema-introspector.js +309 -0
  63. package/dist/postgres/type-map.d.ts +23 -0
  64. package/dist/postgres/type-map.js +53 -0
  65. package/dist/record.d.ts +75 -0
  66. package/dist/record.js +115 -0
  67. package/dist/relationships.d.ts +10 -0
  68. package/dist/relationships.js +35 -0
  69. package/dist/serializer.d.ts +17 -0
  70. package/dist/serializer.js +130 -0
  71. package/dist/setup-rest-server.d.ts +1 -0
  72. package/dist/setup-rest-server.js +54 -0
  73. package/dist/standalone-db.d.ts +58 -0
  74. package/dist/standalone-db.js +142 -0
  75. package/dist/store.d.ts +62 -0
  76. package/dist/store.js +271 -0
  77. package/dist/timescale/query-builder.d.ts +41 -0
  78. package/dist/timescale/query-builder.js +87 -0
  79. package/dist/timescale/timescale-db.d.ts +44 -0
  80. package/dist/timescale/timescale-db.js +81 -0
  81. package/dist/transforms.d.ts +2 -0
  82. package/dist/transforms.js +17 -0
  83. package/dist/types/orm-types.d.ts +142 -0
  84. package/dist/types/orm-types.js +1 -0
  85. package/dist/utils.d.ts +5 -0
  86. package/dist/utils.js +13 -0
  87. package/dist/view-resolver.d.ts +8 -0
  88. package/dist/view-resolver.js +165 -0
  89. package/dist/view.d.ts +11 -0
  90. package/dist/view.js +18 -0
  91. package/package.json +34 -11
  92. package/src/{aggregates.js → aggregates.ts} +27 -13
  93. package/src/{attr.js → attr.ts} +2 -2
  94. package/src/{belongs-to.js → belongs-to.ts} +36 -17
  95. package/src/{cli.js → cli.ts} +17 -11
  96. package/src/{commands.js → commands.ts} +179 -170
  97. package/src/{db.js → db.ts} +35 -26
  98. package/src/exports/db.ts +7 -0
  99. package/src/has-many.ts +91 -0
  100. package/src/{hooks.js → hooks.ts} +23 -27
  101. package/src/{index.js → index.ts} +4 -4
  102. package/src/{main.js → main.ts} +59 -34
  103. package/src/{manage-record.js → manage-record.ts} +41 -22
  104. package/src/{meta-request.js → meta-request.ts} +17 -14
  105. package/src/{migrate.js → migrate.ts} +9 -9
  106. package/src/{model-property.js → model-property.ts} +12 -6
  107. package/src/{model.js → model.ts} +5 -4
  108. package/src/mysql/{connection.js → connection.ts} +43 -28
  109. package/src/mysql/{migration-generator.js → migration-generator.ts} +332 -286
  110. package/src/mysql/{migration-runner.js → migration-runner.ts} +116 -110
  111. package/src/mysql/{mysql-db.js → mysql-db.ts} +533 -473
  112. package/src/mysql/{query-builder.js → query-builder.ts} +69 -64
  113. package/src/mysql/{schema-introspector.js → schema-introspector.ts} +355 -325
  114. package/src/mysql/{type-map.js → type-map.ts} +42 -37
  115. package/src/{orm-request.js → orm-request.ts} +165 -95
  116. package/src/plural-registry.ts +12 -0
  117. package/src/postgres/{connection.js → connection.ts} +14 -5
  118. package/src/postgres/{migration-generator.js → migration-generator.ts} +82 -38
  119. package/src/postgres/{migration-runner.js → migration-runner.ts} +11 -10
  120. package/src/postgres/{postgres-db.js → postgres-db.ts} +195 -114
  121. package/src/postgres/{query-builder.js → query-builder.ts} +27 -28
  122. package/src/postgres/{schema-introspector.js → schema-introspector.ts} +87 -58
  123. package/src/postgres/{type-map.js → type-map.ts} +10 -6
  124. package/src/{record.js → record.ts} +73 -34
  125. package/src/relationships.ts +48 -0
  126. package/src/{serializer.js → serializer.ts} +44 -36
  127. package/src/{setup-rest-server.js → setup-rest-server.ts} +18 -13
  128. package/src/{standalone-db.js → standalone-db.ts} +33 -24
  129. package/src/{store.js → store.ts} +90 -68
  130. package/src/timescale/{query-builder.js → query-builder.ts} +33 -38
  131. package/src/timescale/timescale-db.ts +107 -0
  132. package/src/transforms.ts +20 -0
  133. package/src/types/mysql2.d.ts +30 -0
  134. package/src/types/orm-types.ts +146 -0
  135. package/src/types/pg.d.ts +28 -0
  136. package/src/types/stonyx-cron.d.ts +5 -0
  137. package/src/types/stonyx-events.d.ts +4 -0
  138. package/src/types/stonyx-rest-server.d.ts +11 -0
  139. package/src/types/stonyx-utils.d.ts +33 -0
  140. package/src/types/stonyx.d.ts +21 -0
  141. package/src/utils.ts +16 -0
  142. package/src/{view-resolver.js → view-resolver.ts} +53 -28
  143. package/src/view.ts +22 -0
  144. package/src/has-many.js +0 -68
  145. package/src/relationships.js +0 -43
  146. package/src/timescale/timescale-db.js +0 -111
  147. package/src/transforms.js +0 -20
  148. package/src/utils.js +0 -12
  149. package/src/view.js +0 -21
package/dist/store.js ADDED
@@ -0,0 +1,271 @@
1
+ import Orm, { relationships } from '@stonyx/orm';
2
+ import { TYPES, getHasManyRegistry, getBelongsToRegistry, getPendingRegistry } from './relationships.js';
3
+ import ViewResolver from './view-resolver.js';
4
+ export default class Store {
5
+ static instance;
6
+ data = new Map();
7
+ /**
8
+ * Set by Orm during init — resolves memory flag for a model name.
9
+ */
10
+ _memoryResolver = null;
11
+ /**
12
+ * Set by Orm during init — reference to the SQL adapter instance for on-demand queries.
13
+ */
14
+ _sqlDb = null;
15
+ constructor() {
16
+ if (Store.instance)
17
+ return Store.instance;
18
+ Store.instance = this;
19
+ this.data = new Map();
20
+ }
21
+ get(key, id) {
22
+ if (!id)
23
+ return this.data.get(key);
24
+ return this.data.get(key)?.get(id);
25
+ }
26
+ /**
27
+ * Async authoritative read. Always queries the SQL database for memory: false models.
28
+ * For memory: true models, returns from store (already loaded on boot).
29
+ */
30
+ async find(modelName, id) {
31
+ // For views in non-SQL mode, use view resolver
32
+ if (Orm.instance?.isView?.(modelName) && !this._sqlDb) {
33
+ const resolver = new ViewResolver(modelName);
34
+ return resolver.resolveOne(id);
35
+ }
36
+ // For memory: true models, the store is authoritative
37
+ if (this._isMemoryModel(modelName)) {
38
+ return this.get(modelName, id);
39
+ }
40
+ // For memory: false models, always query the SQL database
41
+ if (this._sqlDb) {
42
+ return this._sqlDb.findRecord(modelName, id);
43
+ }
44
+ // Fallback to store (JSON mode or no SQL adapter)
45
+ return this.get(modelName, id);
46
+ }
47
+ /**
48
+ * Async read for all records of a model. Always queries MySQL for memory: false models.
49
+ * For memory: true models, returns from store.
50
+ */
51
+ async findAll(modelName, conditions) {
52
+ // For views in non-SQL mode, use view resolver
53
+ if (Orm.instance?.isView?.(modelName) && !this._sqlDb) {
54
+ const resolver = new ViewResolver(modelName);
55
+ const records = await resolver.resolveAll();
56
+ if (!conditions || Object.keys(conditions).length === 0)
57
+ return records;
58
+ return records.filter((record) => Object.entries(conditions).every(([key, value]) => record.__data[key] === value));
59
+ }
60
+ // For memory: true models without conditions, return from store
61
+ if (this._isMemoryModel(modelName) && !conditions) {
62
+ const modelStore = this.get(modelName);
63
+ return modelStore ? Array.from(modelStore.values()) : [];
64
+ }
65
+ // For memory: false models (or filtered queries), always query the SQL database
66
+ if (this._sqlDb) {
67
+ return this._sqlDb.findAll(modelName, conditions);
68
+ }
69
+ // Fallback to store (JSON mode) — apply conditions in-memory if provided
70
+ const modelStore = this.get(modelName);
71
+ if (!modelStore)
72
+ return [];
73
+ const records = Array.from(modelStore.values());
74
+ if (!conditions || Object.keys(conditions).length === 0)
75
+ return records;
76
+ return records.filter((record) => Object.entries(conditions).every(([key, value]) => record.__data[key] === value));
77
+ }
78
+ /**
79
+ * Async query — always hits MySQL, never reads from memory cache.
80
+ * Use for complex queries, aggregations, or when you need guaranteed freshness.
81
+ */
82
+ async query(modelName, conditions = {}) {
83
+ if (this._sqlDb) {
84
+ return this._sqlDb.findAll(modelName, conditions);
85
+ }
86
+ // Fallback: filter in-memory store
87
+ const modelStore = this.get(modelName);
88
+ if (!modelStore)
89
+ return [];
90
+ const records = Array.from(modelStore.values());
91
+ if (Object.keys(conditions).length === 0)
92
+ return records;
93
+ return records.filter((record) => Object.entries(conditions).every(([key, value]) => record.__data[key] === value));
94
+ }
95
+ /**
96
+ * Check if a model is configured for in-memory storage.
97
+ * @private
98
+ */
99
+ _isMemoryModel(modelName) {
100
+ if (this._memoryResolver)
101
+ return this._memoryResolver(modelName);
102
+ return false; // default to non-memory if resolver not set yet
103
+ }
104
+ set(key, value) {
105
+ this.data.set(key, value);
106
+ }
107
+ remove(key, id) {
108
+ // Guard: read-only views cannot have records removed
109
+ if (Orm.instance?.isView?.(key)) {
110
+ throw new Error(`Cannot remove records from read-only view '${key}'`);
111
+ }
112
+ if (id)
113
+ return this.unloadRecord(key, id);
114
+ this.unloadAllRecords(key);
115
+ }
116
+ unloadRecord(model, id, options = {}) {
117
+ const modelStore = this.data.get(model);
118
+ if (!modelStore) {
119
+ console.warn(`[Store] Cannot unload record: model "${model}" not found in store`);
120
+ return;
121
+ }
122
+ const record = modelStore.get(id);
123
+ if (!record) {
124
+ console.warn(`[Store] Cannot unload record: ${model}:${id} not found in store`);
125
+ return;
126
+ }
127
+ const { toUnload, visited } = options.includeChildren
128
+ ? this._buildUnloadQueue(record, options)
129
+ : { toUnload: [{ record, modelName: model, recordId: id }], visited: new Set([`${model}:${id}`]) };
130
+ for (const item of toUnload.reverse()) {
131
+ const { record: recordToUnload, modelName, recordId } = item;
132
+ this._removeFromHasManyArrays(modelName, recordId, visited);
133
+ this._nullifyBelongsToReferences(modelName, recordId, visited);
134
+ this._cleanupRelationshipRegistries(modelName, recordId);
135
+ recordToUnload.clean();
136
+ this.data.get(modelName).delete(recordId);
137
+ }
138
+ }
139
+ unloadAllRecords(model, options = {}) {
140
+ const modelStore = this.data.get(model);
141
+ if (!modelStore) {
142
+ console.warn(`[Store] Cannot unload all records: model "${model}" not found in store`);
143
+ return;
144
+ }
145
+ const recordIds = Array.from(modelStore.keys());
146
+ for (const id of recordIds) {
147
+ if (modelStore.has(id)) {
148
+ this.unloadRecord(model, id, options);
149
+ }
150
+ }
151
+ for (const relationshipType of TYPES)
152
+ relationships.get(relationshipType).delete(model);
153
+ }
154
+ _removeFromHasManyArrays(modelName, recordId, visited) {
155
+ const hasManyRegistry = getHasManyRegistry();
156
+ for (const [sourceModel, targetModels] of hasManyRegistry) {
157
+ const targetModelMap = targetModels.get(modelName);
158
+ if (!targetModelMap)
159
+ continue;
160
+ for (const [sourceRecordId, hasManyArray] of targetModelMap) {
161
+ const sourceKey = `${sourceModel}:${sourceRecordId}`;
162
+ // Don't modify arrays of records being deleted
163
+ if (visited.has(sourceKey))
164
+ continue;
165
+ const index = hasManyArray.findIndex(r => r && r.id === recordId);
166
+ if (index !== -1)
167
+ hasManyArray.splice(index, 1);
168
+ }
169
+ }
170
+ }
171
+ _nullifyBelongsToReferences(modelName, recordId, visited) {
172
+ const belongsToRegistry = getBelongsToRegistry();
173
+ for (const [sourceModel, targetModels] of belongsToRegistry) {
174
+ const targetModelMap = targetModels.get(modelName);
175
+ if (!targetModelMap)
176
+ continue;
177
+ for (const [sourceRecordId, belongsToRecord] of targetModelMap) {
178
+ if (belongsToRecord && belongsToRecord.id === recordId) {
179
+ const sourceKey = `${sourceModel}:${sourceRecordId}`;
180
+ if (visited.has(sourceKey))
181
+ continue;
182
+ targetModelMap.set(sourceRecordId, null);
183
+ const sourceRecord = this.get(sourceModel, sourceRecordId);
184
+ if (sourceRecord && sourceRecord.__relationships) {
185
+ for (const [key, value] of Object.entries(sourceRecord.__relationships)) {
186
+ if (value && value.id === recordId) {
187
+ sourceRecord.__relationships[key] = null;
188
+ }
189
+ }
190
+ }
191
+ }
192
+ }
193
+ }
194
+ }
195
+ _cleanupRelationshipRegistries(modelName, recordId) {
196
+ const hasManyMap = getHasManyRegistry().get(modelName);
197
+ if (hasManyMap) {
198
+ for (const [, recordMap] of hasManyMap)
199
+ recordMap.delete(recordId);
200
+ }
201
+ const belongsToMap = getBelongsToRegistry().get(modelName);
202
+ if (belongsToMap) {
203
+ for (const [, recordMap] of belongsToMap)
204
+ recordMap.delete(recordId);
205
+ }
206
+ const pendingMap = getPendingRegistry().get(modelName);
207
+ if (pendingMap)
208
+ pendingMap.delete(recordId);
209
+ }
210
+ /**
211
+ * Extracts hasMany and non-bidirectional belongsTo children from a record
212
+ * @private
213
+ */
214
+ _getChildren(record) {
215
+ const children = [];
216
+ if (!record.__relationships)
217
+ return children;
218
+ for (const [key, value] of Object.entries(record.__relationships)) {
219
+ // hasMany children - always include
220
+ if (Array.isArray(value)) {
221
+ for (const childRecord of value) {
222
+ if (childRecord)
223
+ children.push({ childRecord: childRecord, relationshipKey: key, type: 'hasMany' });
224
+ }
225
+ }
226
+ else if (value && !this._isBidirectionalRelationship(record.__model.__name, value.__model.__name)) {
227
+ children.push({ childRecord: value, relationshipKey: key, type: 'belongsTo' });
228
+ }
229
+ }
230
+ return children;
231
+ }
232
+ _isBidirectionalRelationship(sourceModel, targetModel) {
233
+ const inverseMap = getHasManyRegistry().get(targetModel)?.get(sourceModel);
234
+ return !!inverseMap && inverseMap.size > 0;
235
+ }
236
+ _buildUnloadQueue(record, options) {
237
+ const visited = new Set();
238
+ const toUnload = [];
239
+ const queue = [{
240
+ record,
241
+ modelName: record.__model.__name,
242
+ recordId: record.id,
243
+ isRoot: true,
244
+ depth: 0
245
+ }];
246
+ while (queue.length > 0) {
247
+ const item = queue.shift();
248
+ const key = `${item.modelName}:${item.recordId}`;
249
+ if (visited.has(key))
250
+ continue;
251
+ visited.add(key);
252
+ toUnload.push(item);
253
+ // Add children to queue if includeChildren is enabled
254
+ if (options.includeChildren) {
255
+ const children = this._getChildren(item.record);
256
+ for (const { childRecord } of children) {
257
+ if (childRecord) {
258
+ queue.push({
259
+ record: childRecord,
260
+ modelName: childRecord.__model.__name,
261
+ recordId: childRecord.id,
262
+ isRoot: false,
263
+ depth: (item.depth ?? 0) + 1
264
+ });
265
+ }
266
+ }
267
+ }
268
+ }
269
+ return { toUnload, visited };
270
+ }
271
+ }
@@ -0,0 +1,41 @@
1
+ export { validateIdentifier, buildInsert, buildUpdate, buildDelete, buildSelect } from '../postgres/query-builder.js';
2
+ interface QueryResult {
3
+ sql: string;
4
+ values: unknown[];
5
+ }
6
+ interface SqlResult {
7
+ sql: string;
8
+ }
9
+ interface HypertableOptions {
10
+ chunkInterval?: string;
11
+ }
12
+ interface TimeBucketOptions {
13
+ aggregates?: string[];
14
+ where?: Record<string, unknown>;
15
+ orderBy?: string;
16
+ limit?: number;
17
+ }
18
+ interface ContinuousAggregateOptions {
19
+ withNoData?: boolean;
20
+ }
21
+ /**
22
+ * Build a CREATE TABLE + hypertable conversion statement.
23
+ * TimescaleDB hypertables are regular tables converted via create_hypertable().
24
+ */
25
+ export declare function buildCreateHypertable(table: string, timeColumn: string, options?: HypertableOptions): QueryResult;
26
+ /**
27
+ * Build a time_bucket aggregation query.
28
+ */
29
+ export declare function buildTimeBucket(table: string, timeColumn: string, bucketSize: string, options?: TimeBucketOptions): QueryResult;
30
+ /**
31
+ * Build a continuous aggregate creation statement.
32
+ */
33
+ export declare function buildContinuousAggregate(viewName: string, table: string, timeColumn: string, bucketSize: string, aggregates: string[], options?: ContinuousAggregateOptions): SqlResult;
34
+ /**
35
+ * Build an ADD compression policy statement.
36
+ */
37
+ export declare function buildCompressionPolicy(table: string, compressAfter: string): SqlResult;
38
+ /**
39
+ * Build an ALTER TABLE to enable compression on a hypertable.
40
+ */
41
+ export declare function buildEnableCompression(table: string, segmentBy?: string, orderBy?: string): SqlResult;
@@ -0,0 +1,87 @@
1
+ // Re-export all base PostgreSQL query builders
2
+ export { validateIdentifier, buildInsert, buildUpdate, buildDelete, buildSelect } from '../postgres/query-builder.js';
3
+ import { validateIdentifier } from '../postgres/query-builder.js';
4
+ /**
5
+ * Build a CREATE TABLE + hypertable conversion statement.
6
+ * TimescaleDB hypertables are regular tables converted via create_hypertable().
7
+ */
8
+ export function buildCreateHypertable(table, timeColumn, options = {}) {
9
+ validateIdentifier(table, 'table name');
10
+ validateIdentifier(timeColumn, 'column name');
11
+ const { chunkInterval = '7 days' } = options;
12
+ const sql = `SELECT create_hypertable('"${table}"', '${timeColumn}', chunk_time_interval => INTERVAL '${chunkInterval}', if_not_exists => TRUE)`;
13
+ return { sql, values: [] };
14
+ }
15
+ /**
16
+ * Build a time_bucket aggregation query.
17
+ */
18
+ export function buildTimeBucket(table, timeColumn, bucketSize, options = {}) {
19
+ validateIdentifier(table, 'table name');
20
+ validateIdentifier(timeColumn, 'column name');
21
+ const { aggregates = [], where, orderBy = 'bucket', limit } = options;
22
+ const values = [];
23
+ let paramIndex = 1;
24
+ const selectCols = [`time_bucket($${paramIndex++}, "${timeColumn}") AS bucket`];
25
+ values.push(bucketSize);
26
+ for (const agg of aggregates) {
27
+ selectCols.push(agg);
28
+ }
29
+ const whereClauses = [];
30
+ if (where) {
31
+ for (const [k, v] of Object.entries(where)) {
32
+ validateIdentifier(k, 'column name');
33
+ whereClauses.push(`"${k}" = $${paramIndex++}`);
34
+ values.push(v);
35
+ }
36
+ }
37
+ const whereStr = whereClauses.length > 0 ? ` WHERE ${whereClauses.join(' AND ')}` : '';
38
+ const orderStr = orderBy ? ` ORDER BY ${orderBy}` : '';
39
+ let limitStr = '';
40
+ if (limit != null) {
41
+ limitStr = ` LIMIT $${paramIndex++}`;
42
+ values.push(limit);
43
+ }
44
+ const sql = `SELECT ${selectCols.join(', ')} FROM "${table}"${whereStr} GROUP BY bucket${orderStr}${limitStr}`;
45
+ return { sql, values };
46
+ }
47
+ /**
48
+ * Build a continuous aggregate creation statement.
49
+ */
50
+ export function buildContinuousAggregate(viewName, table, timeColumn, bucketSize, aggregates, options = {}) {
51
+ validateIdentifier(viewName, 'view name');
52
+ validateIdentifier(table, 'table name');
53
+ validateIdentifier(timeColumn, 'column name');
54
+ const { withNoData = false } = options;
55
+ const selectCols = [
56
+ `time_bucket('${bucketSize}', "${timeColumn}") AS bucket`,
57
+ ...aggregates,
58
+ ];
59
+ const withClause = withNoData ? ' WITH NO DATA' : '';
60
+ const sql = `CREATE MATERIALIZED VIEW "${viewName}" WITH (timescaledb.continuous) AS SELECT ${selectCols.join(', ')} FROM "${table}" GROUP BY bucket${withClause}`;
61
+ return { sql };
62
+ }
63
+ /**
64
+ * Build an ADD compression policy statement.
65
+ */
66
+ export function buildCompressionPolicy(table, compressAfter) {
67
+ validateIdentifier(table, 'table name');
68
+ const sql = `SELECT add_compression_policy('"${table}"', INTERVAL '${compressAfter}', if_not_exists => TRUE)`;
69
+ return { sql };
70
+ }
71
+ /**
72
+ * Build an ALTER TABLE to enable compression on a hypertable.
73
+ */
74
+ export function buildEnableCompression(table, segmentBy, orderBy) {
75
+ validateIdentifier(table, 'table name');
76
+ let opts = `timescaledb.compress`;
77
+ if (segmentBy) {
78
+ validateIdentifier(segmentBy, 'column name');
79
+ opts += `, timescaledb.compress_segmentby = '"${segmentBy}"'`;
80
+ }
81
+ if (orderBy) {
82
+ validateIdentifier(orderBy, 'column name');
83
+ opts += `, timescaledb.compress_orderby = '"${orderBy}"'`;
84
+ }
85
+ const sql = `ALTER TABLE "${table}" SET (${opts})`;
86
+ return { sql };
87
+ }
@@ -0,0 +1,44 @@
1
+ import PostgresDB from '../postgres/postgres-db.js';
2
+ interface HypertableOptions {
3
+ chunkInterval?: string;
4
+ }
5
+ interface TimeBucketOptions {
6
+ aggregates?: string[];
7
+ where?: Record<string, unknown>;
8
+ orderBy?: string;
9
+ limit?: number;
10
+ }
11
+ interface ContinuousAggregateOptions {
12
+ withNoData?: boolean;
13
+ }
14
+ interface CompressionOptions {
15
+ segmentBy?: string;
16
+ orderBy?: string;
17
+ }
18
+ export default class TimescaleDB extends PostgresDB {
19
+ static extensions: string[];
20
+ static configKey: string;
21
+ constructor(deps?: Record<string, unknown>);
22
+ /**
23
+ * Convert a table to a TimescaleDB hypertable.
24
+ * Should be called after the table is created (e.g. after initial migration).
25
+ */
26
+ createHypertable(modelName: string, timeColumn: string, options?: HypertableOptions): Promise<void>;
27
+ /**
28
+ * Query time-bucketed aggregations on a hypertable.
29
+ */
30
+ timeBucket(modelName: string, timeColumn: string, bucketSize: string, options?: TimeBucketOptions): Promise<Record<string, unknown>[]>;
31
+ /**
32
+ * Create a continuous aggregate view on a hypertable.
33
+ */
34
+ createContinuousAggregate(viewName: string, modelName: string, timeColumn: string, bucketSize: string, aggregates: string[], options?: ContinuousAggregateOptions): Promise<void>;
35
+ /**
36
+ * Enable compression on a hypertable.
37
+ */
38
+ enableCompression(modelName: string, options?: CompressionOptions): Promise<void>;
39
+ /**
40
+ * Add a compression policy to a hypertable.
41
+ */
42
+ addCompressionPolicy(modelName: string, compressAfter: string): Promise<void>;
43
+ }
44
+ export {};
@@ -0,0 +1,81 @@
1
+ import PostgresDB from '../postgres/postgres-db.js';
2
+ import { isDbError } from '../utils.js';
3
+ import { buildCreateHypertable, buildTimeBucket, buildContinuousAggregate, buildCompressionPolicy, buildEnableCompression } from './query-builder.js';
4
+ export default class TimescaleDB extends PostgresDB {
5
+ static extensions = ['timescaledb'];
6
+ static configKey = 'timescale';
7
+ constructor(deps = {}) {
8
+ super({
9
+ ...deps,
10
+ buildCreateHypertable,
11
+ buildTimeBucket,
12
+ buildContinuousAggregate,
13
+ buildCompressionPolicy,
14
+ buildEnableCompression,
15
+ });
16
+ }
17
+ /**
18
+ * Convert a table to a TimescaleDB hypertable.
19
+ * Should be called after the table is created (e.g. after initial migration).
20
+ */
21
+ async createHypertable(modelName, timeColumn, options = {}) {
22
+ const schemas = this.deps.introspectModels();
23
+ const schema = schemas[modelName];
24
+ if (!schema)
25
+ throw new Error(`Model '${modelName}' not found`);
26
+ const { sql } = this.deps.buildCreateHypertable(schema.table, timeColumn, options);
27
+ await this.requirePool().query(sql);
28
+ }
29
+ /**
30
+ * Query time-bucketed aggregations on a hypertable.
31
+ */
32
+ async timeBucket(modelName, timeColumn, bucketSize, options = {}) {
33
+ const schemas = this.deps.introspectModels();
34
+ const schema = schemas[modelName];
35
+ if (!schema)
36
+ return [];
37
+ const { sql, values } = this.deps.buildTimeBucket(schema.table, timeColumn, bucketSize, options);
38
+ try {
39
+ const result = await this.requirePool().query(sql, values);
40
+ return result.rows;
41
+ }
42
+ catch (error) {
43
+ if (isDbError(error) && error.code === '42P01')
44
+ return [];
45
+ throw error;
46
+ }
47
+ }
48
+ /**
49
+ * Create a continuous aggregate view on a hypertable.
50
+ */
51
+ async createContinuousAggregate(viewName, modelName, timeColumn, bucketSize, aggregates, options = {}) {
52
+ const schemas = this.deps.introspectModels();
53
+ const schema = schemas[modelName];
54
+ if (!schema)
55
+ throw new Error(`Model '${modelName}' not found`);
56
+ const { sql } = this.deps.buildContinuousAggregate(viewName, schema.table, timeColumn, bucketSize, aggregates, options);
57
+ await this.requirePool().query(sql);
58
+ }
59
+ /**
60
+ * Enable compression on a hypertable.
61
+ */
62
+ async enableCompression(modelName, options = {}) {
63
+ const schemas = this.deps.introspectModels();
64
+ const schema = schemas[modelName];
65
+ if (!schema)
66
+ throw new Error(`Model '${modelName}' not found`);
67
+ const { sql } = this.deps.buildEnableCompression(schema.table, options.segmentBy, options.orderBy);
68
+ await this.requirePool().query(sql);
69
+ }
70
+ /**
71
+ * Add a compression policy to a hypertable.
72
+ */
73
+ async addCompressionPolicy(modelName, compressAfter) {
74
+ const schemas = this.deps.introspectModels();
75
+ const schema = schemas[modelName];
76
+ if (!schema)
77
+ throw new Error(`Model '${modelName}' not found`);
78
+ const { sql } = this.deps.buildCompressionPolicy(schema.table, compressAfter);
79
+ await this.requirePool().query(sql);
80
+ }
81
+ }
@@ -0,0 +1,2 @@
1
+ declare const transforms: Record<string, (value: unknown) => unknown>;
2
+ export default transforms;
@@ -0,0 +1,17 @@
1
+ import { getTimestamp } from "@stonyx/utils/date";
2
+ const transforms = {
3
+ boolean: (value) => typeof value === 'string' ? value.trim().toLowerCase() === 'true' : !!value,
4
+ date: (value) => value ? new Date(value) : null,
5
+ float: (value) => parseFloat(value),
6
+ number: (value) => parseInt(value),
7
+ passthrough: (value) => value,
8
+ string: (value) => String(value),
9
+ timestamp: (value) => getTimestamp(value),
10
+ trim: (value) => value?.trim(),
11
+ uppercase: (value) => value?.toUpperCase(),
12
+ };
13
+ // Math Proxies
14
+ ['ceil', 'floor', 'round'].forEach(method => {
15
+ transforms[method] = (value) => Math[method](value);
16
+ });
17
+ export default transforms;
@@ -0,0 +1,142 @@
1
+ import type { AggregateProperty } from '../aggregates.js';
2
+ export interface OrmDbConfig {
3
+ file: string;
4
+ schema: string;
5
+ mode: string;
6
+ directory: string;
7
+ autosave: string;
8
+ saveInterval: unknown;
9
+ }
10
+ export interface OrmMysqlConfig {
11
+ host: string;
12
+ port?: number;
13
+ user: string;
14
+ password: string;
15
+ database: string;
16
+ connectionLimit?: number;
17
+ migrationsDir?: string;
18
+ migrationsTable?: string;
19
+ [key: string]: unknown;
20
+ }
21
+ export interface OrmPostgresConfig {
22
+ host: string;
23
+ port: number;
24
+ user: string;
25
+ password: string;
26
+ database: string;
27
+ connectionLimit?: number;
28
+ migrationsDir?: string;
29
+ migrationsTable?: string;
30
+ [key: string]: unknown;
31
+ }
32
+ export interface OrmPaths {
33
+ model: string;
34
+ serializer: string;
35
+ transform: string;
36
+ view?: string;
37
+ access?: string;
38
+ [key: string]: string | undefined;
39
+ }
40
+ export interface OrmRestServerConfig {
41
+ enabled: string;
42
+ route: string;
43
+ metaRoute: boolean;
44
+ }
45
+ export interface OrmSection {
46
+ db: OrmDbConfig;
47
+ paths: OrmPaths;
48
+ restServer: OrmRestServerConfig;
49
+ mysql?: OrmMysqlConfig;
50
+ postgres?: OrmPostgresConfig;
51
+ timescale?: OrmPostgresConfig;
52
+ [key: string]: unknown;
53
+ }
54
+ export interface OrmConfig {
55
+ rootPath: string;
56
+ orm: OrmSection;
57
+ [key: string]: unknown;
58
+ }
59
+ export interface SourceRecord {
60
+ __model: {
61
+ __name: string;
62
+ [key: string]: unknown;
63
+ };
64
+ __data?: Record<string, unknown>;
65
+ __relationships?: Record<string, unknown>;
66
+ id: unknown;
67
+ [key: string]: unknown;
68
+ }
69
+ export interface OrmRecord {
70
+ id: string | number | unknown;
71
+ __model?: {
72
+ __name: string;
73
+ };
74
+ __data: Record<string, unknown> & {
75
+ id?: unknown;
76
+ __pendingSqlId?: boolean;
77
+ };
78
+ __relationships: Record<string, unknown>;
79
+ toJSON?(options?: {
80
+ fields?: Set<string>;
81
+ baseUrl?: string;
82
+ }): unknown;
83
+ [key: string]: unknown;
84
+ }
85
+ export interface ForeignKeyDef {
86
+ references: string;
87
+ column: string;
88
+ }
89
+ export interface ModelSchema {
90
+ table: string;
91
+ idType: string;
92
+ columns: Record<string, string>;
93
+ foreignKeys: Record<string, ForeignKeyDef>;
94
+ relationships: {
95
+ belongsTo: Record<string, string | null>;
96
+ hasMany: Record<string, string | null>;
97
+ };
98
+ vectorColumns?: Record<string, number>;
99
+ memory: boolean;
100
+ }
101
+ export interface ViewSchema {
102
+ viewName: string;
103
+ source: string;
104
+ groupBy?: string;
105
+ columns: Record<string, string>;
106
+ foreignKeys: Record<string, ForeignKeyDef>;
107
+ aggregates: Record<string, AggregateProperty>;
108
+ relationships: {
109
+ belongsTo: Record<string, string | null>;
110
+ hasMany: Record<string, string | null>;
111
+ };
112
+ isView: boolean;
113
+ memory: boolean;
114
+ }
115
+ /**
116
+ * Typed relationship registry maps.
117
+ * Each key in Orm.relationships stores a different nested Map structure.
118
+ */
119
+ /** Relationship registry map types — source → target → recordId → value */
120
+ export type HasManyMap = Map<string, Map<string, Map<unknown, unknown[]>>>;
121
+ export type BelongsToMap = Map<string, Map<string, Map<unknown, unknown>>>;
122
+ export type GlobalMap = Map<string, unknown[][]>;
123
+ export type PendingMap = Map<string, Map<unknown, unknown[][]>>;
124
+ export type PendingBelongsToMap = Map<string, Map<unknown, unknown[]>>;
125
+ export interface RelationshipMaps {
126
+ hasMany: HasManyMap;
127
+ belongsTo: BelongsToMap;
128
+ global: GlobalMap;
129
+ pending: PendingMap;
130
+ pendingBelongsTo: PendingBelongsToMap;
131
+ }
132
+ export interface SnapshotEntry {
133
+ table?: string;
134
+ idType?: string;
135
+ columns?: Record<string, string>;
136
+ foreignKeys?: Record<string, ForeignKeyDef>;
137
+ vectorColumns?: Record<string, number>;
138
+ isView?: boolean;
139
+ viewName?: string;
140
+ source?: string;
141
+ viewQuery?: string;
142
+ }
@@ -0,0 +1 @@
1
+ export {};