@aetherframework/database 1.1.0 → 1.1.2

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 (47) hide show
  1. package/examples/mysql-test-pressure.js +1530 -0
  2. package/examples/test-direct.js +116 -0
  3. package/examples/transaction_example.js +127 -0
  4. package/package.json +3 -1
  5. package/src/DatabaseManager.js +565 -0
  6. package/src/core/ConnectionManager.js +351 -0
  7. package/src/core/DatabaseFactory.js +188 -0
  8. package/src/core/MongoQueryBuilder.js +576 -0
  9. package/src/core/PluginManager.js +968 -0
  10. package/src/core/QueryBuilder.js +4394 -0
  11. package/src/core/TransactionManager.js +40 -0
  12. package/src/drivers/clickhouse-driver.js +272 -0
  13. package/src/drivers/index.js +273 -0
  14. package/src/drivers/mongodb-driver.js +87 -0
  15. package/src/drivers/mssql-driver.js +117 -0
  16. package/src/drivers/mysql-driver.js +169 -0
  17. package/src/drivers/oracle-driver.js +101 -0
  18. package/src/drivers/postgres-driver.js +234 -0
  19. package/src/drivers/redis-driver.js +52 -0
  20. package/src/drivers/sqlite-driver.js +67 -0
  21. package/src/middleware/connection-pool.js +455 -0
  22. package/src/middleware/performance-monitor.js +652 -0
  23. package/src/middleware/query-cache.js +500 -0
  24. package/src/middleware/query-logger.js +262 -0
  25. package/src/plugins/AuditPlugin.js +447 -0
  26. package/src/plugins/BasePlugin.js +418 -0
  27. package/src/plugins/BatchOperationPlugin.js +165 -0
  28. package/src/plugins/CachePlugin.js +407 -0
  29. package/src/plugins/CtePlugin.js +523 -0
  30. package/src/plugins/DistributedPlugin.js +543 -0
  31. package/src/plugins/EncryptionPlugin.js +211 -0
  32. package/src/plugins/FullTextSearchPlugin.js +164 -0
  33. package/src/plugins/GeospatialPlugin.js +219 -0
  34. package/src/plugins/GraphQLPlugin.js +162 -0
  35. package/src/plugins/HookPlugin.js +211 -0
  36. package/src/plugins/JsonPlugin.js +366 -0
  37. package/src/plugins/OptimisticLockPlugin.js +374 -0
  38. package/src/plugins/PerformancePlugin.js +175 -0
  39. package/src/plugins/ResiliencePlugin.js +114 -0
  40. package/src/plugins/ShardingPlugin.js +227 -0
  41. package/src/plugins/SoftDeletePlugin.js +258 -0
  42. package/src/plugins/SyncPlugin.js +373 -0
  43. package/src/plugins/VersioningPlugin.js +314 -0
  44. package/src/plugins/WindowFunctionPlugin.js +343 -0
  45. package/src/utils/config-loader.js +632 -0
  46. package/src/utils/error-handler.js +724 -0
  47. package/src/utils/migration-runner.js +1066 -0
