vasuzex 2.1.2 → 2.1.4

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 (87) hide show
  1. package/.ai-memory/LOGGER_STRICT_POLICY.md +201 -0
  2. package/.ai-memory/neastore-feature-mapping.md +1114 -0
  3. package/bin/create-vasuzex.js +5 -2
  4. package/examples/runtime-config-examples.js +309 -0
  5. package/framework/Config/DatabaseConfigService.js +348 -0
  6. package/framework/Config/DatabaseConfigServiceProvider.js +69 -0
  7. package/framework/Console/Commands/generate-app.js +97 -4
  8. package/framework/Console/Commands/generate-media-server.js +2 -1
  9. package/framework/Console/Commands/utils/mediaServerTemplates.js +3 -2
  10. package/framework/Console/Commands/utils/webStructure.js +30 -21
  11. package/framework/Console/config/generator.config.js +3 -3
  12. package/framework/Console/plopfile.js +0 -8
  13. package/framework/Console/templates/api/app.js.hbs +5 -4
  14. package/framework/Console/templates/api/server.js.hbs +8 -2
  15. package/framework/Database/DatabaseServiceProvider.js +1 -1
  16. package/framework/Database/Model.js +9 -0
  17. package/framework/Exceptions/index.js +2 -1
  18. package/framework/Foundation/BaseApp.js +19 -0
  19. package/framework/Foundation/BaseService.js +95 -0
  20. package/framework/Foundation/Container.js +18 -3
  21. package/framework/Foundation/Providers/index.js +0 -1
  22. package/framework/Foundation/ServiceProvider.js +42 -0
  23. package/framework/Http/asyncHandler.js +26 -0
  24. package/framework/Http/index.js +1 -0
  25. package/framework/Services/Log/LogManager.js +26 -5
  26. package/framework/Services/Log/LogServiceProvider.js +48 -0
  27. package/framework/Services/Log/index.js +1 -0
  28. package/framework/Services/Mail/MailServiceProvider.js +36 -0
  29. package/framework/Services/Mail/index.js +1 -0
  30. package/framework/Services/Media/MediaServiceProvider.js +2 -2
  31. package/framework/Services/Payment/PaymentServiceProvider.js +35 -0
  32. package/framework/Services/Payment/index.js +1 -0
  33. package/framework/Services/Security/SecurityService.js +253 -0
  34. package/framework/Services/Security/SecurityServiceProvider.js +33 -0
  35. package/framework/Services/Security/index.js +9 -0
  36. package/framework/Services/Storage/StorageManager.js +7 -1
  37. package/framework/Services/Storage/StorageServiceProvider.js +36 -0
  38. package/framework/Services/Storage/index.js +1 -0
  39. package/framework/Services/Upload/UploadManager.js +179 -0
  40. package/framework/Services/index.js +1 -0
  41. package/framework/Support/Facades/Security.js +14 -0
  42. package/framework/Support/Facades/index.js +1 -0
  43. package/framework/Support/Helpers/index.js +1 -0
  44. package/framework/Support/Helpers/utilities.js +348 -0
  45. package/framework/index.js +2 -0
  46. package/frontend/client/Config/ConfigLoader.js +52 -10
  47. package/frontend/client/Http/ApiHelpers.js +99 -0
  48. package/frontend/client/Http/index.js +1 -0
  49. package/frontend/client/index.js +1 -1
  50. package/frontend/client/package.json +14 -66
  51. package/frontend/client/package.json.backup +41 -0
  52. package/frontend/react-ui/components/Avatars/GradientAvatar.jsx +255 -0
  53. package/frontend/react-ui/components/Avatars/index.js +66 -0
  54. package/frontend/react-ui/components/BreadCrumb/BreadCrumb.jsx +69 -0
  55. package/frontend/react-ui/components/BreadCrumb/index.js +2 -0
  56. package/frontend/react-ui/components/DataTable/ActionDefaults.jsx +171 -0
  57. package/frontend/react-ui/components/DataTable/DataTable.jsx +202 -328
  58. package/frontend/react-ui/components/DataTable/Filters.jsx +69 -56
  59. package/frontend/react-ui/components/DataTable/Pagination.jsx +59 -140
  60. package/frontend/react-ui/components/DataTable/TableActions.jsx +11 -20
  61. package/frontend/react-ui/components/DataTable/TableBody.jsx +168 -168
  62. package/frontend/react-ui/components/DataTable/TableHeader.jsx +93 -96
  63. package/frontend/react-ui/components/DataTable/TableState.jsx +33 -0
  64. package/frontend/react-ui/components/DataTable/index.js +10 -8
  65. package/frontend/react-ui/components/ImageLightbox/ImageLightbox.jsx +118 -0
  66. package/frontend/react-ui/components/ImageLightbox/index.js +1 -0
  67. package/frontend/react-ui/components/OrderTimeline/OrderTimeline.jsx +269 -0
  68. package/frontend/react-ui/components/OrderTimeline/index.js +1 -0
  69. package/frontend/react-ui/components/ReadMore/ReadMore.jsx +34 -0
  70. package/frontend/react-ui/components/ReadMore/index.js +1 -0
  71. package/frontend/react-ui/components/Switch/Switch.jsx +34 -0
  72. package/frontend/react-ui/components/Switch/index.js +1 -0
  73. package/frontend/react-ui/hooks/useAppConfig.js +58 -4
  74. package/frontend/react-ui/hooks/useLocalStorage.js +1 -1
  75. package/frontend/react-ui/hooks/useValidationErrors.js +1 -1
  76. package/frontend/react-ui/index.js +10 -0
  77. package/frontend/react-ui/package.json +17 -108
  78. package/frontend/react-ui/providers/ApiClientProvider.jsx +1 -1
  79. package/frontend/react-ui/providers/AppConfigProvider.jsx +212 -20
  80. package/frontend/react-ui/utils/formatters.js +193 -0
  81. package/frontend/react-ui/utils/index.js +30 -0
  82. package/frontend/react-ui/utils/logger.js +62 -0
  83. package/frontend/react-ui/utils/storage.js +90 -0
  84. package/frontend/react-ui/utils/swal.js +134 -0
  85. package/frontend/react-ui/utils/validation.js +207 -0
  86. package/package.json +6 -2
  87. package/framework/Foundation/Providers/LogServiceProvider.js +0 -33
