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,150 @@
1
+ /**
2
+ * PhoenixJS - GatewayManager
3
+ *
4
+ * Manages gateway registration, transport adapters, and lifecycle.
5
+ */
6
+
7
+ import type { Application } from '@framework/core/Application';
8
+ import type { Gateway, GatewayTransport, TransportAdapter } from './Gateway';
9
+
10
+ export class GatewayManager {
11
+ private app: Application;
12
+ private gateways: Map<string, Gateway> = new Map();
13
+ private adapters: Map<GatewayTransport, TransportAdapter> = new Map();
14
+ private booted = false;
15
+
16
+ constructor(app: Application) {
17
+ this.app = app;
18
+ }
19
+
20
+ /**
21
+ * Register a gateway
22
+ */
23
+ register(gateway: Gateway): this {
24
+ if (this.gateways.has(gateway.name)) {
25
+ throw new Error(`Gateway "${gateway.name}" is already registered`);
26
+ }
27
+
28
+ this.gateways.set(gateway.name, gateway);
29
+ gateway.register(this.app);
30
+
31
+ // If already booted, boot the gateway immediately
32
+ if (this.booted && gateway.boot) {
33
+ gateway.boot(this.app);
34
+ }
35
+
36
+ return this;
37
+ }
38
+
39
+ /**
40
+ * Register a transport adapter
41
+ */
42
+ registerAdapter(adapter: TransportAdapter): this {
43
+ if (this.adapters.has(adapter.transport)) {
44
+ throw new Error(`Transport adapter for "${adapter.transport}" is already registered`);
45
+ }
46
+
47
+ this.adapters.set(adapter.transport, adapter);
48
+ return this;
49
+ }
50
+
51
+ /**
52
+ * Get a gateway by name
53
+ */
54
+ get(name: string): Gateway | undefined {
55
+ return this.gateways.get(name);
56
+ }
57
+
58
+ /**
59
+ * Get a transport adapter
60
+ */
61
+ getAdapter(transport: GatewayTransport): TransportAdapter | undefined {
62
+ return this.adapters.get(transport);
63
+ }
64
+
65
+ /**
66
+ * Check if a gateway exists
67
+ */
68
+ has(name: string): boolean {
69
+ return this.gateways.has(name);
70
+ }
71
+
72
+ /**
73
+ * Get all registered gateways
74
+ */
75
+ all(): Gateway[] {
76
+ return Array.from(this.gateways.values());
77
+ }
78
+
79
+ /**
80
+ * Get gateways by transport type
81
+ */
82
+ getByTransport(transport: GatewayTransport): Gateway[] {
83
+ return this.all().filter(g => g.transport === transport);
84
+ }
85
+
86
+ /**
87
+ * Find a gateway by path
88
+ */
89
+ findByPath(path: string): Gateway | undefined {
90
+ return this.all().find(g => g.path === path);
91
+ }
92
+
93
+ /**
94
+ * Boot all gateways
95
+ */
96
+ boot(): void {
97
+ if (this.booted) {
98
+ return;
99
+ }
100
+
101
+ for (const gateway of this.gateways.values()) {
102
+ if (gateway.boot) {
103
+ gateway.boot(this.app);
104
+ }
105
+ }
106
+
107
+ this.booted = true;
108
+ }
109
+
110
+ /**
111
+ * Start all transport adapters
112
+ */
113
+ start(): void {
114
+ // Group gateways by transport
115
+ const transports = new Set(this.all().map(g => g.transport));
116
+
117
+ for (const transport of transports) {
118
+ const adapter = this.adapters.get(transport);
119
+ if (adapter) {
120
+ const gateways = this.getByTransport(transport);
121
+ adapter.start(gateways);
122
+ }
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Stop all transport adapters
128
+ */
129
+ stop(): void {
130
+ for (const adapter of this.adapters.values()) {
131
+ if (adapter.isRunning()) {
132
+ adapter.stop();
133
+ }
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Check if manager is booted
139
+ */
140
+ isBooted(): boolean {
141
+ return this.booted;
142
+ }
143
+
144
+ /**
145
+ * Get gateway count
146
+ */
147
+ count(): number {
148
+ return this.gateways.size;
149
+ }
150
+ }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * PhoenixJS - WebSocketAdapter
3
+ *
4
+ * Built-in transport adapter for Bun's native WebSocket support.
5
+ */
6
+
7
+ import type { ServerWebSocket } from 'bun';
8
+ import type {
9
+ Gateway,
10
+ TransportAdapter,
11
+ WebSocketGatewayInterface,
12
+ WebSocketData,
13
+ GatewayOptions
14
+ } from './Gateway';
15
+
16
+ // Type for Bun server
17
+ type BunServer = ReturnType<typeof Bun.serve>;
18
+
19
+ export class WebSocketAdapter implements TransportAdapter {
20
+ readonly name = 'websocket';
21
+ readonly transport = 'websocket' as const;
22
+
23
+ private gateways: WebSocketGatewayInterface[] = [];
24
+ private server: BunServer | null = null;
25
+ private options: GatewayOptions;
26
+
27
+ constructor(options: GatewayOptions = {}) {
28
+ this.options = {
29
+ idleTimeout: options.idleTimeout ?? 120,
30
+ maxPayloadLength: options.maxPayloadLength ?? 1024 * 1024, // 1MB
31
+ perMessageDeflate: options.perMessageDeflate ?? false,
32
+ };
33
+ }
34
+
35
+ /**
36
+ * Start the WebSocket adapter
37
+ */
38
+ start(gateways: Gateway[]): void {
39
+ this.gateways = gateways.filter(
40
+ (g): g is WebSocketGatewayInterface => g.transport === 'websocket'
41
+ );
42
+ }
43
+
44
+ /**
45
+ * Stop the WebSocket adapter
46
+ */
47
+ stop(): void {
48
+ this.gateways = [];
49
+ this.server = null;
50
+ }
51
+
52
+ /**
53
+ * Check if adapter is running
54
+ */
55
+ isRunning(): boolean {
56
+ return this.server !== null;
57
+ }
58
+
59
+ /**
60
+ * Set the server reference
61
+ */
62
+ setServer(server: BunServer): void {
63
+ this.server = server;
64
+ }
65
+
66
+ /**
67
+ * Get the Bun websocket handlers configuration
68
+ */
69
+ getWebSocketConfig() {
70
+ const self = this;
71
+
72
+ return {
73
+ idleTimeout: this.options.idleTimeout,
74
+ maxPayloadLength: this.options.maxPayloadLength,
75
+ perMessageDeflate: this.options.perMessageDeflate,
76
+
77
+ open(ws: ServerWebSocket<WebSocketData>) {
78
+ const gateway = self.findGateway(ws.data.gateway);
79
+ if (gateway?.onOpen) {
80
+ gateway.onOpen(ws);
81
+ }
82
+ },
83
+
84
+ message(ws: ServerWebSocket<WebSocketData>, message: string | Buffer) {
85
+ const gateway = self.findGateway(ws.data.gateway);
86
+ if (gateway?.onMessage) {
87
+ gateway.onMessage(ws, message);
88
+ }
89
+ },
90
+
91
+ close(ws: ServerWebSocket<WebSocketData>, code: number, reason: string) {
92
+ const gateway = self.findGateway(ws.data.gateway);
93
+ if (gateway?.onClose) {
94
+ gateway.onClose(ws, code, reason);
95
+ }
96
+ },
97
+
98
+ drain(ws: ServerWebSocket<WebSocketData>) {
99
+ const gateway = self.findGateway(ws.data.gateway);
100
+ if (gateway?.onDrain) {
101
+ gateway.onDrain(ws);
102
+ }
103
+ },
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Handle WebSocket upgrade request
109
+ */
110
+ handleUpgrade(
111
+ request: Request,
112
+ server: BunServer,
113
+ customData?: Record<string, unknown>
114
+ ): boolean {
115
+ const url = new URL(request.url);
116
+ const gateway = this.findGatewayByPath(url.pathname);
117
+
118
+ if (!gateway) {
119
+ return false;
120
+ }
121
+
122
+ const data: WebSocketData = {
123
+ gateway: gateway.name,
124
+ connectionId: crypto.randomUUID(),
125
+ connectedAt: Date.now(),
126
+ ...customData,
127
+ };
128
+
129
+ return server.upgrade(request, { data });
130
+ }
131
+
132
+ /**
133
+ * Find a gateway by name
134
+ */
135
+ findGateway(name: string): WebSocketGatewayInterface | undefined {
136
+ return this.gateways.find(g => g.name === name);
137
+ }
138
+
139
+ /**
140
+ * Find a gateway by path
141
+ */
142
+ findGatewayByPath(path: string): WebSocketGatewayInterface | undefined {
143
+ return this.gateways.find(g => g.path === path);
144
+ }
145
+
146
+ /**
147
+ * Get all registered gateways
148
+ */
149
+ getGateways(): WebSocketGatewayInterface[] {
150
+ return this.gateways;
151
+ }
152
+
153
+ /**
154
+ * Check if a path has a registered gateway
155
+ */
156
+ hasGatewayForPath(path: string): boolean {
157
+ return this.findGatewayByPath(path) !== undefined;
158
+ }
159
+ }
@@ -0,0 +1,182 @@
1
+ /**
2
+ * PhoenixJS - WebSocketGateway
3
+ *
4
+ * Abstract base class for WebSocket gateways with pub/sub support.
5
+ */
6
+
7
+ import type { ServerWebSocket } from 'bun';
8
+ import type { Application } from '@framework/core/Application';
9
+ import type { WebSocketGatewayInterface, WebSocketData, GatewayOptions } from './Gateway';
10
+
11
+ export abstract class WebSocketGateway implements WebSocketGatewayInterface {
12
+ readonly transport = 'websocket' as const;
13
+
14
+ /** Gateway name - must be unique */
15
+ abstract readonly name: string;
16
+
17
+ /** Gateway path (e.g., '/ws', '/chat') */
18
+ abstract readonly path: string;
19
+
20
+ /** Application instance */
21
+ protected app!: Application;
22
+
23
+ /** Active connections */
24
+ protected connections: Map<string, ServerWebSocket<WebSocketData>> = new Map();
25
+
26
+ /** Gateway options */
27
+ protected options: GatewayOptions = {};
28
+
29
+ /**
30
+ * Called when gateway is registered
31
+ */
32
+ register(app: Application): void {
33
+ this.app = app;
34
+ this.onRegister();
35
+ }
36
+
37
+ /**
38
+ * Called after all gateways are registered
39
+ */
40
+ boot(app: Application): void {
41
+ this.onBoot();
42
+ }
43
+
44
+ /**
45
+ * Override to perform actions during registration
46
+ */
47
+ protected onRegister(): void { }
48
+
49
+ /**
50
+ * Override to perform actions during boot
51
+ */
52
+ protected onBoot(): void { }
53
+
54
+ /**
55
+ * Called when a connection is opened
56
+ */
57
+ onOpen(ws: ServerWebSocket<WebSocketData>): void {
58
+ this.connections.set(ws.data.connectionId, ws);
59
+ }
60
+
61
+ /**
62
+ * Called when a message is received - override in subclass
63
+ */
64
+ onMessage(ws: ServerWebSocket<WebSocketData>, message: string | Buffer): void {
65
+ // Override in subclass
66
+ }
67
+
68
+ /**
69
+ * Called when a connection is closed
70
+ */
71
+ onClose(ws: ServerWebSocket<WebSocketData>, code: number, reason: string): void {
72
+ this.connections.delete(ws.data.connectionId);
73
+ }
74
+
75
+ /**
76
+ * Called when an error occurs
77
+ */
78
+ onError(ws: ServerWebSocket<WebSocketData>, error: Error): void {
79
+ console.error(`[${this.name}] WebSocket error:`, error);
80
+ }
81
+
82
+ /**
83
+ * Called when the socket is ready to receive more data
84
+ */
85
+ onDrain(ws: ServerWebSocket<WebSocketData>): void {
86
+ // Override if needed
87
+ }
88
+
89
+ /**
90
+ * Send a message to a specific connection
91
+ */
92
+ protected send(ws: ServerWebSocket<WebSocketData>, data: string | Uint8Array): number {
93
+ return ws.send(data as string);
94
+ }
95
+
96
+ /**
97
+ * Send JSON data to a connection
98
+ */
99
+ protected sendJson(ws: ServerWebSocket<WebSocketData>, data: unknown): number {
100
+ return ws.send(JSON.stringify(data));
101
+ }
102
+
103
+ /**
104
+ * Subscribe a connection to a topic
105
+ */
106
+ protected subscribe(ws: ServerWebSocket<WebSocketData>, topic: string): void {
107
+ ws.subscribe(topic);
108
+ }
109
+
110
+ /**
111
+ * Unsubscribe a connection from a topic
112
+ */
113
+ protected unsubscribe(ws: ServerWebSocket<WebSocketData>, topic: string): void {
114
+ ws.unsubscribe(topic);
115
+ }
116
+
117
+ /**
118
+ * Publish a message to a topic
119
+ */
120
+ protected publish(topic: string, data: string | Uint8Array, compress?: boolean): void {
121
+ for (const ws of this.connections.values()) {
122
+ ws.publish(topic, data as string, compress);
123
+ return; // Only need to publish once
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Broadcast a message to all connected clients
129
+ */
130
+ protected broadcast(data: string | Uint8Array, exclude?: ServerWebSocket<WebSocketData>): void {
131
+ for (const ws of this.connections.values()) {
132
+ if (ws !== exclude) {
133
+ ws.send(data as string);
134
+ }
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Broadcast JSON data to all connected clients
140
+ */
141
+ protected broadcastJson(data: unknown, exclude?: ServerWebSocket<WebSocketData>): void {
142
+ this.broadcast(JSON.stringify(data), exclude);
143
+ }
144
+
145
+ /**
146
+ * Get all active connections
147
+ */
148
+ protected getConnections(): ServerWebSocket<WebSocketData>[] {
149
+ return Array.from(this.connections.values());
150
+ }
151
+
152
+ /**
153
+ * Get connection count
154
+ */
155
+ protected getConnectionCount(): number {
156
+ return this.connections.size;
157
+ }
158
+
159
+ /**
160
+ * Get a connection by ID
161
+ */
162
+ protected getConnection(connectionId: string): ServerWebSocket<WebSocketData> | undefined {
163
+ return this.connections.get(connectionId);
164
+ }
165
+
166
+ /**
167
+ * Close a connection
168
+ */
169
+ protected closeConnection(ws: ServerWebSocket<WebSocketData>, code?: number, reason?: string): void {
170
+ ws.close(code, reason);
171
+ }
172
+
173
+ /**
174
+ * Close all connections
175
+ */
176
+ protected closeAll(code?: number, reason?: string): void {
177
+ for (const ws of this.connections.values()) {
178
+ ws.close(code, reason);
179
+ }
180
+ this.connections.clear();
181
+ }
182
+ }