create-phoenixjs 0.1.0

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 (49) hide show
  1. package/index.ts +196 -0
  2. package/package.json +31 -0
  3. package/template/README.md +62 -0
  4. package/template/app/controllers/ExampleController.ts +61 -0
  5. package/template/artisan +2 -0
  6. package/template/bootstrap/app.ts +44 -0
  7. package/template/bunfig.toml +7 -0
  8. package/template/config/database.ts +25 -0
  9. package/template/config/plugins.ts +7 -0
  10. package/template/config/security.ts +158 -0
  11. package/template/framework/cli/Command.ts +17 -0
  12. package/template/framework/cli/ConsoleApplication.ts +55 -0
  13. package/template/framework/cli/artisan.ts +16 -0
  14. package/template/framework/cli/commands/MakeControllerCommand.ts +41 -0
  15. package/template/framework/cli/commands/MakeMiddlewareCommand.ts +41 -0
  16. package/template/framework/cli/commands/MakeModelCommand.ts +36 -0
  17. package/template/framework/cli/commands/MakeValidatorCommand.ts +42 -0
  18. package/template/framework/controller/Controller.ts +222 -0
  19. package/template/framework/core/Application.ts +208 -0
  20. package/template/framework/core/Container.ts +100 -0
  21. package/template/framework/core/Kernel.ts +297 -0
  22. package/template/framework/database/DatabaseAdapter.ts +18 -0
  23. package/template/framework/database/PrismaAdapter.ts +65 -0
  24. package/template/framework/database/SqlAdapter.ts +117 -0
  25. package/template/framework/gateway/Gateway.ts +109 -0
  26. package/template/framework/gateway/GatewayManager.ts +150 -0
  27. package/template/framework/gateway/WebSocketAdapter.ts +159 -0
  28. package/template/framework/gateway/WebSocketGateway.ts +182 -0
  29. package/template/framework/http/Request.ts +608 -0
  30. package/template/framework/http/Response.ts +525 -0
  31. package/template/framework/http/Server.ts +161 -0
  32. package/template/framework/http/UploadedFile.ts +145 -0
  33. package/template/framework/middleware/Middleware.ts +50 -0
  34. package/template/framework/middleware/Pipeline.ts +89 -0
  35. package/template/framework/plugin/Plugin.ts +26 -0
  36. package/template/framework/plugin/PluginManager.ts +61 -0
  37. package/template/framework/routing/RouteRegistry.ts +185 -0
  38. package/template/framework/routing/Router.ts +280 -0
  39. package/template/framework/security/CorsMiddleware.ts +151 -0
  40. package/template/framework/security/CsrfMiddleware.ts +121 -0
  41. package/template/framework/security/HelmetMiddleware.ts +138 -0
  42. package/template/framework/security/InputSanitizerMiddleware.ts +134 -0
  43. package/template/framework/security/RateLimiterMiddleware.ts +189 -0
  44. package/template/framework/security/SecurityManager.ts +128 -0
  45. package/template/framework/validation/Validator.ts +482 -0
  46. package/template/package.json +24 -0
  47. package/template/routes/api.ts +56 -0
  48. package/template/server.ts +29 -0
  49. package/template/tsconfig.json +49 -0
