@stonyx/orm 0.2.7-alpha.0 → 0.3.1

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 (166) hide show
  1. package/README.md +482 -15
  2. package/config/environment.js +63 -6
  3. package/dist/aggregates.d.ts +21 -0
  4. package/dist/aggregates.js +93 -0
  5. package/dist/attr.d.ts +2 -0
  6. package/dist/attr.js +22 -0
  7. package/dist/belongs-to.d.ts +11 -0
  8. package/dist/belongs-to.js +59 -0
  9. package/dist/cli.d.ts +22 -0
  10. package/dist/cli.js +148 -0
  11. package/dist/commands.d.ts +7 -0
  12. package/dist/commands.js +146 -0
  13. package/dist/db.d.ts +21 -0
  14. package/dist/db.js +180 -0
  15. package/dist/exports/db.d.ts +7 -0
  16. package/{src → dist}/exports/db.js +2 -4
  17. package/dist/has-many.d.ts +11 -0
  18. package/dist/has-many.js +58 -0
  19. package/dist/hooks.d.ts +75 -0
  20. package/dist/hooks.js +110 -0
  21. package/dist/index.d.ts +14 -0
  22. package/dist/index.js +34 -0
  23. package/dist/main.d.ts +46 -0
  24. package/dist/main.js +181 -0
  25. package/dist/manage-record.d.ts +13 -0
  26. package/dist/manage-record.js +123 -0
  27. package/dist/meta-request.d.ts +6 -0
  28. package/dist/meta-request.js +52 -0
  29. package/dist/migrate.d.ts +2 -0
  30. package/dist/migrate.js +57 -0
  31. package/dist/model-property.d.ts +9 -0
  32. package/dist/model-property.js +29 -0
  33. package/dist/model.d.ts +15 -0
  34. package/dist/model.js +18 -0
  35. package/dist/mysql/connection.d.ts +14 -0
  36. package/dist/mysql/connection.js +24 -0
  37. package/dist/mysql/migration-generator.d.ts +45 -0
  38. package/dist/mysql/migration-generator.js +254 -0
  39. package/dist/mysql/migration-runner.d.ts +12 -0
  40. package/dist/mysql/migration-runner.js +88 -0
  41. package/dist/mysql/mysql-db.d.ts +100 -0
  42. package/dist/mysql/mysql-db.js +425 -0
  43. package/dist/mysql/query-builder.d.ts +10 -0
  44. package/dist/mysql/query-builder.js +44 -0
  45. package/dist/mysql/schema-introspector.d.ts +19 -0
  46. package/dist/mysql/schema-introspector.js +257 -0
  47. package/dist/mysql/type-map.d.ts +21 -0
  48. package/dist/mysql/type-map.js +36 -0
  49. package/dist/orm-request.d.ts +38 -0
  50. package/dist/orm-request.js +475 -0
  51. package/dist/plural-registry.d.ts +4 -0
  52. package/dist/plural-registry.js +9 -0
  53. package/dist/postgres/connection.d.ts +15 -0
  54. package/dist/postgres/connection.js +32 -0
  55. package/dist/postgres/migration-generator.d.ts +45 -0
  56. package/dist/postgres/migration-generator.js +280 -0
  57. package/dist/postgres/migration-runner.d.ts +10 -0
  58. package/dist/postgres/migration-runner.js +87 -0
  59. package/dist/postgres/postgres-db.d.ts +119 -0
  60. package/dist/postgres/postgres-db.js +477 -0
  61. package/dist/postgres/query-builder.d.ts +27 -0
  62. package/dist/postgres/query-builder.js +98 -0
  63. package/dist/postgres/schema-introspector.d.ts +29 -0
  64. package/dist/postgres/schema-introspector.js +296 -0
  65. package/dist/postgres/type-map.d.ts +23 -0
  66. package/dist/postgres/type-map.js +56 -0
  67. package/dist/record.d.ts +75 -0
  68. package/dist/record.js +129 -0
  69. package/dist/relationships.d.ts +10 -0
  70. package/dist/relationships.js +41 -0
  71. package/dist/schema-helpers.d.ts +20 -0
  72. package/dist/schema-helpers.js +48 -0
  73. package/dist/serializer.d.ts +17 -0
  74. package/dist/serializer.js +136 -0
  75. package/dist/setup-rest-server.d.ts +1 -0
  76. package/dist/setup-rest-server.js +52 -0
  77. package/dist/standalone-db.d.ts +58 -0
  78. package/dist/standalone-db.js +142 -0
  79. package/dist/store.d.ts +62 -0
  80. package/dist/store.js +286 -0
  81. package/dist/timescale/query-builder.d.ts +43 -0
  82. package/dist/timescale/query-builder.js +115 -0
  83. package/dist/timescale/timescale-db.d.ts +45 -0
  84. package/dist/timescale/timescale-db.js +84 -0
  85. package/dist/transforms.d.ts +2 -0
  86. package/dist/transforms.js +17 -0
  87. package/dist/types/orm-types.d.ts +153 -0
  88. package/dist/types/orm-types.js +1 -0
  89. package/dist/utils.d.ts +7 -0
  90. package/dist/utils.js +17 -0
  91. package/dist/view-resolver.d.ts +8 -0
  92. package/dist/view-resolver.js +171 -0
  93. package/dist/view.d.ts +11 -0
  94. package/dist/view.js +18 -0
  95. package/package.json +64 -11
  96. package/src/aggregates.ts +109 -0
  97. package/src/{attr.js → attr.ts} +2 -2
  98. package/src/belongs-to.ts +90 -0
  99. package/src/cli.ts +183 -0
  100. package/src/commands.ts +179 -0
  101. package/src/db.ts +232 -0
  102. package/src/exports/db.ts +7 -0
  103. package/src/has-many.ts +92 -0
  104. package/src/hooks.ts +151 -0
  105. package/src/{index.js → index.ts} +12 -2
  106. package/src/main.ts +229 -0
  107. package/src/manage-record.ts +161 -0
  108. package/src/{meta-request.js → meta-request.ts} +17 -14
  109. package/src/migrate.ts +72 -0
  110. package/src/model-property.ts +35 -0
  111. package/src/model.ts +21 -0
  112. package/src/mysql/connection.ts +43 -0
  113. package/src/mysql/migration-generator.ts +337 -0
  114. package/src/mysql/migration-runner.ts +121 -0
  115. package/src/mysql/mysql-db.ts +543 -0
  116. package/src/mysql/query-builder.ts +69 -0
  117. package/src/mysql/schema-introspector.ts +310 -0
  118. package/src/mysql/type-map.ts +42 -0
  119. package/src/orm-request.ts +582 -0
  120. package/src/plural-registry.ts +12 -0
  121. package/src/postgres/connection.ts +48 -0
  122. package/src/postgres/migration-generator.ts +370 -0
  123. package/src/postgres/migration-runner.ts +115 -0
  124. package/src/postgres/postgres-db.ts +616 -0
  125. package/src/postgres/query-builder.ts +148 -0
  126. package/src/postgres/schema-introspector.ts +360 -0
  127. package/src/postgres/type-map.ts +61 -0
  128. package/src/record.ts +186 -0
  129. package/src/relationships.ts +54 -0
  130. package/src/schema-helpers.ts +59 -0
  131. package/src/serializer.ts +161 -0
  132. package/src/setup-rest-server.ts +62 -0
  133. package/src/standalone-db.ts +185 -0
  134. package/src/store.ts +373 -0
  135. package/src/timescale/query-builder.ts +174 -0
  136. package/src/timescale/timescale-db.ts +119 -0
  137. package/src/transforms.ts +20 -0
  138. package/src/types/mysql2.d.ts +49 -0
  139. package/src/types/orm-types.ts +158 -0
  140. package/src/types/pg.d.ts +32 -0
  141. package/src/types/stonyx-cron.d.ts +5 -0
  142. package/src/types/stonyx-events.d.ts +4 -0
  143. package/src/types/stonyx-rest-server.d.ts +16 -0
  144. package/src/types/stonyx-utils.d.ts +33 -0
  145. package/src/types/stonyx.d.ts +21 -0
  146. package/src/utils.ts +22 -0
  147. package/src/view-resolver.ts +211 -0
  148. package/src/view.ts +22 -0
  149. package/.claude/project-structure.md +0 -578
  150. package/.github/workflows/ci.yml +0 -36
  151. package/.github/workflows/publish.yml +0 -143
  152. package/src/belongs-to.js +0 -63
  153. package/src/db.js +0 -80
  154. package/src/has-many.js +0 -61
  155. package/src/main.js +0 -119
  156. package/src/manage-record.js +0 -103
  157. package/src/model-property.js +0 -29
  158. package/src/model.js +0 -9
  159. package/src/orm-request.js +0 -249
  160. package/src/record.js +0 -100
  161. package/src/relationships.js +0 -43
  162. package/src/serializer.js +0 -138
  163. package/src/setup-rest-server.js +0 -57
  164. package/src/store.js +0 -211
  165. package/src/transforms.js +0 -20
  166. package/stonyx-bootstrap.cjs +0 -30
