@open-core/framework 1.0.1-beta.1 → 1.0.2-beta.1
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/README.md +10 -0
- package/dist/server/bootstrap.js +2 -2
- package/dist/server/controllers/command.controller.d.ts +1 -1
- package/dist/server/controllers/command.controller.js +6 -6
- package/dist/server/decorators/command.d.ts +34 -4
- package/dist/server/decorators/command.js +11 -6
- package/dist/server/decorators/controller.d.ts +22 -0
- package/dist/server/decorators/controller.js +22 -0
- package/dist/server/decorators/coreEvent.d.ts +20 -0
- package/dist/server/decorators/coreEvent.js +20 -0
- package/dist/server/decorators/export.d.ts +38 -0
- package/dist/server/decorators/export.js +38 -0
- package/dist/server/decorators/guard.d.ts +51 -0
- package/dist/server/decorators/guard.js +43 -0
- package/dist/server/decorators/index.d.ts +3 -3
- package/dist/server/decorators/index.js +6 -3
- package/dist/server/decorators/onNet.d.ts +58 -0
- package/dist/server/decorators/onNet.js +57 -0
- package/dist/server/decorators/onTick.d.ts +31 -0
- package/dist/server/decorators/onTick.js +31 -0
- package/dist/server/decorators/public.d.ts +19 -8
- package/dist/server/decorators/public.js +19 -8
- package/dist/server/decorators/requiresState.d.ts +18 -17
- package/dist/server/decorators/requiresState.js +18 -17
- package/dist/server/decorators/throttle.d.ts +39 -0
- package/dist/server/decorators/throttle.js +27 -0
- package/dist/server/decorators/utils.d.ts +50 -0
- package/dist/server/decorators/utils.js +50 -0
- package/dist/server/error-handler.js +2 -2
- package/dist/server/services/command.service.d.ts +1 -1
- package/dist/server/services/command.service.js +9 -6
- package/dist/server/system/processors/command.processor.d.ts +2 -2
- package/dist/server/system/processors/command.processor.js +1 -2
- package/dist/server/system/processors/netEvent.processor.js +5 -4
- package/dist/server/system/schema-generator.d.ts +2 -0
- package/dist/server/system/schema-generator.js +28 -0
- package/dist/server/templates/admin/admin.controller-template.d.ts +7 -5
- package/package.json +19 -1
- package/dist/server/decorators/netEvent.d.ts +0 -36
- package/dist/server/decorators/netEvent.js +0 -40
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.OnTick = OnTick;
|
|
4
4
|
const metadata_server_keys_1 = require("../system/metadata-server.keys");
|
|
5
|
+
/**
|
|
6
|
+
* OnTick
|
|
7
|
+
* ------------------------------------------------------------
|
|
8
|
+
* Marks a method to be executed on every server tick.
|
|
9
|
+
*
|
|
10
|
+
* Methods decorated with `@OnTick()` are automatically
|
|
11
|
+
* registered by the framework's scheduler and invoked on each
|
|
12
|
+
* FXServer tick (~ every 0–10 ms depending on workload).
|
|
13
|
+
*
|
|
14
|
+
* This decorator should be used for lightweight recurring
|
|
15
|
+
* logic: status updates, background checks, cleanup tasks,
|
|
16
|
+
* or time-based processes relevant to gameplay.
|
|
17
|
+
*
|
|
18
|
+
* Heavy or blocking operations should be avoided inside tick
|
|
19
|
+
* handlers, as they directly impact global server performance.
|
|
20
|
+
*
|
|
21
|
+
* ```ts
|
|
22
|
+
* export class SyncController {
|
|
23
|
+
* @OnTick()
|
|
24
|
+
* updatePlayers() {
|
|
25
|
+
* // Runs every tick
|
|
26
|
+
* this.service.syncPositions()
|
|
27
|
+
* }
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* Internally, the decorator only stores metadata. The
|
|
33
|
+
* server bootstrap scans for this metadata and binds the
|
|
34
|
+
* method to FiveM's tick cycle.
|
|
35
|
+
*/
|
|
5
36
|
function OnTick() {
|
|
6
37
|
return (target, propertyKey) => {
|
|
7
38
|
Reflect.defineMetadata(metadata_server_keys_1.METADATA_KEYS.TICK, {}, target, propertyKey);
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Marks a server-side NetEvent handler as publicly accessible.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* This decorator disables authentication requirements for the
|
|
5
|
+
* decorated method. It should only be applied to endpoints used for:
|
|
6
|
+
* - Login
|
|
7
|
+
* - Registration
|
|
8
|
+
* - Public information retrieval
|
|
6
9
|
*
|
|
7
|
-
*
|
|
10
|
+
* ## Security Warning
|
|
11
|
+
* This decorator must be used with caution. Public endpoints must NOT
|
|
12
|
+
* modify sensitive game state unless necessary.
|
|
13
|
+
*
|
|
14
|
+
* ## Example
|
|
8
15
|
* ```ts
|
|
9
16
|
* class AuthServerController {
|
|
10
|
-
* @
|
|
11
|
-
* @
|
|
12
|
-
* async login(player: Server.Player, credentials: AuthCredentials) {
|
|
17
|
+
* @Public()
|
|
18
|
+
* @OnNet("auth:login")
|
|
19
|
+
* async login(player: Server.Player, credentials: AuthCredentials) {
|
|
20
|
+
* // no authentication required for this event
|
|
21
|
+
* }
|
|
13
22
|
* }
|
|
14
23
|
* ```
|
|
15
|
-
*
|
|
24
|
+
*
|
|
25
|
+
* @returns Method decorator that marks a handler as unauthenticated.
|
|
26
|
+
*/
|
|
16
27
|
export declare function Public(): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
@@ -3,20 +3,31 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Public = Public;
|
|
4
4
|
const metadata_server_keys_1 = require("../system/metadata-server.keys");
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Marks a server-side NetEvent handler as publicly accessible.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* This decorator disables authentication requirements for the
|
|
9
|
+
* decorated method. It should only be applied to endpoints used for:
|
|
10
|
+
* - Login
|
|
11
|
+
* - Registration
|
|
12
|
+
* - Public information retrieval
|
|
10
13
|
*
|
|
11
|
-
*
|
|
14
|
+
* ## Security Warning
|
|
15
|
+
* This decorator must be used with caution. Public endpoints must NOT
|
|
16
|
+
* modify sensitive game state unless necessary.
|
|
17
|
+
*
|
|
18
|
+
* ## Example
|
|
12
19
|
* ```ts
|
|
13
20
|
* class AuthServerController {
|
|
14
|
-
* @
|
|
15
|
-
* @
|
|
16
|
-
* async login(player: Server.Player, credentials: AuthCredentials) {
|
|
21
|
+
* @Public()
|
|
22
|
+
* @OnNet("auth:login")
|
|
23
|
+
* async login(player: Server.Player, credentials: AuthCredentials) {
|
|
24
|
+
* // no authentication required for this event
|
|
25
|
+
* }
|
|
17
26
|
* }
|
|
18
27
|
* ```
|
|
19
|
-
*
|
|
28
|
+
*
|
|
29
|
+
* @returns Method decorator that marks a handler as unauthenticated.
|
|
30
|
+
*/
|
|
20
31
|
function Public() {
|
|
21
32
|
return function (target, propertyKey, descriptor) {
|
|
22
33
|
Reflect.defineMetadata(metadata_server_keys_1.METADATA_KEYS.PUBLIC, true, target, propertyKey);
|
|
@@ -22,34 +22,35 @@ export interface StateRequirement {
|
|
|
22
22
|
errorMessage?: string;
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
25
|
+
* Enforces gameplay state requirements before executing a method.
|
|
26
26
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* The decorator intercepts the method call and verifies whether
|
|
28
|
+
* the player satisfies required state flags (e.g. "dead", "cuffed",
|
|
29
|
+
* "on_duty_police").
|
|
30
30
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* - It throws a `GAME_STATE_ERROR` (AppError) if requirements are not met, which should be caught by the global error handler.
|
|
31
|
+
* ## Whitelist (`has`)
|
|
32
|
+
* The player MUST have *all* required states. Missing any state blocks execution.
|
|
34
33
|
*
|
|
35
|
-
*
|
|
34
|
+
* ## Blacklist (`missing`)
|
|
35
|
+
* The player MUST NOT have any of the forbidden states.
|
|
36
36
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
37
|
+
* ## Error Handling
|
|
38
|
+
* - Throws a normal Error if the method is called without a valid Player.
|
|
39
|
+
* - Throws an `AppError(GAME_STATE_ERROR)` if validation fails.
|
|
39
40
|
*
|
|
40
|
-
*
|
|
41
|
+
* ## Example
|
|
41
42
|
* ```ts
|
|
42
|
-
*
|
|
43
|
-
* @RequiresState({ missing: ['dead', 'cuffed'] })
|
|
43
|
+
* @RequiresState({ missing: ["dead", "cuffed"] })
|
|
44
44
|
* openInventory(player: Server.Player) { ... }
|
|
45
45
|
*
|
|
46
|
-
* // Example 2: Action requires being a police officer on duty
|
|
47
46
|
* @RequiresState({
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
47
|
+
* has: ["police_duty"],
|
|
48
|
+
* missing: ["dead"],
|
|
49
|
+
* errorMessage: "You must be on duty to access the armory."
|
|
51
50
|
* })
|
|
52
51
|
* openArmory(player: Server.Player) { ... }
|
|
53
52
|
* ```
|
|
53
|
+
*
|
|
54
|
+
* @param req - State validation configuration.
|
|
54
55
|
*/
|
|
55
56
|
export declare function RequiresState(req: StateRequirement): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
@@ -3,35 +3,36 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.RequiresState = RequiresState;
|
|
4
4
|
const utils_1 = require("../../utils");
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Enforces gameplay state requirements before executing a method.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* The decorator intercepts the method call and verifies whether
|
|
9
|
+
* the player satisfies required state flags (e.g. "dead", "cuffed",
|
|
10
|
+
* "on_duty_police").
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* - It throws a `GAME_STATE_ERROR` (AppError) if requirements are not met, which should be caught by the global error handler.
|
|
12
|
+
* ## Whitelist (`has`)
|
|
13
|
+
* The player MUST have *all* required states. Missing any state blocks execution.
|
|
15
14
|
*
|
|
16
|
-
*
|
|
15
|
+
* ## Blacklist (`missing`)
|
|
16
|
+
* The player MUST NOT have any of the forbidden states.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* ## Error Handling
|
|
19
|
+
* - Throws a normal Error if the method is called without a valid Player.
|
|
20
|
+
* - Throws an `AppError(GAME_STATE_ERROR)` if validation fails.
|
|
20
21
|
*
|
|
21
|
-
*
|
|
22
|
+
* ## Example
|
|
22
23
|
* ```ts
|
|
23
|
-
*
|
|
24
|
-
* @RequiresState({ missing: ['dead', 'cuffed'] })
|
|
24
|
+
* @RequiresState({ missing: ["dead", "cuffed"] })
|
|
25
25
|
* openInventory(player: Server.Player) { ... }
|
|
26
26
|
*
|
|
27
|
-
* // Example 2: Action requires being a police officer on duty
|
|
28
27
|
* @RequiresState({
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
* has: ["police_duty"],
|
|
29
|
+
* missing: ["dead"],
|
|
30
|
+
* errorMessage: "You must be on duty to access the armory."
|
|
32
31
|
* })
|
|
33
32
|
* openArmory(player: Server.Player) { ... }
|
|
34
33
|
* ```
|
|
34
|
+
*
|
|
35
|
+
* @param req - State validation configuration.
|
|
35
36
|
*/
|
|
36
37
|
function RequiresState(req) {
|
|
37
38
|
return function (target, propertyKey, descriptor) {
|
|
@@ -1,9 +1,48 @@
|
|
|
1
1
|
import type { SecurityAction } from '../types/security.types';
|
|
2
2
|
interface ThrottleOptions {
|
|
3
|
+
/**
|
|
4
|
+
* limit of calls per windowMs
|
|
5
|
+
*/
|
|
3
6
|
limit: number;
|
|
7
|
+
/**
|
|
8
|
+
* time window for numeric overload
|
|
9
|
+
*/
|
|
4
10
|
windowMs: number;
|
|
11
|
+
/**
|
|
12
|
+
* action to perform on exceed limit
|
|
13
|
+
*/
|
|
5
14
|
onExceed?: SecurityAction;
|
|
15
|
+
/**
|
|
16
|
+
* custom message to display on exceed limit (ERROR MESSAGE)
|
|
17
|
+
*/
|
|
6
18
|
message?: string;
|
|
7
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Rate-limits how frequently a method can be called by a specific player.
|
|
22
|
+
*
|
|
23
|
+
* Uses `RateLimiterService` and a unique key composed of:
|
|
24
|
+
* `playerID:ClassName:MethodName`.
|
|
25
|
+
*
|
|
26
|
+
* ## Overload
|
|
27
|
+
* - `@Throttle(5, 1000)` → limit = 5 calls per 1000 ms
|
|
28
|
+
* - `@Throttle({ limit, windowMs, onExceed, message })`
|
|
29
|
+
*
|
|
30
|
+
* ## Behavior
|
|
31
|
+
* If the rate limit is exceeded:
|
|
32
|
+
* - A `SecurityError` is thrown.
|
|
33
|
+
* - Optional custom behavior (`onExceed`) may be executed.
|
|
34
|
+
*
|
|
35
|
+
* ## Example
|
|
36
|
+
* ```ts
|
|
37
|
+
* @Throttle(5, 2000)
|
|
38
|
+
* async search(player: Server.Player, query: string) { ... }
|
|
39
|
+
*
|
|
40
|
+
* @Throttle({ limit: 1, windowMs: 5000, message: "Too fast!" })
|
|
41
|
+
* async placeOrder(player: Server.Player) { ... }
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @param optionsOrLimit - Number (simple mode) or full config object.
|
|
45
|
+
* @param windowMs - Time window for numeric overload.
|
|
46
|
+
*/
|
|
8
47
|
export declare function Throttle(optionsOrLimit: number | ThrottleOptions, windowMs?: number): (target: any, propertyKey: string, descriptor?: PropertyDescriptor) => PropertyDescriptor | undefined;
|
|
9
48
|
export {};
|
|
@@ -4,6 +4,33 @@ exports.Throttle = Throttle;
|
|
|
4
4
|
const tsyringe_1 = require("tsyringe");
|
|
5
5
|
const rate_limiter_service_1 = require("../services/rate-limiter.service");
|
|
6
6
|
const utils_1 = require("../../utils");
|
|
7
|
+
/**
|
|
8
|
+
* Rate-limits how frequently a method can be called by a specific player.
|
|
9
|
+
*
|
|
10
|
+
* Uses `RateLimiterService` and a unique key composed of:
|
|
11
|
+
* `playerID:ClassName:MethodName`.
|
|
12
|
+
*
|
|
13
|
+
* ## Overload
|
|
14
|
+
* - `@Throttle(5, 1000)` → limit = 5 calls per 1000 ms
|
|
15
|
+
* - `@Throttle({ limit, windowMs, onExceed, message })`
|
|
16
|
+
*
|
|
17
|
+
* ## Behavior
|
|
18
|
+
* If the rate limit is exceeded:
|
|
19
|
+
* - A `SecurityError` is thrown.
|
|
20
|
+
* - Optional custom behavior (`onExceed`) may be executed.
|
|
21
|
+
*
|
|
22
|
+
* ## Example
|
|
23
|
+
* ```ts
|
|
24
|
+
* @Throttle(5, 2000)
|
|
25
|
+
* async search(player: Server.Player, query: string) { ... }
|
|
26
|
+
*
|
|
27
|
+
* @Throttle({ limit: 1, windowMs: 5000, message: "Too fast!" })
|
|
28
|
+
* async placeOrder(player: Server.Player) { ... }
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @param optionsOrLimit - Number (simple mode) or full config object.
|
|
32
|
+
* @param windowMs - Time window for numeric overload.
|
|
33
|
+
*/
|
|
7
34
|
function Throttle(optionsOrLimit, windowMs) {
|
|
8
35
|
return function (target, propertyKey, descriptor) {
|
|
9
36
|
if (!descriptor) {
|
|
@@ -1,7 +1,57 @@
|
|
|
1
1
|
import type { BindingScope } from './bind';
|
|
2
|
+
/**
|
|
3
|
+
* Marks a class as a framework-managed Service.
|
|
4
|
+
*
|
|
5
|
+
* The decorator binds the class into the dependency injection
|
|
6
|
+
* container using the provided scope (default: `singleton`).
|
|
7
|
+
*
|
|
8
|
+
* Services represent reusable, stateless or stateful logic such as:
|
|
9
|
+
* - Business rules
|
|
10
|
+
* - Utility layers
|
|
11
|
+
* - Gameplay systems
|
|
12
|
+
* - Internal modules
|
|
13
|
+
*
|
|
14
|
+
* This decorator is a convenience wrapper over `@Bind()`, allowing
|
|
15
|
+
* future extension of service-specific behavior without changing
|
|
16
|
+
* end-user code.
|
|
17
|
+
*
|
|
18
|
+
* ## Example
|
|
19
|
+
* ```ts
|
|
20
|
+
* @Service()
|
|
21
|
+
* export class InventoryService {
|
|
22
|
+
* addItem() { ... }
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @param options.scope - Optional binding scope (`singleton` | `transient`)
|
|
27
|
+
*/
|
|
2
28
|
export declare function Service(options?: {
|
|
3
29
|
scope?: BindingScope;
|
|
4
30
|
}): (target: any) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Marks a class as a Repository within the framework.
|
|
33
|
+
*
|
|
34
|
+
* A Repository abstracts persistence operations (e.g., database,
|
|
35
|
+
* API, in-memory, or hybrid storage). It is registered in the
|
|
36
|
+
* dependency injection container using the provided scope
|
|
37
|
+
* (default: `singleton`).
|
|
38
|
+
*
|
|
39
|
+
* `@Repo()` is intentionally separate from `@Service()` to clearly
|
|
40
|
+
* distinguish persistence-oriented classes from business logic.
|
|
41
|
+
* In the future, repository-specific behavior (caching layers,
|
|
42
|
+
* transactional wrappers, profiling, etc.) can be attached here
|
|
43
|
+
* without modifying user code.
|
|
44
|
+
*
|
|
45
|
+
* ## Example
|
|
46
|
+
* ```ts
|
|
47
|
+
* @Repo()
|
|
48
|
+
* export class AccountRepository {
|
|
49
|
+
* async findById(id: string) { ... }
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @param options.scope - Optional binding scope (`singleton` | `transient`)
|
|
54
|
+
*/
|
|
5
55
|
export declare function Repo(options?: {
|
|
6
56
|
scope?: BindingScope;
|
|
7
57
|
}): (target: any) => void;
|
|
@@ -3,10 +3,60 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Service = Service;
|
|
4
4
|
exports.Repo = Repo;
|
|
5
5
|
const bind_1 = require("./bind");
|
|
6
|
+
/**
|
|
7
|
+
* Marks a class as a framework-managed Service.
|
|
8
|
+
*
|
|
9
|
+
* The decorator binds the class into the dependency injection
|
|
10
|
+
* container using the provided scope (default: `singleton`).
|
|
11
|
+
*
|
|
12
|
+
* Services represent reusable, stateless or stateful logic such as:
|
|
13
|
+
* - Business rules
|
|
14
|
+
* - Utility layers
|
|
15
|
+
* - Gameplay systems
|
|
16
|
+
* - Internal modules
|
|
17
|
+
*
|
|
18
|
+
* This decorator is a convenience wrapper over `@Bind()`, allowing
|
|
19
|
+
* future extension of service-specific behavior without changing
|
|
20
|
+
* end-user code.
|
|
21
|
+
*
|
|
22
|
+
* ## Example
|
|
23
|
+
* ```ts
|
|
24
|
+
* @Service()
|
|
25
|
+
* export class InventoryService {
|
|
26
|
+
* addItem() { ... }
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @param options.scope - Optional binding scope (`singleton` | `transient`)
|
|
31
|
+
*/
|
|
6
32
|
function Service(options) {
|
|
7
33
|
var _a;
|
|
8
34
|
return (0, bind_1.Bind)((_a = options === null || options === void 0 ? void 0 : options.scope) !== null && _a !== void 0 ? _a : 'singleton');
|
|
9
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Marks a class as a Repository within the framework.
|
|
38
|
+
*
|
|
39
|
+
* A Repository abstracts persistence operations (e.g., database,
|
|
40
|
+
* API, in-memory, or hybrid storage). It is registered in the
|
|
41
|
+
* dependency injection container using the provided scope
|
|
42
|
+
* (default: `singleton`).
|
|
43
|
+
*
|
|
44
|
+
* `@Repo()` is intentionally separate from `@Service()` to clearly
|
|
45
|
+
* distinguish persistence-oriented classes from business logic.
|
|
46
|
+
* In the future, repository-specific behavior (caching layers,
|
|
47
|
+
* transactional wrappers, profiling, etc.) can be attached here
|
|
48
|
+
* without modifying user code.
|
|
49
|
+
*
|
|
50
|
+
* ## Example
|
|
51
|
+
* ```ts
|
|
52
|
+
* @Repo()
|
|
53
|
+
* export class AccountRepository {
|
|
54
|
+
* async findById(id: string) { ... }
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* @param options.scope - Optional binding scope (`singleton` | `transient`)
|
|
59
|
+
*/
|
|
10
60
|
function Repo(options) {
|
|
11
61
|
var _a;
|
|
12
62
|
return (0, bind_1.Bind)((_a = options === null || options === void 0 ? void 0 : options.scope) !== null && _a !== void 0 ? _a : 'singleton');
|
|
@@ -14,8 +14,8 @@ function normalizeError(error, origin) {
|
|
|
14
14
|
}
|
|
15
15
|
function handleCommandError(error, meta, playerId) {
|
|
16
16
|
const appError = normalizeError(error, 'server');
|
|
17
|
-
logger_1.loggers.command.error(`Command execution failed: /${meta.
|
|
18
|
-
command: meta.
|
|
17
|
+
logger_1.loggers.command.error(`Command execution failed: /${meta.command}`, {
|
|
18
|
+
command: meta.command,
|
|
19
19
|
handler: meta.methodName,
|
|
20
20
|
playerId,
|
|
21
21
|
code: appError.code,
|
|
@@ -8,7 +8,7 @@ export declare class CommandService {
|
|
|
8
8
|
register(meta: CommandMetadata, handler: Function): void;
|
|
9
9
|
execute(player: Server.Player, commandName: string, args: string[], raw: string): Promise<void>;
|
|
10
10
|
getAllCommands(): {
|
|
11
|
-
|
|
11
|
+
command: string;
|
|
12
12
|
description: string;
|
|
13
13
|
usage: string;
|
|
14
14
|
}[];
|
|
@@ -18,26 +18,29 @@ const utils_1 = require("../../utils");
|
|
|
18
18
|
const zod_1 = __importDefault(require("zod"));
|
|
19
19
|
const security_handler_contract_1 = require("../templates/security/security-handler.contract");
|
|
20
20
|
const logger_1 = require("../../shared/logger");
|
|
21
|
+
const schema_generator_1 = require("../system/schema-generator");
|
|
21
22
|
let CommandService = class CommandService {
|
|
22
23
|
constructor(securityHandler) {
|
|
23
24
|
this.securityHandler = securityHandler;
|
|
24
25
|
this.commands = new Map();
|
|
25
26
|
}
|
|
26
27
|
register(meta, handler) {
|
|
27
|
-
this.commands.set(meta.
|
|
28
|
-
logger_1.loggers.command.debug(`Registered: /${meta.
|
|
28
|
+
this.commands.set(meta.command.toLowerCase(), { meta, handler });
|
|
29
|
+
logger_1.loggers.command.debug(`Registered: /${meta.command}${meta.schema ? ' [Validated]' : ''}`);
|
|
29
30
|
}
|
|
30
31
|
async execute(player, commandName, args, raw) {
|
|
32
|
+
var _a;
|
|
31
33
|
const entry = this.commands.get(commandName.toLowerCase());
|
|
32
34
|
if (!entry) {
|
|
33
35
|
return;
|
|
34
36
|
}
|
|
35
37
|
const { meta, handler } = entry;
|
|
36
38
|
let validatedArgs = args;
|
|
37
|
-
|
|
39
|
+
// Use explicit schema or auto-generate from paramTypes
|
|
40
|
+
const schema = (_a = meta.schema) !== null && _a !== void 0 ? _a : (0, schema_generator_1.generateSchemaFromTypes)(meta.paramTypes);
|
|
41
|
+
if (schema) {
|
|
38
42
|
try {
|
|
39
|
-
|
|
40
|
-
validatedArgs = Array.isArray(result) ? result : [result];
|
|
43
|
+
validatedArgs = await schema.parseAsync(args);
|
|
41
44
|
}
|
|
42
45
|
catch (error) {
|
|
43
46
|
if (error instanceof zod_1.default.ZodError) {
|
|
@@ -60,7 +63,7 @@ let CommandService = class CommandService {
|
|
|
60
63
|
return Array.from(this.commands.values()).map((c) => {
|
|
61
64
|
var _a, _b;
|
|
62
65
|
return ({
|
|
63
|
-
|
|
66
|
+
command: c.meta.command,
|
|
64
67
|
description: (_a = c.meta.description) !== null && _a !== void 0 ? _a : '',
|
|
65
68
|
usage: (_b = c.meta.usage) !== null && _b !== void 0 ? _b : '',
|
|
66
69
|
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { DecoratorProcessor } from '../../../system/decorator-processor';
|
|
2
2
|
import { CommandService } from '../../services';
|
|
3
|
-
import {
|
|
3
|
+
import { CommandMetadata } from '../../decorators/command';
|
|
4
4
|
export declare class CommandProcessor implements DecoratorProcessor {
|
|
5
5
|
private commandService;
|
|
6
6
|
readonly metadataKey: string;
|
|
7
7
|
constructor(commandService: CommandService);
|
|
8
|
-
process(target: any, methodName: string, metadata:
|
|
8
|
+
process(target: any, methodName: string, metadata: CommandMetadata): void;
|
|
9
9
|
}
|
|
@@ -20,8 +20,7 @@ let CommandProcessor = class CommandProcessor {
|
|
|
20
20
|
}
|
|
21
21
|
process(target, methodName, metadata) {
|
|
22
22
|
const handler = target[methodName].bind(target);
|
|
23
|
-
|
|
24
|
-
this.commandService.register(fullMeta, handler);
|
|
23
|
+
this.commandService.register(metadata, handler);
|
|
25
24
|
}
|
|
26
25
|
};
|
|
27
26
|
exports.CommandProcessor = CommandProcessor;
|
|
@@ -20,6 +20,7 @@ const security_handler_contract_1 = require("../../templates/security/security-h
|
|
|
20
20
|
const utils_1 = require("../../../utils");
|
|
21
21
|
const logger_1 = require("../../../shared/logger");
|
|
22
22
|
const zod_1 = __importDefault(require("zod"));
|
|
23
|
+
const schema_generator_1 = require("../schema-generator");
|
|
23
24
|
let NetEventProcessor = class NetEventProcessor {
|
|
24
25
|
constructor(playerService, securityHandler) {
|
|
25
26
|
this.playerService = playerService;
|
|
@@ -53,11 +54,11 @@ let NetEventProcessor = class NetEventProcessor {
|
|
|
53
54
|
return;
|
|
54
55
|
}
|
|
55
56
|
let validatedArgs = args;
|
|
56
|
-
|
|
57
|
+
const schema = (0, schema_generator_1.generateSchemaFromTypes)(metadata.paramTypes);
|
|
58
|
+
if (schema) {
|
|
57
59
|
try {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
validatedArgs = [parsed];
|
|
60
|
+
const parsed = schema.parse(args);
|
|
61
|
+
validatedArgs = Array.isArray(parsed) ? parsed : [parsed];
|
|
61
62
|
}
|
|
62
63
|
catch (error) {
|
|
63
64
|
if (error instanceof zod_1.default.ZodError) {
|
|
@@ -0,0 +1,28 @@
|
|
|
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.generateSchemaFromTypes = generateSchemaFromTypes;
|
|
7
|
+
const zod_1 = __importDefault(require("zod"));
|
|
8
|
+
const player_1 = require("../entities/player");
|
|
9
|
+
function typeToZodSchema(type) {
|
|
10
|
+
switch (type) {
|
|
11
|
+
case String: return zod_1.default.coerce.string();
|
|
12
|
+
case Number: return zod_1.default.coerce.number();
|
|
13
|
+
case Boolean: return zod_1.default.coerce.boolean();
|
|
14
|
+
case Array: return zod_1.default.array(zod_1.default.any());
|
|
15
|
+
case Object: return zod_1.default.any();
|
|
16
|
+
default: return zod_1.default.any();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function generateSchemaFromTypes(paramTypes) {
|
|
20
|
+
var _a;
|
|
21
|
+
if (!paramTypes || paramTypes.length <= 1)
|
|
22
|
+
return null;
|
|
23
|
+
if (paramTypes[0] !== player_1.Player) {
|
|
24
|
+
throw new Error(`@OnNet: First parameter must be Player, got ${(_a = paramTypes[0]) === null || _a === void 0 ? void 0 : _a.name}`);
|
|
25
|
+
}
|
|
26
|
+
const argSchemas = paramTypes.slice(1).map(typeToZodSchema);
|
|
27
|
+
return zod_1.default.tuple(argSchemas);
|
|
28
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Player } from "../../entities";
|
|
2
|
+
type CommandArgs = string[] | any;
|
|
2
3
|
/**
|
|
3
4
|
* Template for admin controller implementations.
|
|
4
5
|
*/
|
|
5
6
|
export interface AdminControllerTemplate {
|
|
6
|
-
handleBanCommand(player:
|
|
7
|
-
handleKickCommand(player:
|
|
8
|
-
handleMuteCommand(player:
|
|
9
|
-
handleUnmuteCommand(player:
|
|
7
|
+
handleBanCommand(player: Player, args: CommandArgs, raw?: string): Promise<void>;
|
|
8
|
+
handleKickCommand(player: Player, args: CommandArgs, raw?: string): Promise<void>;
|
|
9
|
+
handleMuteCommand(player: Player, args: CommandArgs, raw?: string): Promise<void>;
|
|
10
|
+
handleUnmuteCommand(player: Player, args: CommandArgs, raw?: string): Promise<void>;
|
|
10
11
|
}
|
|
12
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-core/framework",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2-beta.1",
|
|
4
4
|
"description": "Secure, Event-Driven, OOP Engine for FiveM. Stop scripting, start engineering.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -18,6 +18,24 @@
|
|
|
18
18
|
"README.md",
|
|
19
19
|
"LICENSE"
|
|
20
20
|
],
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.js",
|
|
25
|
+
"require": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./server": {
|
|
28
|
+
"types": "./dist/server/index.d.ts",
|
|
29
|
+
"import": "./dist/server/index.js",
|
|
30
|
+
"require": "./dist/server/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./client": {
|
|
33
|
+
"types": "./dist/client/index.d.ts",
|
|
34
|
+
"import": "./dist/client/index.js",
|
|
35
|
+
"require": "./dist/client/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./package.json": "./package.json"
|
|
38
|
+
},
|
|
21
39
|
"scripts": {
|
|
22
40
|
"build": "tsc -p tsconfig.build.json",
|
|
23
41
|
"watch": "tsc -p tsconfig.build.json --watch",
|