@@ -0,0 +1,69 @@
1
+ /**
2
+ * DatabaseConfigServiceProvider
3
+ * Service provider for database-driven configuration
4
+ *
5
+ * Loads configurations from database (app_configs and system_configs)
6
+ * and merges them into the ConfigRepository during boot phase
7
+ *
8
+ * @example
9
+ * // In app.js
10
+ * import { DatabaseConfigServiceProvider } from '#framework/Config/DatabaseConfigServiceProvider.js';
11
+ *
12
+ * const providers = [
13
+ * DatabaseConfigServiceProvider,
14
+ * // ... other providers
15
+ * ];
16
+ */
17
+
18
+ import { ServiceProvider } from '../Foundation/ServiceProvider.js';
19
+ import { DatabaseConfigService } from './DatabaseConfigService.js';
20
+
21
+ export class DatabaseConfigServiceProvider extends ServiceProvider {
22
+ /**
23
+ * Register services in the container
24
+ */
25
+ register() {
26
+ // Register DatabaseConfigService as singleton
27
+ this.singleton('db.config', (app) => {
28
+ const environment = process.env.NODE_ENV || 'development';
29
+
30
+ return new DatabaseConfigService(app, {
31
+ environment,
32
+ cacheDuration: 5 * 60 * 1000, // 5 minutes
33
+ });
34
+ });
35
+
36
+ // Create alias for convenience
37
+ this.alias('database.config', 'db.config');
38
+ }
39
+
40
+ /**
41
+ * Bootstrap services
42
+ * Load database configs and merge into ConfigRepository
43
+ */
44
+ async boot() {
45
+ try {
46
+ const dbConfigService = this.make('db.config');
47
+
48
+ // Load configs from database
49
+ await dbConfigService.load();
50
+
51
+ console.log('[DatabaseConfigServiceProvider] Database configs loaded');
52
+
53
+ // Log cache stats in debug mode
54
+ if (this.config('app.debug', false)) {
55
+ const stats = dbConfigService.getCacheStats();
56
+ console.log('[DatabaseConfigServiceProvider] Cache stats:', {
57
+ appConfigs: stats.appConfigsCount,
58
+ systemConfigs: stats.systemConfigsCount,
59
+ cacheValid: stats.isValid,
60
+ });
61
+ }
62
+ } catch (error) {
63
+ console.error('[DatabaseConfigServiceProvider] Failed to load database configs:', error.message);
64
+ // Don't throw - app should continue with file-based configs
65
+ }
66
+ }
67
+ }
68
+
69
+ export default DatabaseConfigServiceProvider;
@@ -5,6 +5,8 @@
5
5
  */
