@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
@@ -0,0 +1,146 @@
1
+ import type { AggregateProperty } from '../aggregates.js';
2
+
3
+ export interface OrmDbConfig {
4
+ file: string;
5
+ schema: string;
6
+ mode: string;
7
+ directory: string;
8
+ autosave: string;
9
+ saveInterval: unknown;
10
+ }
11
+
12
+ export interface OrmMysqlConfig {
13
+ host: string;
14
+ port?: number;
15
+ user: string;
16
+ password: string;
17
+ database: string;
18
+ connectionLimit?: number;
19
+ migrationsDir?: string;
20
+ migrationsTable?: string;
21
+ [key: string]: unknown;
22
+ }
23
+
24
+ export interface OrmPostgresConfig {
25
+ host: string;
26
+ port: number;
27
+ user: string;
28
+ password: string;
29
+ database: string;
30
+ connectionLimit?: number;
31
+ migrationsDir?: string;
32
+ migrationsTable?: string;
33
+ [key: string]: unknown;
34
+ }
35
+
36
+ export interface OrmPaths {
37
+ model: string;
38
+ serializer: string;
39
+ transform: string;
40
+ view?: string;
41
+ access?: string;
42
+ [key: string]: string | undefined;
43
+ }
44
+
45
+ export interface OrmRestServerConfig {
46
+ enabled: string;
47
+ route: string;
48
+ metaRoute: boolean;
49
+ }
50
+
51
+ export interface OrmSection {
52
+ db: OrmDbConfig;
53
+ paths: OrmPaths;
54
+ restServer: OrmRestServerConfig;
55
+ mysql?: OrmMysqlConfig;
56
+ postgres?: OrmPostgresConfig;
57
+ timescale?: OrmPostgresConfig;
58
+ [key: string]: unknown;
59
+ }
60
+
61
+ export interface OrmConfig {
62
+ rootPath: string;
63
+ orm: OrmSection;
64
+ [key: string]: unknown;
65
+ }
66
+
67
+ export interface SourceRecord {
68
+ __model: { __name: string; [key: string]: unknown };
69
+ __data?: Record<string, unknown>;
70
+ __relationships?: Record<string, unknown>;
71
+ id: unknown;
72
+ [key: string]: unknown;
73
+ }
74
+
75
+ export interface OrmRecord {
76
+ id: string | number | unknown;
77
+ __model?: { __name: string };
78
+ __data: Record<string, unknown> & { id?: unknown; __pendingSqlId?: boolean };
79
+ __relationships: Record<string, unknown>;
80
+ toJSON?(options?: { fields?: Set<string>; baseUrl?: string }): unknown;
81
+ [key: string]: unknown;
82
+ }
83
+
84
+ export interface ForeignKeyDef {
85
+ references: string;
86
+ column: string;
87
+ }
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
+
102
+ export interface ViewSchema {
103
+ viewName: string;
104
+ source: string;
105
+ groupBy?: string;
106
+ columns: Record<string, string>;
107
+ foreignKeys: Record<string, ForeignKeyDef>;
108
+ aggregates: Record<string, AggregateProperty>;
109
+ relationships: {
110
+ belongsTo: Record<string, string | null>;
111
+ hasMany: Record<string, string | null>;
112
+ };
113
+ isView: boolean;
114
+ memory: boolean;
115
+ }
116
+
117
+ /**
118
+ * Typed relationship registry maps.
119
+ * Each key in Orm.relationships stores a different nested Map structure.
120
+ */
121
+ /** Relationship registry map types — source → target → recordId → value */
122
+ export type HasManyMap = Map<string, Map<string, Map<unknown, unknown[]>>>;
123
+ export type BelongsToMap = Map<string, Map<string, Map<unknown, unknown>>>;
124
+ export type GlobalMap = Map<string, unknown[][]>;
125
+ export type PendingMap = Map<string, Map<unknown, unknown[][]>>;
126
+ export type PendingBelongsToMap = Map<string, Map<unknown, unknown[]>>;
127
+
128
+ export interface RelationshipMaps {
129
+ hasMany: HasManyMap;
130
+ belongsTo: BelongsToMap;
131
+ global: GlobalMap;
132
+ pending: PendingMap;
133
+ pendingBelongsTo: PendingBelongsToMap;
134
+ }
135
+
136
+ export interface SnapshotEntry {
137
+ table?: string;
138
+ idType?: string;
139
+ columns?: Record<string, string>;
140
+ foreignKeys?: Record<string, ForeignKeyDef>;
141
+ vectorColumns?: Record<string, number>;
142
+ isView?: boolean;
143
+ viewName?: string;
144
+ source?: string;
145
+ viewQuery?: string;
146
+ }
@@ -0,0 +1,28 @@
1
+ declare module 'pg' {
2
+ interface PoolConfig {
3
+ host?: string;
4
+ user?: string;
5
+ password?: string;
6
+ database?: string;
7
+ port?: number;
8
+ [key: string]: unknown;
9
+ }
10
+
11
+ interface QueryResult {
12
+ rows: Record<string, unknown>[];
13
+ rowCount: number;
14
+ fields?: { name: string }[];
15
+ }
16
+
17
+ export class Pool {
18
+ constructor(config?: PoolConfig);
19
+ query(sql: string, params?: unknown[]): Promise<QueryResult>;
20
+ connect(): Promise<PoolClient>;
21
+ end(): Promise<void>;
22
+ }
23
+
24
+ export class PoolClient {
25
+ query(sql: string, params?: unknown[]): Promise<QueryResult>;
26
+ release(): void;
27
+ }
28
+ }
@@ -0,0 +1,5 @@
1
+ declare module '@stonyx/cron' {
2
+ export default class Cron {
3
+ register(name: string, fn: () => void | Promise<void>, interval: unknown): void;
4
+ }
5
+ }
@@ -0,0 +1,4 @@
1
+ declare module '@stonyx/events' {
2
+ export function setup(events: string[]): void;
3
+ export function emit(event: string, ...args: unknown[]): Promise<void>;
4
+ }
@@ -0,0 +1,11 @@
1
+ declare module '@stonyx/rest-server' {
2
+ export class Request {
3
+ constructor(...args: unknown[]);
4
+ }
5
+
6
+ export default class RestServer {
7
+ static instance: RestServer;
8
+ static close(): void;
9
+ mountRoute(RequestClass: unknown, options: { name: string; options?: unknown }): void;
10
+ }
11
+ }
@@ -0,0 +1,33 @@
1
+ declare module '@stonyx/utils/file' {
2
+ export function createFile(path: string, data: unknown, options?: { json?: boolean }): Promise<void>;
3
+ export function createDirectory(path: string): Promise<void>;
4
+ export function updateFile(path: string, data: unknown, options?: { json?: boolean }): Promise<void>;
5
+ export function readFile(path: string, options?: { json?: boolean; missingFileCallback?: () => Promise<unknown> }): Promise<unknown>;
6
+ export function fileExists(path: string): Promise<boolean>;
7
+ export function deleteDirectory(path: string): Promise<void>;
8
+ export function forEachFileImport(
9
+ path: string,
10
+ callback: (exported: unknown, meta: { name: string }) => unknown,
11
+ options?: { ignoreAccessFailure?: boolean; rawName?: boolean; recursive?: boolean; recursiveNaming?: boolean }
12
+ ): Promise<void>;
13
+ }
14
+
15
+ declare module '@stonyx/utils/string' {
16
+ export function pluralize(word: string): string;
17
+ export function kebabCaseToPascalCase(str: string): string;
18
+ export function camelCaseToKebabCase(str: string): string;
19
+ }
20
+
21
+ declare module '@stonyx/utils/object' {
22
+ export function get(obj: unknown, path: string): unknown;
23
+ export function getOrSet<T>(map: Map<unknown, T>, key: unknown, defaultValue: T): T;
24
+ export function makeArray<T>(value: T | T[]): T[];
25
+ }
26
+
27
+ declare module '@stonyx/utils/date' {
28
+ export function getTimestamp(value?: unknown): number;
29
+ }
30
+
31
+ declare module '@stonyx/utils/prompt' {
32
+ export function confirm(message: string): Promise<boolean>;
33
+ }
@@ -0,0 +1,21 @@
1
+ declare module 'stonyx/config' {
2
+ import type { OrmConfig } from './orm-types.js';
3
+ const config: OrmConfig;
4
+ export default config;
5
+ }
6
+
7
+ declare module 'stonyx/log' {
8
+ const log: Record<string, ((...args: unknown[]) => void) | undefined>;
9
+ export default log;
10
+ }
11
+
12
+ declare module 'stonyx' {
13
+ export function waitForModule(name: string): Promise<void>;
14
+ }
15
+
16
+ declare module 'stonyx/test-helpers' {
17
+ export function setupIntegrationTests(hooks: {
18
+ before(fn: () => void | Promise<void>): void;
19
+ after(fn: () => void | Promise<void>): void;
20
+ }): void;
21
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { pluralize as basePluralize } from '@stonyx/utils/string';
2
+
3
+ export function isDbError(error: unknown): error is { code: string; message: string } {
4
+ return typeof error === 'object' && error !== null && 'code' in error && typeof (error as Record<string, unknown>).code === 'string' && 'message' in error && typeof (error as Record<string, unknown>).message === 'string';
5
+ }
6
+
7
+ // Wrapper to handle dasherized model names (e.g., "access-link" → "access-links")
8
+ export function pluralize(word: string): string {
9
+ if (word.includes('-')) {
10
+ const parts = word.split('-');
11
+ const pluralizedLast = basePluralize(parts.pop()!);
12
+ return [...parts, pluralizedLast].join('-');
13
+ }
14
+
15
+ return basePluralize(word);
16
+ }
@@ -1,30 +1,41 @@
1
- import Orm, { createRecord, store } from '@stonyx/orm';
1
+ import Orm, { store } from '@stonyx/orm';
2
+ import { createRecord } from './manage-record.js';
2
3
  import { AggregateProperty } from './aggregates.js';
3
4
  import { get } from '@stonyx/utils/object';
5
+ import type { SourceRecord } from './types/orm-types.js';
6
+
7
+ interface ViewClass {
8
+ source?: string;
9
+ resolve?: Record<string, unknown>;
10
+ groupBy?: string;
11
+ new (viewName: string): Record<string, unknown>;
12
+ }
4
13
 
5
14
  export default class ViewResolver {
6
- constructor(viewName) {
15
+ viewName: string;
16
+
17
+ constructor(viewName: string) {
7
18
  this.viewName = viewName;
8
19
  }
9
20
 
10
- async resolveAll() {
21
+ async resolveAll(): Promise<unknown[]> {
11
22
  const orm = Orm.instance;
12
- const { modelClass: viewClass } = orm.getRecordClasses(this.viewName);
23
+ const { modelClass: viewClass } = orm.getRecordClasses(this.viewName) as { modelClass: ViewClass | undefined; serializerClass: unknown };
13
24
 
14
25
  if (!viewClass) return [];
15
26
 
16
27
  const source = viewClass.source;
17
28
  if (!source) return [];
18
29
 
19
- const sourceRecords = await store.findAll(source);
30
+ const sourceRecords = await store.findAll(source) as SourceRecord[];
20
31
  if (!sourceRecords || sourceRecords.length === 0) {
21
32
  return [];
22
33
  }
23
34
 
24
35
  const resolveMap = viewClass.resolve || {};
25
36
  const viewInstance = new viewClass(this.viewName);
26
- const aggregateFields = {};
27
- const regularFields = {};
37
+ const aggregateFields: Record<string, AggregateProperty> = {};
38
+ const regularFields: Record<string, unknown> = {};
28
39
 
29
40
  // Categorize fields on the view instance
30
41
  for (const [key, value] of Object.entries(viewInstance)) {
@@ -48,16 +59,22 @@ export default class ViewResolver {
48
59
  return this._resolvePerRecord(sourceRecords, aggregateFields, regularFields, resolveMap, viewClass);
49
60
  }
50
61
 
51
- _resolvePerRecord(sourceRecords, aggregateFields, regularFields, resolveMap, viewClass) {
52
- const results = [];
62
+ private _resolvePerRecord(
63
+ sourceRecords: SourceRecord[],
64
+ aggregateFields: Record<string, AggregateProperty>,
65
+ regularFields: Record<string, unknown>,
66
+ resolveMap: Record<string, unknown>,
67
+ viewClass: ViewClass
68
+ ): unknown[] {
69
+ const results: unknown[] = [];
53
70
 
54
71
  for (const sourceRecord of sourceRecords) {
55
- const rawData = { id: sourceRecord.id };
72
+ const rawData: Record<string, unknown> = { id: sourceRecord.id };
56
73
 
57
74
  // Compute aggregate fields from source record's relationships
58
75
  for (const [key, aggProp] of Object.entries(aggregateFields)) {
59
- const relatedRecords = sourceRecord.__relationships?.[aggProp.relationship]
60
- || sourceRecord[aggProp.relationship];
76
+ const relatedRecords = sourceRecord.__relationships?.[aggProp.relationship!]
77
+ || sourceRecord[aggProp.relationship!];
61
78
  const relArray = Array.isArray(relatedRecords) ? relatedRecords : [];
62
79
  rawData[key] = aggProp.compute(relArray);
63
80
  }
@@ -93,8 +110,8 @@ export default class ViewResolver {
93
110
 
94
111
  // Clear existing record from store to allow re-resolution
95
112
  const viewStore = store.get(this.viewName);
96
- if (viewStore?.has(rawData.id)) {
97
- viewStore.delete(rawData.id);
113
+ if (viewStore?.has(rawData.id as number | string)) {
114
+ viewStore.delete(rawData.id as number | string);
98
115
  }
99
116
 
100
117
  const record = createRecord(this.viewName, rawData, { isDbRecord: true });
@@ -104,21 +121,28 @@ export default class ViewResolver {
104
121
  return results;
105
122
  }
106
123
 
107
- _resolveGroupBy(sourceRecords, groupByField, aggregateFields, regularFields, resolveMap, viewClass) {
124
+ private _resolveGroupBy(
125
+ sourceRecords: SourceRecord[],
126
+ groupByField: string,
127
+ aggregateFields: Record<string, AggregateProperty>,
128
+ regularFields: Record<string, unknown>,
129
+ resolveMap: Record<string, unknown>,
130
+ viewClass: ViewClass
131
+ ): unknown[] {
108
132
  // Group source records by the groupBy field value
109
- const groups = new Map();
133
+ const groups = new Map<unknown, SourceRecord[]>();
110
134
  for (const record of sourceRecords) {
111
135
  const key = record.__data?.[groupByField] ?? record[groupByField];
112
136
  if (!groups.has(key)) {
113
137
  groups.set(key, []);
114
138
  }
115
- groups.get(key).push(record);
139
+ groups.get(key)!.push(record);
116
140
  }
117
141
 
118
- const results = [];
142
+ const results: unknown[] = [];
119
143
 
120
144
  for (const [groupKey, groupRecords] of groups) {
121
- const rawData = { id: groupKey };
145
+ const rawData: Record<string, unknown> = { id: groupKey };
122
146
 
123
147
  // Compute aggregate fields
124
148
  for (const [key, aggProp] of Object.entries(aggregateFields)) {
@@ -127,15 +151,15 @@ export default class ViewResolver {
127
151
  rawData[key] = aggProp.compute(groupRecords);
128
152
  } else {
129
153
  // Relationship aggregate — flatten related records across all group members
130
- const allRelated = [];
154
+ const allRelated: unknown[] = [];
131
155
  for (const record of groupRecords) {
132
- const relatedRecords = record.__relationships?.[aggProp.relationship]
133
- || record[aggProp.relationship];
156
+ const relatedRecords = record.__relationships?.[aggProp.relationship!]
157
+ || record[aggProp.relationship!];
134
158
  if (Array.isArray(relatedRecords)) {
135
159
  allRelated.push(...relatedRecords);
136
160
  }
137
161
  }
138
- rawData[key] = aggProp.compute(allRelated);
162
+ rawData[key] = aggProp.compute(allRelated as { __data?: Record<string, unknown>; [key: string]: unknown }[]);
139
163
  }
140
164
  }
141
165
 
@@ -163,8 +187,8 @@ export default class ViewResolver {
163
187
 
164
188
  // Clear existing record from store to allow re-resolution
165
189
  const viewStore = store.get(this.viewName);
166
- if (viewStore?.has(rawData.id)) {
167
- viewStore.delete(rawData.id);
190
+ if (viewStore?.has(rawData.id as number | string)) {
191
+ viewStore.delete(rawData.id as number | string);
168
192
  }
169
193
 
170
194
  const record = createRecord(this.viewName, rawData, { isDbRecord: true });
@@ -174,10 +198,11 @@ export default class ViewResolver {
174
198
  return results;
175
199
  }
176
200
 
177
- async resolveOne(id) {
201
+ async resolveOne(id: unknown): Promise<unknown> {
178
202
  const all = await this.resolveAll();
179
- return all.find(record => {
180
- return record.id === id || record.id == id;
203
+ return all.find((record: unknown) => {
204
+ const r = record as SourceRecord;
205
+ return r.id === id || r.id == id;
181
206
  });
182
207
  }
183
208
  }
package/src/view.ts ADDED
@@ -0,0 +1,22 @@
1
+ import attr from './attr.js';
2
+
3
+ export default class View {
4
+ static memory: boolean = false;
5
+ static readOnly: boolean = true;
6
+ static pluralName: string | undefined = undefined;
7
+ static source: string | undefined = undefined;
8
+ static groupBy: string | undefined = undefined;
9
+ static resolve: ((record: unknown) => unknown) | undefined = undefined;
10
+
11
+ id = attr('number');
12
+ __name: string;
13
+
14
+ constructor(name: string) {
15
+ this.__name = name;
16
+
17
+ // Enforce readOnly — cannot be overridden to false
18
+ if ((this.constructor as typeof View).readOnly !== true) {
19
+ throw new Error(`View '${name}' cannot override readOnly to false`);
20
+ }
21
+ }
22
+ }
package/src/has-many.js DELETED
@@ -1,68 +0,0 @@
1
- import { createRecord, relationships, store } from '@stonyx/orm';
2
- import { getRelationships } from './relationships.js';
3
- import { getOrSet, makeArray } from '@stonyx/utils/object';
4
- import { dbKey } from './db.js';
5
-
6
- function queuePendingRelationship(pendingRelationshipQueue, pendingRelationships, modelName, id) {
7
- pendingRelationshipQueue.push({
8
- pendingRelationship: getOrSet(pendingRelationships, modelName, new Map()),
9
- id
10
- });
11
-
12
- return null;
13
- }
14
-
15
- export default function hasMany(modelName) {
16
- const globalRelationships = relationships.get('global');
17
- const pendingRelationships = relationships.get('pending');
18
-
19
- const fn = (sourceRecord, rawData, options) => {
20
- const { __name: sourceModelName } = sourceRecord.__model;
21
- const relationshipId = sourceRecord.id;
22
- const relationship = getRelationships('hasMany', sourceModelName, modelName, relationshipId);
23
- const modelStore = store.get(modelName);
24
- const pendingRelationshipQueue = [];
25
-
26
- const output = !rawData ? [] : makeArray(rawData).map(elementData => {
27
- let record;
28
-
29
- if (typeof elementData !== 'object') {
30
- if (!modelStore) {
31
- return queuePendingRelationship(pendingRelationshipQueue, pendingRelationships, modelName, elementData);
32
- }
33
-
34
- record = modelStore.get(elementData);
35
-
36
- if (!record) {
37
- return queuePendingRelationship(pendingRelationshipQueue, pendingRelationships, modelName, elementData);
38
- }
39
- } else {
40
- if (elementData !== Object(elementData)) {
41
- return queuePendingRelationship(pendingRelationshipQueue, pendingRelationships, modelName, elementData);
42
- }
43
-
44
- record = createRecord(modelName, elementData, options);
45
- }
46
-
47
- // Populate belongTo side if the relationship is defined
48
- const otherSide = relationships.get('belongsTo').get(modelName)?.get(sourceModelName)?.get(record.id);
49
-
50
- if (otherSide) Object.assign(otherSide, sourceRecord);
51
-
52
- return record;
53
- }).filter(value => value);
54
-
55
- relationship.set(relationshipId, output);
56
-
57
- // Assign global relationship
58
- if (options.global || sourceModelName === dbKey) getOrSet(globalRelationships, modelName, []).push(output);
59
-
60
- // Assign pending relationships
61
- for (const { pendingRelationship, id } of pendingRelationshipQueue) getOrSet(pendingRelationship, id, []).push(output);
62
-
63
- return output;
64
- }
65
-
66
- Object.defineProperty(fn, '__relatedModelName', { value: modelName });
67
- return fn;
68
- }
@@ -1,43 +0,0 @@
1
- import { relationships } from "@stonyx/orm";
2
-
3
- export default class Relationships {
4
- constructor() {
5
- if (Relationships.instance) return Relationships.instance;
6
- Relationships.instance = this;
7
-
8
- this.data = new Map();
9
- }
10
-
11
- get(key) {
12
- return this.data.get(key);
13
- }
14
-
15
- set(key, value) {
16
- this.data.set(key, value);
17
- }
18
- }
19
-
20
- // TODO: Refactor mapping to remove a level of iteration
21
- export function getRelationships(type, sourceModel, targetModel, relationshipId) {
22
- const allRelationships = relationships.get(type);
23
-
24
- // create relationship map for this type of it doesn't already exist
25
- if (!allRelationships.has(sourceModel)) allRelationships.set(sourceModel, new Map());
26
-
27
- const modelRelationship = allRelationships.get(sourceModel);
28
-
29
- if (!modelRelationship.has(targetModel)) modelRelationship.set(targetModel, new Map());
30
-
31
- const relationship = modelRelationship.get(targetModel);
32
-
33
- // TODO: Determine whether already having id should be handled differently
34
- //if (relationship.has(relationshipId)) return;
35
-
36
- return relationship;
37
- }
38
-
39
- export function getHasManyRelationships(sourceModel, targetModel) {
40
- return relationships.get('hasMany').get(sourceModel)?.get(targetModel);
41
- }
42
-
43
- export const TYPES = ['global', 'hasMany', 'belongsTo', 'pending'];
@@ -1,111 +0,0 @@
1
- import PostgresDB from '../postgres/postgres-db.js';
2
- import { buildCreateHypertable, buildTimeBucket, buildContinuousAggregate, buildCompressionPolicy, buildEnableCompression } from './query-builder.js';
3
-
4
- export default class TimescaleDB extends PostgresDB {
5
- static extensions = ['timescaledb'];
6
- static configKey = 'timescale';
7
-
8
- constructor(deps = {}) {
9
- super({
10
- ...deps,
11
- buildCreateHypertable,
12
- buildTimeBucket,
13
- buildContinuousAggregate,
14
- buildCompressionPolicy,
15
- buildEnableCompression,
16
- });
17
- }
18
-
19
- /**
20
- * Convert a table to a TimescaleDB hypertable.
21
- * Should be called after the table is created (e.g. after initial migration).
22
- * @param {string} modelName
23
- * @param {string} timeColumn - The time-partitioning column
24
- * @param {Object} [options]
25
- * @param {string} [options.chunkInterval='7 days']
26
- */
27
- async createHypertable(modelName, timeColumn, options = {}) {
28
- const schemas = this.deps.introspectModels();
29
- const schema = schemas[modelName];
30
- if (!schema) throw new Error(`Model '${modelName}' not found`);
31
-
32
- const { sql } = this.deps.buildCreateHypertable(schema.table, timeColumn, options);
33
- await this.pool.query(sql);
34
- }
35
-
36
- /**
37
- * Query time-bucketed aggregations on a hypertable.
38
- * @param {string} modelName
39
- * @param {string} timeColumn - Timestamp column to bucket
40
- * @param {string} bucketSize - Bucket interval (e.g. '1 hour', '5 minutes')
41
- * @param {Object} [options]
42
- * @param {string[]} [options.aggregates] - Aggregate expressions
43
- * @param {Object} [options.where] - WHERE conditions
44
- * @param {number} [options.limit]
45
- * @returns {Promise<Object[]>} Rows with bucket + aggregate columns
46
- */
47
- async timeBucket(modelName, timeColumn, bucketSize, options = {}) {
48
- const schemas = this.deps.introspectModels();
49
- const schema = schemas[modelName];
50
- if (!schema) return [];
51
-
52
- const { sql, values } = this.deps.buildTimeBucket(schema.table, timeColumn, bucketSize, options);
53
-
54
- try {
55
- const result = await this.pool.query(sql, values);
56
- return result.rows;
57
- } catch (error) {
58
- if (error.code === '42P01') return [];
59
- throw error;
60
- }
61
- }
62
-
63
- /**
64
- * Create a continuous aggregate view on a hypertable.
65
- * @param {string} viewName - Name for the materialized view
66
- * @param {string} modelName - Source hypertable model
67
- * @param {string} timeColumn - Timestamp column
68
- * @param {string} bucketSize - Bucket interval
69
- * @param {string[]} aggregates - Aggregate expressions
70
- * @param {Object} [options]
71
- * @param {boolean} [options.withNoData=false]
72
- */
73
- async createContinuousAggregate(viewName, modelName, timeColumn, bucketSize, aggregates, options = {}) {
74
- const schemas = this.deps.introspectModels();
75
- const schema = schemas[modelName];
76
- if (!schema) throw new Error(`Model '${modelName}' not found`);
77
-
78
- const { sql } = this.deps.buildContinuousAggregate(viewName, schema.table, timeColumn, bucketSize, aggregates, options);
79
- await this.pool.query(sql);
80
- }
81
-
82
- /**
83
- * Enable compression on a hypertable.
84
- * @param {string} modelName
85
- * @param {Object} [options]
86
- * @param {string} [options.segmentBy] - Column to segment by
87
- * @param {string} [options.orderBy] - Column to order by
88
- */
89
- async enableCompression(modelName, options = {}) {
90
- const schemas = this.deps.introspectModels();
91
- const schema = schemas[modelName];
92
- if (!schema) throw new Error(`Model '${modelName}' not found`);
93
-
94
- const { sql } = this.deps.buildEnableCompression(schema.table, options.segmentBy, options.orderBy);
95
- await this.pool.query(sql);
96
- }
97
-
98
- /**
99
- * Add a compression policy to a hypertable.
100
- * @param {string} modelName
101
- * @param {string} compressAfter - Interval after which to compress (e.g. '7 days')
102
- */
103
- async addCompressionPolicy(modelName, compressAfter) {
104
- const schemas = this.deps.introspectModels();
105
- const schema = schemas[modelName];
106
- if (!schema) throw new Error(`Model '${modelName}' not found`);
107
-
108
- const { sql } = this.deps.buildCompressionPolicy(schema.table, compressAfter);
109
- await this.pool.query(sql);
110
- }
111
- }