@esengine/server 4.3.0 → 4.5.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/dist/{Room-BnKpl5Sj.d.ts → Room-5owFVIFR.d.ts} +18 -0
- package/dist/auth/index.d.ts +4 -4
- package/dist/auth/testing/index.d.ts +2 -2
- package/dist/chunk-NWZLKNGV.js +2045 -0
- package/dist/chunk-NWZLKNGV.js.map +1 -0
- package/dist/{chunk-FACTBKJ3.js → chunk-T3QJOPNG.js} +37 -2
- package/dist/chunk-T3QJOPNG.js.map +1 -0
- package/dist/chunk-ZUTL4RI7.js +285 -0
- package/dist/chunk-ZUTL4RI7.js.map +1 -0
- package/dist/ecs/index.d.ts +31 -4
- package/dist/ecs/index.js +3 -280
- package/dist/ecs/index.js.map +1 -1
- package/dist/index-B1sr5YAl.d.ts +1076 -0
- package/dist/index.d.ts +1453 -7
- package/dist/index.js +1502 -4
- package/dist/index.js.map +1 -1
- package/dist/ratelimit/index.d.ts +1 -1
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +2 -2
- package/dist/{types-C7sS8Sfi.d.ts → types-BCTRacMF.d.ts} +2 -2
- package/package.json +1 -1
- package/dist/chunk-FACTBKJ3.js.map +0 -1
- package/dist/chunk-M7VONMZJ.js +0 -938
- package/dist/chunk-M7VONMZJ.js.map +0 -1
- package/dist/decorators-DY8nZ8Nh.d.ts +0 -26
- package/dist/index-lcuKuQsL.d.ts +0 -470
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { ECSRoom } from './chunk-ZUTL4RI7.js';
|
|
2
|
+
export { DistributedRoomManager, MemoryAdapter, RoomManager, createHttpRouter, createServer } from './chunk-NWZLKNGV.js';
|
|
2
3
|
import './chunk-I4QQSQ72.js';
|
|
3
|
-
export { Player, Room, onMessage } from './chunk-
|
|
4
|
-
import { __name } from './chunk-T626JPC7.js';
|
|
4
|
+
export { Player, Room, onMessage } from './chunk-T3QJOPNG.js';
|
|
5
|
+
import { __name, __publicField } from './chunk-T626JPC7.js';
|
|
5
6
|
export { ErrorCode, RpcError } from '@esengine/rpc';
|
|
6
7
|
|
|
7
8
|
// src/helpers/define.ts
|
|
@@ -9,15 +10,1512 @@ function defineApi(definition) {
|
|
|
9
10
|
return definition;
|
|
10
11
|
}
|
|
11
12
|
__name(defineApi, "defineApi");
|
|
13
|
+
function defineApiWithSchema(schema, definition) {
|
|
14
|
+
return {
|
|
15
|
+
...definition,
|
|
16
|
+
schema
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
__name(defineApiWithSchema, "defineApiWithSchema");
|
|
12
20
|
function defineMsg(definition) {
|
|
13
21
|
return definition;
|
|
14
22
|
}
|
|
15
23
|
__name(defineMsg, "defineMsg");
|
|
24
|
+
function defineMsgWithSchema(schema, definition) {
|
|
25
|
+
return {
|
|
26
|
+
...definition,
|
|
27
|
+
schema
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
__name(defineMsgWithSchema, "defineMsgWithSchema");
|
|
16
31
|
function defineHttp(definition) {
|
|
17
32
|
return definition;
|
|
18
33
|
}
|
|
19
34
|
__name(defineHttp, "defineHttp");
|
|
20
35
|
|
|
21
|
-
|
|
36
|
+
// src/schema/base.ts
|
|
37
|
+
var _BaseValidator = class _BaseValidator {
|
|
38
|
+
constructor() {
|
|
39
|
+
__publicField(this, "_options", {});
|
|
40
|
+
}
|
|
41
|
+
validate(value, path = []) {
|
|
42
|
+
if (value === void 0) {
|
|
43
|
+
if (this._options.isOptional) {
|
|
44
|
+
if (this._options.defaultValue !== void 0) {
|
|
45
|
+
return {
|
|
46
|
+
success: true,
|
|
47
|
+
data: this._options.defaultValue
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
success: true,
|
|
52
|
+
data: void 0
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
success: false,
|
|
57
|
+
error: {
|
|
58
|
+
path,
|
|
59
|
+
message: "Required",
|
|
60
|
+
expected: this.typeName,
|
|
61
|
+
received: void 0
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (value === null) {
|
|
66
|
+
if (this._options.isNullable) {
|
|
67
|
+
return {
|
|
68
|
+
success: true,
|
|
69
|
+
data: null
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
success: false,
|
|
74
|
+
error: {
|
|
75
|
+
path,
|
|
76
|
+
message: "Expected non-null value",
|
|
77
|
+
expected: this.typeName,
|
|
78
|
+
received: null
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return this._validate(value, path);
|
|
83
|
+
}
|
|
84
|
+
is(value) {
|
|
85
|
+
return this.validate(value).success;
|
|
86
|
+
}
|
|
87
|
+
optional() {
|
|
88
|
+
const clone = this._clone();
|
|
89
|
+
clone._options.isOptional = true;
|
|
90
|
+
return clone;
|
|
91
|
+
}
|
|
92
|
+
default(defaultValue) {
|
|
93
|
+
const clone = this._clone();
|
|
94
|
+
clone._options.isOptional = true;
|
|
95
|
+
clone._options.defaultValue = defaultValue;
|
|
96
|
+
return clone;
|
|
97
|
+
}
|
|
98
|
+
nullable() {
|
|
99
|
+
const clone = this._clone();
|
|
100
|
+
clone._options.isNullable = true;
|
|
101
|
+
return clone;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
__name(_BaseValidator, "BaseValidator");
|
|
105
|
+
var BaseValidator = _BaseValidator;
|
|
106
|
+
|
|
107
|
+
// src/schema/primitives.ts
|
|
108
|
+
var _StringValidator = class _StringValidator extends BaseValidator {
|
|
109
|
+
constructor() {
|
|
110
|
+
super(...arguments);
|
|
111
|
+
__publicField(this, "typeName", "string");
|
|
112
|
+
__publicField(this, "_stringOptions", {});
|
|
113
|
+
}
|
|
114
|
+
_validate(value, path) {
|
|
115
|
+
if (typeof value !== "string") {
|
|
116
|
+
return {
|
|
117
|
+
success: false,
|
|
118
|
+
error: {
|
|
119
|
+
path,
|
|
120
|
+
message: `Expected string, received ${typeof value}`,
|
|
121
|
+
expected: "string",
|
|
122
|
+
received: value
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const { minLength, maxLength, pattern } = this._stringOptions;
|
|
127
|
+
if (minLength !== void 0 && value.length < minLength) {
|
|
128
|
+
return {
|
|
129
|
+
success: false,
|
|
130
|
+
error: {
|
|
131
|
+
path,
|
|
132
|
+
message: `String must be at least ${minLength} characters`,
|
|
133
|
+
expected: `string(minLength: ${minLength})`,
|
|
134
|
+
received: value
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (maxLength !== void 0 && value.length > maxLength) {
|
|
139
|
+
return {
|
|
140
|
+
success: false,
|
|
141
|
+
error: {
|
|
142
|
+
path,
|
|
143
|
+
message: `String must be at most ${maxLength} characters`,
|
|
144
|
+
expected: `string(maxLength: ${maxLength})`,
|
|
145
|
+
received: value
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (pattern && !pattern.test(value)) {
|
|
150
|
+
return {
|
|
151
|
+
success: false,
|
|
152
|
+
error: {
|
|
153
|
+
path,
|
|
154
|
+
message: `String does not match pattern ${pattern}`,
|
|
155
|
+
expected: `string(pattern: ${pattern})`,
|
|
156
|
+
received: value
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
success: true,
|
|
162
|
+
data: value
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
_clone() {
|
|
166
|
+
const clone = new _StringValidator();
|
|
167
|
+
clone._options = {
|
|
168
|
+
...this._options
|
|
169
|
+
};
|
|
170
|
+
clone._stringOptions = {
|
|
171
|
+
...this._stringOptions
|
|
172
|
+
};
|
|
173
|
+
return clone;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* @zh 设置最小长度
|
|
177
|
+
* @en Set minimum length
|
|
178
|
+
*/
|
|
179
|
+
min(length) {
|
|
180
|
+
const clone = this._clone();
|
|
181
|
+
clone._stringOptions.minLength = length;
|
|
182
|
+
return clone;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* @zh 设置最大长度
|
|
186
|
+
* @en Set maximum length
|
|
187
|
+
*/
|
|
188
|
+
max(length) {
|
|
189
|
+
const clone = this._clone();
|
|
190
|
+
clone._stringOptions.maxLength = length;
|
|
191
|
+
return clone;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* @zh 设置长度范围
|
|
195
|
+
* @en Set length range
|
|
196
|
+
*/
|
|
197
|
+
length(min, max) {
|
|
198
|
+
const clone = this._clone();
|
|
199
|
+
clone._stringOptions.minLength = min;
|
|
200
|
+
clone._stringOptions.maxLength = max;
|
|
201
|
+
return clone;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* @zh 设置正则模式
|
|
205
|
+
* @en Set regex pattern
|
|
206
|
+
*/
|
|
207
|
+
regex(pattern) {
|
|
208
|
+
const clone = this._clone();
|
|
209
|
+
clone._stringOptions.pattern = pattern;
|
|
210
|
+
return clone;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* @zh 邮箱格式验证
|
|
214
|
+
* @en Email format validation
|
|
215
|
+
*/
|
|
216
|
+
email() {
|
|
217
|
+
return this.regex(/^[^\s@]+@[^\s@]+\.[^\s@]+$/);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* @zh URL 格式验证
|
|
221
|
+
* @en URL format validation
|
|
222
|
+
*/
|
|
223
|
+
url() {
|
|
224
|
+
return this.regex(/^https?:\/\/.+/);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
__name(_StringValidator, "StringValidator");
|
|
228
|
+
var StringValidator = _StringValidator;
|
|
229
|
+
var _NumberValidator = class _NumberValidator extends BaseValidator {
|
|
230
|
+
constructor() {
|
|
231
|
+
super(...arguments);
|
|
232
|
+
__publicField(this, "typeName", "number");
|
|
233
|
+
__publicField(this, "_numberOptions", {});
|
|
234
|
+
}
|
|
235
|
+
_validate(value, path) {
|
|
236
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
237
|
+
return {
|
|
238
|
+
success: false,
|
|
239
|
+
error: {
|
|
240
|
+
path,
|
|
241
|
+
message: `Expected number, received ${typeof value}`,
|
|
242
|
+
expected: "number",
|
|
243
|
+
received: value
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const { min, max, integer } = this._numberOptions;
|
|
248
|
+
if (integer && !Number.isInteger(value)) {
|
|
249
|
+
return {
|
|
250
|
+
success: false,
|
|
251
|
+
error: {
|
|
252
|
+
path,
|
|
253
|
+
message: "Expected integer",
|
|
254
|
+
expected: "integer",
|
|
255
|
+
received: value
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
if (min !== void 0 && value < min) {
|
|
260
|
+
return {
|
|
261
|
+
success: false,
|
|
262
|
+
error: {
|
|
263
|
+
path,
|
|
264
|
+
message: `Number must be >= ${min}`,
|
|
265
|
+
expected: `number(min: ${min})`,
|
|
266
|
+
received: value
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
if (max !== void 0 && value > max) {
|
|
271
|
+
return {
|
|
272
|
+
success: false,
|
|
273
|
+
error: {
|
|
274
|
+
path,
|
|
275
|
+
message: `Number must be <= ${max}`,
|
|
276
|
+
expected: `number(max: ${max})`,
|
|
277
|
+
received: value
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
success: true,
|
|
283
|
+
data: value
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
_clone() {
|
|
287
|
+
const clone = new _NumberValidator();
|
|
288
|
+
clone._options = {
|
|
289
|
+
...this._options
|
|
290
|
+
};
|
|
291
|
+
clone._numberOptions = {
|
|
292
|
+
...this._numberOptions
|
|
293
|
+
};
|
|
294
|
+
return clone;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* @zh 设置最小值
|
|
298
|
+
* @en Set minimum value
|
|
299
|
+
*/
|
|
300
|
+
min(value) {
|
|
301
|
+
const clone = this._clone();
|
|
302
|
+
clone._numberOptions.min = value;
|
|
303
|
+
return clone;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* @zh 设置最大值
|
|
307
|
+
* @en Set maximum value
|
|
308
|
+
*/
|
|
309
|
+
max(value) {
|
|
310
|
+
const clone = this._clone();
|
|
311
|
+
clone._numberOptions.max = value;
|
|
312
|
+
return clone;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* @zh 设置范围
|
|
316
|
+
* @en Set range
|
|
317
|
+
*/
|
|
318
|
+
range(min, max) {
|
|
319
|
+
const clone = this._clone();
|
|
320
|
+
clone._numberOptions.min = min;
|
|
321
|
+
clone._numberOptions.max = max;
|
|
322
|
+
return clone;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* @zh 要求为整数
|
|
326
|
+
* @en Require integer
|
|
327
|
+
*/
|
|
328
|
+
int() {
|
|
329
|
+
const clone = this._clone();
|
|
330
|
+
clone._numberOptions.integer = true;
|
|
331
|
+
return clone;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* @zh 要求为正数
|
|
335
|
+
* @en Require positive
|
|
336
|
+
*/
|
|
337
|
+
positive() {
|
|
338
|
+
return this.min(0);
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* @zh 要求为负数
|
|
342
|
+
* @en Require negative
|
|
343
|
+
*/
|
|
344
|
+
negative() {
|
|
345
|
+
return this.max(0);
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
__name(_NumberValidator, "NumberValidator");
|
|
349
|
+
var NumberValidator = _NumberValidator;
|
|
350
|
+
var _BooleanValidator = class _BooleanValidator extends BaseValidator {
|
|
351
|
+
constructor() {
|
|
352
|
+
super(...arguments);
|
|
353
|
+
__publicField(this, "typeName", "boolean");
|
|
354
|
+
}
|
|
355
|
+
_validate(value, path) {
|
|
356
|
+
if (typeof value !== "boolean") {
|
|
357
|
+
return {
|
|
358
|
+
success: false,
|
|
359
|
+
error: {
|
|
360
|
+
path,
|
|
361
|
+
message: `Expected boolean, received ${typeof value}`,
|
|
362
|
+
expected: "boolean",
|
|
363
|
+
received: value
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
success: true,
|
|
369
|
+
data: value
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
_clone() {
|
|
373
|
+
const clone = new _BooleanValidator();
|
|
374
|
+
clone._options = {
|
|
375
|
+
...this._options
|
|
376
|
+
};
|
|
377
|
+
return clone;
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
__name(_BooleanValidator, "BooleanValidator");
|
|
381
|
+
var BooleanValidator = _BooleanValidator;
|
|
382
|
+
var _LiteralValidator = class _LiteralValidator extends BaseValidator {
|
|
383
|
+
constructor(literal2) {
|
|
384
|
+
super();
|
|
385
|
+
__publicField(this, "typeName");
|
|
386
|
+
__publicField(this, "_literal");
|
|
387
|
+
this._literal = literal2;
|
|
388
|
+
this.typeName = `literal(${JSON.stringify(literal2)})`;
|
|
389
|
+
}
|
|
390
|
+
_validate(value, path) {
|
|
391
|
+
if (value !== this._literal) {
|
|
392
|
+
return {
|
|
393
|
+
success: false,
|
|
394
|
+
error: {
|
|
395
|
+
path,
|
|
396
|
+
message: `Expected ${JSON.stringify(this._literal)}, received ${JSON.stringify(value)}`,
|
|
397
|
+
expected: this.typeName,
|
|
398
|
+
received: value
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
return {
|
|
403
|
+
success: true,
|
|
404
|
+
data: value
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
_clone() {
|
|
408
|
+
const clone = new _LiteralValidator(this._literal);
|
|
409
|
+
clone._options = {
|
|
410
|
+
...this._options
|
|
411
|
+
};
|
|
412
|
+
return clone;
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
__name(_LiteralValidator, "LiteralValidator");
|
|
416
|
+
var LiteralValidator = _LiteralValidator;
|
|
417
|
+
var _AnyValidator = class _AnyValidator extends BaseValidator {
|
|
418
|
+
constructor() {
|
|
419
|
+
super(...arguments);
|
|
420
|
+
__publicField(this, "typeName", "any");
|
|
421
|
+
}
|
|
422
|
+
_validate(value) {
|
|
423
|
+
return {
|
|
424
|
+
success: true,
|
|
425
|
+
data: value
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
_clone() {
|
|
429
|
+
const clone = new _AnyValidator();
|
|
430
|
+
clone._options = {
|
|
431
|
+
...this._options
|
|
432
|
+
};
|
|
433
|
+
return clone;
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
__name(_AnyValidator, "AnyValidator");
|
|
437
|
+
var AnyValidator = _AnyValidator;
|
|
438
|
+
function string() {
|
|
439
|
+
return new StringValidator();
|
|
440
|
+
}
|
|
441
|
+
__name(string, "string");
|
|
442
|
+
function number() {
|
|
443
|
+
return new NumberValidator();
|
|
444
|
+
}
|
|
445
|
+
__name(number, "number");
|
|
446
|
+
function boolean() {
|
|
447
|
+
return new BooleanValidator();
|
|
448
|
+
}
|
|
449
|
+
__name(boolean, "boolean");
|
|
450
|
+
function literal(value) {
|
|
451
|
+
return new LiteralValidator(value);
|
|
452
|
+
}
|
|
453
|
+
__name(literal, "literal");
|
|
454
|
+
function any() {
|
|
455
|
+
return new AnyValidator();
|
|
456
|
+
}
|
|
457
|
+
__name(any, "any");
|
|
458
|
+
|
|
459
|
+
// src/schema/composites.ts
|
|
460
|
+
var _ObjectValidator = class _ObjectValidator extends BaseValidator {
|
|
461
|
+
constructor(shape) {
|
|
462
|
+
super();
|
|
463
|
+
__publicField(this, "typeName", "object");
|
|
464
|
+
__publicField(this, "_shape");
|
|
465
|
+
__publicField(this, "_objectOptions", {});
|
|
466
|
+
this._shape = shape;
|
|
467
|
+
}
|
|
468
|
+
_validate(value, path) {
|
|
469
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
470
|
+
return {
|
|
471
|
+
success: false,
|
|
472
|
+
error: {
|
|
473
|
+
path,
|
|
474
|
+
message: `Expected object, received ${Array.isArray(value) ? "array" : typeof value}`,
|
|
475
|
+
expected: "object",
|
|
476
|
+
received: value
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
const result = {};
|
|
481
|
+
const obj = value;
|
|
482
|
+
for (const [key, validator] of Object.entries(this._shape)) {
|
|
483
|
+
const fieldValue = obj[key];
|
|
484
|
+
const fieldPath = [
|
|
485
|
+
...path,
|
|
486
|
+
key
|
|
487
|
+
];
|
|
488
|
+
const fieldResult = validator.validate(fieldValue, fieldPath);
|
|
489
|
+
if (!fieldResult.success) {
|
|
490
|
+
return fieldResult;
|
|
491
|
+
}
|
|
492
|
+
result[key] = fieldResult.data;
|
|
493
|
+
}
|
|
494
|
+
if (this._objectOptions.strict) {
|
|
495
|
+
const knownKeys = new Set(Object.keys(this._shape));
|
|
496
|
+
for (const key of Object.keys(obj)) {
|
|
497
|
+
if (!knownKeys.has(key)) {
|
|
498
|
+
return {
|
|
499
|
+
success: false,
|
|
500
|
+
error: {
|
|
501
|
+
path: [
|
|
502
|
+
...path,
|
|
503
|
+
key
|
|
504
|
+
],
|
|
505
|
+
message: `Unknown key "${key}"`,
|
|
506
|
+
expected: "known key",
|
|
507
|
+
received: key
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return {
|
|
514
|
+
success: true,
|
|
515
|
+
data: result
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
_clone() {
|
|
519
|
+
const clone = new _ObjectValidator(this._shape);
|
|
520
|
+
clone._options = {
|
|
521
|
+
...this._options
|
|
522
|
+
};
|
|
523
|
+
clone._objectOptions = {
|
|
524
|
+
...this._objectOptions
|
|
525
|
+
};
|
|
526
|
+
return clone;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* @zh 严格模式(不允许额外字段)
|
|
530
|
+
* @en Strict mode (no extra fields allowed)
|
|
531
|
+
*/
|
|
532
|
+
strict() {
|
|
533
|
+
const clone = this._clone();
|
|
534
|
+
clone._objectOptions.strict = true;
|
|
535
|
+
return clone;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* @zh 部分模式(所有字段可选)
|
|
539
|
+
* @en Partial mode (all fields optional)
|
|
540
|
+
*/
|
|
541
|
+
partial() {
|
|
542
|
+
const partialShape = {};
|
|
543
|
+
for (const [key, validator] of Object.entries(this._shape)) {
|
|
544
|
+
partialShape[key] = validator.optional();
|
|
545
|
+
}
|
|
546
|
+
return new _ObjectValidator(partialShape);
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* @zh 选择部分字段
|
|
550
|
+
* @en Pick specific fields
|
|
551
|
+
*/
|
|
552
|
+
pick(...keys) {
|
|
553
|
+
const pickedShape = {};
|
|
554
|
+
for (const key of keys) {
|
|
555
|
+
pickedShape[key] = this._shape[key];
|
|
556
|
+
}
|
|
557
|
+
return new _ObjectValidator(pickedShape);
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* @zh 排除部分字段
|
|
561
|
+
* @en Omit specific fields
|
|
562
|
+
*/
|
|
563
|
+
omit(...keys) {
|
|
564
|
+
const keySet = new Set(keys);
|
|
565
|
+
const omittedShape = {};
|
|
566
|
+
for (const [key, validator] of Object.entries(this._shape)) {
|
|
567
|
+
if (!keySet.has(key)) {
|
|
568
|
+
omittedShape[key] = validator;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return new _ObjectValidator(omittedShape);
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* @zh 扩展对象 Schema
|
|
575
|
+
* @en Extend object schema
|
|
576
|
+
*/
|
|
577
|
+
extend(shape) {
|
|
578
|
+
const extendedShape = {
|
|
579
|
+
...this._shape,
|
|
580
|
+
...shape
|
|
581
|
+
};
|
|
582
|
+
return new _ObjectValidator(extendedShape);
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
__name(_ObjectValidator, "ObjectValidator");
|
|
586
|
+
var ObjectValidator = _ObjectValidator;
|
|
587
|
+
var _ArrayValidator = class _ArrayValidator extends BaseValidator {
|
|
588
|
+
constructor(element) {
|
|
589
|
+
super();
|
|
590
|
+
__publicField(this, "typeName", "array");
|
|
591
|
+
__publicField(this, "_element");
|
|
592
|
+
__publicField(this, "_arrayOptions", {});
|
|
593
|
+
this._element = element;
|
|
594
|
+
}
|
|
595
|
+
_validate(value, path) {
|
|
596
|
+
if (!Array.isArray(value)) {
|
|
597
|
+
return {
|
|
598
|
+
success: false,
|
|
599
|
+
error: {
|
|
600
|
+
path,
|
|
601
|
+
message: `Expected array, received ${typeof value}`,
|
|
602
|
+
expected: "array",
|
|
603
|
+
received: value
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
const { minLength, maxLength } = this._arrayOptions;
|
|
608
|
+
if (minLength !== void 0 && value.length < minLength) {
|
|
609
|
+
return {
|
|
610
|
+
success: false,
|
|
611
|
+
error: {
|
|
612
|
+
path,
|
|
613
|
+
message: `Array must have at least ${minLength} items`,
|
|
614
|
+
expected: `array(minLength: ${minLength})`,
|
|
615
|
+
received: value
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
if (maxLength !== void 0 && value.length > maxLength) {
|
|
620
|
+
return {
|
|
621
|
+
success: false,
|
|
622
|
+
error: {
|
|
623
|
+
path,
|
|
624
|
+
message: `Array must have at most ${maxLength} items`,
|
|
625
|
+
expected: `array(maxLength: ${maxLength})`,
|
|
626
|
+
received: value
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
const result = [];
|
|
631
|
+
for (let i = 0; i < value.length; i++) {
|
|
632
|
+
const itemPath = [
|
|
633
|
+
...path,
|
|
634
|
+
String(i)
|
|
635
|
+
];
|
|
636
|
+
const itemResult = this._element.validate(value[i], itemPath);
|
|
637
|
+
if (!itemResult.success) {
|
|
638
|
+
return itemResult;
|
|
639
|
+
}
|
|
640
|
+
result.push(itemResult.data);
|
|
641
|
+
}
|
|
642
|
+
return {
|
|
643
|
+
success: true,
|
|
644
|
+
data: result
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
_clone() {
|
|
648
|
+
const clone = new _ArrayValidator(this._element);
|
|
649
|
+
clone._options = {
|
|
650
|
+
...this._options
|
|
651
|
+
};
|
|
652
|
+
clone._arrayOptions = {
|
|
653
|
+
...this._arrayOptions
|
|
654
|
+
};
|
|
655
|
+
return clone;
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* @zh 设置最小长度
|
|
659
|
+
* @en Set minimum length
|
|
660
|
+
*/
|
|
661
|
+
min(length) {
|
|
662
|
+
const clone = this._clone();
|
|
663
|
+
clone._arrayOptions.minLength = length;
|
|
664
|
+
return clone;
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* @zh 设置最大长度
|
|
668
|
+
* @en Set maximum length
|
|
669
|
+
*/
|
|
670
|
+
max(length) {
|
|
671
|
+
const clone = this._clone();
|
|
672
|
+
clone._arrayOptions.maxLength = length;
|
|
673
|
+
return clone;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* @zh 设置长度范围
|
|
677
|
+
* @en Set length range
|
|
678
|
+
*/
|
|
679
|
+
length(min, max) {
|
|
680
|
+
const clone = this._clone();
|
|
681
|
+
clone._arrayOptions.minLength = min;
|
|
682
|
+
clone._arrayOptions.maxLength = max;
|
|
683
|
+
return clone;
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* @zh 要求非空数组
|
|
687
|
+
* @en Require non-empty array
|
|
688
|
+
*/
|
|
689
|
+
nonempty() {
|
|
690
|
+
return this.min(1);
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
__name(_ArrayValidator, "ArrayValidator");
|
|
694
|
+
var ArrayValidator = _ArrayValidator;
|
|
695
|
+
var _TupleValidator = class _TupleValidator extends BaseValidator {
|
|
696
|
+
constructor(elements) {
|
|
697
|
+
super();
|
|
698
|
+
__publicField(this, "typeName", "tuple");
|
|
699
|
+
__publicField(this, "_elements");
|
|
700
|
+
this._elements = elements;
|
|
701
|
+
}
|
|
702
|
+
_validate(value, path) {
|
|
703
|
+
if (!Array.isArray(value)) {
|
|
704
|
+
return {
|
|
705
|
+
success: false,
|
|
706
|
+
error: {
|
|
707
|
+
path,
|
|
708
|
+
message: `Expected tuple, received ${typeof value}`,
|
|
709
|
+
expected: "tuple",
|
|
710
|
+
received: value
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
if (value.length !== this._elements.length) {
|
|
715
|
+
return {
|
|
716
|
+
success: false,
|
|
717
|
+
error: {
|
|
718
|
+
path,
|
|
719
|
+
message: `Expected tuple of length ${this._elements.length}, received length ${value.length}`,
|
|
720
|
+
expected: `tuple(length: ${this._elements.length})`,
|
|
721
|
+
received: value
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
const result = [];
|
|
726
|
+
for (let i = 0; i < this._elements.length; i++) {
|
|
727
|
+
const itemPath = [
|
|
728
|
+
...path,
|
|
729
|
+
String(i)
|
|
730
|
+
];
|
|
731
|
+
const itemResult = this._elements[i].validate(value[i], itemPath);
|
|
732
|
+
if (!itemResult.success) {
|
|
733
|
+
return itemResult;
|
|
734
|
+
}
|
|
735
|
+
result.push(itemResult.data);
|
|
736
|
+
}
|
|
737
|
+
return {
|
|
738
|
+
success: true,
|
|
739
|
+
data: result
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
_clone() {
|
|
743
|
+
const clone = new _TupleValidator(this._elements);
|
|
744
|
+
clone._options = {
|
|
745
|
+
...this._options
|
|
746
|
+
};
|
|
747
|
+
return clone;
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
__name(_TupleValidator, "TupleValidator");
|
|
751
|
+
var TupleValidator = _TupleValidator;
|
|
752
|
+
var _UnionValidator = class _UnionValidator extends BaseValidator {
|
|
753
|
+
constructor(variants) {
|
|
754
|
+
super();
|
|
755
|
+
__publicField(this, "typeName");
|
|
756
|
+
__publicField(this, "_variants");
|
|
757
|
+
this._variants = variants;
|
|
758
|
+
this.typeName = `union(${variants.map((v) => v.typeName).join(" | ")})`;
|
|
759
|
+
}
|
|
760
|
+
_validate(value, path) {
|
|
761
|
+
const errors = [];
|
|
762
|
+
for (const variant of this._variants) {
|
|
763
|
+
const result = variant.validate(value, path);
|
|
764
|
+
if (result.success) {
|
|
765
|
+
return result;
|
|
766
|
+
}
|
|
767
|
+
errors.push(variant.typeName);
|
|
768
|
+
}
|
|
769
|
+
return {
|
|
770
|
+
success: false,
|
|
771
|
+
error: {
|
|
772
|
+
path,
|
|
773
|
+
message: `Expected one of: ${errors.join(", ")}`,
|
|
774
|
+
expected: this.typeName,
|
|
775
|
+
received: value
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
_clone() {
|
|
780
|
+
const clone = new _UnionValidator(this._variants);
|
|
781
|
+
clone._options = {
|
|
782
|
+
...this._options
|
|
783
|
+
};
|
|
784
|
+
return clone;
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
__name(_UnionValidator, "UnionValidator");
|
|
788
|
+
var UnionValidator = _UnionValidator;
|
|
789
|
+
var _RecordValidator = class _RecordValidator extends BaseValidator {
|
|
790
|
+
constructor(valueValidator) {
|
|
791
|
+
super();
|
|
792
|
+
__publicField(this, "typeName", "record");
|
|
793
|
+
__publicField(this, "_valueValidator");
|
|
794
|
+
this._valueValidator = valueValidator;
|
|
795
|
+
}
|
|
796
|
+
_validate(value, path) {
|
|
797
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
798
|
+
return {
|
|
799
|
+
success: false,
|
|
800
|
+
error: {
|
|
801
|
+
path,
|
|
802
|
+
message: `Expected object, received ${Array.isArray(value) ? "array" : typeof value}`,
|
|
803
|
+
expected: "record",
|
|
804
|
+
received: value
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
const result = {};
|
|
809
|
+
const obj = value;
|
|
810
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
811
|
+
const fieldPath = [
|
|
812
|
+
...path,
|
|
813
|
+
key
|
|
814
|
+
];
|
|
815
|
+
const fieldResult = this._valueValidator.validate(val, fieldPath);
|
|
816
|
+
if (!fieldResult.success) {
|
|
817
|
+
return fieldResult;
|
|
818
|
+
}
|
|
819
|
+
result[key] = fieldResult.data;
|
|
820
|
+
}
|
|
821
|
+
return {
|
|
822
|
+
success: true,
|
|
823
|
+
data: result
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
_clone() {
|
|
827
|
+
const clone = new _RecordValidator(this._valueValidator);
|
|
828
|
+
clone._options = {
|
|
829
|
+
...this._options
|
|
830
|
+
};
|
|
831
|
+
return clone;
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
__name(_RecordValidator, "RecordValidator");
|
|
835
|
+
var RecordValidator = _RecordValidator;
|
|
836
|
+
var _EnumValidator = class _EnumValidator extends BaseValidator {
|
|
837
|
+
constructor(values) {
|
|
838
|
+
super();
|
|
839
|
+
__publicField(this, "typeName");
|
|
840
|
+
__publicField(this, "_values");
|
|
841
|
+
__publicField(this, "_valuesArray");
|
|
842
|
+
this._valuesArray = values;
|
|
843
|
+
this._values = new Set(values);
|
|
844
|
+
this.typeName = `enum(${values.map((v) => JSON.stringify(v)).join(", ")})`;
|
|
845
|
+
}
|
|
846
|
+
_validate(value, path) {
|
|
847
|
+
if (!this._values.has(value)) {
|
|
848
|
+
return {
|
|
849
|
+
success: false,
|
|
850
|
+
error: {
|
|
851
|
+
path,
|
|
852
|
+
message: `Expected one of: ${this._valuesArray.map((v) => JSON.stringify(v)).join(", ")}`,
|
|
853
|
+
expected: this.typeName,
|
|
854
|
+
received: value
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
return {
|
|
859
|
+
success: true,
|
|
860
|
+
data: value
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
_clone() {
|
|
864
|
+
const clone = new _EnumValidator(this._valuesArray);
|
|
865
|
+
clone._options = {
|
|
866
|
+
...this._options
|
|
867
|
+
};
|
|
868
|
+
return clone;
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
__name(_EnumValidator, "EnumValidator");
|
|
872
|
+
var EnumValidator = _EnumValidator;
|
|
873
|
+
function object(shape) {
|
|
874
|
+
return new ObjectValidator(shape);
|
|
875
|
+
}
|
|
876
|
+
__name(object, "object");
|
|
877
|
+
function array(element) {
|
|
878
|
+
return new ArrayValidator(element);
|
|
879
|
+
}
|
|
880
|
+
__name(array, "array");
|
|
881
|
+
function tuple(elements) {
|
|
882
|
+
return new TupleValidator(elements);
|
|
883
|
+
}
|
|
884
|
+
__name(tuple, "tuple");
|
|
885
|
+
function union(variants) {
|
|
886
|
+
return new UnionValidator(variants);
|
|
887
|
+
}
|
|
888
|
+
__name(union, "union");
|
|
889
|
+
function record(valueValidator) {
|
|
890
|
+
return new RecordValidator(valueValidator);
|
|
891
|
+
}
|
|
892
|
+
__name(record, "record");
|
|
893
|
+
function nativeEnum(values) {
|
|
894
|
+
return new EnumValidator(values);
|
|
895
|
+
}
|
|
896
|
+
__name(nativeEnum, "nativeEnum");
|
|
897
|
+
|
|
898
|
+
// src/schema/index.ts
|
|
899
|
+
var s = {
|
|
900
|
+
// Primitives
|
|
901
|
+
string,
|
|
902
|
+
number,
|
|
903
|
+
boolean,
|
|
904
|
+
literal,
|
|
905
|
+
any,
|
|
906
|
+
// Composites
|
|
907
|
+
object,
|
|
908
|
+
array,
|
|
909
|
+
tuple,
|
|
910
|
+
union,
|
|
911
|
+
record,
|
|
912
|
+
/**
|
|
913
|
+
* @zh 创建枚举验证器
|
|
914
|
+
* @en Create enum validator
|
|
915
|
+
*
|
|
916
|
+
* @example
|
|
917
|
+
* ```typescript
|
|
918
|
+
* const RoleSchema = s.enum(['admin', 'user', 'guest'] as const);
|
|
919
|
+
* type Role = s.infer<typeof RoleSchema>; // 'admin' | 'user' | 'guest'
|
|
920
|
+
* ```
|
|
921
|
+
*/
|
|
922
|
+
enum: nativeEnum,
|
|
923
|
+
/**
|
|
924
|
+
* @zh 类型推断辅助(仅用于类型层面)
|
|
925
|
+
* @en Type inference helper (type-level only)
|
|
926
|
+
*
|
|
927
|
+
* @zh 这是一个类型辅助,用于从验证器推断类型
|
|
928
|
+
* @en This is a type helper to infer types from validators
|
|
929
|
+
*/
|
|
930
|
+
infer: void 0
|
|
931
|
+
};
|
|
932
|
+
function parse(validator, value) {
|
|
933
|
+
const result = validator.validate(value);
|
|
934
|
+
if (!result.success) {
|
|
935
|
+
const pathStr = result.error.path.length > 0 ? ` at "${result.error.path.join(".")}"` : "";
|
|
936
|
+
throw new Error(`Validation failed${pathStr}: ${result.error.message}`);
|
|
937
|
+
}
|
|
938
|
+
return result.data;
|
|
939
|
+
}
|
|
940
|
+
__name(parse, "parse");
|
|
941
|
+
function safeParse(validator, value) {
|
|
942
|
+
return validator.validate(value);
|
|
943
|
+
}
|
|
944
|
+
__name(safeParse, "safeParse");
|
|
945
|
+
function createGuard(validator) {
|
|
946
|
+
return (value) => validator.is(value);
|
|
947
|
+
}
|
|
948
|
+
__name(createGuard, "createGuard");
|
|
949
|
+
|
|
950
|
+
// src/distributed/adapters/RedisAdapter.ts
|
|
951
|
+
var RELEASE_LOCK_SCRIPT = `
|
|
952
|
+
if redis.call("get", KEYS[1]) == ARGV[1] then
|
|
953
|
+
return redis.call("del", KEYS[1])
|
|
954
|
+
else
|
|
955
|
+
return 0
|
|
956
|
+
end
|
|
957
|
+
`;
|
|
958
|
+
var EXTEND_LOCK_SCRIPT = `
|
|
959
|
+
if redis.call("get", KEYS[1]) == ARGV[1] then
|
|
960
|
+
return redis.call("pexpire", KEYS[1], ARGV[2])
|
|
961
|
+
else
|
|
962
|
+
return 0
|
|
963
|
+
end
|
|
964
|
+
`;
|
|
965
|
+
var _RedisAdapter = class _RedisAdapter {
|
|
966
|
+
constructor(config) {
|
|
967
|
+
__publicField(this, "_config");
|
|
968
|
+
__publicField(this, "_client", null);
|
|
969
|
+
__publicField(this, "_subscriber", null);
|
|
970
|
+
__publicField(this, "_connected", false);
|
|
971
|
+
// 锁的 owner token(用于安全释放)
|
|
972
|
+
__publicField(this, "_lockTokens", /* @__PURE__ */ new Map());
|
|
973
|
+
// 事件处理器
|
|
974
|
+
__publicField(this, "_handlers", /* @__PURE__ */ new Map());
|
|
975
|
+
__publicField(this, "_messageHandler", null);
|
|
976
|
+
this._config = {
|
|
977
|
+
prefix: "dist:",
|
|
978
|
+
serverTtl: 30,
|
|
979
|
+
roomTtl: 0,
|
|
980
|
+
snapshotTtl: 86400,
|
|
981
|
+
channel: "distributed:events",
|
|
982
|
+
...config,
|
|
983
|
+
factory: config.factory
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
// =========================================================================
|
|
987
|
+
// Key 生成器 | Key Generators
|
|
988
|
+
// =========================================================================
|
|
989
|
+
_key(type, id) {
|
|
990
|
+
return id ? `${this._config.prefix}${type}:${id}` : `${this._config.prefix}${type}`;
|
|
991
|
+
}
|
|
992
|
+
_serverKey(serverId) {
|
|
993
|
+
return this._key("server", serverId);
|
|
994
|
+
}
|
|
995
|
+
_roomKey(roomId) {
|
|
996
|
+
return this._key("room", roomId);
|
|
997
|
+
}
|
|
998
|
+
_snapshotKey(roomId) {
|
|
999
|
+
return this._key("snapshot", roomId);
|
|
1000
|
+
}
|
|
1001
|
+
_lockKey(key) {
|
|
1002
|
+
return this._key("lock", key);
|
|
1003
|
+
}
|
|
1004
|
+
_serversSetKey() {
|
|
1005
|
+
return this._key("servers");
|
|
1006
|
+
}
|
|
1007
|
+
_roomsSetKey() {
|
|
1008
|
+
return this._key("rooms");
|
|
1009
|
+
}
|
|
1010
|
+
_serverRoomsKey(serverId) {
|
|
1011
|
+
return this._key("server-rooms", serverId);
|
|
1012
|
+
}
|
|
1013
|
+
// =========================================================================
|
|
1014
|
+
// 生命周期 | Lifecycle
|
|
1015
|
+
// =========================================================================
|
|
1016
|
+
async connect() {
|
|
1017
|
+
if (this._connected) return;
|
|
1018
|
+
this._client = await this._config.factory();
|
|
1019
|
+
this._subscriber = this._client.duplicate();
|
|
1020
|
+
this._messageHandler = (channel, message) => {
|
|
1021
|
+
if (channel !== this._config.channel) return;
|
|
1022
|
+
try {
|
|
1023
|
+
const event = JSON.parse(message);
|
|
1024
|
+
this._dispatchEvent(event);
|
|
1025
|
+
} catch (error) {
|
|
1026
|
+
console.error("[RedisAdapter] Failed to parse event:", error);
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
this._subscriber.on("message", this._messageHandler);
|
|
1030
|
+
await this._subscriber.subscribe(this._config.channel);
|
|
1031
|
+
this._connected = true;
|
|
1032
|
+
}
|
|
1033
|
+
async disconnect() {
|
|
1034
|
+
if (!this._connected) return;
|
|
1035
|
+
if (this._subscriber) {
|
|
1036
|
+
if (this._messageHandler) {
|
|
1037
|
+
this._subscriber.off("message", this._messageHandler);
|
|
1038
|
+
}
|
|
1039
|
+
await this._subscriber.unsubscribe(this._config.channel);
|
|
1040
|
+
this._subscriber.disconnect();
|
|
1041
|
+
this._subscriber = null;
|
|
1042
|
+
}
|
|
1043
|
+
if (this._client) {
|
|
1044
|
+
await this._client.quit();
|
|
1045
|
+
this._client = null;
|
|
1046
|
+
}
|
|
1047
|
+
this._handlers.clear();
|
|
1048
|
+
this._lockTokens.clear();
|
|
1049
|
+
this._connected = false;
|
|
1050
|
+
}
|
|
1051
|
+
isConnected() {
|
|
1052
|
+
return this._connected;
|
|
1053
|
+
}
|
|
1054
|
+
_ensureConnected() {
|
|
1055
|
+
if (!this._connected || !this._client) {
|
|
1056
|
+
throw new Error("RedisAdapter is not connected");
|
|
1057
|
+
}
|
|
1058
|
+
return this._client;
|
|
1059
|
+
}
|
|
1060
|
+
// =========================================================================
|
|
1061
|
+
// 服务器注册 | Server Registry
|
|
1062
|
+
// =========================================================================
|
|
1063
|
+
async registerServer(server) {
|
|
1064
|
+
const client = this._ensureConnected();
|
|
1065
|
+
const key = this._serverKey(server.serverId);
|
|
1066
|
+
await client.hmset(key, "serverId", server.serverId, "address", server.address, "port", String(server.port), "roomCount", String(server.roomCount), "playerCount", String(server.playerCount), "capacity", String(server.capacity), "status", server.status, "lastHeartbeat", String(Date.now()), "metadata", JSON.stringify(server.metadata ?? {}));
|
|
1067
|
+
await client.expire(key, this._config.serverTtl);
|
|
1068
|
+
await client.sadd(this._serversSetKey(), server.serverId);
|
|
1069
|
+
await this.publish({
|
|
1070
|
+
type: "server:online",
|
|
1071
|
+
serverId: server.serverId,
|
|
1072
|
+
payload: server,
|
|
1073
|
+
timestamp: Date.now()
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
async unregisterServer(serverId) {
|
|
1077
|
+
const client = this._ensureConnected();
|
|
1078
|
+
const key = this._serverKey(serverId);
|
|
1079
|
+
await client.del(key);
|
|
1080
|
+
await client.srem(this._serversSetKey(), serverId);
|
|
1081
|
+
const roomIds = await client.smembers(this._serverRoomsKey(serverId));
|
|
1082
|
+
for (const roomId of roomIds) {
|
|
1083
|
+
await this.unregisterRoom(roomId);
|
|
1084
|
+
}
|
|
1085
|
+
await client.del(this._serverRoomsKey(serverId));
|
|
1086
|
+
await this.publish({
|
|
1087
|
+
type: "server:offline",
|
|
1088
|
+
serverId,
|
|
1089
|
+
payload: {
|
|
1090
|
+
serverId
|
|
1091
|
+
},
|
|
1092
|
+
timestamp: Date.now()
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
async heartbeat(serverId) {
|
|
1096
|
+
const client = this._ensureConnected();
|
|
1097
|
+
const key = this._serverKey(serverId);
|
|
1098
|
+
await client.hset(key, "lastHeartbeat", String(Date.now()));
|
|
1099
|
+
await client.expire(key, this._config.serverTtl);
|
|
1100
|
+
}
|
|
1101
|
+
async getServers() {
|
|
1102
|
+
const client = this._ensureConnected();
|
|
1103
|
+
const serverIds = await client.smembers(this._serversSetKey());
|
|
1104
|
+
const servers = [];
|
|
1105
|
+
for (const serverId of serverIds) {
|
|
1106
|
+
const server = await this.getServer(serverId);
|
|
1107
|
+
if (server && server.status === "online") {
|
|
1108
|
+
servers.push(server);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
return servers;
|
|
1112
|
+
}
|
|
1113
|
+
async getServer(serverId) {
|
|
1114
|
+
const client = this._ensureConnected();
|
|
1115
|
+
const key = this._serverKey(serverId);
|
|
1116
|
+
const data = await client.hgetall(key);
|
|
1117
|
+
if (!data || !data.serverId) return null;
|
|
1118
|
+
return {
|
|
1119
|
+
serverId: data.serverId,
|
|
1120
|
+
address: data.address,
|
|
1121
|
+
port: parseInt(data.port, 10),
|
|
1122
|
+
roomCount: parseInt(data.roomCount, 10),
|
|
1123
|
+
playerCount: parseInt(data.playerCount, 10),
|
|
1124
|
+
capacity: parseInt(data.capacity, 10),
|
|
1125
|
+
status: data.status,
|
|
1126
|
+
lastHeartbeat: parseInt(data.lastHeartbeat, 10),
|
|
1127
|
+
metadata: data.metadata ? JSON.parse(data.metadata) : {}
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
async updateServer(serverId, updates) {
|
|
1131
|
+
const client = this._ensureConnected();
|
|
1132
|
+
const key = this._serverKey(serverId);
|
|
1133
|
+
const args = [];
|
|
1134
|
+
if (updates.address !== void 0) args.push("address", updates.address);
|
|
1135
|
+
if (updates.port !== void 0) args.push("port", String(updates.port));
|
|
1136
|
+
if (updates.roomCount !== void 0) args.push("roomCount", String(updates.roomCount));
|
|
1137
|
+
if (updates.playerCount !== void 0) args.push("playerCount", String(updates.playerCount));
|
|
1138
|
+
if (updates.capacity !== void 0) args.push("capacity", String(updates.capacity));
|
|
1139
|
+
if (updates.status !== void 0) args.push("status", updates.status);
|
|
1140
|
+
if (updates.metadata !== void 0) args.push("metadata", JSON.stringify(updates.metadata));
|
|
1141
|
+
if (args.length > 0) {
|
|
1142
|
+
await client.hmset(key, ...args);
|
|
1143
|
+
}
|
|
1144
|
+
if (updates.status === "draining") {
|
|
1145
|
+
await this.publish({
|
|
1146
|
+
type: "server:draining",
|
|
1147
|
+
serverId,
|
|
1148
|
+
payload: {
|
|
1149
|
+
serverId
|
|
1150
|
+
},
|
|
1151
|
+
timestamp: Date.now()
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
// =========================================================================
|
|
1156
|
+
// 房间注册 | Room Registry
|
|
1157
|
+
// =========================================================================
|
|
1158
|
+
async registerRoom(room) {
|
|
1159
|
+
const client = this._ensureConnected();
|
|
1160
|
+
const key = this._roomKey(room.roomId);
|
|
1161
|
+
await client.hmset(key, "roomId", room.roomId, "roomType", room.roomType, "serverId", room.serverId, "serverAddress", room.serverAddress, "playerCount", String(room.playerCount), "maxPlayers", String(room.maxPlayers), "isLocked", room.isLocked ? "1" : "0", "metadata", JSON.stringify(room.metadata), "createdAt", String(room.createdAt), "updatedAt", String(room.updatedAt));
|
|
1162
|
+
if (this._config.roomTtl > 0) {
|
|
1163
|
+
await client.expire(key, this._config.roomTtl);
|
|
1164
|
+
}
|
|
1165
|
+
await client.sadd(this._roomsSetKey(), room.roomId);
|
|
1166
|
+
await client.sadd(this._serverRoomsKey(room.serverId), room.roomId);
|
|
1167
|
+
const roomCount = (await client.smembers(this._serverRoomsKey(room.serverId))).length;
|
|
1168
|
+
await client.hset(this._serverKey(room.serverId), "roomCount", String(roomCount));
|
|
1169
|
+
await this.publish({
|
|
1170
|
+
type: "room:created",
|
|
1171
|
+
serverId: room.serverId,
|
|
1172
|
+
roomId: room.roomId,
|
|
1173
|
+
payload: {
|
|
1174
|
+
roomType: room.roomType
|
|
1175
|
+
},
|
|
1176
|
+
timestamp: Date.now()
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
async unregisterRoom(roomId) {
|
|
1180
|
+
const client = this._ensureConnected();
|
|
1181
|
+
const room = await this.getRoom(roomId);
|
|
1182
|
+
if (!room) return;
|
|
1183
|
+
const key = this._roomKey(roomId);
|
|
1184
|
+
await client.del(key);
|
|
1185
|
+
await client.srem(this._roomsSetKey(), roomId);
|
|
1186
|
+
await client.srem(this._serverRoomsKey(room.serverId), roomId);
|
|
1187
|
+
const roomCount = (await client.smembers(this._serverRoomsKey(room.serverId))).length;
|
|
1188
|
+
await client.hset(this._serverKey(room.serverId), "roomCount", String(roomCount));
|
|
1189
|
+
await this.deleteSnapshot(roomId);
|
|
1190
|
+
await this.publish({
|
|
1191
|
+
type: "room:disposed",
|
|
1192
|
+
serverId: room.serverId,
|
|
1193
|
+
roomId,
|
|
1194
|
+
payload: {},
|
|
1195
|
+
timestamp: Date.now()
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
async updateRoom(roomId, updates) {
|
|
1199
|
+
const client = this._ensureConnected();
|
|
1200
|
+
const room = await this.getRoom(roomId);
|
|
1201
|
+
if (!room) return;
|
|
1202
|
+
const key = this._roomKey(roomId);
|
|
1203
|
+
const args = [];
|
|
1204
|
+
if (updates.playerCount !== void 0) args.push("playerCount", String(updates.playerCount));
|
|
1205
|
+
if (updates.maxPlayers !== void 0) args.push("maxPlayers", String(updates.maxPlayers));
|
|
1206
|
+
if (updates.isLocked !== void 0) args.push("isLocked", updates.isLocked ? "1" : "0");
|
|
1207
|
+
if (updates.metadata !== void 0) args.push("metadata", JSON.stringify(updates.metadata));
|
|
1208
|
+
args.push("updatedAt", String(Date.now()));
|
|
1209
|
+
if (args.length > 0) {
|
|
1210
|
+
await client.hmset(key, ...args);
|
|
1211
|
+
}
|
|
1212
|
+
await this.publish({
|
|
1213
|
+
type: "room:updated",
|
|
1214
|
+
serverId: room.serverId,
|
|
1215
|
+
roomId,
|
|
1216
|
+
payload: updates,
|
|
1217
|
+
timestamp: Date.now()
|
|
1218
|
+
});
|
|
1219
|
+
if (updates.isLocked !== void 0) {
|
|
1220
|
+
await this.publish({
|
|
1221
|
+
type: updates.isLocked ? "room:locked" : "room:unlocked",
|
|
1222
|
+
serverId: room.serverId,
|
|
1223
|
+
roomId,
|
|
1224
|
+
payload: {},
|
|
1225
|
+
timestamp: Date.now()
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
async getRoom(roomId) {
|
|
1230
|
+
const client = this._ensureConnected();
|
|
1231
|
+
const key = this._roomKey(roomId);
|
|
1232
|
+
const data = await client.hgetall(key);
|
|
1233
|
+
if (!data || !data.roomId) return null;
|
|
1234
|
+
return {
|
|
1235
|
+
roomId: data.roomId,
|
|
1236
|
+
roomType: data.roomType,
|
|
1237
|
+
serverId: data.serverId,
|
|
1238
|
+
serverAddress: data.serverAddress,
|
|
1239
|
+
playerCount: parseInt(data.playerCount, 10),
|
|
1240
|
+
maxPlayers: parseInt(data.maxPlayers, 10),
|
|
1241
|
+
isLocked: data.isLocked === "1",
|
|
1242
|
+
metadata: data.metadata ? JSON.parse(data.metadata) : {},
|
|
1243
|
+
createdAt: parseInt(data.createdAt, 10),
|
|
1244
|
+
updatedAt: parseInt(data.updatedAt, 10)
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
async queryRooms(query) {
|
|
1248
|
+
const client = this._ensureConnected();
|
|
1249
|
+
const roomIds = await client.smembers(this._roomsSetKey());
|
|
1250
|
+
let results = [];
|
|
1251
|
+
for (const roomId of roomIds) {
|
|
1252
|
+
const room = await this.getRoom(roomId);
|
|
1253
|
+
if (room) results.push(room);
|
|
1254
|
+
}
|
|
1255
|
+
if (query.roomType) {
|
|
1256
|
+
results = results.filter((r) => r.roomType === query.roomType);
|
|
1257
|
+
}
|
|
1258
|
+
if (query.hasSpace) {
|
|
1259
|
+
results = results.filter((r) => r.playerCount < r.maxPlayers);
|
|
1260
|
+
}
|
|
1261
|
+
if (query.notLocked) {
|
|
1262
|
+
results = results.filter((r) => !r.isLocked);
|
|
1263
|
+
}
|
|
1264
|
+
if (query.metadata) {
|
|
1265
|
+
results = results.filter((r) => {
|
|
1266
|
+
for (const [key, value] of Object.entries(query.metadata)) {
|
|
1267
|
+
if (r.metadata[key] !== value) return false;
|
|
1268
|
+
}
|
|
1269
|
+
return true;
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
if (query.offset) {
|
|
1273
|
+
results = results.slice(query.offset);
|
|
1274
|
+
}
|
|
1275
|
+
if (query.limit) {
|
|
1276
|
+
results = results.slice(0, query.limit);
|
|
1277
|
+
}
|
|
1278
|
+
return results;
|
|
1279
|
+
}
|
|
1280
|
+
async findAvailableRoom(roomType) {
|
|
1281
|
+
const rooms = await this.queryRooms({
|
|
1282
|
+
roomType,
|
|
1283
|
+
hasSpace: true,
|
|
1284
|
+
notLocked: true,
|
|
1285
|
+
limit: 1
|
|
1286
|
+
});
|
|
1287
|
+
return rooms[0] ?? null;
|
|
1288
|
+
}
|
|
1289
|
+
async getRoomsByServer(serverId) {
|
|
1290
|
+
const client = this._ensureConnected();
|
|
1291
|
+
const roomIds = await client.smembers(this._serverRoomsKey(serverId));
|
|
1292
|
+
const rooms = [];
|
|
1293
|
+
for (const roomId of roomIds) {
|
|
1294
|
+
const room = await this.getRoom(roomId);
|
|
1295
|
+
if (room) rooms.push(room);
|
|
1296
|
+
}
|
|
1297
|
+
return rooms;
|
|
1298
|
+
}
|
|
1299
|
+
// =========================================================================
|
|
1300
|
+
// 房间状态 | Room State
|
|
1301
|
+
// =========================================================================
|
|
1302
|
+
async saveSnapshot(snapshot) {
|
|
1303
|
+
const client = this._ensureConnected();
|
|
1304
|
+
const key = this._snapshotKey(snapshot.roomId);
|
|
1305
|
+
await client.set(key, JSON.stringify(snapshot));
|
|
1306
|
+
await client.expire(key, this._config.snapshotTtl);
|
|
1307
|
+
}
|
|
1308
|
+
async loadSnapshot(roomId) {
|
|
1309
|
+
const client = this._ensureConnected();
|
|
1310
|
+
const key = this._snapshotKey(roomId);
|
|
1311
|
+
const data = await client.get(key);
|
|
1312
|
+
return data ? JSON.parse(data) : null;
|
|
1313
|
+
}
|
|
1314
|
+
async deleteSnapshot(roomId) {
|
|
1315
|
+
const client = this._ensureConnected();
|
|
1316
|
+
const key = this._snapshotKey(roomId);
|
|
1317
|
+
await client.del(key);
|
|
1318
|
+
}
|
|
1319
|
+
// =========================================================================
|
|
1320
|
+
// 发布/订阅 | Pub/Sub
|
|
1321
|
+
// =========================================================================
|
|
1322
|
+
async publish(event) {
|
|
1323
|
+
const client = this._ensureConnected();
|
|
1324
|
+
await client.publish(this._config.channel, JSON.stringify(event));
|
|
1325
|
+
}
|
|
1326
|
+
async subscribe(pattern, handler) {
|
|
1327
|
+
if (!this._handlers.has(pattern)) {
|
|
1328
|
+
this._handlers.set(pattern, /* @__PURE__ */ new Set());
|
|
1329
|
+
}
|
|
1330
|
+
this._handlers.get(pattern).add(handler);
|
|
1331
|
+
return () => {
|
|
1332
|
+
const handlers = this._handlers.get(pattern);
|
|
1333
|
+
if (handlers) {
|
|
1334
|
+
handlers.delete(handler);
|
|
1335
|
+
if (handlers.size === 0) {
|
|
1336
|
+
this._handlers.delete(pattern);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
async sendToRoom(roomId, messageType, data, playerId) {
|
|
1342
|
+
const room = await this.getRoom(roomId);
|
|
1343
|
+
if (!room) return;
|
|
1344
|
+
await this.publish({
|
|
1345
|
+
type: "room:message",
|
|
1346
|
+
serverId: room.serverId,
|
|
1347
|
+
roomId,
|
|
1348
|
+
payload: {
|
|
1349
|
+
messageType,
|
|
1350
|
+
data,
|
|
1351
|
+
playerId
|
|
1352
|
+
},
|
|
1353
|
+
timestamp: Date.now()
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
_dispatchEvent(event) {
|
|
1357
|
+
const wildcardHandlers = this._handlers.get("*");
|
|
1358
|
+
if (wildcardHandlers) {
|
|
1359
|
+
for (const handler of wildcardHandlers) {
|
|
1360
|
+
try {
|
|
1361
|
+
handler(event);
|
|
1362
|
+
} catch (error) {
|
|
1363
|
+
console.error("[RedisAdapter] Event handler error:", error);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
const typeHandlers = this._handlers.get(event.type);
|
|
1368
|
+
if (typeHandlers) {
|
|
1369
|
+
for (const handler of typeHandlers) {
|
|
1370
|
+
try {
|
|
1371
|
+
handler(event);
|
|
1372
|
+
} catch (error) {
|
|
1373
|
+
console.error("[RedisAdapter] Event handler error:", error);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
// =========================================================================
|
|
1379
|
+
// 分布式锁 | Distributed Lock
|
|
1380
|
+
// =========================================================================
|
|
1381
|
+
async acquireLock(key, ttlMs) {
|
|
1382
|
+
const client = this._ensureConnected();
|
|
1383
|
+
const lockKey = this._lockKey(key);
|
|
1384
|
+
const token = `${Date.now()}_${Math.random().toString(36).substring(2)}`;
|
|
1385
|
+
const ttlSeconds = Math.ceil(ttlMs / 1e3);
|
|
1386
|
+
const result = await client.set(lockKey, token, "NX", "EX", ttlSeconds);
|
|
1387
|
+
if (result === "OK") {
|
|
1388
|
+
this._lockTokens.set(key, token);
|
|
1389
|
+
return true;
|
|
1390
|
+
}
|
|
1391
|
+
return false;
|
|
1392
|
+
}
|
|
1393
|
+
async releaseLock(key) {
|
|
1394
|
+
const client = this._ensureConnected();
|
|
1395
|
+
const lockKey = this._lockKey(key);
|
|
1396
|
+
const token = this._lockTokens.get(key);
|
|
1397
|
+
if (!token) return;
|
|
1398
|
+
await client.eval(RELEASE_LOCK_SCRIPT, 1, lockKey, token);
|
|
1399
|
+
this._lockTokens.delete(key);
|
|
1400
|
+
}
|
|
1401
|
+
async extendLock(key, ttlMs) {
|
|
1402
|
+
const client = this._ensureConnected();
|
|
1403
|
+
const lockKey = this._lockKey(key);
|
|
1404
|
+
const token = this._lockTokens.get(key);
|
|
1405
|
+
if (!token) return false;
|
|
1406
|
+
const result = await client.eval(EXTEND_LOCK_SCRIPT, 1, lockKey, token, String(ttlMs));
|
|
1407
|
+
return result === 1;
|
|
1408
|
+
}
|
|
1409
|
+
};
|
|
1410
|
+
__name(_RedisAdapter, "RedisAdapter");
|
|
1411
|
+
var RedisAdapter = _RedisAdapter;
|
|
1412
|
+
function createRedisAdapter(config) {
|
|
1413
|
+
return new RedisAdapter(config);
|
|
1414
|
+
}
|
|
1415
|
+
__name(createRedisAdapter, "createRedisAdapter");
|
|
1416
|
+
|
|
1417
|
+
// src/distributed/routing/LoadBalancedRouter.ts
|
|
1418
|
+
var _LoadBalancedRouter = class _LoadBalancedRouter {
|
|
1419
|
+
constructor(config = {}) {
|
|
1420
|
+
__publicField(this, "_config");
|
|
1421
|
+
__publicField(this, "_roundRobinIndex", 0);
|
|
1422
|
+
this._config = {
|
|
1423
|
+
strategy: config.strategy ?? "least-rooms",
|
|
1424
|
+
preferLocal: config.preferLocal ?? true,
|
|
1425
|
+
localPreferenceThreshold: config.localPreferenceThreshold ?? 0.8
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
/**
|
|
1429
|
+
* @zh 选择最优服务器
|
|
1430
|
+
* @en Select optimal server
|
|
1431
|
+
*
|
|
1432
|
+
* @param servers - 可用服务器列表 | Available servers
|
|
1433
|
+
* @param localServerId - 本地服务器 ID | Local server ID
|
|
1434
|
+
* @returns 最优服务器,如果没有可用服务器返回 null | Optimal server, or null if none available
|
|
1435
|
+
*/
|
|
1436
|
+
selectServer(servers, localServerId) {
|
|
1437
|
+
const availableServers = servers.filter((s2) => s2.status === "online" && s2.roomCount < s2.capacity);
|
|
1438
|
+
if (availableServers.length === 0) {
|
|
1439
|
+
return null;
|
|
1440
|
+
}
|
|
1441
|
+
if (this._config.preferLocal && localServerId) {
|
|
1442
|
+
const localServer = availableServers.find((s2) => s2.serverId === localServerId);
|
|
1443
|
+
if (localServer) {
|
|
1444
|
+
const loadRatio = localServer.roomCount / localServer.capacity;
|
|
1445
|
+
if (loadRatio < this._config.localPreferenceThreshold) {
|
|
1446
|
+
return localServer;
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
switch (this._config.strategy) {
|
|
1451
|
+
case "round-robin":
|
|
1452
|
+
return this._selectRoundRobin(availableServers);
|
|
1453
|
+
case "least-rooms":
|
|
1454
|
+
return this._selectLeastRooms(availableServers);
|
|
1455
|
+
case "least-players":
|
|
1456
|
+
return this._selectLeastPlayers(availableServers);
|
|
1457
|
+
case "random":
|
|
1458
|
+
return this._selectRandom(availableServers);
|
|
1459
|
+
case "weighted":
|
|
1460
|
+
return this._selectWeighted(availableServers);
|
|
1461
|
+
default:
|
|
1462
|
+
return this._selectLeastRooms(availableServers);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
/**
|
|
1466
|
+
* @zh 选择创建房间的最优服务器
|
|
1467
|
+
* @en Select optimal server for room creation
|
|
1468
|
+
*/
|
|
1469
|
+
selectServerForCreation(servers, localServerId) {
|
|
1470
|
+
return this.selectServer(servers, localServerId);
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* @zh 重置轮询索引
|
|
1474
|
+
* @en Reset round-robin index
|
|
1475
|
+
*/
|
|
1476
|
+
resetRoundRobin() {
|
|
1477
|
+
this._roundRobinIndex = 0;
|
|
1478
|
+
}
|
|
1479
|
+
// =========================================================================
|
|
1480
|
+
// 私有方法 | Private Methods
|
|
1481
|
+
// =========================================================================
|
|
1482
|
+
_selectRoundRobin(servers) {
|
|
1483
|
+
const server = servers[this._roundRobinIndex % servers.length];
|
|
1484
|
+
this._roundRobinIndex++;
|
|
1485
|
+
return server;
|
|
1486
|
+
}
|
|
1487
|
+
_selectLeastRooms(servers) {
|
|
1488
|
+
return servers.reduce((best, current) => current.roomCount < best.roomCount ? current : best);
|
|
1489
|
+
}
|
|
1490
|
+
_selectLeastPlayers(servers) {
|
|
1491
|
+
return servers.reduce((best, current) => current.playerCount < best.playerCount ? current : best);
|
|
1492
|
+
}
|
|
1493
|
+
_selectRandom(servers) {
|
|
1494
|
+
return servers[Math.floor(Math.random() * servers.length)];
|
|
1495
|
+
}
|
|
1496
|
+
_selectWeighted(servers) {
|
|
1497
|
+
const weights = servers.map((s2) => ({
|
|
1498
|
+
server: s2,
|
|
1499
|
+
weight: (s2.capacity - s2.roomCount) / s2.capacity
|
|
1500
|
+
}));
|
|
1501
|
+
const totalWeight = weights.reduce((sum, w) => sum + w.weight, 0);
|
|
1502
|
+
let random = Math.random() * totalWeight;
|
|
1503
|
+
for (const { server, weight } of weights) {
|
|
1504
|
+
random -= weight;
|
|
1505
|
+
if (random <= 0) {
|
|
1506
|
+
return server;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
return servers[0];
|
|
1510
|
+
}
|
|
1511
|
+
};
|
|
1512
|
+
__name(_LoadBalancedRouter, "LoadBalancedRouter");
|
|
1513
|
+
var LoadBalancedRouter = _LoadBalancedRouter;
|
|
1514
|
+
function createLoadBalancedRouter(config) {
|
|
1515
|
+
return new LoadBalancedRouter(config);
|
|
1516
|
+
}
|
|
1517
|
+
__name(createLoadBalancedRouter, "createLoadBalancedRouter");
|
|
1518
|
+
|
|
1519
|
+
export { AnyValidator, ArrayValidator, BooleanValidator, EnumValidator, LiteralValidator, LoadBalancedRouter, NumberValidator, ObjectValidator, RecordValidator, RedisAdapter, StringValidator, TupleValidator, UnionValidator, any, array, boolean, createGuard, createLoadBalancedRouter, createRedisAdapter, defineApi, defineApiWithSchema, defineHttp, defineMsg, defineMsgWithSchema, literal, nativeEnum, number, object, parse, record, s, safeParse, string, tuple, union };
|
|
22
1520
|
//# sourceMappingURL=index.js.map
|
|
23
1521
|
//# sourceMappingURL=index.js.map
|