6
6
 
7
7
  import { join } from 'path';
8
+ import { readdirSync, existsSync, readFileSync } from 'fs';
9
+ import { GENERATOR_CONFIG } from '../config/generator.config.js';
8
10
  import {
9
11
  // File Operations
10
12
  getAppPath,
@@ -38,6 +40,62 @@ import {
38
40
  displayWebNextSteps,
39
41
  } from './utils/index.js';
40
42
 
43
+ /**
44
+ * Get next available port for app type by checking existing apps
45
+ * API apps: start from 3000 and increment (3000, 3001, 3002...)
46
+ * Web apps: start from 4000 and increment (4000, 4001, 4002...)
47
+ */
48
+ function getNextAvailablePort(type) {
49
+ const projectRoot = process.cwd();
50
+ const appsDir = join(projectRoot, 'apps');
51
+
52
+ // Starting ports based on type
53
+ const startPort = type === 'api' ? GENERATOR_CONFIG.ports.apiStart : GENERATOR_CONFIG.ports.webStart;
54
+
55
+ // If apps directory doesn't exist, return start port
56
+ if (!existsSync(appsDir)) {
57
+ return startPort;
58
+ }
59
+
60
+ // Get all existing apps and their ports
61
+ const usedPorts = new Set();
62
+
63
+ try {
64
+ const appFolders = readdirSync(appsDir, { withFileTypes: true })
65
+ .filter(dirent => dirent.isDirectory())
66
+ .map(dirent => dirent.name);
67
+
68
+ // Check each app folder for app type subdirectories
69
+ for (const appFolder of appFolders) {
70
+ const appTypePath = join(appsDir, appFolder, type);
71
+ const envPath = join(appTypePath, '.env');
72
+
73
+ if (existsSync(envPath)) {
74
+ try {
75
+ const envContent = readFileSync(envPath, 'utf8');
76
+ const portMatch = envContent.match(/APP_PORT=(\d+)/);
77
+ if (portMatch) {
78
+ usedPorts.add(parseInt(portMatch[1]));
79
+ }
80
+ } catch (err) {
81
+ // Skip if can't read file
82
+ }
83
+ }
84
+ }
85
+ } catch (err) {
86
+ // If can't read apps directory, return start port
87
+ return startPort;
88
+ }
89
+
90
+ // Find next available port starting from startPort
91
+ let nextPort = startPort;
92
+ while (usedPorts.has(nextPort)) {
93
+ nextPort++;
94
+ }
95
+
96
+ return nextPort;
97
+ }
98
+
41
99
  /**
42
100
  * Main generate app function
43
101
  */
@@ -163,8 +221,8 @@ async function generateWebApp(name, targetDir, framework) {
163
221
  }
164
222
 
165
223
  /**
166
- * Generate common files (package.json, README, .gitignore)
167
- * NO .env files at app level - all config is centralized at root!
224
+ * Generate common files (package.json, README, .gitignore, .env)
225
+ * App-specific .env with APP_PORT/APP_URL based on app type
168
226
  */
169
227
  async function generateCommonFiles(name, type, targetDir, framework) {
170
228
  // package.json
@@ -184,6 +242,41 @@ async function generateCommonFiles(name, type, targetDir, framework) {
184
242
  generateGitignoreTemplate()
185
243
  );
186
244
 
187
- // NOTE: NO .env files here!
188
- // All configuration is in root .env file (Laravel monorepo style)
245
+ // App-specific .env file with correct port from GENERATOR_CONFIG
246
+ await generateAppEnvFile(name, type, targetDir);
247
+ }
248
+
249
+ /**
250
+ * Generate app-specific .env file with APP_PORT and APP_URL
251
+ * Automatically finds next available port for the app type
252
+ */
253
+ async function generateAppEnvFile(name, type, targetDir) {
254
+ // Get next available port based on app type (auto-increment)
255
+ const port = getNextAvailablePort(type);
256
+
257
+ console.log(`📌 Assigning port ${port} to ${name} ${type}`);
258
+
259
+ let envContent = `# ${name.toUpperCase()} ${type.toUpperCase()} Configuration
260
+ APP_PORT=${port}
261
+ APP_URL=http://localhost:${port}
262
+ `;
263
+
264
+ // For API apps, add CORS_ORIGIN pointing to corresponding web app port
265
+ if (type === 'api') {
266
+ const webPort = getNextAvailablePort('web');
267
+ envContent += `
268
+ # CORS Configuration
269
+ CORS_ORIGIN=http://localhost:${webPort}
270
+ `;
271
+ }
272
+
273
+ await writeFileContent(
274
+ join(targetDir, '.env'),
275
+ envContent
276
+ );
277
+
278
+ await writeFileContent(
279
+ join(targetDir, '.env.example'),
280
+ envContent
281
+ );
189
282
  }
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { join } from 'path';
7
+ import { GENERATOR_CONFIG } from '../config/generator.config.js';
7
8
  import {
8
9
  // File Operations
9
10
  getProjectRoot,
@@ -36,7 +37,7 @@ import {
36
37
  * Main generate media server function
37
38
  */
38
39
  export async function generateMediaServer(options) {
39
- const port = options.port || '4003';
40
+ const port = options.port || '5000'; // Media server is static service, hard-coded port
40
41
  const targetDir = join(getProjectRoot(), 'apps', 'media-server');
41
42
 
42
43
  console.log('🖼️ Generating Media Server...\n');
@@ -9,7 +9,7 @@
9
9
  export function generateMediaServerConfigTemplate(port) {
10
10
  return `module.exports = {
11
11
  name: 'media-server',
12
- port: process.env.MEDIA_SERVER_PORT || ${port},
12
+ port: process.env.APP_PORT || ${port},
13
13
  env: process.env.NODE_ENV || 'development',
14
14
  debug: process.env.NODE_ENV !== 'production',
15
15
  };
@@ -21,7 +21,8 @@ export function generateMediaServerConfigTemplate(port) {
21
21
  */
22
22
  export function generateMediaServerEnvTemplate(port) {
23
23
  return `# Media Server Configuration
24
- MEDIA_SERVER_PORT=${port}
24
+ APP_PORT=${port}
25
+ APP_URL=http://localhost:${port}
25
26
 
26
27
  # Storage Configuration (uses framework's storage config)
27
28
  STORAGE_DRIVER=local
@@ -135,44 +135,53 @@ export async function generateSvelteApp(targetDir, appName) {
135
135
  */
136
136
  function generateViteConfig(framework) {
137
137
  if (framework === 'react') {
138
- return `import { defineConfig } from 'vite';
138
+ return `import { defineConfig, loadEnv } from 'vite';
139
139
  import react from '@vitejs/plugin-react';
140
140
  import path from 'path';
141
141
 
142
- export default defineConfig({
143
- plugins: [react()],
144
- cacheDir: path.resolve(__dirname, '../../../node_modules/.vite'),
145
- server: {
146
- port: 3001,
147
- },
142
+ export default defineConfig(({ mode }) => {
143
+ const env = loadEnv(mode, process.cwd(), '');
144
+ return {
145
+ plugins: [react()],
146
+ cacheDir: path.resolve(__dirname, '../../../node_modules/.vite'),
147
+ server: {
148
+ port: parseInt(env.APP_PORT) || 4000,
149
+ },
150
+ };
148
151
  });`;
149
152
  }
150
153
 
151
154
  if (framework === 'vue') {
152
- return `import { defineConfig } from 'vite';
155
+ return `import { defineConfig, loadEnv } from 'vite';
153
156
  import vue from '@vitejs/plugin-vue';
154
157
  import path from 'path';
155
158
 
156
- export default defineConfig({
157
- plugins: [vue()],
158
- cacheDir: path.resolve(__dirname, '../../../node_modules/.vite'),
159
- server: {
160
- port: 3001,
161
- },
159
+ export default defineConfig(({ mode }) => {
160
+ const env = loadEnv(mode, process.cwd(), '');
161
+ return {
162
+ plugins: [vue()],
163
+ cacheDir: path.resolve(__dirname, '../../../node_modules/.vite'),
164
+ server: {
165
+ port: parseInt(env.APP_PORT) || 4000,
166
+ },
167
+ };
162
168
  });`;
163
169
  }
164
170
 
165
171
  if (framework === 'svelte') {
166
- return `import { defineConfig } from 'vite';
172
+ return `import { defineConfig, loadEnv } from 'vite';
167
173
  import { svelte } from '@sveltejs/vite-plugin-svelte';
168
174
  import path from 'path';
169
175
 
170
- export default defineConfig({
171
- plugins: [svelte()],
172
- cacheDir: path.resolve(__dirname, '../../../node_modules/.vite'),
173
- server: {
174
- port: 3001,
175
- },
176
+ export default defineConfig(({ mode }) => {
177
+ const env = loadEnv(mode, process.cwd(), '');
178
+ return {
179
+ plugins: [svelte()],
180
+ cacheDir: path.resolve(__dirname, '../../../node_modules/.vite'),
181
+ server: {
182
+ port: parseInt(env.APP_PORT) || 4000,
183
+ },
184
+ };
176
185
  });`;
177
186
  }
178
187
 
@@ -19,9 +19,9 @@ export const GENERATOR_CONFIG = {
19
19
 
20
20
  // Default Ports
21
21
  ports: {
22
- api: 3000,
23
- web: 3001,
24
- mediaServer: 4003,
22
+ apiStart: 3000, // API apps start from 3000 and auto-increment
23
+ webStart: 4000, // Web apps start from 4000 and auto-increment
24
+ mediaServer: 5000, // Media server is static service (hard-coded)
25
25
  },
26
26
 
27
27
  // Database Defaults
@@ -113,14 +113,6 @@ export default function (plop) {
113
113
  skipIfExists: true
114
114
  });
115
115
 
116
- // Config
117
- actions.push({
118
- type: 'add',
119
- path: `${basePath}/src/config/database.js`,
120
- templateFile: 'templates/api/config/database.js.hbs',
121
- skipIfExists: true
122
- });
123
-
124
116
  // Main app files
125
117
  actions.push({
126
118
  type: 'add',
@@ -14,10 +14,11 @@ import { getAllRoutes } from './routes/index.js';
14
14
  * Organized and maintainable Express app configuration
15
15
  */
16
16
  class {{pascalCase appName}}App extends BaseApp {
17
- constructor() {
17
+ constructor(projectRoot) {
18
18
  super({
19
19
  serviceName: process.env.APP_NAME || '{{appName}}-api',
20
- corsOrigin: env('CORS_ORIGIN', 'http://localhost:3001')
20
+ corsOrigin: env('CORS_ORIGIN', '*'),
21
+ projectRoot: projectRoot
21
22
  });
22
23
 
23
24
  // Register service providers
@@ -65,7 +66,7 @@ class {{pascalCase appName}}App extends BaseApp {
65
66
  /**
66
67
  * Create and configure the Express app
67
68
  */
68
- export function createApp() {
69
- const app = new {{pascalCase appName}}App();
69
+ export function createApp(projectRoot) {
70
+ const app = new {{pascalCase appName}}App(projectRoot);
70
71
  return app;
71
72
  }
@@ -9,9 +9,15 @@ import { createApp as buildApp } from './app.js';
9
9
 
10
10
  // Import centralized database (triggers connection automatically)
11
11
  import '@{{projectName}}/database';
12
+ import path from 'path';
13
+ import { fileURLToPath } from 'url';
14
+
15
+ // Get monorepo root (3 levels up from apps/{{appName}}/api)
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = path.dirname(__filename);
18
+ const projectRoot = path.resolve(__dirname, '..', '..', '..', '..');
12
19
 
13
20
  // Load root .env file
14
- const projectRoot = process.cwd();
15
21
  dotenv.config({ path: `${projectRoot}/.env` });
16
22
 
17
23
  /**
@@ -59,7 +65,7 @@ class {{pascalCase appName}}Server extends BaseServer {
59
65
  * Create Express app
60
66
  */
61
67
  createApp() {
62
- return buildApp();
68
+ return buildApp(this.projectRoot);
63
69
  }
64
70
 
65
71
  /**
@@ -3,7 +3,7 @@
3
3
  * Registers database services and model components
4
4
  */
5
5
 
6
- import ServiceProvider from '../Support/ServiceProvider.js';
6
+ import ServiceProvider from '../Foundation/ServiceProvider.js';
7
7
  import Model from './Model.js';
8
8
 
9
9
  export class DatabaseServiceProvider extends ServiceProvider {
@@ -410,6 +410,15 @@ export class Model extends GuruORMModel {
410
410
  dirty[this.constructor.updatedAt] = this.attributes[this.constructor.updatedAt];
411
411
  }
412
412
 
413
+ // Filter out internal tracking properties before update
414
+ delete dirty.isDirtyFlag; // Remove internal tracking fields
415
+ delete dirty.pendingMutators; // Remove async mutator tracking
416
+ delete dirty.isHydrating; // Remove hydration flag
417
+ delete dirty.exists; // Remove exists flag
418
+ delete dirty.wasRecentlyCreated; // Remove recently created flag
419
+ delete dirty.original; // Remove original tracking
420
+ delete dirty.relations; // Remove relations (handled separately)
421
+
413
422
  const pk = this.constructor.primaryKey || 'id';
414
423
 
415
424
  // Use GuruORM's update
@@ -20,4 +20,5 @@ export {
20
20
  AuthorizationError,
21
21
  } from './ErrorTypes.js';
22
22
  export { ExceptionHandler, createExceptionHandler } from './Handler.js';
23
- export { asyncHandler, asyncMiddleware, catchAsync } from './asyncHandler.js';
23
+ // asyncHandler moved to Http module - import from 'vasuzex/Http' instead
24
+ export { asyncMiddleware, catchAsync } from './asyncHandler.js';
@@ -1,6 +1,10 @@
1
1
  import express from 'express';
2
2
  import { Application } from './Application.js';
3
3
  import { applySecurityMiddleware } from '../Http/Middleware/SecurityMiddleware.js';
4
+ import { LogServiceProvider } from '../Services/Log/LogServiceProvider.js';
5
+ import { HashServiceProvider } from './Providers/HashServiceProvider.js';
6
+ import { ValidationServiceProvider } from './Providers/ValidationServiceProvider.js';
7
+ import { EncryptionServiceProvider } from './Providers/EncryptionServiceProvider.js';
4
8
 
5
9
  /**
6
10
  * BaseApp - Base class for application-level apps (Express apps)
@@ -19,6 +23,21 @@ export class BaseApp extends Application {
19
23
  this.middlewareSetup = false;
20
24
  this.routesSetup = false;
21
25
  this.securitySetup = false;
26
+
27
+ // Register core service providers automatically
28
+ this.registerCoreServiceProviders();
29
+ }
30
+
31
+ /**
32
+ * Register core framework service providers
33
+ * These are registered automatically for all apps
34
+ */
35
+ registerCoreServiceProviders() {
36
+ // Core services that every app needs
37
+ this.register(LogServiceProvider);
38
+ this.register(HashServiceProvider);
39
+ this.register(ValidationServiceProvider);
40
+ this.register(EncryptionServiceProvider);
22
41
  }
23
42
 
24
43
  /**
@@ -19,6 +19,101 @@ export class BaseService {
19
19
  this.sortableFields = ['id', 'created_at', 'updated_at'];
20
20
  this.defaultPerPage = 15;
21
21
  this.maxPerPage = 100;
22
+ this.request = null; // Will be set from controller
23
+ }
24
+
25
+ /**
26
+ * Convert camelCase to snake_case for PostgreSQL column names
27
+ * PostgreSQL convention uses snake_case
28
+ * @param {string} str - camelCase string
29
+ * @returns {string} snake_case string
30
+ */
31
+ toSnakeCase(str) {
32
+ return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
33
+ }
34
+
35
+ /**
36
+ * Set request context (call from controller before service methods)
37
+ * @param {Object} req - Express request object
38
+ * @returns {this} For chaining
39
+ */
40
+ setContext(req) {
41
+ this.request = req;
42
+ return this;
43
+ }
44
+
45
+ /**
46
+ * Get query options from request
47
+ * Standardizes pagination, search, sort, filters from req.query
48
+ * @returns {Object} Standardized query options
49
+ */
50
+ getQueryOptions() {
51
+ if (!this.request) {
52
+ return {};
53
+ }
54
+
55
+ const { query } = this.request;
56
+ const columnSearch = this.extractColumnSearch(query);
57
+
58
+ return {
59
+ page: parseInt(query.page) || 1,
60
+ limit: parseInt(query.limit) || 10,
61
+ search: query.search || '',
62
+ sortBy: query.sortBy || query.sort_by || 'created_at',
63
+ sortOrder: query.sortOrder || query.sort_order || 'desc',
64
+ filters: this.extractFilters(query),
65
+ columnSearch: columnSearch,
66
+ relations: query.with ? query.with.split(',') : []
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Extract column-specific search from query params
72
+ * Frontend sends: columnSearch[name]=john&columnSearch[email]=test
73
+ * Backend receives as: req.query['columnSearch[name]'] = 'john'
74
+ * This method parses them into: {name: 'john', email: 'test'}
75
+ * @param {Object} query - Request query params
76
+ * @returns {Object} Column search object
77
+ */
78
+ extractColumnSearch(query) {
79
+ const columnSearch = {};
80
+ const columnSearchPattern = /^columnSearch\[(.+)\]$/;
81
+
82
+ Object.keys(query).forEach(key => {
83
+ const match = key.match(columnSearchPattern);
84
+ if (match && query[key]) {
85
+ const fieldName = match[1];
86
+ columnSearch[fieldName] = query[key];
87
+ }
88
+ });
89
+
90
+ return columnSearch;
91
+ }
92
+
93
+ /**
94
+ * Extract filters from query params
95
+ * Override in child services for custom filter extraction
96
+ * @param {Object} query - Request query params
97
+ * @returns {Object} Filters object
98
+ */
99
+ extractFilters(query) {
100
+ const filters = {};
101
+ const excludedKeys = ['page', 'limit', 'search', 'sortBy', 'sort_by', 'sortOrder', 'sort_order', 'with'];
102
+ const columnSearchPattern = /^columnSearch\[(.+)\]$/;
103
+
104
+ Object.keys(query).forEach(key => {
105
+ // Skip excluded keys and columnSearch params
106
+ if (excludedKeys.includes(key) || columnSearchPattern.test(key)) {
107
+ return;
108
+ }
109
+
110
+ const value = query[key];
111
+ if (value !== undefined && value !== null && value !== '') {
112
+ filters[key] = value;
113
+ }
114
+ });
115
+
116
+ return filters;
22
117
  }
23
118
 
24
119
  /**
@@ -58,9 +58,24 @@ export class Container {
58
58
  throw new Error(`No binding found for "${abstract}"`);
59
59
  }
60
60
 
61
- const instance = typeof concrete === 'function'
62
- ? new concrete()
63
- : concrete;
61
+ // Handle factory functions vs constructors
62
+ let instance;
63
+ if (typeof concrete === 'function') {
64
+ // Check if it's a factory function (arrow function or function that takes app parameter)
65
+ // Factory functions receive the app instance as parameter
66
+ const isFactory = concrete.length > 0 || concrete.toString().includes('=>');
67
+
68
+ if (isFactory) {
69
+ // Call factory function with app instance
70
+ instance = concrete(this);
71
+ } else {
72
+ // Call as constructor
73
+ instance = new concrete();
74
+ }
75
+ } else {
76
+ // Direct value
77
+ instance = concrete;
78
+ }
64
79
 
65
80
  if (this.singletons.has(key)) {
66
81
  this.instances.set(key, instance);
@@ -4,7 +4,6 @@ export { NotificationServiceProvider } from './NotificationServiceProvider.js';
4
4
  export { HashServiceProvider } from './HashServiceProvider.js';
5
5
  export { ValidationServiceProvider } from './ValidationServiceProvider.js';
6
6
  export { EventServiceProvider } from './EventServiceProvider.js';
7
- export { LogServiceProvider } from './LogServiceProvider.js';
8
7
  export { EncryptionServiceProvider } from './EncryptionServiceProvider.js';
9
8
  export { CookieServiceProvider } from './CookieServiceProvider.js';
10
9
  export { SessionServiceProvider } from './SessionServiceProvider.js';
@@ -22,6 +22,48 @@ export class ServiceProvider {
22
22
  boot() {
23
23
  // Override in subclass
24
24
  }
25
+
26
+ /**
27
+ * Bind a service to the container
28
+ */
29
+ bind(abstract, concrete, singleton = false) {
30
+ return this.app.bind(abstract, concrete, singleton);
31
+ }
32
+
33
+ /**
34
+ * Bind a singleton to the container
35
+ */
36
+ singleton(abstract, concrete) {
37
+ return this.app.singleton(abstract, concrete);
38
+ }
39
+
40
+ /**
41
+ * Bind an instance to the container
42
+ */
43
+ instance(abstract, instance) {
44
+ return this.app.instance(abstract, instance);
45
+ }
46
+
47
+ /**
48
+ * Create an alias
49
+ */
50
+ alias(alias, abstract) {
51
+ return this.app.alias(alias, abstract);
52
+ }
53
+
54
+ /**
55
+ * Resolve a service from the container
56
+ */
57
+ make(abstract) {
58
+ return this.app.make(abstract);
59
+ }
60
+
61
+ /**
62
+ * Get configuration value
63
+ */
64
+ config(key, defaultValue = null) {
65
+ return this.app.config(key, defaultValue);
66
+ }
25
67
  }
26
68
 
27
69
  export default ServiceProvider;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Async Handler Middleware
3
+ * Wraps async controller methods to catch errors automatically
4
+ * Laravel-style error handling for Express async routes
5
+ *
6
+ * @example
7
+ * import { asyncHandler } from 'vasuzex/Http';
8
+ *
9
+ * router.get('/users', asyncHandler(async (req, res) => {
10
+ * const users = await User.all();
11
+ * res.json(users);
12
+ * }));
13
+ */
14
+
15
+ /**
16
+ * Wrap an async function to catch errors
17
+ * @param {Function} fn - Async function to wrap
18
+ * @returns {Function} Express middleware function
19
+ */
20
+ export function asyncHandler(fn) {
21
+ return (req, res, next) => {
22
+ Promise.resolve(fn(req, res, next)).catch(next);
23
+ };
24
+ }
25
+
26
+ export default asyncHandler;