@@ -0,0 +1,41 @@
1
+ import { Command } from '../Command';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ export class MakeControllerCommand extends Command {
6
+ signature = 'make:controller {name}';
7
+ description = 'Create a new controller class';
8
+
9
+ async handle(args: Record<string, any>): Promise<void> {
10
+ const name = args.name;
11
+ if (!name) {
12
+ console.error('Error: Controller name is required.');
13
+ return;
14
+ }
15
+
16
+ const className = path.basename(name);
17
+ // Handle nested directories e.g. user/UserController
18
+ const relativePath = name.endsWith('.ts') ? name : `${name}.ts`;
19
+ const fullPath = path.join(process.cwd(), 'app', 'controllers', relativePath);
20
+ const directory = path.dirname(fullPath);
21
+
22
+ if (fs.existsSync(fullPath)) {
23
+ console.error(`Error: Controller "${name}" already exists.`);
24
+ return;
25
+ }
26
+
27
+ fs.mkdirSync(directory, { recursive: true });
28
+
29
+ const template = `import { Controller } from '@framework/controller/Controller';
30
+
31
+ export class ${className} extends Controller {
32
+ async index() {
33
+ return this.json({ message: 'Hello from ${className}' });
34
+ }
35
+ }
36
+ `;
37
+
38
+ fs.writeFileSync(fullPath, template);
39
+ console.log(`Controller created successfully: app/controllers/${relativePath}`);
40
+ }
41
+ }
@@ -0,0 +1,41 @@
1
+ import { Command } from '../Command';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ export class MakeMiddlewareCommand extends Command {
6
+ signature = 'make:middleware {name}';
7
+ description = 'Create a new middleware class';
8
+
9
+ async handle(args: Record<string, any>): Promise<void> {
10
+ const name = args.name;
11
+ if (!name) {
12
+ console.error('Error: Middleware name is required.');
13
+ return;
14
+ }
15
+
16
+ const className = path.basename(name);
17
+ const relativePath = name.endsWith('.ts') ? name : `${name}.ts`;
18
+ const fullPath = path.join(process.cwd(), 'app', 'middlewares', relativePath);
19
+ const directory = path.dirname(fullPath);
20
+
21
+ if (fs.existsSync(fullPath)) {
22
+ console.error(`Error: Middleware "${name}" already exists.`);
23
+ return;
24
+ }
25
+
26
+ fs.mkdirSync(directory, { recursive: true });
27
+
28
+ const template = `import { Middleware, MiddlewareHandler, NextFunction, Request, Response } from '@framework/middleware/Middleware';
29
+
30
+ export class ${className} implements Middleware {
31
+ async handle(request: Request, next: NextFunction): Promise<Response | any> {
32
+ // middleware logic
33
+ return next(request);
34
+ }
35
+ }
36
+ `;
37
+
38
+ fs.writeFileSync(fullPath, template);
39
+ console.log(`Middleware created successfully: app/middlewares/${relativePath}`);
40
+ }
41
+ }
@@ -0,0 +1,36 @@
1
+ import { Command } from '../Command';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ export class MakeModelCommand extends Command {
6
+ signature = 'make:model {name}';
7
+ description = 'Create a new model class';
8
+
9
+ async handle(args: Record<string, any>): Promise<void> {
10
+ const name = args.name;
11
+ if (!name) {
12
+ console.error('Error: Model name is required.');
13
+ return;
14
+ }
15
+
16
+ const className = path.basename(name);
17
+ const relativePath = name.endsWith('.ts') ? name : `${name}.ts`;
18
+ const fullPath = path.join(process.cwd(), 'app', 'models', relativePath);
19
+ const directory = path.dirname(fullPath);
20
+
21
+ if (fs.existsSync(fullPath)) {
22
+ console.error(`Error: Model "${name}" already exists.`);
23
+ return;
24
+ }
25
+
26
+ fs.mkdirSync(directory, { recursive: true });
27
+
28
+ const template = `export class ${className} {
29
+ // Model definition
30
+ }
31
+ `;
32
+
33
+ fs.writeFileSync(fullPath, template);
34
+ console.log(`Model created successfully: app/models/${relativePath}`);
35
+ }
36
+ }
@@ -0,0 +1,42 @@
1
+ import { Command } from '../Command';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ export class MakeValidatorCommand extends Command {
6
+ signature = 'make:validator {name}';
7
+ description = 'Create a new validator class';
8
+
9
+ async handle(args: Record<string, any>): Promise<void> {
10
+ const name = args.name;
11
+ if (!name) {
12
+ console.error('Error: Validator name is required.');
13
+ return;
14
+ }
15
+
16
+ const className = path.basename(name);
17
+ const relativePath = name.endsWith('.ts') ? name : `${name}.ts`;
18
+ const fullPath = path.join(process.cwd(), 'app', 'validators', relativePath);
19
+ const directory = path.dirname(fullPath);
20
+
21
+ if (fs.existsSync(fullPath)) {
22
+ console.error(`Error: Validator "${name}" already exists.`);
23
+ return;
24
+ }
25
+
26
+ fs.mkdirSync(directory, { recursive: true });
27
+
28
+ const template = `import { Validator } from '@framework/validation/Validator';
29
+
30
+ export class ${className} extends Validator {
31
+ rules() {
32
+ return {
33
+ // 'field': ['required', 'string']
34
+ };
35
+ }
36
+ }
37
+ `;
38
+
39
+ fs.writeFileSync(fullPath, template);
40
+ console.log(`Validator created successfully: app/validators/${relativePath}`);
41
+ }
42
+ }
@@ -0,0 +1,222 @@
1
+ /**
2
+ * PhoenixJS - Base Controller
3
+ *
4
+ * Abstract base class for all controllers.
5
+ * Provides request injection and response helpers.
6
+ */
7
+
8
+ import { FrameworkRequest } from '@framework/http/Request';
9
+ import { FrameworkResponse, ResponseBuilder } from '@framework/http/Response';
10
+ import type { ValidationResult } from '@framework/validation/Validator';
11
+
12
+ /**
13
+ * Controller constructor type for dependency injection
14
+ */
15
+ export interface ControllerConstructor {
16
+ new(): Controller;
17
+ }
18
+
19
+ /**
20
+ * Base Controller class
21
+ *
22
+ * All application controllers should extend this class.
23
+ * Provides access to the current request and response helpers.
24
+ */
25
+ export abstract class Controller {
26
+ /**
27
+ * The current request instance (injected by Kernel)
28
+ */
29
+ protected request!: FrameworkRequest;
30
+
31
+ /**
32
+ * Route parameters (injected by Kernel)
33
+ */
34
+ protected params: Record<string, string> = {};
35
+
36
+ /**
37
+ * Set the request instance (called by Kernel)
38
+ */
39
+ setRequest(request: FrameworkRequest): void {
40
+ this.request = request;
41
+ }
42
+
43
+ /**
44
+ * Set route parameters (called by Kernel)
45
+ */
46
+ setParams(params: Record<string, string>): void {
47
+ this.params = params;
48
+ }
49
+
50
+ /**
51
+ * Get a route parameter
52
+ */
53
+ protected param(key: string, defaultValue?: string): string | undefined {
54
+ return this.params[key] ?? defaultValue;
55
+ }
56
+
57
+ // ==========================================
58
+ // Response Helpers
59
+ // ==========================================
60
+
61
+ /**
62
+ * Return a JSON response
63
+ */
64
+ protected json(data: unknown, status = 200): Response {
65
+ return FrameworkResponse.json(data, status);
66
+ }
67
+
68
+ /**
69
+ * Return a text response
70
+ */
71
+ protected text(content: string, status = 200): Response {
72
+ return FrameworkResponse.text(content, status);
73
+ }
74
+
75
+ /**
76
+ * Return an HTML response
77
+ */
78
+ protected html(content: string, status = 200): Response {
79
+ return FrameworkResponse.html(content, status);
80
+ }
81
+
82
+ /**
83
+ * Return an XML response
84
+ */
85
+ protected xml(content: string, status = 200): Response {
86
+ return FrameworkResponse.xml(content, status);
87
+ }
88
+
89
+ /**
90
+ * Return a redirect response
91
+ */
92
+ protected redirect(url: string, permanent = false): Response {
93
+ return FrameworkResponse.redirect(url, permanent ? 301 : 302);
94
+ }
95
+
96
+ /**
97
+ * Return a 204 No Content response
98
+ */
99
+ protected noContent(): Response {
100
+ return FrameworkResponse.noContent();
101
+ }
102
+
103
+ /**
104
+ * Return an error response
105
+ */
106
+ protected error(message: string, status = 500, details?: Record<string, unknown>): Response {
107
+ return FrameworkResponse.error(message, status, details);
108
+ }
109
+
110
+ /**
111
+ * Return a 400 Bad Request response
112
+ */
113
+ protected badRequest(message: string, details?: Record<string, unknown>): Response {
114
+ return FrameworkResponse.badRequest(message, details);
115
+ }
116
+
117
+ /**
118
+ * Return a 401 Unauthorized response
119
+ */
120
+ protected unauthorized(message = 'Unauthorized'): Response {
121
+ return FrameworkResponse.unauthorized(message);
122
+ }
123
+
124
+ /**
125
+ * Return a 403 Forbidden response
126
+ */
127
+ protected forbidden(message = 'Forbidden'): Response {
128
+ return FrameworkResponse.forbidden(message);
129
+ }
130
+
131
+ /**
132
+ * Return a 404 Not Found response
133
+ */
134
+ protected notFound(message = 'Not Found'): Response {
135
+ return FrameworkResponse.notFound(message);
136
+ }
137
+
138
+ /**
139
+ * Return a 422 Validation Error response
140
+ */
141
+ protected validationError(errors: Record<string, string[]>): Response {
142
+ return FrameworkResponse.validationError(errors);
143
+ }
144
+
145
+ /**
146
+ * Create a fluent response builder
147
+ */
148
+ protected response(): ResponseBuilder {
149
+ return FrameworkResponse.create();
150
+ }
151
+
152
+ // ==========================================
153
+ // Validation
154
+ // ==========================================
155
+
156
+ /**
157
+ * Validate the request input using a Validator class
158
+ *
159
+ * @param validatorClass - The Validator class to use
160
+ * @returns ValidationResult with passes/fails and errors
161
+ */
162
+ protected async validate<T extends { new(): { validate(data: Record<string, unknown>): ValidationResult } }>(
163
+ validatorClass: T
164
+ ): Promise<ValidationResult> {
165
+ const validator = new validatorClass();
166
+ const data = await this.request.all();
167
+ return validator.validate(data);
168
+ }
169
+
170
+ /**
171
+ * Validate data and return error response if validation fails
172
+ * Returns null if validation passes, Response if fails
173
+ */
174
+ protected async validateOrFail<T extends { new(): { validate(data: Record<string, unknown>): ValidationResult } }>(
175
+ validatorClass: T
176
+ ): Promise<Response | null> {
177
+ const result = await this.validate(validatorClass);
178
+ if (!result.passes) {
179
+ return this.validationError(result.errors);
180
+ }
181
+ return null;
182
+ }
183
+
184
+ // ==========================================
185
+ // Request Helpers
186
+ // ==========================================
187
+
188
+ /**
189
+ * Get the current request
190
+ */
191
+ protected getRequest(): FrameworkRequest {
192
+ return this.request;
193
+ }
194
+
195
+ /**
196
+ * Get all input data
197
+ */
198
+ protected async all(): Promise<Record<string, unknown>> {
199
+ return this.request.all();
200
+ }
201
+
202
+ /**
203
+ * Get an input value
204
+ */
205
+ protected async input<T = unknown>(key?: string, defaultValue?: T): Promise<T | Record<string, unknown>> {
206
+ return this.request.input(key, defaultValue);
207
+ }
208
+
209
+ /**
210
+ * Get only specified input keys
211
+ */
212
+ protected async only(...keys: string[]): Promise<Record<string, unknown>> {
213
+ return this.request.only(...keys);
214
+ }
215
+
216
+ /**
217
+ * Get all input except specified keys
218
+ */
219
+ protected async except(...keys: string[]): Promise<Record<string, unknown>> {
220
+ return this.request.except(...keys);
221
+ }
222
+ }
@@ -0,0 +1,208 @@
1
+ /**
2
+ * PhoenixJS - Application
3
+ *
4
+ * The main application class that bootstraps the framework.
5
+ * Extends Container to provide dependency injection capabilities.
6
+ */
7
+
8
+ import { Container } from '@framework/core/Container';
9
+ import { PluginManager } from '@framework/plugin/PluginManager';
10
+ import { Plugin } from '@framework/plugin/Plugin';
11
+ import { GatewayManager } from '@framework/gateway/GatewayManager';
12
+ import { SecurityManager } from '@framework/security/SecurityManager';
13
+ import type { Gateway } from '@framework/gateway/Gateway';
14
+ import type { SecurityConfig } from '@/config/security';
15
+
16
+ export interface AppConfig {
17
+ name: string;
18
+ env: 'development' | 'production' | 'testing';
19
+ debug: boolean;
20
+ port: number;
21
+ host: string;
22
+ plugins?: Plugin[];
23
+ gateways?: Gateway[];
24
+ security?: Partial<SecurityConfig>;
25
+ }
26
+
27
+ const defaultConfig: AppConfig = {
28
+ name: 'PhoenixJS',
29
+ env: 'development',
30
+ debug: true,
31
+ port: 4000,
32
+ host: 'localhost',
33
+ };
34
+
35
+ export class Application extends Container {
36
+ private static instance: Application | null = null;
37
+ private config: AppConfig;
38
+ private booted = false;
39
+ private basePath: string;
40
+ private pluginManager: PluginManager;
41
+ private gatewayManager: GatewayManager;
42
+ private securityManager: SecurityManager;
43
+
44
+ constructor(basePath: string = process.cwd()) {
45
+ super();
46
+ this.basePath = basePath;
47
+ this.config = { ...defaultConfig };
48
+ this.pluginManager = new PluginManager(this);
49
+ this.gatewayManager = new GatewayManager(this);
50
+ this.securityManager = new SecurityManager(this);
51
+ this.registerBaseBindings();
52
+ }
53
+
54
+ /**
55
+ * Get the singleton application instance
56
+ */
57
+ static getInstance(): Application {
58
+ if (!Application.instance) {
59
+ throw new Error('Application has not been initialized');
60
+ }
61
+ return Application.instance;
62
+ }
63
+
64
+ /**
65
+ * Create a new application instance
66
+ */
67
+ static create(basePath?: string): Application {
68
+ Application.instance = new Application(basePath);
69
+ return Application.instance;
70
+ }
71
+
72
+ /**
73
+ * Register base bindings
74
+ */
75
+ private registerBaseBindings(): void {
76
+ this.instance('app', this);
77
+ this.instance('config', this.config);
78
+ this.instance('pluginManager', this.pluginManager);
79
+ this.instance('gatewayManager', this.gatewayManager);
80
+ this.instance('securityManager', this.securityManager);
81
+ }
82
+
83
+ /**
84
+ * Configure the application
85
+ */
86
+ configure(config: Partial<AppConfig>): this {
87
+ this.config = { ...this.config, ...config };
88
+ this.instance('config', this.config);
89
+
90
+ // Register plugins from config
91
+ if (this.config.plugins) {
92
+ this.config.plugins.forEach(plugin => {
93
+ this.registerPlugin(plugin);
94
+ });
95
+ }
96
+
97
+ // Register gateways from config
98
+ if (this.config.gateways) {
99
+ this.config.gateways.forEach(gateway => {
100
+ this.registerGateway(gateway);
101
+ });
102
+ }
103
+
104
+ // Configure security from config
105
+ if (this.config.security) {
106
+ this.securityManager.configure(this.config.security);
107
+ }
108
+
109
+ return this;
110
+ }
111
+
112
+ /**
113
+ * Register a plugin
114
+ */
115
+ registerPlugin(plugin: Plugin): this {
116
+ this.pluginManager.register(plugin);
117
+ return this;
118
+ }
119
+
120
+ /**
121
+ * Get the plugin manager
122
+ */
123
+ getPluginManager(): PluginManager {
124
+ return this.pluginManager;
125
+ }
126
+
127
+ /**
128
+ * Register a gateway
129
+ */
130
+ registerGateway(gateway: Gateway): this {
131
+ this.gatewayManager.register(gateway);
132
+ return this;
133
+ }
134
+
135
+ /**
136
+ * Get the gateway manager
137
+ */
138
+ getGatewayManager(): GatewayManager {
139
+ return this.gatewayManager;
140
+ }
141
+
142
+ /**
143
+ * Get the security manager
144
+ */
145
+ getSecurityManager(): SecurityManager {
146
+ return this.securityManager;
147
+ }
148
+
149
+ /**
150
+ * Get configuration value
151
+ */
152
+ getConfig<K extends keyof AppConfig>(key: K): AppConfig[K] {
153
+ return this.config[key];
154
+ }
155
+
156
+ /**
157
+ * Get full configuration
158
+ */
159
+ getAllConfig(): AppConfig {
160
+ return { ...this.config };
161
+ }
162
+
163
+ /**
164
+ * Get the base path
165
+ */
166
+ getBasePath(): string {
167
+ return this.basePath;
168
+ }
169
+
170
+ /**
171
+ * Check if application is booted
172
+ */
173
+ isBooted(): boolean {
174
+ return this.booted;
175
+ }
176
+
177
+ /**
178
+ * Boot the application
179
+ */
180
+ boot(): this {
181
+ if (this.booted) {
182
+ return this;
183
+ }
184
+
185
+ // Boot all plugins
186
+ this.pluginManager.boot();
187
+
188
+ // Boot all gateways
189
+ this.gatewayManager.boot();
190
+
191
+ this.booted = true;
192
+ return this;
193
+ }
194
+
195
+ /**
196
+ * Get environment
197
+ */
198
+ environment(): string {
199
+ return this.config.env;
200
+ }
201
+
202
+ /**
203
+ * Check if in debug mode
204
+ */
205
+ isDebug(): boolean {
206
+ return this.config.debug;
207
+ }
208
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * PhoenixJS - Dependency Injection Container
3
+ *
4
+ * A simple IoC container for managing dependencies.
5
+ */
6
+
7
+ type Factory<T> = () => T;
8
+ type Constructor<T> = new (...args: unknown[]) => T;
9
+
10
+ interface Binding<T> {
11
+ resolver: Factory<T>;
12
+ singleton: boolean;
13
+ instance?: T;
14
+ }
15
+
16
+ export class Container {
17
+ private bindings: Map<string, Binding<unknown>> = new Map();
18
+ private instances: Map<string, unknown> = new Map();
19
+
20
+ /**
21
+ * Register a binding in the container
22
+ */
23
+ bind<T>(abstract: string, resolver: Factory<T>, singleton = false): this {
24
+ this.bindings.set(abstract, {
25
+ resolver,
26
+ singleton,
27
+ instance: undefined,
28
+ });
29
+ return this;
30
+ }
31
+
32
+ /**
33
+ * Register a singleton binding
34
+ */
35
+ singleton<T>(abstract: string, resolver: Factory<T>): this {
36
+ return this.bind(abstract, resolver, true);
37
+ }
38
+
39
+ /**
40
+ * Register an existing instance in the container
41
+ */
42
+ instance<T>(abstract: string, instance: T): this {
43
+ this.instances.set(abstract, instance);
44
+ return this;
45
+ }
46
+
47
+ /**
48
+ * Resolve a binding from the container
49
+ */
50
+ make<T>(abstract: string): T {
51
+ // Check if we have a direct instance
52
+ if (this.instances.has(abstract)) {
53
+ return this.instances.get(abstract) as T;
54
+ }
55
+
56
+ // Check if we have a binding
57
+ const binding = this.bindings.get(abstract);
58
+ if (!binding) {
59
+ throw new Error(`No binding found for: ${abstract}`);
60
+ }
61
+
62
+ // If it's a singleton and we have an instance, return it
63
+ if (binding.singleton && binding.instance !== undefined) {
64
+ return binding.instance as T;
65
+ }
66
+
67
+ // Resolve the binding
68
+ const instance = binding.resolver() as T;
69
+
70
+ // Store singleton instance
71
+ if (binding.singleton) {
72
+ binding.instance = instance;
73
+ }
74
+
75
+ return instance;
76
+ }
77
+
78
+ /**
79
+ * Check if a binding exists
80
+ */
81
+ has(abstract: string): boolean {
82
+ return this.bindings.has(abstract) || this.instances.has(abstract);
83
+ }
84
+
85
+ /**
86
+ * Remove a binding from the container
87
+ */
88
+ forget(abstract: string): void {
89
+ this.bindings.delete(abstract);
90
+ this.instances.delete(abstract);
91
+ }
92
+
93
+ /**
94
+ * Clear all bindings
95
+ */
96
+ flush(): void {
97
+ this.bindings.clear();
98
+ this.instances.clear();
99
+ }
100
+ }