create-fluxstack 1.15.0 → 1.17.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.
- package/CHANGELOG.md +80 -0
- package/LLMD/INDEX.md +4 -3
- package/LLMD/resources/live-binary-delta.md +507 -0
- package/LLMD/resources/live-components.md +1 -0
- package/LLMD/resources/live-rooms.md +731 -333
- package/app/client/src/App.tsx +23 -14
- package/app/client/src/components/AppLayout.tsx +4 -4
- package/app/client/src/live/AuthDemo.tsx +4 -4
- package/app/client/src/live/PingPongDemo.tsx +199 -0
- package/app/client/src/live/RoomChatDemo.tsx +187 -22
- package/app/client/src/live/SharedCounterDemo.tsx +142 -0
- package/app/server/live/LivePingPong.ts +61 -0
- package/app/server/live/LiveRoomChat.ts +106 -38
- package/app/server/live/LiveSharedCounter.ts +73 -0
- package/app/server/live/rooms/ChatRoom.ts +68 -0
- package/app/server/live/rooms/CounterRoom.ts +51 -0
- package/app/server/live/rooms/DirectoryRoom.ts +42 -0
- package/app/server/live/rooms/PingRoom.ts +40 -0
- package/core/build/bundler.ts +40 -26
- package/core/build/flux-plugins-generator.ts +325 -325
- package/core/build/index.ts +92 -21
- package/core/cli/command-registry.ts +44 -46
- package/core/cli/commands/build.ts +11 -6
- package/core/cli/commands/create.ts +7 -5
- package/core/cli/commands/dev.ts +6 -5
- package/core/cli/commands/help.ts +3 -2
- package/core/cli/commands/make-plugin.ts +8 -7
- package/core/cli/commands/plugin-add.ts +60 -43
- package/core/cli/commands/plugin-deps.ts +73 -57
- package/core/cli/commands/plugin-list.ts +44 -41
- package/core/cli/commands/plugin-remove.ts +33 -22
- package/core/cli/generators/component.ts +770 -769
- package/core/cli/generators/controller.ts +9 -8
- package/core/cli/generators/index.ts +148 -146
- package/core/cli/generators/interactive.ts +228 -227
- package/core/cli/generators/plugin.ts +11 -10
- package/core/cli/generators/prompts.ts +83 -82
- package/core/cli/generators/route.ts +7 -6
- package/core/cli/generators/service.ts +10 -9
- package/core/cli/generators/template-engine.ts +2 -1
- package/core/cli/generators/types.ts +7 -7
- package/core/cli/generators/utils.ts +191 -191
- package/core/cli/index.ts +9 -8
- package/core/cli/plugin-discovery.ts +2 -2
- package/core/client/hooks/useAuth.ts +48 -48
- package/core/client/index.ts +0 -16
- package/core/client/standalone.ts +18 -17
- package/core/client/state/createStore.ts +192 -192
- package/core/client/state/index.ts +14 -14
- package/core/config/index.ts +1 -0
- package/core/framework/client.ts +131 -131
- package/core/framework/index.ts +7 -7
- package/core/framework/server.ts +72 -112
- package/core/framework/types.ts +2 -2
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +6 -3
- package/core/plugins/built-in/monitoring/index.ts +110 -68
- package/core/plugins/built-in/static/index.ts +2 -2
- package/core/plugins/built-in/swagger/index.ts +9 -9
- package/core/plugins/built-in/vite/index.ts +3 -3
- package/core/plugins/built-in/vite/vite-dev.ts +3 -3
- package/core/plugins/config.ts +50 -47
- package/core/plugins/discovery.ts +10 -4
- package/core/plugins/executor.ts +2 -2
- package/core/plugins/index.ts +206 -203
- package/core/plugins/manager.ts +21 -20
- package/core/plugins/registry.ts +76 -12
- package/core/plugins/types.ts +14 -14
- package/core/server/framework.ts +3 -189
- package/core/server/live/auto-generated-components.ts +11 -35
- package/core/server/live/index.ts +41 -36
- package/core/server/live/websocket-plugin.ts +48 -3
- package/core/server/middleware/elysia-helpers.ts +16 -15
- package/core/server/middleware/errorHandling.ts +14 -14
- package/core/server/middleware/index.ts +31 -31
- package/core/server/plugins/database.ts +181 -180
- package/core/server/plugins/static-files-plugin.ts +4 -3
- package/core/server/plugins/swagger.ts +11 -8
- package/core/server/rooms/RoomBroadcaster.ts +11 -10
- package/core/server/rooms/RoomSystem.ts +14 -11
- package/core/server/services/BaseService.ts +7 -7
- package/core/server/services/ServiceContainer.ts +5 -5
- package/core/server/services/index.ts +8 -8
- package/core/templates/create-project.ts +28 -27
- package/core/testing/index.ts +9 -9
- package/core/testing/setup.ts +73 -73
- package/core/types/api.ts +168 -168
- package/core/types/config.ts +5 -5
- package/core/types/index.ts +1 -1
- package/core/types/plugin.ts +2 -2
- package/core/types/types.ts +3 -3
- package/core/utils/build-logger.ts +324 -324
- package/core/utils/config-schema.ts +480 -480
- package/core/utils/env.ts +10 -8
- package/core/utils/errors/codes.ts +114 -114
- package/core/utils/errors/handlers.ts +30 -20
- package/core/utils/errors/index.ts +54 -46
- package/core/utils/errors/middleware.ts +113 -113
- package/core/utils/helpers.ts +19 -16
- package/core/utils/logger/colors.ts +114 -114
- package/core/utils/logger/config.ts +2 -2
- package/core/utils/logger/formatter.ts +82 -82
- package/core/utils/logger/group-logger.ts +101 -101
- package/core/utils/logger/index.ts +13 -3
- package/core/utils/logger/startup-banner.ts +2 -2
- package/core/utils/logger/winston-logger.ts +152 -152
- package/core/utils/monitoring/index.ts +211 -211
- package/core/utils/sync-version.ts +67 -66
- package/core/utils/version.ts +1 -1
- package/package.json +11 -6
- package/playwright-report/index.html +85 -0
- package/playwright.config.ts +31 -0
- package/plugins/crypto-auth/client/CryptoAuthClient.ts +302 -302
- package/plugins/crypto-auth/client/components/index.ts +11 -11
- package/plugins/crypto-auth/client/index.ts +11 -11
- package/plugins/crypto-auth/package.json +65 -65
- package/plugins/crypto-auth/server/CryptoAuthService.ts +185 -185
- package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +6 -5
- package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +6 -5
- package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +3 -3
- package/plugins/crypto-auth/server/middlewares/index.ts +22 -22
- package/plugins/crypto-auth/server/middlewares.ts +19 -19
- package/tsconfig.json +4 -1
- package/vite.config.ts +13 -0
- package/app/client/.live-stubs/LiveAdminPanel.js +0 -5
- package/app/client/.live-stubs/LiveChat.js +0 -7
- package/app/client/.live-stubs/LiveCounter.js +0 -9
- package/app/client/.live-stubs/LiveForm.js +0 -11
- package/app/client/.live-stubs/LiveLocalCounter.js +0 -8
- package/app/client/.live-stubs/LiveRoomChat.js +0 -10
- package/app/client/.live-stubs/LiveTodoList.js +0 -9
- package/app/client/.live-stubs/LiveUpload.js +0 -15
- package/app/client/src/live/ChatDemo.tsx +0 -107
- package/app/client/src/live/LiveDebuggerPanel.tsx +0 -779
- package/app/client/src/live/TodoListDemo.tsx +0 -158
- package/app/server/live/LiveChat.ts +0 -78
- package/app/server/live/LiveTodoList.ts +0 -110
- package/app/server/live/register-components.ts +0 -19
- package/core/build/live-components-generator.ts +0 -312
- package/core/client/components/LiveDebugger.tsx +0 -1324
- package/core/live/ComponentRegistry.ts +0 -403
- package/core/live/types.ts +0 -241
- package/workspace.json +0 -6
|
@@ -1,181 +1,182 @@
|
|
|
1
|
-
import type { FluxStack, PluginContext, CliCommand, Plugin } from "../../plugins/types"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"flux database:migrate
|
|
23
|
-
"flux database:migrate --
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
"flux database:seed
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
"flux database:reset
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
1
|
+
import type { FluxStack, PluginContext, CliCommand, Plugin } from "../../plugins/types"
|
|
2
|
+
import { logger } from "@core/utils/logger"
|
|
3
|
+
|
|
4
|
+
// Database plugin with CLI commands
|
|
5
|
+
export const databasePlugin: Plugin = {
|
|
6
|
+
name: "database",
|
|
7
|
+
description: "Database management plugin with CLI commands",
|
|
8
|
+
author: "FluxStack Team",
|
|
9
|
+
category: "data",
|
|
10
|
+
|
|
11
|
+
setup: (context: PluginContext) => {
|
|
12
|
+
context.logger.info("Database plugin initialized")
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
commands: [
|
|
16
|
+
{
|
|
17
|
+
name: "migrate",
|
|
18
|
+
description: "Run database migrations",
|
|
19
|
+
category: "Database",
|
|
20
|
+
usage: "flux database:migrate [options]",
|
|
21
|
+
examples: [
|
|
22
|
+
"flux database:migrate # Run all pending migrations",
|
|
23
|
+
"flux database:migrate --rollback # Rollback last migration",
|
|
24
|
+
"flux database:migrate --to 001 # Migrate to specific version"
|
|
25
|
+
],
|
|
26
|
+
options: [
|
|
27
|
+
{
|
|
28
|
+
name: "rollback",
|
|
29
|
+
short: "r",
|
|
30
|
+
description: "Rollback the last migration",
|
|
31
|
+
type: "boolean"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "to",
|
|
35
|
+
description: "Migrate to specific version",
|
|
36
|
+
type: "string"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "dry-run",
|
|
40
|
+
description: "Show what would be migrated without executing",
|
|
41
|
+
type: "boolean"
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
handler: async (args, options, context) => {
|
|
45
|
+
if (options["dry-run"]) {
|
|
46
|
+
logger.info("Dry run mode - showing planned migrations:")
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (options.rollback) {
|
|
50
|
+
logger.info("Rolling back last migration...")
|
|
51
|
+
// Simulate rollback
|
|
52
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
53
|
+
logger.info("Rollback completed")
|
|
54
|
+
} else if (options.to) {
|
|
55
|
+
logger.info(`Migrating to version: ${options.to}`)
|
|
56
|
+
// Simulate migration to version
|
|
57
|
+
await new Promise(resolve => setTimeout(resolve, 1500))
|
|
58
|
+
logger.info(`Migrated to version ${options.to}`)
|
|
59
|
+
} else {
|
|
60
|
+
logger.info("Running all pending migrations...")
|
|
61
|
+
// Simulate migration
|
|
62
|
+
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
63
|
+
logger.info("All migrations completed")
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "seed",
|
|
69
|
+
description: "Seed the database with initial data",
|
|
70
|
+
category: "Database",
|
|
71
|
+
usage: "flux database:seed [seeder]",
|
|
72
|
+
examples: [
|
|
73
|
+
"flux database:seed # Run all seeders",
|
|
74
|
+
"flux database:seed users # Run specific seeder"
|
|
75
|
+
],
|
|
76
|
+
arguments: [
|
|
77
|
+
{
|
|
78
|
+
name: "seeder",
|
|
79
|
+
description: "Specific seeder to run",
|
|
80
|
+
required: false,
|
|
81
|
+
type: "string"
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
options: [
|
|
85
|
+
{
|
|
86
|
+
name: "force",
|
|
87
|
+
short: "f",
|
|
88
|
+
description: "Force seeding even if data exists",
|
|
89
|
+
type: "boolean"
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
handler: async (args, options, context) => {
|
|
93
|
+
const [seeder] = args
|
|
94
|
+
|
|
95
|
+
if (seeder) {
|
|
96
|
+
logger.info(`Running seeder: ${seeder}`)
|
|
97
|
+
logger.info(` Force mode: ${options.force ? 'ON' : 'OFF'}`)
|
|
98
|
+
} else {
|
|
99
|
+
logger.info("Running all seeders...")
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Simulate seeding
|
|
103
|
+
await new Promise(resolve => setTimeout(resolve, 1500))
|
|
104
|
+
logger.info("Database seeded successfully")
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "reset",
|
|
109
|
+
description: "Reset the database (drop all tables and recreate)",
|
|
110
|
+
category: "Database",
|
|
111
|
+
usage: "flux database:reset [options]",
|
|
112
|
+
examples: [
|
|
113
|
+
"flux database:reset # Reset and migrate",
|
|
114
|
+
"flux database:reset --seed # Reset, migrate and seed"
|
|
115
|
+
],
|
|
116
|
+
options: [
|
|
117
|
+
{
|
|
118
|
+
name: "seed",
|
|
119
|
+
short: "s",
|
|
120
|
+
description: "Run seeders after reset",
|
|
121
|
+
type: "boolean"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: "confirm",
|
|
125
|
+
description: "Skip confirmation prompt",
|
|
126
|
+
type: "boolean"
|
|
127
|
+
}
|
|
128
|
+
],
|
|
129
|
+
handler: async (args, options, context) => {
|
|
130
|
+
if (!options.confirm) {
|
|
131
|
+
logger.warn("WARNING: This will delete all data in the database!")
|
|
132
|
+
logger.info("Use --confirm to skip this prompt.")
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
logger.info("Dropping all tables...")
|
|
137
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
138
|
+
|
|
139
|
+
logger.info("Running migrations...")
|
|
140
|
+
await new Promise(resolve => setTimeout(resolve, 1500))
|
|
141
|
+
|
|
142
|
+
if (options.seed) {
|
|
143
|
+
logger.info("Running seeders...")
|
|
144
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
logger.info("Database reset completed")
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: "status",
|
|
152
|
+
description: "Show database migration status",
|
|
153
|
+
category: "Database",
|
|
154
|
+
aliases: ["info"],
|
|
155
|
+
handler: async (args, options, context) => {
|
|
156
|
+
logger.info("Database Status:")
|
|
157
|
+
logger.info("------------------")
|
|
158
|
+
logger.info("Connected: Yes")
|
|
159
|
+
logger.info("Tables: 15")
|
|
160
|
+
logger.info("Last migration: 2024_01_15_create_users_table")
|
|
161
|
+
logger.info("Pending migrations: 2")
|
|
162
|
+
logger.info("Database size: 2.3 MB")
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Utility functions that could be used by the plugin
|
|
169
|
+
export async function runMigration(version?: string): Promise<void> {
|
|
170
|
+
// Actual migration logic would go here
|
|
171
|
+
logger.info(`Running migration ${version || 'all'}`)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export async function rollbackMigration(): Promise<void> {
|
|
175
|
+
// Actual rollback logic would go here
|
|
176
|
+
logger.info("Rolling back migration")
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function seedDatabase(seeder?: string): Promise<void> {
|
|
180
|
+
// Actual seeding logic would go here
|
|
181
|
+
logger.info(`Seeding database ${seeder || 'all'}`)
|
|
181
182
|
}
|
|
@@ -76,7 +76,7 @@ export const staticFilesPlugin: Plugin = {
|
|
|
76
76
|
const enableUploads = pluginsConfig.staticEnableUploads
|
|
77
77
|
|
|
78
78
|
// Async handler — uses Bun.file() APIs instead of Node fs
|
|
79
|
-
const serveFile = (baseDir: string, isUpload: boolean) => async ({ params, set, request }:
|
|
79
|
+
const serveFile = (baseDir: string, isUpload: boolean) => async ({ params, set, request }: { params: Record<string, string>; set: { status: number; headers: Record<string, string> }; request: Request }) => {
|
|
80
80
|
const requestedPath: string = params['*'] || ''
|
|
81
81
|
|
|
82
82
|
// Reject null bytes early — prevents filesystem confusion
|
|
@@ -155,15 +155,16 @@ export const staticFilesPlugin: Plugin = {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
// Register routes based on config flags
|
|
158
|
+
const app = context.app as import('elysia').Elysia
|
|
158
159
|
if (enablePublic) {
|
|
159
160
|
await mkdir(publicDir, { recursive: true })
|
|
160
|
-
|
|
161
|
+
app.get('/api/static/*', serveFile(publicDir, false) as never)
|
|
161
162
|
context.logger.debug('Static public files route registered: /api/static/*')
|
|
162
163
|
}
|
|
163
164
|
|
|
164
165
|
if (enableUploads) {
|
|
165
166
|
await mkdir(uploadsDir, { recursive: true })
|
|
166
|
-
|
|
167
|
+
app.get('/api/uploads/*', serveFile(uploadsDir, true) as never)
|
|
167
168
|
context.logger.debug('Static uploads route registered: /api/uploads/*')
|
|
168
169
|
}
|
|
169
170
|
|
|
@@ -4,7 +4,10 @@ import type { Plugin, PluginContext } from '@core/plugins/types'
|
|
|
4
4
|
export const swaggerPlugin: Plugin = {
|
|
5
5
|
name: 'swagger',
|
|
6
6
|
setup(context: PluginContext) {
|
|
7
|
-
context.app.
|
|
7
|
+
const app = context.app as import('elysia').Elysia
|
|
8
|
+
const config = context.config as Record<string, unknown>
|
|
9
|
+
const server = config.server as Record<string, unknown> | undefined
|
|
10
|
+
app.use(swagger({
|
|
8
11
|
path: '/swagger',
|
|
9
12
|
documentation: {
|
|
10
13
|
info: {
|
|
@@ -13,18 +16,18 @@ export const swaggerPlugin: Plugin = {
|
|
|
13
16
|
description: 'Modern full-stack TypeScript framework with type-safe API endpoints'
|
|
14
17
|
},
|
|
15
18
|
tags: [
|
|
16
|
-
{
|
|
17
|
-
name: 'Health',
|
|
18
|
-
description: 'Health check endpoints'
|
|
19
|
+
{
|
|
20
|
+
name: 'Health',
|
|
21
|
+
description: 'Health check endpoints'
|
|
19
22
|
},
|
|
20
|
-
{
|
|
21
|
-
name: 'Users',
|
|
22
|
-
description: 'User management endpoints'
|
|
23
|
+
{
|
|
24
|
+
name: 'Users',
|
|
25
|
+
description: 'User management endpoints'
|
|
23
26
|
}
|
|
24
27
|
],
|
|
25
28
|
servers: [
|
|
26
29
|
{
|
|
27
|
-
url: `http://localhost:${
|
|
30
|
+
url: `http://localhost:${server?.port || 3000}`,
|
|
28
31
|
description: 'Development server'
|
|
29
32
|
}
|
|
30
33
|
]
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type { ServerWebSocket } from 'bun'
|
|
4
4
|
import type { Room, RoomSystem, SystemEvents } from './RoomSystem'
|
|
5
|
+
import { logger } from "@core/utils/logger"
|
|
5
6
|
|
|
6
7
|
type WebSocketLike = {
|
|
7
8
|
send: (data: string) => void
|
|
@@ -19,12 +20,12 @@ interface BroadcastMessage {
|
|
|
19
20
|
type: 'room:event' | 'room:state' | 'room:system'
|
|
20
21
|
roomId: string
|
|
21
22
|
event: string
|
|
22
|
-
data:
|
|
23
|
+
data: unknown
|
|
23
24
|
timestamp: number
|
|
24
25
|
senderId?: string
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
export class RoomBroadcaster<TState, TEvents extends Record<string,
|
|
28
|
+
export class RoomBroadcaster<TState, TEvents extends Record<string, unknown>> {
|
|
28
29
|
private connections = new Map<string, RoomConnection>()
|
|
29
30
|
private roomConnections = new Map<string, Set<string>>() // roomId -> connectionIds
|
|
30
31
|
private roomSystem: RoomSystem<TState, TEvents>
|
|
@@ -156,7 +157,7 @@ export class RoomBroadcaster<TState, TEvents extends Record<string, any>> {
|
|
|
156
157
|
broadcast(
|
|
157
158
|
roomId: string,
|
|
158
159
|
event: string,
|
|
159
|
-
data:
|
|
160
|
+
data: unknown,
|
|
160
161
|
options?: { exclude?: string | string[] }
|
|
161
162
|
): number {
|
|
162
163
|
const roomConns = this.roomConnections.get(roomId)
|
|
@@ -200,7 +201,7 @@ export class RoomBroadcaster<TState, TEvents extends Record<string, any>> {
|
|
|
200
201
|
/**
|
|
201
202
|
* Envia evento do sistema para todos na sala
|
|
202
203
|
*/
|
|
203
|
-
broadcastSystem(roomId: string, event: string, data:
|
|
204
|
+
broadcastSystem(roomId: string, event: string, data: unknown): number {
|
|
204
205
|
const roomConns = this.roomConnections.get(roomId)
|
|
205
206
|
if (!roomConns || roomConns.size === 0) return 0
|
|
206
207
|
|
|
@@ -238,7 +239,7 @@ export class RoomBroadcaster<TState, TEvents extends Record<string, any>> {
|
|
|
238
239
|
connection.ws.send(JSON.stringify(message))
|
|
239
240
|
return true
|
|
240
241
|
} catch (error) {
|
|
241
|
-
|
|
242
|
+
logger.error(`[RoomBroadcaster] Error sending to ${connectionId}:`, error)
|
|
242
243
|
return false
|
|
243
244
|
}
|
|
244
245
|
}
|
|
@@ -246,7 +247,7 @@ export class RoomBroadcaster<TState, TEvents extends Record<string, any>> {
|
|
|
246
247
|
/**
|
|
247
248
|
* Envia para um usuário específico (todas as conexões do usuário)
|
|
248
249
|
*/
|
|
249
|
-
sendToUser(userId: string, roomId: string, event: string, data:
|
|
250
|
+
sendToUser(userId: string, roomId: string, event: string, data: unknown): number {
|
|
250
251
|
let sent = 0
|
|
251
252
|
|
|
252
253
|
for (const [connId, conn] of this.connections) {
|
|
@@ -277,15 +278,15 @@ export class RoomBroadcaster<TState, TEvents extends Record<string, any>> {
|
|
|
277
278
|
roomId: string,
|
|
278
279
|
initialState: TState,
|
|
279
280
|
connectionId?: string
|
|
280
|
-
): Room<TState, TEvents> & { broadcastEmit: (event:
|
|
281
|
+
): Room<TState, TEvents> & { broadcastEmit: <K extends keyof TEvents>(event: K, data: TEvents[K]) => void } {
|
|
281
282
|
const room = this.roomSystem.getOrCreate(roomId, initialState)
|
|
282
283
|
|
|
283
284
|
// Adicionar método de broadcast
|
|
284
285
|
const enhanced = room as Room<TState, TEvents> & {
|
|
285
|
-
broadcastEmit: (event:
|
|
286
|
+
broadcastEmit: <K extends keyof TEvents>(event: K, data: TEvents[K]) => void
|
|
286
287
|
}
|
|
287
288
|
|
|
288
|
-
enhanced.broadcastEmit = (event:
|
|
289
|
+
enhanced.broadcastEmit = <K extends keyof TEvents>(event: K, data: TEvents[K]) => {
|
|
289
290
|
// Emitir localmente
|
|
290
291
|
room.emit(event, data)
|
|
291
292
|
// Broadcast via WebSocket
|
|
@@ -347,7 +348,7 @@ export class RoomBroadcaster<TState, TEvents extends Record<string, any>> {
|
|
|
347
348
|
|
|
348
349
|
// Factory
|
|
349
350
|
export function createRoomBroadcaster<
|
|
350
|
-
TDef extends { state:
|
|
351
|
+
TDef extends { state: unknown; events: Record<string, unknown> }
|
|
351
352
|
>(
|
|
352
353
|
roomSystem: RoomSystem<TDef['state'], TDef['events']>
|
|
353
354
|
): RoomBroadcaster<TDef['state'], TDef['events']> {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// 🔥 FluxStack Room System - Pub/Sub tipado para comunicação entre componentes
|
|
2
2
|
|
|
3
|
+
import { logger } from "@core/utils/logger"
|
|
4
|
+
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- default `any` needed for contravariant handler storage
|
|
3
6
|
type EventHandler<T = any> = (data: T) => void
|
|
4
7
|
type Unsubscribe = () => void
|
|
5
8
|
|
|
@@ -9,7 +12,7 @@ export type SystemEvents<TState> = {
|
|
|
9
12
|
'$room:destroyed': { roomId: string; reason: 'manual' | 'empty' | 'ttl'; finalState: TState }
|
|
10
13
|
'$sub:join': { subscriberId: string; event: string; count: number }
|
|
11
14
|
'$sub:leave': { subscriberId: string; event: string; count: number }
|
|
12
|
-
'$state:change': { path?: string; oldValue:
|
|
15
|
+
'$state:change': { path?: string; oldValue: unknown; newValue: unknown }
|
|
13
16
|
'$state:reset': { oldState: TState; newState: TState }
|
|
14
17
|
'$error': { error: Error; context: string }
|
|
15
18
|
}
|
|
@@ -25,7 +28,7 @@ interface Subscription {
|
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
// Room instance
|
|
28
|
-
export class Room<TState, TEvents extends Record<string,
|
|
31
|
+
export class Room<TState, TEvents extends Record<string, unknown>> {
|
|
29
32
|
public readonly id: string
|
|
30
33
|
public readonly createdAt: number
|
|
31
34
|
|
|
@@ -36,12 +39,12 @@ export class Room<TState, TEvents extends Record<string, any>> {
|
|
|
36
39
|
private destroyed = false
|
|
37
40
|
|
|
38
41
|
// Callback para notificar o sistema global
|
|
39
|
-
private onSystemEvent?: (event: string, data:
|
|
42
|
+
private onSystemEvent?: (event: string, data: unknown) => void
|
|
40
43
|
|
|
41
44
|
constructor(
|
|
42
45
|
id: string,
|
|
43
46
|
initialState: TState,
|
|
44
|
-
onSystemEvent?: (event: string, data:
|
|
47
|
+
onSystemEvent?: (event: string, data: unknown) => void
|
|
45
48
|
) {
|
|
46
49
|
this.id = id
|
|
47
50
|
this.createdAt = Date.now()
|
|
@@ -167,7 +170,7 @@ export class Room<TState, TEvents extends Record<string, any>> {
|
|
|
167
170
|
this.onSystemEvent?.(event as string, { roomId: this.id, ...data })
|
|
168
171
|
}
|
|
169
172
|
|
|
170
|
-
private emitInternal(event: string, data:
|
|
173
|
+
private emitInternal(event: string, data: unknown): number {
|
|
171
174
|
const subs = this.subscriptions.get(event)
|
|
172
175
|
if (!subs || subs.size === 0) return 0
|
|
173
176
|
|
|
@@ -177,7 +180,7 @@ export class Room<TState, TEvents extends Record<string, any>> {
|
|
|
177
180
|
sub.handler(data)
|
|
178
181
|
notified++
|
|
179
182
|
} catch (error) {
|
|
180
|
-
|
|
183
|
+
logger.error(`[Room:${this.id}] Error in handler for '${event}':`, error)
|
|
181
184
|
this.emitSystem('$error', {
|
|
182
185
|
error: error as Error,
|
|
183
186
|
context: `Handler for event '${event}'`
|
|
@@ -242,7 +245,7 @@ export interface RoomSystemOptions {
|
|
|
242
245
|
defaultTTL?: number // TTL padrão para salas
|
|
243
246
|
}
|
|
244
247
|
|
|
245
|
-
export class RoomSystem<TState, TEvents extends Record<string,
|
|
248
|
+
export class RoomSystem<TState, TEvents extends Record<string, unknown>> {
|
|
246
249
|
public readonly name: string
|
|
247
250
|
private rooms = new Map<string, Room<TState, TEvents>>()
|
|
248
251
|
private globalSubscriptions = new Map<string, Set<EventHandler>>()
|
|
@@ -383,11 +386,11 @@ export class RoomSystem<TState, TEvents extends Record<string, any>> {
|
|
|
383
386
|
}
|
|
384
387
|
}
|
|
385
388
|
|
|
386
|
-
private handleSystemEvent(event: string, data:
|
|
389
|
+
private handleSystemEvent(event: string, data: unknown): void {
|
|
387
390
|
this.emitGlobal(event, data)
|
|
388
391
|
}
|
|
389
392
|
|
|
390
|
-
private emitGlobal(event: string, data:
|
|
393
|
+
private emitGlobal(event: string, data: unknown): void {
|
|
391
394
|
const handlers = this.globalSubscriptions.get(event)
|
|
392
395
|
if (!handlers) return
|
|
393
396
|
|
|
@@ -395,7 +398,7 @@ export class RoomSystem<TState, TEvents extends Record<string, any>> {
|
|
|
395
398
|
try {
|
|
396
399
|
handler(data)
|
|
397
400
|
} catch (error) {
|
|
398
|
-
|
|
401
|
+
logger.error(`[RoomSystem:${this.name}] Error in global handler for '${event}':`, error)
|
|
399
402
|
}
|
|
400
403
|
}
|
|
401
404
|
}
|
|
@@ -448,7 +451,7 @@ export class RoomSystem<TState, TEvents extends Record<string, any>> {
|
|
|
448
451
|
// ============================================
|
|
449
452
|
|
|
450
453
|
export function createRoomSystem<
|
|
451
|
-
TDef extends { state:
|
|
454
|
+
TDef extends { state: unknown; events: Record<string, unknown> }
|
|
452
455
|
>(
|
|
453
456
|
name: string,
|
|
454
457
|
options?: RoomSystemOptions
|