@open-core/framework 0.3.0 → 0.3.2
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/runtime/server/bootstrap.js +44 -23
- package/dist/runtime/server/controllers/command-export.controller.js +13 -3
- package/dist/runtime/server/controllers/ready.controller.d.ts +0 -1
- package/dist/runtime/server/controllers/ready.controller.js +2 -7
- package/dist/runtime/server/controllers/remote-command-execution.controller.js +14 -0
- package/dist/runtime/server/decorators/command.d.ts +2 -0
- package/dist/runtime/server/decorators/command.js +4 -1
- package/dist/runtime/server/helpers/command-validation.helper.d.ts +0 -3
- package/dist/runtime/server/helpers/command-validation.helper.js +15 -8
- package/dist/runtime/server/helpers/function-helper.d.ts +5 -0
- package/dist/runtime/server/helpers/function-helper.js +17 -0
- package/dist/runtime/server/helpers/process-tuple-schema.d.ts +20 -0
- package/dist/runtime/server/helpers/process-tuple-schema.js +62 -0
- package/dist/runtime/server/services/remote/remote-command.service.js +23 -8
- package/dist/runtime/server/system/processors/netEvent.processor.js +3 -1
- package/dist/runtime/server/system/schema-generator.js +3 -1
- package/package.json +1 -1
|
@@ -162,23 +162,45 @@ async function initServer(options) {
|
|
|
162
162
|
if (ctx.mode === 'CORE' &&
|
|
163
163
|
index_1.GLOBAL_CONTAINER.isRegistered(adapters_1.IEngineEvents) &&
|
|
164
164
|
index_1.GLOBAL_CONTAINER.isRegistered(adapters_1.INetTransport)) {
|
|
165
|
-
const engineEvents = index_1.GLOBAL_CONTAINER.resolve(adapters_1.IEngineEvents);
|
|
166
|
-
const net = index_1.GLOBAL_CONTAINER.resolve(adapters_1.INetTransport);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
165
|
+
const engineEvents = index_1.GLOBAL_CONTAINER.resolve(adapters_1.IEngineEvents);
|
|
166
|
+
const net = index_1.GLOBAL_CONTAINER.resolve(adapters_1.INetTransport);
|
|
167
|
+
// 1. Broadast to resources already running
|
|
168
|
+
engineEvents.emit('core:ready');
|
|
169
|
+
net.emitNet('core:ready', 'all');
|
|
170
|
+
// 2. Listen for 'core:request-ready' for resources starting late (hot-reload)
|
|
171
|
+
engineEvents.on('core:request-ready', () => {
|
|
172
|
+
engineEvents.emit('core:ready');
|
|
173
|
+
});
|
|
174
|
+
logger_1.loggers.bootstrap.info(`'core:ready' logic initialized and broadcasted`);
|
|
170
175
|
}
|
|
176
|
+
const logLevelLabel = logger_1.LogLevelLabels[(0, logger_1.getLogLevel)()];
|
|
177
|
+
logger_1.loggers.bootstrap.info(`LogLevel Setted: ${logLevelLabel}`);
|
|
171
178
|
}
|
|
172
179
|
function createCoreDependency(coreName) {
|
|
180
|
+
logger_1.loggers.bootstrap.debug(`Setting up detection mechanisms for Core '${coreName}'...`);
|
|
173
181
|
return new Promise((resolve, reject) => {
|
|
174
182
|
let resolved = false;
|
|
183
|
+
let pollingInterval;
|
|
184
|
+
let timeout;
|
|
175
185
|
const engineEvents = index_1.GLOBAL_CONTAINER.resolve(adapters_1.IEngineEvents);
|
|
176
186
|
const cleanup = () => {
|
|
177
187
|
resolved = true;
|
|
178
|
-
|
|
179
|
-
|
|
188
|
+
if (timeout)
|
|
189
|
+
clearTimeout(timeout);
|
|
190
|
+
if (pollingInterval)
|
|
191
|
+
clearInterval(pollingInterval);
|
|
180
192
|
};
|
|
181
|
-
// 1.
|
|
193
|
+
// 1. Register listener FIRST (before any requests)
|
|
194
|
+
const onReady = () => {
|
|
195
|
+
if (!resolved) {
|
|
196
|
+
logger_1.loggers.bootstrap.debug(`Core '${coreName}' detected via 'core:ready' event!`);
|
|
197
|
+
cleanup();
|
|
198
|
+
resolve();
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
engineEvents.on('core:ready', onReady);
|
|
202
|
+
logger_1.loggers.bootstrap.debug(`Listening for 'core:ready' event from Core`);
|
|
203
|
+
// 2. Check if already ready via export (Polling)
|
|
182
204
|
const checkReady = () => {
|
|
183
205
|
var _a, _b;
|
|
184
206
|
if (resolved)
|
|
@@ -186,34 +208,33 @@ function createCoreDependency(coreName) {
|
|
|
186
208
|
try {
|
|
187
209
|
const globalExports = globalThis.exports;
|
|
188
210
|
const isReady = (_b = (_a = globalExports === null || globalExports === void 0 ? void 0 : globalExports[coreName]) === null || _a === void 0 ? void 0 : _a.isCoreReady) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
211
|
+
logger_1.loggers.bootstrap.debug(`Polling isCoreReady export: ${isReady}`);
|
|
189
212
|
if (isReady === true) {
|
|
190
|
-
logger_1.loggers.bootstrap.debug(`Core '${coreName}' detected
|
|
213
|
+
logger_1.loggers.bootstrap.debug(`Core '${coreName}' detected via isCoreReady export!`);
|
|
191
214
|
cleanup();
|
|
192
215
|
resolve();
|
|
193
216
|
}
|
|
194
217
|
}
|
|
195
|
-
catch (
|
|
196
|
-
|
|
218
|
+
catch (e) {
|
|
219
|
+
logger_1.loggers.bootstrap.debug(`Export check failed: ${e}`);
|
|
197
220
|
}
|
|
198
221
|
};
|
|
199
|
-
|
|
222
|
+
pollingInterval = setInterval(checkReady, 500);
|
|
200
223
|
checkReady(); // Initial check
|
|
201
|
-
//
|
|
202
|
-
|
|
224
|
+
// 3. Request status (for hot-reload cases where Core is already up)
|
|
225
|
+
// This is sent AFTER registering the listener so we can receive the response
|
|
226
|
+
if (!resolved) {
|
|
227
|
+
logger_1.loggers.bootstrap.debug(`Requesting Core status via 'core:request-ready' event`);
|
|
228
|
+
engineEvents.emit('core:request-ready');
|
|
229
|
+
}
|
|
230
|
+
// 4. Timeout protection
|
|
231
|
+
timeout = setTimeout(() => {
|
|
203
232
|
if (!resolved) {
|
|
233
|
+
logger_1.loggers.bootstrap.warn(`Timeout waiting for Core '${coreName}' after ${CORE_WAIT_TIMEOUT}ms`);
|
|
204
234
|
cleanup();
|
|
205
235
|
reject(new Error(`[OpenCore] Timeout waiting for CORE '${coreName}'. The Core did not emit 'core:ready' or expose 'isCoreReady' within ${CORE_WAIT_TIMEOUT}ms.`));
|
|
206
236
|
}
|
|
207
237
|
}, CORE_WAIT_TIMEOUT);
|
|
208
|
-
// 3. Listen for the event (for resources starting after/during CORE init)
|
|
209
|
-
const onReady = () => {
|
|
210
|
-
if (!resolved) {
|
|
211
|
-
logger_1.loggers.bootstrap.debug(`Core '${coreName}' detected via 'core:ready' event.`);
|
|
212
|
-
cleanup();
|
|
213
|
-
resolve();
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
engineEvents.on('core:ready', onReady);
|
|
217
238
|
});
|
|
218
239
|
}
|
|
219
240
|
async function dependencyResolver(waitFor, onReady) {
|
|
@@ -159,12 +159,22 @@ let CommandExportController = class CommandExportController {
|
|
|
159
159
|
* Exported as: `exports[coreResourceName].registerCommand`
|
|
160
160
|
*/
|
|
161
161
|
registerCommand(metadata) {
|
|
162
|
-
var _a;
|
|
163
162
|
const commandKey = metadata.command.toLowerCase();
|
|
164
|
-
|
|
163
|
+
const existing = this.remoteCommands.get(commandKey);
|
|
164
|
+
if (existing) {
|
|
165
|
+
// Allow re-registration from the same resource (hot-reload scenario)
|
|
166
|
+
if (existing.resourceName === metadata.resourceName) {
|
|
167
|
+
logger_1.loggers.command.debug(`Re-registering command '${metadata.command}' from same resource (hot-reload)`, { command: metadata.command, resource: metadata.resourceName });
|
|
168
|
+
// Update the entry with new metadata
|
|
169
|
+
this.remoteCommands.set(commandKey, {
|
|
170
|
+
metadata,
|
|
171
|
+
resourceName: metadata.resourceName,
|
|
172
|
+
});
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
165
175
|
logger_1.loggers.command.warn(`Remote command '${metadata.command}' already registered`, {
|
|
166
176
|
command: metadata.command,
|
|
167
|
-
existingResource:
|
|
177
|
+
existingResource: existing.resourceName,
|
|
168
178
|
newResource: metadata.resourceName,
|
|
169
179
|
});
|
|
170
180
|
return;
|
|
@@ -14,11 +14,7 @@ const controller_1 = require("../decorators/controller");
|
|
|
14
14
|
const export_1 = require("../decorators/export");
|
|
15
15
|
let ReadyController = class ReadyController {
|
|
16
16
|
constructor() {
|
|
17
|
-
this.isReady =
|
|
18
|
-
// Set ready after a small tick to ensure bootstrap finishes
|
|
19
|
-
setTimeout(() => {
|
|
20
|
-
this.isReady = true;
|
|
21
|
-
}, 0);
|
|
17
|
+
this.isReady = true;
|
|
22
18
|
}
|
|
23
19
|
isCoreReady() {
|
|
24
20
|
return this.isReady;
|
|
@@ -32,6 +28,5 @@ __decorate([
|
|
|
32
28
|
__metadata("design:returntype", Boolean)
|
|
33
29
|
], ReadyController.prototype, "isCoreReady", null);
|
|
34
30
|
exports.ReadyController = ReadyController = __decorate([
|
|
35
|
-
(0, controller_1.Controller)()
|
|
36
|
-
__metadata("design:paramtypes", [])
|
|
31
|
+
(0, controller_1.Controller)()
|
|
37
32
|
], ReadyController);
|
|
@@ -85,6 +85,11 @@ let RemoteCommandExecutionController = class RemoteCommandExecutionController {
|
|
|
85
85
|
* @param args - Command arguments
|
|
86
86
|
*/
|
|
87
87
|
async handleCommandExecution(clientID, commandName, args) {
|
|
88
|
+
logger_1.loggers.command.debug(`Received command execution request`, {
|
|
89
|
+
command: commandName,
|
|
90
|
+
clientID,
|
|
91
|
+
args,
|
|
92
|
+
});
|
|
88
93
|
const player = this.playerDirectory.getByClient(clientID);
|
|
89
94
|
if (!player) {
|
|
90
95
|
logger_1.loggers.command.warn(`Command execution failed: player not found`, {
|
|
@@ -93,8 +98,17 @@ let RemoteCommandExecutionController = class RemoteCommandExecutionController {
|
|
|
93
98
|
});
|
|
94
99
|
return;
|
|
95
100
|
}
|
|
101
|
+
logger_1.loggers.command.debug(`Executing command for player`, {
|
|
102
|
+
command: commandName,
|
|
103
|
+
playerName: player.name,
|
|
104
|
+
clientID,
|
|
105
|
+
});
|
|
96
106
|
try {
|
|
97
107
|
await this.commandService.execute(player, commandName, args);
|
|
108
|
+
logger_1.loggers.command.debug(`Command executed successfully`, {
|
|
109
|
+
command: commandName,
|
|
110
|
+
clientID,
|
|
111
|
+
});
|
|
98
112
|
}
|
|
99
113
|
catch (error) {
|
|
100
114
|
// Do not notify the player here. Report through the global observer.
|
|
@@ -34,6 +34,8 @@ export interface CommandMetadata extends CommandConfig {
|
|
|
34
34
|
isPublic?: boolean;
|
|
35
35
|
/** Security metadata for remote validation */
|
|
36
36
|
security?: SecurityMetadata;
|
|
37
|
+
/** True if the last parameter uses the spread operator (...args) */
|
|
38
|
+
hasSpreadParam?: boolean;
|
|
37
39
|
}
|
|
38
40
|
type ServerCommandHandler = (() => any) | ((player: Player, ...args: any[]) => any);
|
|
39
41
|
/**
|
|
@@ -24,9 +24,12 @@ function Command(configOrName, schema) {
|
|
|
24
24
|
throw new Error(`@Command '${config.command}': first parameter must be Player if parameters are present`);
|
|
25
25
|
}
|
|
26
26
|
const paramNames = (0, function_helper_1.getParameterNames)(descriptor.value);
|
|
27
|
+
const spreadIndices = (0, function_helper_1.getSpreadParameterIndices)(descriptor.value);
|
|
28
|
+
const hasSpreadParam = spreadIndices.length > 0 && spreadIndices[spreadIndices.length - 1];
|
|
27
29
|
const metadata = Object.assign(Object.assign({}, config), { methodName: propertyKey, target: target.constructor, paramTypes,
|
|
28
30
|
paramNames,
|
|
29
|
-
expectsPlayer
|
|
31
|
+
expectsPlayer,
|
|
32
|
+
hasSpreadParam });
|
|
30
33
|
Reflect.defineMetadata(metadata_server_keys_1.METADATA_KEYS.COMMAND, metadata, target, propertyKey);
|
|
31
34
|
};
|
|
32
35
|
}
|
|
@@ -1,6 +1,3 @@
|
|
|
1
1
|
import { CommandMetadata } from '../decorators/command';
|
|
2
2
|
import { Player } from '../entities';
|
|
3
|
-
/**
|
|
4
|
-
* Centraliza validación de argumentos de comandos.
|
|
5
|
-
*/
|
|
6
3
|
export declare function validateAndExecuteCommand(meta: CommandMetadata, player: Player, args: string[], handler: (...args: any[]) => any): Promise<any>;
|
|
@@ -7,13 +7,10 @@ exports.validateAndExecuteCommand = validateAndExecuteCommand;
|
|
|
7
7
|
const zod_1 = __importDefault(require("zod"));
|
|
8
8
|
const kernel_1 = require("../../../kernel");
|
|
9
9
|
const schema_generator_1 = require("../system/schema-generator");
|
|
10
|
-
|
|
11
|
-
* Centraliza validación de argumentos de comandos.
|
|
12
|
-
*/
|
|
10
|
+
const process_tuple_schema_1 = require("./process-tuple-schema");
|
|
13
11
|
async function validateAndExecuteCommand(meta, player, args, handler) {
|
|
14
12
|
const paramNames = meta.expectsPlayer ? meta.paramNames.slice(1) : meta.paramNames;
|
|
15
13
|
let schema = meta.schema;
|
|
16
|
-
// Caso: comando sin player → no espera argumentos
|
|
17
14
|
if (!meta.expectsPlayer) {
|
|
18
15
|
if (args.length > 0) {
|
|
19
16
|
throw new kernel_1.AppError('GAME:BAD_REQUEST', `Incorrect usage, use: ${meta.usage}`, 'client', {
|
|
@@ -22,7 +19,6 @@ async function validateAndExecuteCommand(meta, player, args, handler) {
|
|
|
22
19
|
}
|
|
23
20
|
return await handler();
|
|
24
21
|
}
|
|
25
|
-
// Autogenerar esquema si no lo definieron
|
|
26
22
|
if (!schema) {
|
|
27
23
|
schema = (0, schema_generator_1.generateSchemaFromTypes)(meta.paramTypes);
|
|
28
24
|
if (!schema) {
|
|
@@ -32,7 +28,6 @@ async function validateAndExecuteCommand(meta, player, args, handler) {
|
|
|
32
28
|
return await handler(player);
|
|
33
29
|
}
|
|
34
30
|
}
|
|
35
|
-
// OBJETO schema
|
|
36
31
|
if (schema instanceof zod_1.default.ZodObject) {
|
|
37
32
|
const keys = Object.keys(schema.shape);
|
|
38
33
|
for (const p of paramNames) {
|
|
@@ -60,12 +55,24 @@ async function validateAndExecuteCommand(meta, player, args, handler) {
|
|
|
60
55
|
}
|
|
61
56
|
// TUPLA schema
|
|
62
57
|
if (schema instanceof zod_1.default.ZodTuple) {
|
|
63
|
-
const
|
|
58
|
+
const processedArgs = (0, process_tuple_schema_1.processTupleSchema)(schema, args);
|
|
59
|
+
const validated = await schema.parseAsync(processedArgs).catch(() => {
|
|
64
60
|
throw new kernel_1.AppError('GAME:BAD_REQUEST', `Incorrect usage, use: ${meta.usage}`, 'client', {
|
|
65
61
|
usage: meta.usage,
|
|
66
62
|
});
|
|
67
63
|
});
|
|
68
|
-
|
|
64
|
+
const finalArgs = validated;
|
|
65
|
+
// If the handler uses spread operator (...args), flatten the last array argument
|
|
66
|
+
// so the handler receives individual arguments instead of a single array.
|
|
67
|
+
if (meta.hasSpreadParam &&
|
|
68
|
+
finalArgs.length > 0 &&
|
|
69
|
+
Array.isArray(finalArgs[finalArgs.length - 1])) {
|
|
70
|
+
const positional = finalArgs.slice(0, finalArgs.length - 1);
|
|
71
|
+
const rest = finalArgs[finalArgs.length - 1];
|
|
72
|
+
return await handler(player, ...positional, ...rest);
|
|
73
|
+
}
|
|
74
|
+
// For regular array parameters (args: string[]), pass as-is
|
|
75
|
+
return await handler(player, ...finalArgs);
|
|
69
76
|
}
|
|
70
77
|
// fallback
|
|
71
78
|
return await handler(player);
|
|
@@ -1 +1,6 @@
|
|
|
1
1
|
export declare function getParameterNames(func: (...args: any[]) => any): string[];
|
|
2
|
+
/**
|
|
3
|
+
* Detects which parameter indices use the spread operator (...args).
|
|
4
|
+
* Returns an array of booleans where true means the parameter at that index is a spread parameter.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getSpreadParameterIndices(func: (...args: any[]) => any): boolean[];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getParameterNames = getParameterNames;
|
|
4
|
+
exports.getSpreadParameterIndices = getSpreadParameterIndices;
|
|
4
5
|
function getParameterNames(func) {
|
|
5
6
|
const stripped = func
|
|
6
7
|
.toString()
|
|
@@ -13,3 +14,19 @@ function getParameterNames(func) {
|
|
|
13
14
|
.filter(Boolean);
|
|
14
15
|
return args;
|
|
15
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Detects which parameter indices use the spread operator (...args).
|
|
19
|
+
* Returns an array of booleans where true means the parameter at that index is a spread parameter.
|
|
20
|
+
*/
|
|
21
|
+
function getSpreadParameterIndices(func) {
|
|
22
|
+
const stripped = func
|
|
23
|
+
.toString()
|
|
24
|
+
.replace(/\/\/.*$/gm, '')
|
|
25
|
+
.replace(/\/\*[\s\S]*?\*\//gm, '');
|
|
26
|
+
const argsString = stripped.slice(stripped.indexOf('(') + 1, stripped.indexOf(')'));
|
|
27
|
+
const args = argsString
|
|
28
|
+
.split(',')
|
|
29
|
+
.map((arg) => arg.trim())
|
|
30
|
+
.filter(Boolean);
|
|
31
|
+
return args.map((arg) => arg.startsWith('...'));
|
|
32
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Processes tuple schema validation with greedy handling for rest parameters.
|
|
4
|
+
*
|
|
5
|
+
* This function handles two cases:
|
|
6
|
+
* 1. If last parameter is ZodArray and there are MORE args than schema items,
|
|
7
|
+
* collect the extra args into the array position.
|
|
8
|
+
* 2. If last parameter is ZodString and there are MORE args than schema items,
|
|
9
|
+
* join the extra args into a single string.
|
|
10
|
+
*
|
|
11
|
+
* Examples:
|
|
12
|
+
* - handler(player, action: string, ...rest: string[]) with args ["hello", "world", "!"]
|
|
13
|
+
* → schema is [z.string(), z.array(z.string())] (2 items)
|
|
14
|
+
* → args has 3 items, so we group extra: ["hello", ["world", "!"]]
|
|
15
|
+
*
|
|
16
|
+
* - handler(player, command: string, args: string[]) with args ["vida", ["arg1"]]
|
|
17
|
+
* → schema is [z.string(), z.array(z.string())] (2 items)
|
|
18
|
+
* → args has 2 items, matches schema, no processing needed
|
|
19
|
+
*/
|
|
20
|
+
export declare function processTupleSchema(schema: z.ZodTuple, args: any[]): any[];
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.processTupleSchema = processTupleSchema;
|
|
7
|
+
const zod_1 = __importDefault(require("zod"));
|
|
8
|
+
/**
|
|
9
|
+
* Processes tuple schema validation with greedy handling for rest parameters.
|
|
10
|
+
*
|
|
11
|
+
* This function handles two cases:
|
|
12
|
+
* 1. If last parameter is ZodArray and there are MORE args than schema items,
|
|
13
|
+
* collect the extra args into the array position.
|
|
14
|
+
* 2. If last parameter is ZodString and there are MORE args than schema items,
|
|
15
|
+
* join the extra args into a single string.
|
|
16
|
+
*
|
|
17
|
+
* Examples:
|
|
18
|
+
* - handler(player, action: string, ...rest: string[]) with args ["hello", "world", "!"]
|
|
19
|
+
* → schema is [z.string(), z.array(z.string())] (2 items)
|
|
20
|
+
* → args has 3 items, so we group extra: ["hello", ["world", "!"]]
|
|
21
|
+
*
|
|
22
|
+
* - handler(player, command: string, args: string[]) with args ["vida", ["arg1"]]
|
|
23
|
+
* → schema is [z.string(), z.array(z.string())] (2 items)
|
|
24
|
+
* → args has 2 items, matches schema, no processing needed
|
|
25
|
+
*/
|
|
26
|
+
function processTupleSchema(schema, args) {
|
|
27
|
+
const items = schema.description ? [] : schema._def.items;
|
|
28
|
+
if (items.length === 0) {
|
|
29
|
+
return args;
|
|
30
|
+
}
|
|
31
|
+
const lastItem = items[items.length - 1];
|
|
32
|
+
const positionalCount = items.length - 1;
|
|
33
|
+
// Case: More args than items (Greedy grouping)
|
|
34
|
+
if (args.length > items.length) {
|
|
35
|
+
// If last parameter is a string, join extra args with space
|
|
36
|
+
if (lastItem instanceof zod_1.default.ZodString) {
|
|
37
|
+
const positional = args.slice(0, positionalCount);
|
|
38
|
+
const restString = args.slice(positionalCount).join(' ');
|
|
39
|
+
return [...positional, restString];
|
|
40
|
+
}
|
|
41
|
+
// If last parameter is an array, we keep them as individual elements
|
|
42
|
+
// for the handler's spread operator (...args) or just as the array itself
|
|
43
|
+
// if ZodTuple is being used to parse.
|
|
44
|
+
// However, to avoid nesting [arg1, [arg2, arg3]], we return them flat
|
|
45
|
+
// if the handler expects a spread, OR we return the array if it's a single param.
|
|
46
|
+
if (lastItem instanceof zod_1.default.ZodArray) {
|
|
47
|
+
// For ZodTuple.parse() to work with a ZodArray at the end,
|
|
48
|
+
// it actually expects the array as a single element in that position.
|
|
49
|
+
const positional = args.slice(0, positionalCount);
|
|
50
|
+
const restArray = args.slice(positionalCount);
|
|
51
|
+
return [...positional, restArray];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Case: Exact match but last is array
|
|
55
|
+
if (args.length === items.length) {
|
|
56
|
+
if (lastItem instanceof zod_1.default.ZodArray && !Array.isArray(args[positionalCount])) {
|
|
57
|
+
const positional = args.slice(0, positionalCount);
|
|
58
|
+
return [...positional, [args[positionalCount]]];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return args;
|
|
62
|
+
}
|
|
@@ -72,20 +72,35 @@ let RemoteCommandService = class RemoteCommandService extends command_execution_
|
|
|
72
72
|
register(metadata, handler) {
|
|
73
73
|
var _a, _b, _c, _d;
|
|
74
74
|
const commandKey = metadata.command.toLowerCase();
|
|
75
|
+
const resourceName = GetCurrentResourceName();
|
|
76
|
+
logger_1.loggers.command.debug(`Registering command locally`, {
|
|
77
|
+
command: metadata.command,
|
|
78
|
+
resource: resourceName,
|
|
79
|
+
});
|
|
75
80
|
// Store handler with full metadata locally (for schema validation)
|
|
76
81
|
this.commands.set(commandKey, {
|
|
77
82
|
meta: metadata,
|
|
78
83
|
handler,
|
|
79
84
|
});
|
|
80
85
|
// Register metadata with CORE (security only, schema is not serializable)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
try {
|
|
87
|
+
this.core.registerCommand({
|
|
88
|
+
command: metadata.command,
|
|
89
|
+
description: metadata.description,
|
|
90
|
+
usage: metadata.usage,
|
|
91
|
+
isPublic: (_a = metadata.isPublic) !== null && _a !== void 0 ? _a : false,
|
|
92
|
+
resourceName,
|
|
93
|
+
security: metadata.security,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
logger_1.loggers.command.error(`Failed to register command with CORE`, {
|
|
98
|
+
command: metadata.command,
|
|
99
|
+
resource: resourceName,
|
|
100
|
+
error: e,
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
89
104
|
const publicFlag = metadata.isPublic ? ' [Public]' : '';
|
|
90
105
|
const schemaFlag = metadata.schema ? ' [Validated]' : '';
|
|
91
106
|
const securityFlags = [];
|
|
@@ -23,6 +23,7 @@ const INetTransport_1 = require("../../../../adapters/contracts/INetTransport");
|
|
|
23
23
|
const logger_1 = require("../../../../kernel/logger");
|
|
24
24
|
const net_event_security_observer_contract_1 = require("../../contracts/security/net-event-security-observer.contract");
|
|
25
25
|
const security_handler_contract_1 = require("../../contracts/security/security-handler.contract");
|
|
26
|
+
const process_tuple_schema_1 = require("../../helpers/process-tuple-schema");
|
|
26
27
|
const resolve_method_1 = require("../../helpers/resolve-method");
|
|
27
28
|
const player_directory_port_1 = require("../../services/ports/player-directory.port");
|
|
28
29
|
const metadata_server_keys_1 = require("../metadata-server.keys");
|
|
@@ -81,7 +82,8 @@ let NetEventProcessor = class NetEventProcessor {
|
|
|
81
82
|
}
|
|
82
83
|
try {
|
|
83
84
|
if (schema instanceof zod_1.default.ZodTuple) {
|
|
84
|
-
|
|
85
|
+
const processedArgs = (0, process_tuple_schema_1.processTupleSchema)(schema, args);
|
|
86
|
+
validatedArgs = schema.parse(processedArgs);
|
|
85
87
|
}
|
|
86
88
|
else {
|
|
87
89
|
if (args.length !== 1) {
|
|
@@ -18,7 +18,9 @@ function typeToZodSchema(type) {
|
|
|
18
18
|
case Boolean:
|
|
19
19
|
return zod_1.default.coerce.boolean();
|
|
20
20
|
case Array:
|
|
21
|
-
|
|
21
|
+
// Command/event arguments are always strings, so Array means string[]
|
|
22
|
+
// This enables spread operator support: handler(player: Player, ...args: string[])
|
|
23
|
+
return zod_1.default.array(zod_1.default.string());
|
|
22
24
|
case Object:
|
|
23
25
|
return undefined;
|
|
24
26
|
}
|