@@ -0,0 +1,153 @@
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: string | number;
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: string | number;
67
+ [key: string]: unknown;
68
+ }
69
+ export interface OrmRecord {
70
+ id: string | number;
71
+ __model?: {
72
+ __name: string;
73
+ };
74
+ __data: Record<string, unknown> & {
75
+ id?: string | number;
76
+ __pendingSqlId?: boolean;
77
+ };
78
+ __relationships: Record<string, unknown>;
79
+ toJSON?(options?: {
80
+ fields?: Set<string>;
81
+ baseUrl?: string;
82
+ }): Record<string, unknown>;
83
+ [key: string]: unknown;
84
+ }
85
+ export interface ForeignKeyDef {
86
+ references: string;
87
+ column: string;
88
+ }
89
+ export interface HypertableConfig {
90
+ timeColumn: string;
91
+ chunkInterval?: string;
92
+ compress?: {
93
+ segmentBy?: string;
94
+ orderBy?: string;
95
+ after?: string;
96
+ };
97
+ }
98
+ export interface ModelSchema {
99
+ table: string;
100
+ idType: string;
101
+ columns: Record<string, string>;
102
+ foreignKeys: Record<string, ForeignKeyDef>;
103
+ relationships: {
104
+ belongsTo: Record<string, string | null>;
105
+ hasMany: Record<string, string | null>;
106
+ };
107
+ vectorColumns?: Record<string, number>;
108
+ hypertable?: HypertableConfig;
109
+ memory: boolean;
110
+ }
111
+ export interface ViewSchema {
112
+ viewName: string;
113
+ source: string;
114
+ groupBy?: string;
115
+ columns: Record<string, string>;
116
+ foreignKeys: Record<string, ForeignKeyDef>;
117
+ aggregates: Record<string, AggregateProperty>;
118
+ relationships: {
119
+ belongsTo: Record<string, string | null>;
120
+ hasMany: Record<string, string | null>;
121
+ };
122
+ isView: boolean;
123
+ memory: boolean;
124
+ }
125
+ /**
126
+ * Typed relationship registry maps.
127
+ * Each key in Orm.relationships stores a different nested Map structure.
128
+ */
129
+ /** Relationship registry map types — source → target → recordId → value */
130
+ export type HasManyMap = Map<string, Map<string, Map<unknown, unknown[]>>>;
131
+ export type BelongsToMap = Map<string, Map<string, Map<unknown, unknown>>>;
132
+ export type GlobalMap = Map<string, unknown[][]>;
133
+ export type PendingMap = Map<string, Map<unknown, unknown[][]>>;
134
+ export type PendingBelongsToMap = Map<string, Map<unknown, unknown[]>>;
135
+ export interface RelationshipMaps {
136
+ hasMany: HasManyMap;
137
+ belongsTo: BelongsToMap;
138
+ global: GlobalMap;
139
+ pending: PendingMap;
140
+ pendingBelongsTo: PendingBelongsToMap;
141
+ }
142
+ export interface SnapshotEntry {
143
+ table?: string;
144
+ idType?: string;
145
+ columns?: Record<string, string>;
146
+ foreignKeys?: Record<string, ForeignKeyDef>;
147
+ vectorColumns?: Record<string, number>;
148
+ hypertable?: HypertableConfig;
149
+ isView?: boolean;
150
+ viewName?: string;
151
+ source?: string;
152
+ viewQuery?: string;
153
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import type { OrmRecord } from './types/orm-types.js';
2
+ export declare function isDbError(error: unknown): error is {
3
+ code: string;
4
+ message: string;
5
+ };
6
+ export declare function isOrmRecord(value: unknown): value is OrmRecord;
7
+ export declare function pluralize(word: string): string;
package/dist/utils.js ADDED
@@ -0,0 +1,17 @@
1
+ import { pluralize as basePluralize } from '@stonyx/utils/string';
2
+ export function isDbError(error) {
3
+ return typeof error === 'object' && error !== null && 'code' in error && typeof error.code === 'string' && 'message' in error && typeof error.message === 'string';
4
+ }
5
+ export function isOrmRecord(value) {
6
+ return typeof value === 'object' && value !== null && '__data' in value && '__relationships' in value;
7
+ }
8
+ // Wrapper to handle dasherized model names (e.g., "access-link" → "access-links")
9
+ export function pluralize(word) {
10
+ if (word.includes('-')) {
11
+ const parts = word.split('-');
12
+ const last = parts.pop();
13
+ const pluralizedLast = basePluralize(last);
14
+ return [...parts, pluralizedLast].join('-');
15
+ }
16
+ return basePluralize(word);
17
+ }
@@ -0,0 +1,8 @@
1
+ export default class ViewResolver {
2
+ viewName: string;
3
+ constructor(viewName: string);
4
+ resolveAll(): Promise<unknown[]>;
5
+ private _resolvePerRecord;
6
+ private _resolveGroupBy;
7
+ resolveOne(id: unknown): Promise<unknown>;
8
+ }
@@ -0,0 +1,171 @@
1
+ import Orm, { store } from '@stonyx/orm';
2
+ import { createRecord } from './manage-record.js';
3
+ import { AggregateProperty } from './aggregates.js';
4
+ import { get } from '@stonyx/utils/object';
5
+ export default class ViewResolver {
6
+ viewName;
7
+ constructor(viewName) {
8
+ this.viewName = viewName;
9
+ }
10
+ async resolveAll() {
11
+ const orm = Orm.instance;
12
+ const { modelClass: viewClass } = orm.getRecordClasses(this.viewName);
13
+ if (!viewClass)
14
+ return [];
15
+ const source = viewClass.source;
16
+ if (!source)
17
+ return [];
18
+ const sourceRecords = await store.findAll(source);
19
+ if (!sourceRecords || sourceRecords.length === 0) {
20
+ return [];
21
+ }
22
+ const resolveMap = viewClass.resolve || {};
23
+ const viewInstance = new viewClass(this.viewName);
24
+ const aggregateFields = {};
25
+ const regularFields = {};
26
+ // Categorize fields on the view instance
27
+ for (const [key, value] of Object.entries(viewInstance)) {
28
+ if (key.startsWith('__'))
29
+ continue;
30
+ if (key === 'id')
31
+ continue;
32
+ if (value instanceof AggregateProperty) {
33
+ aggregateFields[key] = value;
34
+ }
35
+ else if (typeof value !== 'function') {
36
+ // Regular attr or direct value — not a relationship handler
37
+ regularFields[key] = value;
38
+ }
39
+ }
40
+ const groupByField = viewClass.groupBy;
41
+ if (groupByField) {
42
+ return this._resolveGroupBy(sourceRecords, groupByField, aggregateFields, regularFields, resolveMap, viewClass);
43
+ }
44
+ return this._resolvePerRecord(sourceRecords, aggregateFields, regularFields, resolveMap, viewClass);
45
+ }
46
+ _resolvePerRecord(sourceRecords, aggregateFields, regularFields, resolveMap, viewClass) {
47
+ const results = [];
48
+ for (const sourceRecord of sourceRecords) {
49
+ const rawData = { id: sourceRecord.id };
50
+ // Compute aggregate fields from source record's relationships
51
+ for (const [key, aggProp] of Object.entries(aggregateFields)) {
52
+ if (!aggProp.relationship)
53
+ continue;
54
+ const relatedRecords = sourceRecord.__relationships?.[aggProp.relationship]
55
+ || sourceRecord[aggProp.relationship];
56
+ const relArray = Array.isArray(relatedRecords) ? relatedRecords : [];
57
+ rawData[key] = aggProp.compute(relArray);
58
+ }
59
+ // Apply resolve map entries
60
+ for (const [key, resolver] of Object.entries(resolveMap)) {
61
+ if (typeof resolver === 'function') {
62
+ rawData[key] = resolver(sourceRecord);
63
+ }
64
+ else if (typeof resolver === 'string') {
65
+ rawData[key] = get(sourceRecord.__data || sourceRecord, resolver)
66
+ ?? get(sourceRecord, resolver);
67
+ }
68
+ }
69
+ // Map regular attr fields from source record if not already set
70
+ for (const key of Object.keys(regularFields)) {
71
+ if (rawData[key] !== undefined)
72
+ continue;
73
+ const sourceValue = sourceRecord.__data?.[key] ?? sourceRecord[key];
74
+ if (sourceValue !== undefined) {
75
+ rawData[key] = sourceValue;
76
+ }
77
+ }
78
+ // Set belongsTo source relationship
79
+ const viewInstanceForRel = new viewClass(this.viewName);
80
+ for (const [key, value] of Object.entries(viewInstanceForRel)) {
81
+ if (typeof value === 'function' && key !== 'id') {
82
+ // This is a relationship handler — pass the source record id
83
+ rawData[key] = sourceRecord.id;
84
+ }
85
+ }
86
+ // Clear existing record from store to allow re-resolution
87
+ const viewStore = store.get(this.viewName);
88
+ if (viewStore?.has(rawData.id)) {
89
+ viewStore.delete(rawData.id);
90
+ }
91
+ const record = createRecord(this.viewName, rawData, { isDbRecord: true });
92
+ results.push(record);
93
+ }
94
+ return results;
95
+ }
96
+ _resolveGroupBy(sourceRecords, groupByField, aggregateFields, regularFields, resolveMap, viewClass) {
97
+ // Group source records by the groupBy field value
98
+ const groups = new Map();
99
+ for (const record of sourceRecords) {
100
+ const key = record.__data?.[groupByField] ?? record[groupByField];
101
+ if (!groups.has(key)) {
102
+ groups.set(key, []);
103
+ }
104
+ const group = groups.get(key);
105
+ if (group)
106
+ group.push(record);
107
+ }
108
+ const results = [];
109
+ for (const [groupKey, groupRecords] of groups) {
110
+ const rawData = { id: groupKey };
111
+ // Compute aggregate fields
112
+ for (const [key, aggProp] of Object.entries(aggregateFields)) {
113
+ if (aggProp.relationship === undefined) {
114
+ // Field-level aggregate — compute over group records directly
115
+ rawData[key] = aggProp.compute(groupRecords);
116
+ }
117
+ else {
118
+ // Relationship aggregate — flatten related records across all group members
119
+ if (!aggProp.relationship)
120
+ continue;
121
+ const allRelated = [];
122
+ for (const record of groupRecords) {
123
+ const relatedRecords = record.__relationships?.[aggProp.relationship]
124
+ || record[aggProp.relationship];
125
+ if (Array.isArray(relatedRecords)) {
126
+ allRelated.push(...relatedRecords);
127
+ }
128
+ }
129
+ rawData[key] = aggProp.compute(allRelated);
130
+ }
131
+ }
132
+ // Apply resolve map entries — functions receive the group array
133
+ for (const [key, resolver] of Object.entries(resolveMap)) {
134
+ if (typeof resolver === 'function') {
135
+ rawData[key] = resolver(groupRecords);
136
+ }
137
+ else if (typeof resolver === 'string') {
138
+ // String path — take value from first record in group
139
+ const first = groupRecords[0];
140
+ rawData[key] = get(first.__data || first, resolver)
141
+ ?? get(first, resolver);
142
+ }
143
+ }
144
+ // Map regular attr fields from first record if not already set
145
+ for (const key of Object.keys(regularFields)) {
146
+ if (rawData[key] !== undefined)
147
+ continue;
148
+ const first = groupRecords[0];
149
+ const sourceValue = first.__data?.[key] ?? first[key];
150
+ if (sourceValue !== undefined) {
151
+ rawData[key] = sourceValue;
152
+ }
153
+ }
154
+ // Clear existing record from store to allow re-resolution
155
+ const viewStore = store.get(this.viewName);
156
+ if (viewStore?.has(rawData.id)) {
157
+ viewStore.delete(rawData.id);
158
+ }
159
+ const record = createRecord(this.viewName, rawData, { isDbRecord: true });
160
+ results.push(record);
161
+ }
162
+ return results;
163
+ }
164
+ async resolveOne(id) {
165
+ const all = await this.resolveAll();
166
+ return all.find((record) => {
167
+ const r = record;
168
+ return r.id === id || r.id == id;
169
+ });
170
+ }
171
+ }
package/dist/view.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ export default class View {
2
+ static memory: boolean;
3
+ static readOnly: boolean;
4
+ static pluralName: string | undefined;
5
+ static source: string | undefined;
6
+ static groupBy: string | undefined;
7
+ static resolve: ((record: unknown) => unknown) | undefined;
8
+ id: import("./model-property.js").default;
9
+ __name: string;
10
+ constructor(name: string);
11
+ }
package/dist/view.js ADDED
@@ -0,0 +1,18 @@
1
+ import attr from './attr.js';
2
+ export default class View {
3
+ static memory = false;
4
+ static readOnly = true;
5
+ static pluralName = undefined;
6
+ static source = undefined;
7
+ static groupBy = undefined;
8
+ static resolve = undefined;
9
+ id = attr('number');
10
+ __name;
11
+ constructor(name) {
12
+ this.__name = name;
13
+ // Enforce readOnly — cannot be overridden to false
14
+ if (this.constructor.readOnly !== true) {
15
+ throw new Error(`View '${name}' cannot override readOnly to false`);
16
+ }
17
+ }
18
+ }
package/package.json CHANGED
@@ -4,13 +4,38 @@
4
4
  "stonyx-async",
5
5
  "stonyx-module"
6
6
  ],
