@rpgjs/vite 5.0.0-alpha.42 → 5.0.0-alpha.44

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.
@@ -0,0 +1,8 @@
1
+ import { Plugin } from 'vite';
2
+ interface MmorpgBuildPluginOptions {
3
+ rpgType: string;
4
+ serverEntry: string;
5
+ adapterEntries?: Record<string, string>;
6
+ }
7
+ export declare function mmorpgBuildPlugin({ rpgType, serverEntry, adapterEntries, }: MmorpgBuildPluginOptions): Plugin;
8
+ export {};
@@ -1,8 +1,13 @@
1
+ type MmorpgEntryPoints = string | {
2
+ client?: string;
3
+ server?: string;
4
+ adapters?: Record<string, string>;
5
+ };
1
6
  interface RpgjsPluginOptions {
2
7
  server: any;
3
8
  entryPoints?: {
4
- rpg: string;
5
- mmorpg: string;
9
+ rpg?: string;
10
+ mmorpg?: MmorpgEntryPoints;
6
11
  };
7
12
  }
8
13
  export declare function rpgjs({ server, entryPoints }: RpgjsPluginOptions): (import('vite').Plugin<any> | {
@@ -1,285 +1,8 @@
1
- import { RpgServerEngine } from '@rpgjs/server';
1
+ import { RpgTransportServerConstructor } from '@rpgjs/server/node';
2
2
  import { ViteDevServer } from 'vite';
3
- interface WSConnection {
4
- readyState: number;
5
- send(data: string): void;
6
- close(): void;
7
- on(event: string, callback: (...args: any[]) => void): void;
8
- }
9
- /**
10
- * PartyConnection class compatible with PartyKit's Party.Connection interface
11
- *
12
- * This class implements the Connection interface expected by RPG-JS server,
13
- * providing WebSocket communication capabilities and connection state management.
14
- * Includes optional packet loss simulation for testing network conditions.
15
- *
16
- * @example
17
- * ```typescript
18
- * const connection = new PartyConnection(websocket, 'player123');
19
- * connection.send('Hello player!');
20
- * connection.setState({ username: 'Alice' });
21
- * ```
22
- */
23
- declare class PartyConnection {
24
- private ws;
25
- id: string;
26
- uri: string;
27
- private _state;
28
- private messageQueue;
29
- private isProcessingQueue;
30
- private sequenceCounter;
31
- private incomingQueue;
32
- private isProcessingIncomingQueue;
33
- static packetLossRate: number;
34
- static packetLossEnabled: boolean;
35
- static packetLossFilter: string;
36
- static bandwidthEnabled: boolean;
37
- static bandwidthKbps: number;
38
- static bandwidthFilter: string;
39
- static latencyEnabled: boolean;
40
- static latencyMs: number;
41
- static latencyFilter: string;
42
- constructor(ws: WSConnection, id?: string, uri?: string);
43
- /**
44
- * Generates a unique identifier for the connection
45
- *
46
- * @returns {string} Unique identifier based on timestamp and random number
47
- */
48
- private generateId;
49
- /**
50
- * Sends data to the client via WebSocket with bandwidth and latency simulation
51
- *
52
- * Messages are queued and sent in the order they were called, with bandwidth limitations
53
- * and network latency that simulate a slow, distant network connection. This ensures that
54
- * if send(A) is called before send(B), A will always be sent before B, but both will be
55
- * slowed down by bandwidth constraints and network latency.
56
- *
57
- * @param {any} data - Data to send (automatically serialized to JSON if not string)
58
- */
59
- send(data: any): Promise<void>;
60
- /**
61
- * Processes the outgoing queue in order.
62
- *
63
- * Each message receives its own fixed latency (if enabled), while preserving
64
- * original spacing and order.
65
- */
66
- private processMessageQueue;
67
- /**
68
- * Flushes the send queue sequentially, respecting bandwidth constraints.
69
- */
70
- private flushSendQueue;
71
- private shouldApplyLatency;
72
- private waitUntil;
73
- /**
74
- * Closes the WebSocket connection
75
- */
76
- close(): void;
77
- /**
78
- * Sets state data for this connection
79
- *
80
- * @param {any} value - State data to store (max 2KB as per PartyKit spec)
81
- */
82
- setState(value: any): void;
83
- /**
84
- * Gets the current state of this connection
85
- *
86
- * @returns {any} Current connection state
87
- */
88
- get state(): any;
89
- /**
90
- * Buffers incoming messages to simulate TCP latency on reception.
91
- *
92
- * Messages are processed in strict order. Each message keeps its own fixed
93
- * latency delay relative to the moment it arrived.
94
- *
95
- * @param {string} message - Raw incoming message
96
- * @param {(messages: string[]) => Promise<void>} processor - Async batch processor
97
- *
98
- * @example
99
- * await connection.bufferIncoming(raw, async (batch) => {
100
- * for (const msg of batch) await handle(msg)
101
- * })
102
- */
103
- bufferIncoming(message: string, processor: (messages: string[]) => Promise<void>): void;
104
- private processIncomingQueue;
105
- /**
106
- * Configures packet loss simulation settings
107
- *
108
- * @param {boolean} enabled - Whether to enable packet loss simulation
109
- * @param {number} rate - Packet loss rate (0.0 to 1.0, e.g., 0.1 = 10% loss)
110
- * @param {string} filter - Optional filter string to only simulate loss for messages containing this string
111
- *
112
- * @example
113
- * ```typescript
114
- * PartyConnection.configurePacketLoss(true, 0.15); // 15% packet loss
115
- * PartyConnection.configurePacketLoss(true, 0.2, 'sync'); // 20% loss only for sync messages
116
- * ```
117
- */
118
- static configurePacketLoss(enabled: boolean, rate: number, filter?: string): void;
119
- /**
120
- * Gets current packet loss simulation status
121
- *
122
- * @returns {Object} Current configuration
123
- */
124
- static getPacketLossStatus(): {
125
- enabled: boolean;
126
- rate: number;
127
- filter: string;
128
- };
129
- /**
130
- * Configures bandwidth simulation settings
131
- *
132
- * @param {boolean} enabled - Whether to enable bandwidth simulation
133
- * @param {number} kbps - Bandwidth in kilobits per second (e.g., 100 = 100 kbps)
134
- * @param {string} filter - Optional filter string to only simulate bandwidth for messages containing this string
135
- *
136
- * @example
137
- * ```typescript
138
- * PartyConnection.configureBandwidth(true, 50); // 50 kbps (very slow connection)
139
- * PartyConnection.configureBandwidth(true, 1000, 'sync'); // 1 Mbps only for sync messages
140
- * ```
141
- */
142
- static configureBandwidth(enabled: boolean, kbps: number, filter?: string): void;
143
- /**
144
- * Gets current bandwidth simulation status
145
- *
146
- * @returns {Object} Current configuration
147
- */
148
- static getBandwidthStatus(): {
149
- enabled: boolean;
150
- kbps: number;
151
- filter: string;
152
- };
153
- /**
154
- * Configures latency simulation settings
155
- *
156
- * Latency simulates the ping time to a distant server. Each message gets the same fixed delay,
157
- * regardless of when it was sent. This means if you send 3 messages rapidly, they will all
158
- * have the same latency delay applied to them.
159
- *
160
- * @param {boolean} enabled - Whether to enable latency simulation
161
- * @param {number} ms - Fixed latency in milliseconds (simulates ping to distant server)
162
- * @param {string} filter - Optional filter string to only simulate latency for messages containing this string
163
- *
164
- * @example
165
- * ```typescript
166
- * PartyConnection.configureLatency(true, 100); // 100ms latency (distant server)
167
- * PartyConnection.configureLatency(true, 200, 'sync'); // 200ms latency only for sync messages
168
- * ```
169
- */
170
- static configureLatency(enabled: boolean, ms: number, filter?: string): void;
171
- /**
172
- * Gets current latency simulation status
173
- *
174
- * @returns {Object} Current configuration
175
- */
176
- static getLatencyStatus(): {
177
- enabled: boolean;
178
- ms: number;
179
- filter: string;
180
- };
181
- }
182
- /**
183
- * Room class compatible with PartyKit's Party.Room interface
184
- *
185
- * This class manages multiple WebSocket connections and provides broadcasting
186
- * capabilities, storage, and connection management as expected by RPG-JS server.
187
- *
188
- * @example
189
- * ```typescript
190
- * const room = new Room('lobby-1');
191
- * room.broadcast('Game started!');
192
- * const playerCount = [...room.getConnections()].length;
193
- * ```
194
- */
195
- declare class Room {
196
- id: string;
197
- internalID: string;
198
- env: Record<string, any>;
199
- context: any;
200
- private connections;
201
- private storageData;
202
- constructor(id: string);
203
- /**
204
- * Broadcasts a message to all connected clients with bandwidth simulation
205
- *
206
- * Messages are sent to each connection in parallel, but each connection maintains
207
- * its own ordered queue of messages with bandwidth limitations. This ensures that
208
- * broadcast messages are queued in the correct order for each individual connection,
209
- * while being slowed down by simulated bandwidth constraints.
210
- *
211
- * @param {any} message - Message to broadcast
212
- * @param {string[]} except - Array of connection IDs to exclude from broadcast
213
- */
214
- broadcast(message: any, except?: string[]): Promise<void>;
215
- /**
216
- * Gets a connection by its ID
217
- *
218
- * @param {string} id - Connection ID
219
- * @returns {PartyConnection | undefined} The connection or undefined if not found
220
- */
221
- getConnection(id: string): PartyConnection | undefined;
222
- /**
223
- * Gets all currently connected clients
224
- *
225
- * @param {string} tag - Optional tag to filter connections (not implemented yet)
226
- * @returns {IterableIterator<PartyConnection>} Iterator of all connections
227
- */
228
- getConnections(tag?: string): IterableIterator<PartyConnection>;
229
- /**
230
- * Adds a connection to this room
231
- *
232
- * @param {PartyConnection} connection - Connection to add
233
- */
234
- addConnection(connection: PartyConnection): void;
235
- /**
236
- * Removes a connection from this room
237
- *
238
- * @param {string} connectionId - ID of connection to remove
239
- */
240
- removeConnection(connectionId: string): void;
241
- /**
242
- * Simple key-value storage for the room
243
- */
244
- get storage(): {
245
- put: (key: string, value: any) => Promise<void>;
246
- get: <T = any>(key: string) => Promise<T | undefined>;
247
- delete: (key: string) => Promise<void>;
248
- list: () => Promise<[string, any][]>;
249
- };
250
- }
251
- /**
252
- * Creates a Vite plugin for integrating RPG-JS server functionality
253
- *
254
- * This plugin configures the development server to automatically start
255
- * an RPG-JS server instance when Vite's dev server starts. It handles
256
- * the instantiation and initialization of the server module, and sets up
257
- * HTTP request and WebSocket connection forwarding to the RPG-JS server.
258
- *
259
- * The plugin intercepts:
260
- * - HTTP requests to `/parties/*` paths and forwards them to the RPG-JS server
261
- * - WebSocket upgrade requests and establishes connections with the RPG-JS server
262
- *
263
- * @param {new () => RpgServerEngine} serverModule - A class constructor that extends RpgServerEngine
264
- * @returns {Object} Vite plugin configuration object
265
- *
266
- * @example
267
- * ```typescript
268
- * // In vite.config.ts
269
- * import { serverPlugin } from '@rpgjs/vite';
270
- * import startServer from './src/server';
271
- *
272
- * export default defineConfig({
273
- * plugins: [
274
- * serverPlugin(startServer)
275
- * ]
276
- * });
277
- * ```
278
- */
279
- export declare function serverPlugin(serverModule: new (room: Room) => RpgServerEngine): {
3
+ export declare function serverPlugin(serverModule: RpgTransportServerConstructor): {
280
4
  name: string;
281
5
  configureServer(server: ViteDevServer): Promise<void>;
282
6
  buildStart(): void;
283
7
  buildEnd(): void;
284
8
  };
285
- export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpgjs/vite",
3
- "version": "5.0.0-alpha.42",
3
+ "version": "5.0.0-alpha.44",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "keywords": [],
@@ -12,12 +12,13 @@
12
12
  "access": "public"
13
13
  },
14
14
  "dependencies": {
15
- "@canvasengine/compiler": "2.0.0-beta.53",
15
+ "@canvasengine/tiled": "2.0.0-beta.54",
16
+ "@canvasengine/compiler": "2.0.0-beta.54",
16
17
  "@hono/vite-dev-server": "^0.25.0",
17
- "@rpgjs/server": "5.0.0-alpha.42",
18
- "@types/node": "^25.2.3",
19
- "acorn": "^8.15.0",
20
- "acorn-walk": "^8.3.4",
18
+ "@rpgjs/server": "5.0.0-alpha.44",
19
+ "@types/node": "^25.3.5",
20
+ "acorn": "^8.16.0",
21
+ "acorn-walk": "^8.3.5",
21
22
  "magic-string": "^0.30.21",
22
23
  "typescript": "^5.9.3",
23
24
  "vite": "^7.3.1",
@@ -0,0 +1,123 @@
1
+ import { existsSync, rmSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { build as viteBuild, type Plugin, type ResolvedConfig } from "vite";
4
+
5
+ interface MmorpgBuildPluginOptions {
6
+ rpgType: string;
7
+ serverEntry: string;
8
+ adapterEntries?: Record<string, string>;
9
+ }
10
+
11
+ function isBareImport(id: string): boolean {
12
+ return !id.startsWith(".") && !id.startsWith("/") && !id.startsWith("\0");
13
+ }
14
+
15
+ function resolveEntry(root: string, entry: string): string {
16
+ return resolve(root, entry);
17
+ }
18
+
19
+ function collectServerEntries(
20
+ root: string,
21
+ serverEntry: string,
22
+ adapterEntries: Record<string, string>,
23
+ ): Record<string, string> {
24
+ const entries: Record<string, string> = {};
25
+ const resolvedServerEntry = resolveEntry(root, serverEntry);
26
+
27
+ if (!existsSync(resolvedServerEntry)) {
28
+ throw new Error(`[rpgjs:mmorpg-build] Missing server entry: ${serverEntry}`);
29
+ }
30
+
31
+ entries.server = resolvedServerEntry;
32
+
33
+ for (const [name, entry] of Object.entries(adapterEntries)) {
34
+ const resolvedAdapterEntry = resolveEntry(root, entry);
35
+ if (!existsSync(resolvedAdapterEntry)) {
36
+ throw new Error(`[rpgjs:mmorpg-build] Missing adapter entry "${name}": ${entry}`);
37
+ }
38
+ entries[name] = resolvedAdapterEntry;
39
+ }
40
+
41
+ return entries;
42
+ }
43
+
44
+ export function mmorpgBuildPlugin({
45
+ rpgType,
46
+ serverEntry,
47
+ adapterEntries = {},
48
+ }: MmorpgBuildPluginOptions): Plugin {
49
+ let config: ResolvedConfig;
50
+ let didBuildServer = false;
51
+ let didCleanDist = false;
52
+
53
+ return {
54
+ name: "rpgjs:mmorpg-build",
55
+ apply: "build",
56
+ enforce: "post",
57
+ config(_, env) {
58
+ if (env.command !== "build" || rpgType !== "mmorpg") {
59
+ return;
60
+ }
61
+
62
+ return {
63
+ build: {
64
+ outDir: "dist/client",
65
+ },
66
+ };
67
+ },
68
+ configResolved(resolvedConfig) {
69
+ config = resolvedConfig;
70
+ },
71
+ buildStart() {
72
+ if (rpgType !== "mmorpg" || didCleanDist) {
73
+ return;
74
+ }
75
+ didCleanDist = true;
76
+ rmSync(resolve(config.root, "dist"), { recursive: true, force: true });
77
+ },
78
+ async closeBundle() {
79
+ if (rpgType !== "mmorpg" || didBuildServer) {
80
+ return;
81
+ }
82
+ didBuildServer = true;
83
+
84
+ const entries = collectServerEntries(config.root, serverEntry, adapterEntries);
85
+ console.log(
86
+ `[rpgjs:mmorpg-build] Building server bundle(s): ${Object.keys(entries).join(", ")}`,
87
+ );
88
+
89
+ await viteBuild({
90
+ configFile: false,
91
+ root: config.root,
92
+ define: config.define,
93
+ resolve: {
94
+ alias: config.resolve.alias,
95
+ dedupe: config.resolve.dedupe,
96
+ extensions: config.resolve.extensions,
97
+ mainFields: config.resolve.mainFields,
98
+ conditions: config.resolve.conditions,
99
+ },
100
+ publicDir: false,
101
+ build: {
102
+ emptyOutDir: true,
103
+ minify: false,
104
+ outDir: "dist/server",
105
+ sourcemap: config.build.sourcemap,
106
+ target: "node18",
107
+ lib: {
108
+ entry: entries,
109
+ formats: ["es"],
110
+ },
111
+ rollupOptions: {
112
+ external(id) {
113
+ return isBareImport(id);
114
+ },
115
+ output: {
116
+ entryFileNames: "[name].js",
117
+ },
118
+ },
119
+ },
120
+ });
121
+ },
122
+ };
123
+ }
@@ -2,12 +2,37 @@ import canvasengine from "@canvasengine/compiler";
2
2
  import { replaceConfigImport } from "./replace-config-import";
3
3
  import { serverPlugin } from "./server-plugin";
4
4
  import { entryPointPlugin } from "./entry-point-plugin";
5
+ import { mmorpgBuildPlugin } from "./mmorpg-build-plugin";
6
+
7
+ type MmorpgEntryPoints =
8
+ | string
9
+ | {
10
+ client?: string;
11
+ server?: string;
12
+ adapters?: Record<string, string>;
13
+ };
5
14
 
6
15
  interface RpgjsPluginOptions {
7
16
  server: any;
8
17
  entryPoints?: {
9
- rpg: string;
10
- mmorpg: string;
18
+ rpg?: string;
19
+ mmorpg?: MmorpgEntryPoints;
20
+ };
21
+ }
22
+
23
+ function normalizeMmorpgEntryPoints(entryPoints?: MmorpgEntryPoints) {
24
+ if (!entryPoints || typeof entryPoints === "string") {
25
+ return {
26
+ client: entryPoints ?? "./src/client.ts",
27
+ server: "./src/server.ts",
28
+ adapters: {},
29
+ };
30
+ }
31
+
32
+ return {
33
+ client: entryPoints.client ?? "./src/client.ts",
34
+ server: entryPoints.server ?? "./src/server.ts",
35
+ adapters: entryPoints.adapters ?? {},
11
36
  };
12
37
  }
13
38
 
@@ -15,15 +40,22 @@ export function rpgjs({
15
40
  server,
16
41
  entryPoints
17
42
  }: RpgjsPluginOptions) {
43
+ const mmorpgEntryPoints = normalizeMmorpgEntryPoints(entryPoints?.mmorpg);
44
+
18
45
  return [
19
46
  canvasengine(),
20
47
  replaceConfigImport(),
21
48
  serverPlugin(server),
49
+ mmorpgBuildPlugin({
50
+ rpgType: process.env.RPG_TYPE || "rpg",
51
+ serverEntry: mmorpgEntryPoints.server,
52
+ adapterEntries: mmorpgEntryPoints.adapters,
53
+ }),
22
54
  entryPointPlugin({
23
55
  entryPoints: {
24
56
  rpg: entryPoints?.rpg ?? './src/standalone.ts',
25
- mmorpg: entryPoints?.mmorpg ?? './src/client.ts'
57
+ mmorpg: mmorpgEntryPoints.client,
26
58
  }
27
59
  })
28
60
  ]
29
- }
61
+ }