@stonyx/orm 0.2.1-beta.82 → 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 (150) hide show
  1. package/config/environment.js +17 -0
  2. package/dist/aggregates.d.ts +21 -0
  3. package/dist/aggregates.js +90 -0
  4. package/dist/attr.d.ts +2 -0
  5. package/dist/attr.js +22 -0
  6. package/dist/belongs-to.d.ts +11 -0
  7. package/dist/belongs-to.js +58 -0
  8. package/dist/cli.d.ts +22 -0
  9. package/dist/cli.js +148 -0
  10. package/dist/commands.d.ts +7 -0
  11. package/dist/commands.js +146 -0
  12. package/dist/db.d.ts +21 -0
  13. package/dist/db.js +174 -0
  14. package/dist/exports/db.d.ts +7 -0
  15. package/{src → dist}/exports/db.js +2 -4
  16. package/dist/has-many.d.ts +11 -0
  17. package/dist/has-many.js +57 -0
  18. package/dist/hooks.d.ts +47 -0
  19. package/dist/hooks.js +106 -0
  20. package/dist/index.d.ts +14 -0
  21. package/dist/index.js +34 -0
  22. package/dist/main.d.ts +46 -0
  23. package/dist/main.js +178 -0
  24. package/dist/manage-record.d.ts +13 -0
  25. package/dist/manage-record.js +113 -0
  26. package/dist/meta-request.d.ts +6 -0
  27. package/dist/meta-request.js +52 -0
  28. package/dist/migrate.d.ts +2 -0
  29. package/dist/migrate.js +57 -0
  30. package/dist/model-property.d.ts +9 -0
  31. package/dist/model-property.js +29 -0
  32. package/dist/model.d.ts +15 -0
  33. package/dist/model.js +18 -0
  34. package/dist/mysql/connection.d.ts +14 -0
  35. package/dist/mysql/connection.js +24 -0
  36. package/dist/mysql/migration-generator.d.ts +45 -0
  37. package/dist/mysql/migration-generator.js +245 -0
  38. package/dist/mysql/migration-runner.d.ts +12 -0
  39. package/dist/mysql/migration-runner.js +83 -0
  40. package/dist/mysql/mysql-db.d.ts +100 -0
  41. package/dist/mysql/mysql-db.js +411 -0
  42. package/dist/mysql/query-builder.d.ts +10 -0
  43. package/dist/mysql/query-builder.js +44 -0
  44. package/dist/mysql/schema-introspector.d.ts +19 -0
  45. package/dist/mysql/schema-introspector.js +286 -0
  46. package/dist/mysql/type-map.d.ts +21 -0
  47. package/dist/mysql/type-map.js +36 -0
  48. package/dist/orm-request.d.ts +38 -0
  49. package/dist/orm-request.js +453 -0
  50. package/dist/plural-registry.d.ts +4 -0
  51. package/{src → dist}/plural-registry.js +3 -6
  52. package/dist/postgres/connection.d.ts +15 -0
  53. package/dist/postgres/connection.js +30 -0
  54. package/dist/postgres/migration-generator.d.ts +45 -0
  55. package/dist/postgres/migration-generator.js +257 -0
  56. package/dist/postgres/migration-runner.d.ts +10 -0
  57. package/dist/postgres/migration-runner.js +82 -0
  58. package/dist/postgres/postgres-db.d.ts +119 -0
  59. package/dist/postgres/postgres-db.js +473 -0
  60. package/dist/postgres/query-builder.d.ts +27 -0
  61. package/dist/postgres/query-builder.js +98 -0
  62. package/dist/postgres/schema-introspector.d.ts +29 -0
  63. package/dist/postgres/schema-introspector.js +309 -0
  64. package/dist/postgres/type-map.d.ts +23 -0
  65. package/dist/postgres/type-map.js +53 -0
  66. package/dist/record.d.ts +75 -0
  67. package/dist/record.js +115 -0
  68. package/dist/relationships.d.ts +10 -0
  69. package/dist/relationships.js +35 -0
  70. package/dist/serializer.d.ts +17 -0
  71. package/dist/serializer.js +130 -0
  72. package/dist/setup-rest-server.d.ts +1 -0
  73. package/dist/setup-rest-server.js +54 -0
  74. package/dist/standalone-db.d.ts +58 -0
  75. package/dist/standalone-db.js +142 -0
  76. package/dist/store.d.ts +62 -0
  77. package/dist/store.js +271 -0
  78. package/dist/timescale/query-builder.d.ts +41 -0
  79. package/dist/timescale/query-builder.js +87 -0
  80. package/dist/timescale/timescale-db.d.ts +44 -0
  81. package/dist/timescale/timescale-db.js +81 -0
  82. package/dist/transforms.d.ts +2 -0
  83. package/dist/transforms.js +17 -0
  84. package/dist/types/orm-types.d.ts +142 -0
  85. package/dist/types/orm-types.js +1 -0
  86. package/dist/utils.d.ts +5 -0
  87. package/dist/utils.js +13 -0
  88. package/dist/view-resolver.d.ts +8 -0
  89. package/dist/view-resolver.js +165 -0
  90. package/dist/view.d.ts +11 -0
  91. package/dist/view.js +18 -0
  92. package/package.json +34 -11
  93. package/src/{aggregates.js → aggregates.ts} +27 -13
  94. package/src/{attr.js → attr.ts} +2 -2
  95. package/src/{belongs-to.js → belongs-to.ts} +36 -17
  96. package/src/{cli.js → cli.ts} +17 -11
  97. package/src/{commands.js → commands.ts} +179 -170
  98. package/src/{db.js → db.ts} +35 -26
  99. package/src/exports/db.ts +7 -0
  100. package/src/has-many.ts +91 -0
  101. package/src/{hooks.js → hooks.ts} +23 -27
  102. package/src/{index.js → index.ts} +4 -4
  103. package/src/{main.js → main.ts} +64 -34
  104. package/src/{manage-record.js → manage-record.ts} +41 -22
  105. package/src/{meta-request.js → meta-request.ts} +17 -14
  106. package/src/{migrate.js → migrate.ts} +9 -9
  107. package/src/{model-property.js → model-property.ts} +12 -6
  108. package/src/{model.js → model.ts} +5 -4
  109. package/src/mysql/{connection.js → connection.ts} +43 -28
  110. package/src/mysql/{migration-generator.js → migration-generator.ts} +332 -286
  111. package/src/mysql/{migration-runner.js → migration-runner.ts} +116 -110
  112. package/src/mysql/{mysql-db.js → mysql-db.ts} +533 -473
  113. package/src/mysql/{query-builder.js → query-builder.ts} +69 -64
  114. package/src/mysql/{schema-introspector.js → schema-introspector.ts} +355 -325
  115. package/src/mysql/{type-map.js → type-map.ts} +42 -37
  116. package/src/{orm-request.js → orm-request.ts} +165 -95
  117. package/src/plural-registry.ts +12 -0
  118. package/src/postgres/connection.ts +46 -0
  119. package/src/postgres/{migration-generator.js → migration-generator.ts} +82 -38
  120. package/src/postgres/{migration-runner.js → migration-runner.ts} +11 -10
  121. package/src/postgres/{postgres-db.js → postgres-db.ts} +199 -111
  122. package/src/postgres/{query-builder.js → query-builder.ts} +27 -28
  123. package/src/postgres/{schema-introspector.js → schema-introspector.ts} +87 -58
  124. package/src/postgres/{type-map.js → type-map.ts} +10 -6
  125. package/src/{record.js → record.ts} +73 -34
  126. package/src/relationships.ts +48 -0
  127. package/src/{serializer.js → serializer.ts} +44 -36
  128. package/src/{setup-rest-server.js → setup-rest-server.ts} +18 -13
  129. package/src/{standalone-db.js → standalone-db.ts} +33 -24
  130. package/src/{store.js → store.ts} +90 -68
  131. package/src/timescale/query-builder.ts +137 -0
  132. package/src/timescale/timescale-db.ts +107 -0
  133. package/src/transforms.ts +20 -0
  134. package/src/types/mysql2.d.ts +30 -0
  135. package/src/types/orm-types.ts +146 -0
  136. package/src/types/pg.d.ts +28 -0
  137. package/src/types/stonyx-cron.d.ts +5 -0
  138. package/src/types/stonyx-events.d.ts +4 -0
  139. package/src/types/stonyx-rest-server.d.ts +11 -0
  140. package/src/types/stonyx-utils.d.ts +33 -0
  141. package/src/types/stonyx.d.ts +21 -0
  142. package/src/utils.ts +16 -0
  143. package/src/{view-resolver.js → view-resolver.ts} +53 -28
  144. package/src/view.ts +22 -0
  145. package/src/has-many.js +0 -68
  146. package/src/postgres/connection.js +0 -30
  147. package/src/relationships.js +0 -43
  148. package/src/transforms.js +0 -20
  149. package/src/utils.js +0 -12
  150. package/src/view.js +0 -21