7
- "version": "0.2.7-alpha.0",
7
+ "version": "0.3.1",
8
8
  "description": "",
9
- "main": "src/main.js",
9
+ "main": "dist/index.js",
10
10
  "type": "module",
11
+ "bin": {
12
+ "stonyx-orm": "./dist/cli.js"
13
+ },
11
14
  "exports": {
12
- ".": "./src/index.js",
13
- "./db": "./src/exports/db.js"
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.js"
18
+ },
19
+ "./db": {
20
+ "types": "./dist/exports/db.d.ts",
21
+ "default": "./dist/exports/db.js"
22
+ },
23
+ "./standalone-db": {
24
+ "types": "./dist/standalone-db.d.ts",
25
+ "default": "./dist/standalone-db.js"
26
+ },
27
+ "./migrate": {
28
+ "types": "./dist/migrate.d.ts",
29
+ "default": "./dist/migrate.js"
30
+ },
31
+ "./commands": {
32
+ "types": "./dist/commands.d.ts",
33
+ "default": "./dist/commands.js"
34
+ },
35
+ "./hooks": {
36
+ "types": "./dist/hooks.d.ts",
37
+ "default": "./dist/hooks.js"
38
+ }
14
39
  },
15
40
  "repository": {
16
41
  "type": "git",
@@ -21,6 +46,12 @@
21
46
  "contributors": [
22
47
  "Stone Costa <stone.costa@synamicd.com>"
23
48
  ],
49
+ "files": [
50
+ "dist",
51
+ "src",
52
+ "config",
53
+ "README.md"
54
+ ],
24
55
  "publishConfig": {
25
56
  "access": "public",
26
57
  "provenance": true
@@ -30,17 +61,39 @@
30
61
  },
31
62
  "homepage": "https://github.com/abofs/stonyx-orm#readme",
32
63
  "dependencies": {
33
- "stonyx": "^0.2.2",
34
- "@stonyx/events": "^0.1.0",
35
- "@stonyx/cron": "^0.2.0"
64
+ "@stonyx/cron": "0.2.1-beta.29",
65
+ "@stonyx/events": "0.1.1-beta.9",
66
+ "stonyx": "0.2.3-beta.11"
67
+ },
68
+ "peerDependencies": {
69
+ "@stonyx/rest-server": ">=0.2.1-beta.14",
70
+ "mysql2": "^3.0.0",
71
+ "pg": "^8.0.0"
72
+ },
73
+ "peerDependenciesMeta": {
74
+ "mysql2": {
75
+ "optional": true
76
+ },
77
+ "pg": {
78
+ "optional": true
79
+ },
80
+ "@stonyx/rest-server": {
81
+ "optional": true
82
+ }
36
83
  },
37
84
  "devDependencies": {
38
- "@stonyx/rest-server": "^0.2.0",
39
- "@stonyx/utils": "^0.2.2",
85
+ "@stonyx/rest-server": "0.2.1-beta.30",
86
+ "@stonyx/utils": "0.2.3-beta.7",
87
+ "@types/node": "^25.6.0",
88
+ "mysql2": "^3.20.0",
89
+ "pg": "^8.20.0",
40
90
  "qunit": "^2.24.1",
41
- "sinon": "^21.0.0"
91
+ "sinon": "^21.0.0",
92
+ "typescript": "^5.8.3"
42
93
  },
43
94
  "scripts": {
44
- "test": "qunit --require ./stonyx-bootstrap.cjs"
95
+ "build": "tsc",
96
+ "build:test": "tsc -p tsconfig.test.json",
97
+ "test": "npm run build && npm run build:test && stonyx test 'dist-test/test/**/*-test.js'"
45
98
  }
46
99
  }