@@ -0,0 +1,162 @@
1
+ /**
2
+ * @license MIT
3
+ * Copyright (c) 2026-present AetherFramework Contributors.
4
+ * SPDX-License-Identifier: MIT
5
+ * @module @aetherframework/database/plugin/GrapthQLPlugin
6
+ */
7
+ import { BasePlugin } from "./BasePlugin.js";
8
+
9
+ /**
10
+ * GraphQL Plugin - Provides GraphQL-style field selection and relation loading
11
+ */
12
+ export class GraphQLPlugin extends BasePlugin {
13
+ constructor(queryBuilder) {
14
+ super(queryBuilder);
15
+ this.pluginName = "GraphQLPlugin";
16
+ this.graphqlFields = null;
17
+ }
18
+
19
+ _registerMethods() {
20
+ // Register GraphQL-style methods to QueryBuilder
21
+ this.queryBuilder.selectFields = this.selectFields.bind(this);
22
+ this.queryBuilder.extractGraphQLFields = this.extractGraphQLFields.bind(this);
23
+ this.queryBuilder.with = this.with.bind(this);
24
+ this.queryBuilder.executeWithRelations = this.executeWithRelations.bind(this);
25
+ this.queryBuilder.loadRelation = this.loadRelation.bind(this);
26
+ }
27
+
28
+ /**
29
+ * GraphQL-style field selection
30
+ * @param {string|Array|Object} fields - Fields to select
31
+ * @returns {QueryBuilder} Query builder instance
32
+ */
33
+ selectFields(fields) {
34
+ if (typeof fields === "string") {
35
+ // Comma-separated string
36
+ this.queryBuilder.query.columns = fields.split(",").map((f) => f.trim());
37
+ } else if (Array.isArray(fields)) {
38
+ // Array of fields
39
+ this.queryBuilder.query.columns = fields;
40
+ } else if (typeof fields === "object") {
41
+ // GraphQL-style object with nested fields
42
+ this.graphqlFields = fields;
43
+ this.queryBuilder.query.columns = this.extractGraphQLFields(fields);
44
+ }
45
+ return this.queryBuilder;
46
+ }
47
+
48
+ /**
49
+ * Extract fields from GraphQL-style selection
50
+ * @param {Object} fields - GraphQL fields object
51
+ * @param {string} prefix - Field prefix
52
+ * @returns {Array} Extracted fields
53
+ */
54
+ extractGraphQLFields(fields, prefix = "") {
55
+ const extracted = [];
56
+
57
+ for (const [key, value] of Object.entries(fields)) {
58
+ if (value === true || value === 1) {
59
+ // Simple field
60
+ extracted.push(prefix ? `${prefix}.${key}` : key);
61
+ } else if (typeof value === "object") {
62
+ // Nested field or sub-query
63
+ if (value.fields) {
64
+ // Sub-query with fields
65
+ const subFields = this.extractGraphQLFields(value.fields, key);
66
+ extracted.push(...subFields);
67
+ } else {
68
+ // Nested object
69
+ const nestedFields = this.extractGraphQLFields(value, key);
70
+ extracted.push(...nestedFields);
71
+ }
72
+ }
73
+ }
74
+
75
+ return extracted;
76
+ }
77
+
78
+ /**
79
+ * Include related data (eager loading)
80
+ * @param {string|Object} relations - Relations to include
81
+ * @returns {QueryBuilder} Query builder instance
82
+ */
83
+ with(relations) {
84
+ if (typeof relations === "string") {
85
+ this.queryBuilder.query.with = [relations];
86
+ } else if (Array.isArray(relations)) {
87
+ this.queryBuilder.query.with = relations;
88
+ } else if (typeof relations === "object") {
89
+ this.queryBuilder.query.with = Object.keys(relations);
90
+ this.graphqlFields = relations;
91
+ }
92
+ return this.queryBuilder;
93
+ }
94
+
95
+ /**
96
+ * Execute query with GraphQL-style field selection
97
+ * @returns {Promise<Object>} Query result with nested relations
98
+ */
99
+ async executeWithRelations() {
100
+ const result = await this.queryBuilder.execute();
101
+
102
+ if (!this.queryBuilder.query.with || !result.rows || result.rows.length === 0) {
103
+ return result;
104
+ }
105
+
106
+ // Load relations for each row
107
+ for (const relation of this.queryBuilder.query.with) {
108
+ await this.loadRelation(result.rows, relation);
109
+ }
110
+
111
+ return result;
112
+ }
113
+
114
+ /**
115
+ * Load relation for rows
116
+ * @param {Array} rows - Parent rows
117
+ * @param {string} relation - Relation name
118
+ * @returns {Promise<void>}
119
+ */
120
+ async loadRelation(rows, relation) {
121
+ const relationConfig = this.graphqlFields?.[relation];
122
+ if (!relationConfig) return;
123
+
124
+ // Extract parent IDs
125
+ const parentIds = rows.map((row) => row.id).filter((id) => id);
126
+ if (parentIds.length === 0) return;
127
+
128
+ // Build relation query
129
+ const relationQuery = new this.queryBuilder.constructor(
130
+ relation,
131
+ this.queryBuilder.connection,
132
+ this.queryBuilder.dialect,
133
+ );
134
+
135
+ // Apply GraphQL field selection if specified
136
+ if (relationConfig.fields) {
137
+ relationQuery.selectFields(relationConfig.fields);
138
+ }
139
+
140
+ // Add WHERE condition for relation
141
+ const foreignKey =
142
+ relationConfig.foreignKey || `${this.queryBuilder.tableName.slice(0, -1)}_id`;
143
+ const relatedRows = await relationQuery
144
+ .whereIn(foreignKey, parentIds)
145
+ .get();
146
+
147
+ // Group related rows by foreign key
148
+ const relatedByParent = {};
149
+ relatedRows.forEach((row) => {
150
+ const parentId = row[foreignKey];
151
+ if (!relatedByParent[parentId]) {
152
+ relatedByParent[parentId] = [];
153
+ }
154
+ relatedByParent[parentId].push(row);
155
+ });
156
+
157
+ // Attach related rows to parent rows
158
+ rows.forEach((row) => {
159
+ row[relation] = relatedByParent[row.id] || [];
160
+ });
161
+ }
162
+ }
@@ -0,0 +1,211 @@
1
+ /**
2
+ * @license MIT
3
+ * Copyright (c) 2026-present AetherFramework Contributors.
4
+ * SPDX-License-Identifier: MIT
5
+ * @module @aetherframework/database/plugin/HoolPlugin
6
+ */
7
+ import { BasePlugin } from './BasePlugin.js';
8
+
9
+ export class HookPlugin extends BasePlugin {
10
+ constructor(queryBuilder) {
11
+ super(queryBuilder);
12
+ this.hooks = {};
13
+ this.validationRules = {};
14
+ }
15
+
16
+ /**
17
+ * Register plugin methods to QueryBuilder
18
+ * @protected
19
+ */
20
+ _registerMethods() {
21
+ // Register hook management methods
22
+ this.queryBuilder.addHook = this.addHook.bind(this);
23
+ this.queryBuilder.triggerHook = this.triggerHook.bind(this);
24
+ this.queryBuilder.setValidationRules = this.setValidationRules.bind(this);
25
+ this.queryBuilder.validateData = this.validateData.bind(this);
26
+
27
+ // Register specific hook shortcuts
28
+ this.queryBuilder.beforeInsert = (callback) => this.addHook('beforeInsert', callback);
29
+ this.queryBuilder.afterInsert = (callback) => this.addHook('afterInsert', callback);
30
+ this.queryBuilder.beforeUpdate = (callback) => this.addHook('beforeUpdate', callback);
31
+ this.queryBuilder.afterUpdate = (callback) => this.addHook('afterUpdate', callback);
32
+ this.queryBuilder.beforeDelete = (callback) => this.addHook('beforeDelete', callback);
33
+ this.queryBuilder.afterDelete = (callback) => this.addHook('afterDelete', callback);
34
+ this.queryBuilder.beforeSelect = (callback) => this.addHook('beforeSelect', callback);
35
+ this.queryBuilder.afterSelect = (callback) => this.addHook('afterSelect', callback);
36
+ this.queryBuilder.onBeforeInsert = (callback) => this.addHook('beforeInsert', callback);
37
+ this.queryBuilder.onAfterInsert = (callback) => this.addHook('afterInsert', callback);
38
+ this.queryBuilder.onBeforeUpdate = (callback) => this.addHook('beforeUpdate', callback);
39
+ this.queryBuilder.onAfterUpdate = (callback) => this.addHook('afterUpdate', callback);
40
+ this.queryBuilder.onBeforeDelete = (callback) => this.addHook('beforeDelete', callback);
41
+ this.queryBuilder.onAfterDelete = (callback) => this.addHook('afterDelete', callback);
42
+ this.queryBuilder.onBeforeSelect = (callback) => this.addHook('beforeSelect', callback);
43
+ this.queryBuilder.onAfterSelect = (callback) => this.addHook('afterSelect', callback);
44
+ }
45
+
46
+ /**
47
+ * Add a hook for specific event
48
+ * @param {string} event - Hook event name
49
+ * @param {Function} callback - Hook callback function
50
+ * @returns {QueryBuilder} Query builder instance
51
+ */
52
+ addHook(event, callback) {
53
+ if (!this.hooks[event]) {
54
+ this.hooks[event] = [];
55
+ }
56
+ this.hooks[event].push(callback);
57
+ return this.queryBuilder;
58
+ }
59
+
60
+ /**
61
+ * Trigger hooks for specific event
62
+ * @param {string} event - Hook event name
63
+ * @param {*} data - Data to pass to hooks
64
+ * @returns {Promise<void>}
65
+ */
66
+ async triggerHook(event, data) {
67
+ if (!this.hooks[event]) {
68
+ return;
69
+ }
70
+
71
+ for (const hook of this.hooks[event]) {
72
+ const result = await hook(data, this.queryBuilder);
73
+ if (result === false) {
74
+ throw new Error(`Hook ${event} returned false, operation cancelled`);
75
+ }
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Set validation rules for data
81
+ * @param {Object} rules - Validation rules object
82
+ * @returns {QueryBuilder} Query builder instance
83
+ */
84
+ setValidationRules(rules) {
85
+ this.validationRules = rules;
86
+ return this.queryBuilder;
87
+ }
88
+
89
+ /**
90
+ * Validate data against rules
91
+ * @param {Object} data - Data to validate
92
+ * @returns {Array} Array of validation errors
93
+ */
94
+ validateData(data) {
95
+ const errors = [];
96
+
97
+ for (const [field, rule] of Object.entries(this.validationRules)) {
98
+ const value = data[field];
99
+
100
+ // Check required field
101
+ if (rule.required && (value === undefined || value === null || value === '')) {
102
+ errors.push(`${field} is required`);
103
+ continue;
104
+ }
105
+
106
+ // Skip validation if value is not provided and not required
107
+ if (value === undefined || value === null) {
108
+ continue;
109
+ }
110
+
111
+ // Type validation
112
+ if (rule.type) {
113
+ const typeCheck = this._validateType(value, rule.type);
114
+ if (!typeCheck.valid) {
115
+ errors.push(`${field} must be ${rule.type}, got ${typeCheck.actual}`);
116
+ continue;
117
+ }
118
+ }
119
+
120
+ // Minimum value validation
121
+ if (rule.min !== undefined) {
122
+ if (typeof value === 'number' && value < rule.min) {
123
+ errors.push(`${field} must be at least ${rule.min}`);
124
+ } else if (typeof value === 'string' && value.length < rule.min) {
125
+ errors.push(`${field} must be at least ${rule.min} characters`);
126
+ }
127
+ }
128
+
129
+ // Maximum value validation
130
+ if (rule.max !== undefined) {
131
+ if (typeof value === 'number' && value > rule.max) {
132
+ errors.push(`${field} must be at most ${rule.max}`);
133
+ } else if (typeof value === 'string' && value.length > rule.max) {
134
+ errors.push(`${field} must be at most ${rule.max} characters`);
135
+ }
136
+ }
137
+
138
+ // Pattern validation
139
+ if (rule.pattern && !rule.pattern.test(value)) {
140
+ errors.push(`${field} format is invalid`);
141
+ }
142
+
143
+ // Enum validation
144
+ if (rule.enum && !rule.enum.includes(value)) {
145
+ errors.push(`${field} must be one of: ${rule.enum.join(', ')}`);
146
+ }
147
+
148
+ // Custom validation function
149
+ if (rule.validate && typeof rule.validate === 'function') {
150
+ const customResult = rule.validate(value, data);
151
+ if (customResult !== true) {
152
+ errors.push(`${field}: ${customResult}`);
153
+ }
154
+ }
155
+ }
156
+
157
+ return errors;
158
+ }
159
+
160
+ /**
161
+ * Validate data type
162
+ * @private
163
+ */
164
+ _validateType(value, expectedType) {
165
+ const actualType = typeof value;
166
+
167
+ // Handle special cases
168
+ if (expectedType === 'array' && Array.isArray(value)) {
169
+ return { valid: true, actual: 'array' };
170
+ }
171
+
172
+ if (expectedType === 'object' && value !== null && !Array.isArray(value) && actualType === 'object') {
173
+ return { valid: true, actual: 'object' };
174
+ }
175
+
176
+ if (expectedType === 'integer' && Number.isInteger(value)) {
177
+ return { valid: true, actual: 'integer' };
178
+ }
179
+
180
+ if (expectedType === 'float' && typeof value === 'number' && !Number.isInteger(value)) {
181
+ return { valid: true, actual: 'float' };
182
+ }
183
+
184
+ if (expectedType === 'date' && value instanceof Date) {
185
+ return { valid: true, actual: 'date' };
186
+ }
187
+
188
+ if (expectedType === 'email' && typeof value === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
189
+ return { valid: true, actual: 'email' };
190
+ }
191
+
192
+ if (expectedType === 'url' && typeof value === 'string' && /^https?:\/\/[^\s$.?#].[^\s]*$/.test(value)) {
193
+ return { valid: true, actual: 'url' };
194
+ }
195
+
196
+ return { valid: actualType === expectedType, actual: actualType };
197
+ }
198
+
199
+ /**
200
+ * Get plugin metadata
201
+ * @returns {Object} Plugin metadata
202
+ */
203
+ getMetadata() {
204
+ return {
205
+ ...super.getMetadata(),
206
+ description: 'Hook and validation plugin for QueryBuilder',
207
+ hooks: Object.keys(this.hooks),
208
+ validationRules: Object.keys(this.validationRules)
209
+ };
210
+ }
211
+ }