@@ -0,0 +1,5 @@
1
+ export declare function isDbError(error: unknown): error is {
2
+ code: string;
3
+ message: string;
4
+ };
5
+ export declare function pluralize(word: string): string;
package/dist/utils.js ADDED
@@ -0,0 +1,13 @@
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
+ // Wrapper to handle dasherized model names (e.g., "access-link" → "access-links")
6
+ export function pluralize(word) {
7
+ if (word.includes('-')) {
8
+ const parts = word.split('-');
9
+ const pluralizedLast = basePluralize(parts.pop());
10
+ return [...parts, pluralizedLast].join('-');
11
+ }
12
+ return basePluralize(word);
13
+ }
@@ -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,165 @@
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
+ const relatedRecords = sourceRecord.__relationships?.[aggProp.relationship]
53
+ || sourceRecord[aggProp.relationship];
54
+ const relArray = Array.isArray(relatedRecords) ? relatedRecords : [];
55
+ rawData[key] = aggProp.compute(relArray);
56
+ }
57
+ // Apply resolve map entries
58
+ for (const [key, resolver] of Object.entries(resolveMap)) {
59
+ if (typeof resolver === 'function') {
60
+ rawData[key] = resolver(sourceRecord);
61
+ }
62
+ else if (typeof resolver === 'string') {
63
+ rawData[key] = get(sourceRecord.__data || sourceRecord, resolver)
64
+ ?? get(sourceRecord, resolver);
65
+ }
66
+ }
67
+ // Map regular attr fields from source record if not already set
68
+ for (const key of Object.keys(regularFields)) {
69
+ if (rawData[key] !== undefined)
70
+ continue;
71
+ const sourceValue = sourceRecord.__data?.[key] ?? sourceRecord[key];
72
+ if (sourceValue !== undefined) {
73
+ rawData[key] = sourceValue;
74
+ }
75
+ }
76
+ // Set belongsTo source relationship
77
+ const viewInstanceForRel = new viewClass(this.viewName);
78
+ for (const [key, value] of Object.entries(viewInstanceForRel)) {
79
+ if (typeof value === 'function' && key !== 'id') {
80
+ // This is a relationship handler — pass the source record id
81
+ rawData[key] = sourceRecord.id;
82
+ }
83
+ }
84
+ // Clear existing record from store to allow re-resolution
85
+ const viewStore = store.get(this.viewName);
86
+ if (viewStore?.has(rawData.id)) {
87
+ viewStore.delete(rawData.id);
88
+ }
89
+ const record = createRecord(this.viewName, rawData, { isDbRecord: true });
90
+ results.push(record);
91
+ }
92
+ return results;
93
+ }
94
+ _resolveGroupBy(sourceRecords, groupByField, aggregateFields, regularFields, resolveMap, viewClass) {
95
+ // Group source records by the groupBy field value
96
+ const groups = new Map();
97
+ for (const record of sourceRecords) {
98
+ const key = record.__data?.[groupByField] ?? record[groupByField];
99
+ if (!groups.has(key)) {
100
+ groups.set(key, []);
101
+ }
102
+ groups.get(key).push(record);
103
+ }
104
+ const results = [];
105
+ for (const [groupKey, groupRecords] of groups) {
106
+ const rawData = { id: groupKey };
107
+ // Compute aggregate fields
108
+ for (const [key, aggProp] of Object.entries(aggregateFields)) {
109
+ if (aggProp.relationship === undefined) {
110
+ // Field-level aggregate — compute over group records directly
111
+ rawData[key] = aggProp.compute(groupRecords);
112
+ }
113
+ else {
114
+ // Relationship aggregate — flatten related records across all group members
115
+ const allRelated = [];
116
+ for (const record of groupRecords) {
117
+ const relatedRecords = record.__relationships?.[aggProp.relationship]
118
+ || record[aggProp.relationship];
119
+ if (Array.isArray(relatedRecords)) {
120
+ allRelated.push(...relatedRecords);
121
+ }
122
+ }
123
+ rawData[key] = aggProp.compute(allRelated);
124
+ }
125
+ }
126
+ // Apply resolve map entries — functions receive the group array
127
+ for (const [key, resolver] of Object.entries(resolveMap)) {
128
+ if (typeof resolver === 'function') {
129
+ rawData[key] = resolver(groupRecords);
130
+ }
131
+ else if (typeof resolver === 'string') {
132
+ // String path — take value from first record in group
133
+ const first = groupRecords[0];
134
+ rawData[key] = get(first.__data || first, resolver)
135
+ ?? get(first, resolver);
136
+ }
137
+ }
138
+ // Map regular attr fields from first record if not already set
139
+ for (const key of Object.keys(regularFields)) {
140
+ if (rawData[key] !== undefined)
141
+ continue;
142
+ const first = groupRecords[0];
143
+ const sourceValue = first.__data?.[key] ?? first[key];
144
+ if (sourceValue !== undefined) {
145
+ rawData[key] = sourceValue;
146
+ }
147
+ }
148
+ // Clear existing record from store to allow re-resolution
149
+ const viewStore = store.get(this.viewName);
150
+ if (viewStore?.has(rawData.id)) {
151
+ viewStore.delete(rawData.id);
152
+ }
153
+ const record = createRecord(this.viewName, rawData, { isDbRecord: true });
154
+ results.push(record);
155
+ }
156
+ return results;
157
+ }
158
+ async resolveOne(id) {
159
+ const all = await this.resolveAll();
160
+ return all.find((record) => {
161
+ const r = record;
162
+ return r.id === id || r.id == id;
163
+ });
164
+ }
165
+ }
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,20 +4,38 @@
4
4
  "stonyx-async",