@@ -0,0 +1,109 @@
1
+ type AggregateType = 'count' | 'avg' | 'sum' | 'min' | 'max';
2
+
3
+ interface AggregateRecord {
4
+ __data?: Record<string, unknown>;
5
+ [key: string]: unknown;
6
+ }
7
+
8
+ export class AggregateProperty {
9
+ readonly __kind = 'AggregateProperty' as const;
10
+ readonly aggregateType: AggregateType;
11
+ readonly relationship: string | undefined;
12
+ readonly field: string | undefined;
13
+ readonly mysqlFunction: string;
14
+ readonly resultType: 'float' | 'number';
15
+
16
+ constructor(aggregateType: AggregateType, relationship?: string, field?: string) {
17
+ this.aggregateType = aggregateType;
18
+ this.relationship = relationship;
19
+ this.field = field;
20
+ this.mysqlFunction = aggregateType.toUpperCase();
21
+ this.resultType = aggregateType === 'avg' ? 'float' : 'number';
22
+ }
23
+
24
+ compute(relatedRecords: AggregateRecord[]): number | null {
25
+ if (!relatedRecords || !Array.isArray(relatedRecords) || relatedRecords.length === 0) {
26
+ if (this.aggregateType === 'min' || this.aggregateType === 'max') return null;
27
+ return 0;
28
+ }
29
+
30
+ if (this.aggregateType === 'count') return relatedRecords.length;
31
+
32
+ const field = this.field;
33
+ if (!field) return null;
34
+
35
+ switch (this.aggregateType) {
36
+ case 'sum':
37
+ return relatedRecords.reduce((acc, record) => {
38
+ const val = parseFloat(record?.__data?.[field] as string ?? record?.[field] as string);
39
+ return acc + (isNaN(val) ? 0 : val);
40
+ }, 0);
41
+
42
+ case 'avg': {
43
+ let sum = 0;
44
+ let count = 0;
45
+ for (const record of relatedRecords) {
46
+ const val = parseFloat(record?.__data?.[field] as string ?? record?.[field] as string);
47
+ if (!isNaN(val)) {
48
+ sum += val;
49
+ count++;
50
+ }
51
+ }
52
+ return count === 0 ? 0 : sum / count;
53
+ }
54
+
55
+ case 'min': {
56
+ let min: number | null = null;
57
+ for (const record of relatedRecords) {
58
+ const val = parseFloat(record?.__data?.[field] as string ?? record?.[field] as string);
59
+ if (!isNaN(val) && (min === null || val < min)) min = val;
60
+ }
61
+ return min;
62
+ }
63
+
64
+ case 'max': {
65
+ let max: number | null = null;
66
+ for (const record of relatedRecords) {
67
+ const val = parseFloat(record?.__data?.[field] as string ?? record?.[field] as string);
68
+ if (!isNaN(val) && (max === null || val > max)) max = val;
69
+ }
70
+ return max;
71
+ }
72
+
73
+ default:
74
+ return null;
75
+ }
76
+ }
77
+ }
78
+
79
+ export function count(relationship: string): AggregateProperty {
80
+ return new AggregateProperty('count', relationship);
81
+ }
82
+
83
+ export function avg(relationshipOrField: string, field?: string): AggregateProperty {
84
+ if (field !== undefined) {
85
+ return new AggregateProperty('avg', relationshipOrField, field);
86
+ }
87
+ return new AggregateProperty('avg', undefined, relationshipOrField);
88
+ }
89
+
90
+ export function sum(relationshipOrField: string, field?: string): AggregateProperty {
91
+ if (field !== undefined) {
92
+ return new AggregateProperty('sum', relationshipOrField, field);
93
+ }
94
+ return new AggregateProperty('sum', undefined, relationshipOrField);
95
+ }
96
+
97
+ export function min(relationshipOrField: string, field?: string): AggregateProperty {
98
+ if (field !== undefined) {
99
+ return new AggregateProperty('min', relationshipOrField, field);
100
+ }
101
+ return new AggregateProperty('min', undefined, relationshipOrField);
102
+ }
103
+
104
+ export function max(relationshipOrField: string, field?: string): AggregateProperty {
105
+ if (field !== undefined) {
106
+ return new AggregateProperty('max', relationshipOrField, field);
107
+ }
108
+ return new AggregateProperty('max', undefined, relationshipOrField);
109
+ }
@@ -1,7 +1,7 @@
1
1
  import ModelProperty from './model-property.js';
2
2
 
3
- export default function attr() {
4
- const modelProp = new ModelProperty(...arguments);
3
+ export default function attr(type?: string, defaultValue?: unknown): ModelProperty {
4
+ const modelProp = new ModelProperty(type, defaultValue);
5
5
 
6
6
  return new Proxy(modelProp, {
7
7
  get(target, prop, receiver) {