5
5
  "stonyx-module"
6
6
  ],
7
- "version": "0.2.1-beta.82",
7
+ "version": "0.2.1-beta.84",
8
8
  "description": "",
9
- "main": "src/main.js",
9
+ "main": "dist/index.js",
10
10
  "type": "module",
11
11
  "bin": {
12
- "stonyx-orm": "./src/cli.js"
12
+ "stonyx-orm": "./dist/cli.js"
13
13
  },
14
14
  "exports": {
15
- ".": "./src/index.js",
16
- "./db": "./src/exports/db.js",
17
- "./standalone-db": "./src/standalone-db.js",
18
- "./migrate": "./src/migrate.js",
19
- "./commands": "./src/commands.js",
20
- "./hooks": "./src/hooks.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
+ }
21
39
  },
22
40
  "repository": {
23
41
  "type": "git",
@@ -29,6 +47,7 @@
29
47
  "Stone Costa <stone.costa@synamicd.com>"
30
48
  ],
31
49
  "files": [
50
+ "dist",
32
51
  "src",
33
52
  "config",
34
53
  "README.md"
@@ -65,12 +84,16 @@
65
84
  "devDependencies": {
66
85
  "@stonyx/rest-server": "0.2.1-beta.30",
67
86
  "@stonyx/utils": "0.2.3-beta.7",
87
+ "@types/node": "^25.6.0",
68
88
  "mysql2": "^3.20.0",
69
89
  "pg": "^8.20.0",
70
90
  "qunit": "^2.24.1",
71
- "sinon": "^21.0.0"
91
+ "sinon": "^21.0.0",
92
+ "typescript": "^5.8.3"
72
93
  },
73
94
  "scripts": {
74
- "test": "stonyx test"
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'"
75
98
  }
76
99
  }
@@ -1,5 +1,19 @@
1
+ type AggregateType = 'count' | 'avg' | 'sum' | 'min' | 'max';
2
+
3
+ interface AggregateRecord {
4
+ __data?: Record<string, unknown>;
5
+ [key: string]: unknown;
6
+ }
7
+
1
8
  export class AggregateProperty {
2
- constructor(aggregateType, relationship, field) {
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) {
3
17
  this.aggregateType = aggregateType;
4
18
  this.relationship = relationship;
5
19
  this.field = field;
@@ -7,7 +21,7 @@ export class AggregateProperty {
7
21
  this.resultType = aggregateType === 'avg' ? 'float' : 'number';
8
22
  }
9
23
 
10
- compute(relatedRecords) {
24
+ compute(relatedRecords: AggregateRecord[]): number | null {
11
25
  if (!relatedRecords || !Array.isArray(relatedRecords) || relatedRecords.length === 0) {
12
26
  if (this.aggregateType === 'min' || this.aggregateType === 'max') return null;
13
27
  return 0;
@@ -19,7 +33,7 @@ export class AggregateProperty {
19
33
 
20
34
  case 'sum':
21
35
  return relatedRecords.reduce((acc, record) => {
22
- const val = parseFloat(record?.__data?.[this.field] ?? record?.[this.field]);
36
+ const val = parseFloat(record?.__data?.[this.field!] as string ?? record?.[this.field!] as string);
23
37
  return acc + (isNaN(val) ? 0 : val);
24
38
  }, 0);
25
39
 
@@ -27,7 +41,7 @@ export class AggregateProperty {
27
41
  let sum = 0;
28
42
  let count = 0;
29
43
  for (const record of relatedRecords) {
30
- const val = parseFloat(record?.__data?.[this.field] ?? record?.[this.field]);
44
+ const val = parseFloat(record?.__data?.[this.field!] as string ?? record?.[this.field!] as string);
31
45
  if (!isNaN(val)) {
32
46
  sum += val;
33
47
  count++;
@@ -37,18 +51,18 @@ export class AggregateProperty {
37
51
  }
38
52
 
39
53
  case 'min': {
40
- let min = null;
54
+ let min: number | null = null;
41
55
  for (const record of relatedRecords) {
42
- const val = parseFloat(record?.__data?.[this.field] ?? record?.[this.field]);
56
+ const val = parseFloat(record?.__data?.[this.field!] as string ?? record?.[this.field!] as string);
43
57
  if (!isNaN(val) && (min === null || val < min)) min = val;
44
58
  }
45
59
  return min;
46
60
  }
47
61
 
48
62
  case 'max': {
49
- let max = null;
63
+ let max: number | null = null;
50
64
  for (const record of relatedRecords) {
51
- const val = parseFloat(record?.__data?.[this.field] ?? record?.[this.field]);
65
+ const val = parseFloat(record?.__data?.[this.field!] as string ?? record?.[this.field!] as string);
52
66
  if (!isNaN(val) && (max === null || val > max)) max = val;
53
67
  }
54
68
  return max;
@@ -60,32 +74,32 @@ export class AggregateProperty {
60
74
  }
61
75
  }
62
76
 
63
- export function count(relationship) {
77
+ export function count(relationship: string): AggregateProperty {
64
78
  return new AggregateProperty('count', relationship);
65
79
  }
66
80
 
67
- export function avg(relationshipOrField, field) {
81
+ export function avg(relationshipOrField: string, field?: string): AggregateProperty {
68
82
  if (field !== undefined) {
69
83
  return new AggregateProperty('avg', relationshipOrField, field);
70
84
  }
71
85
  return new AggregateProperty('avg', undefined, relationshipOrField);
72
86
  }
73
87
 
74
- export function sum(relationshipOrField, field) {
88
+ export function sum(relationshipOrField: string, field?: string): AggregateProperty {
75
89
  if (field !== undefined) {
76
90
  return new AggregateProperty('sum', relationshipOrField, field);
77
91
  }
78
92
  return new AggregateProperty('sum', undefined, relationshipOrField);
79
93
  }
80
94
 
81
- export function min(relationshipOrField, field) {
95
+ export function min(relationshipOrField: string, field?: string): AggregateProperty {
82
96
  if (field !== undefined) {
83
97
  return new AggregateProperty('min', relationshipOrField, field);
84
98
  }
85
99
  return new AggregateProperty('min', undefined, relationshipOrField);
86
100
  }
87
101
 
88
- export function max(relationshipOrField, field) {
102
+ export function max(relationshipOrField: string, field?: string): AggregateProperty {
89
103
  if (field !== undefined) {
90
104
  return new AggregateProperty('max', relationshipOrField, field);
91
105
  }
@@ -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) {
@@ -1,32 +1,50 @@
1
- import { createRecord, relationships, store } from '@stonyx/orm';
2
- import { getRelationships } from './relationships.js';
1
+ import { createRecord, store } from '@stonyx/orm';
2
+ import { getRelationships, getHasManyRegistry, getPendingRegistry, getPendingBelongsToRegistry } from './relationships.js';
3
+ import type { SourceRecord } from './types/orm-types.js';
3
4
 
4
- function getOrSet(map, key, defaultValue) {
5
+ function getOrSet<K, V>(map: Map<K, V>, key: K, defaultValue: V): V {
5
6
  if (!map.has(key)) map.set(key, defaultValue);
6
- return map.get(key);
7
+ return map.get(key)!;
7
8
  }
8
9
 
9
- export default function belongsTo(modelName) {
10
- const hasManyRelationships = relationships.get('hasMany');
11
- const pendingHasManyQueue = relationships.get('pending');
12
- const pendingBelongsToQueue = relationships.get('pendingBelongsTo');
10
+ interface BelongsToOptions {
11
+ _relationshipKey?: string;
12
+ [key: string]: unknown;
13
+ }
14
+
15
+ interface PendingBelongsToEntry {
16
+ sourceRecord: SourceRecord;
17
+ sourceModelName: string;
18
+ relationshipKey: string | undefined;
19
+ relationshipId: unknown;
20
+ }
13
21
 
14
- const fn = (sourceRecord, rawData, options) => {
22
+ type RelationshipHandler = ((sourceRecord: SourceRecord, rawData: unknown, options: BelongsToOptions) => unknown) & {
23
+ __relatedModelName: string;
24
+ __relationshipType: 'belongsTo';
25
+ };
26
+
27
+ export default function belongsTo(modelName: string): RelationshipHandler {
28
+ const hasManyRelationships = getHasManyRegistry();
29
+ const pendingHasManyQueue = getPendingRegistry();
30
+ const pendingBelongsToQueue = getPendingBelongsToRegistry();
31
+
32
+ const fn = (sourceRecord: SourceRecord, rawData: unknown, options: BelongsToOptions): unknown => {
15
33
  if (!rawData) return null;
16
34
 
17
35
  const { __name: sourceModelName } = sourceRecord.__model;
18
36
  const relationshipId = sourceRecord.id;
19
37
  const relationshipKey = options._relationshipKey;
20
- const relationship = getRelationships('belongsTo', sourceModelName, modelName, relationshipId);
38
+ const relationship = getRelationships('belongsTo', sourceModelName, modelName, relationshipId as string) as Map<unknown, unknown>;
21
39
  const modelStore = store.get(modelName);
22
40
 
23
41
  // Try to get existing record
24
- let output;
42
+ let output: unknown;
25
43
 
26
44
  if (typeof rawData === 'object') {
27
- output = createRecord(modelName, rawData, options);
45
+ output = createRecord(modelName, rawData as Record<string, unknown>, options);
28
46
  } else if (modelStore) {
29
- output = modelStore.get(rawData);
47
+ output = modelStore.get(rawData as number | string);
30
48
  }
31
49
 
32
50
  // If not found and is a string ID, register as pending
@@ -51,7 +69,7 @@ export default function belongsTo(modelName) {
51
69
  relationship.set(relationshipId, output || {});
52
70
 
53
71
  // Populate hasMany side if the relationship is defined
54
- const otherSide = hasManyRelationships.get(modelName)?.get(sourceModelName)?.get(output?.id);
72
+ const otherSide = hasManyRelationships.get(modelName)?.get(sourceModelName)?.get((output as SourceRecord)?.id) as unknown[] | undefined;
55
73
 
56
74
  if (otherSide) {
57
75
  otherSide.push(sourceRecord);
@@ -63,8 +81,9 @@ export default function belongsTo(modelName) {
63
81
  }
64
82
 
65
83
  return output;
66
- }
84
+ };
67
85
 
68
86
  Object.defineProperty(fn, '__relatedModelName', { value: modelName });
69
- return fn;
70
- }
87
+ Object.defineProperty(fn, '__relationshipType', { value: 'belongsTo' as const });
88
+ return fn as RelationshipHandler;
89
+ }
@@ -13,17 +13,23 @@
13
13
  * stonyx-orm delete <collection> <id>
14
14
  *
15
15
  * Configuration (environment variables):
16
- * DB_MODE 'file' or 'directory' (default: 'directory')
17
- * DB_PATH Path to db.json (default: 'db.json')
18
- * DB_DIRECTORY Directory name for collection files (default: 'db')
16
+ * DB_MODE -- 'file' or 'directory' (default: 'directory')
17
+ * DB_PATH -- Path to db.json (default: 'db.json')
18
+ * DB_DIRECTORY -- Directory name for collection files (default: 'db')
19
19
  *
20
20
  * Configuration (CLI flag):
21
- * --config <path> Path to a JSON config file with { mode, dbPath, directory }
21
+ * --config <path> -- Path to a JSON config file with { mode, dbPath, directory }
22
22
  */
23
23
 
24
24
  import StandaloneDB from './standalone-db.js';
25
25
  import fs from 'fs/promises';
26
26
 
27
+ interface CLIConfig {
28
+ mode: 'file' | 'directory';
29
+ dbPath: string;
30
+ directory: string;
31
+ }
32
+
27
33
  const USAGE = `Usage: stonyx-orm <command> [options]
28
34
 
29
35
  Commands:
@@ -41,8 +47,8 @@ Environment variables:
41
47
  DB_PATH Path to db.json (default: 'db.json')
42
48
  DB_DIRECTORY Directory name for collection files (default: 'db')`;
43
49
 
44
- async function loadConfig(args) {
45
- const config = {};
50
+ async function loadConfig(args: string[]): Promise<CLIConfig> {
51
+ const config: Record<string, string> = {};
46
52
 
47
53
  // Check for --config flag
48
54
  const configIndex = args.indexOf('--config');
@@ -54,7 +60,7 @@ async function loadConfig(args) {
54
60
  const content = await fs.readFile(configPath, 'utf-8');
55
61
  Object.assign(config, JSON.parse(content));
56
62
  } catch (err) {
57
- console.error(`Error reading config file '${configPath}': ${err.message}`);
63
+ console.error(`Error reading config file '${configPath}': ${err instanceof Error ? err.message : String(err)}`);
58
64
  process.exit(1);
59
65
  }
60
66
 
@@ -64,13 +70,13 @@ async function loadConfig(args) {
64
70
 
65
71
  // Environment variables override config file, config file overrides defaults
66
72
  return {
67
- mode: process.env.DB_MODE || config.mode || 'directory',
73
+ mode: (process.env.DB_MODE || config.mode || 'directory') as 'file' | 'directory',
68
74
  dbPath: process.env.DB_PATH || config.dbPath || 'db.json',
69
75
  directory: process.env.DB_DIRECTORY || config.directory || 'db',
70
76
  };
71
77
  }
72
78
 
73
- function parseArgs(argv) {
79
+ function parseArgs(argv: string[]): string[] {
74
80
  // Strip node binary and script path
75
81
  const args = argv.slice(2);
76
82
 
@@ -82,7 +88,7 @@ function parseArgs(argv) {
82
88
  return args;
83
89
  }
84
90
 
85
- async function run() {
91
+ async function run(): Promise<void> {
86
92
  const args = parseArgs(process.argv);
87
93
  const config = await loadConfig(args);
88
94
  const db = new StandaloneDB(config);
@@ -169,7 +175,7 @@ async function run() {
169
175
  process.exit(1);
170
176
  }
171
177
  } catch (err) {
172
- console.error(`Error: ${err.message}`);
178
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
173
179
  process.exit(1);
174
180
  }
175
181
  }