@open-core/framework 0.2.8 → 0.2.9

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 CHANGED
@@ -1,3 +1,11 @@
1
+ [![CI](https://github.com/newcore-network/opencore/actions/workflows/ci.yml/badge.svg)](https://github.com/newcore-network/opencore/actions/workflows/ci.yml)
2
+ ![npm](https://img.shields.io/npm/v/@open-core/framework?style=flat-square)
3
+ ![license](https://img.shields.io/github/license/newcore-network/opencore?style=flat-square)
4
+ ![typescript](https://img.shields.io/badge/TypeScript-first-blue?style=flat-square)
5
+ [![cli](https://img.shields.io/badge/CLI-opencore--cli-purple?style=flat-square)](https://opencorejs.dev/docs/cli/introduction)
6
+ [![website](https://img.shields.io/badge/web-opencorejs.dev-black?style=flat-square)](https://opencorejs.dev)
7
+
8
+
1
9
  # OpenCore Framework - Open Stable beta
2
10
 
3
11
  OpenCore is a TypeScript multiplayer runtime framework targeting FiveM via an adapter.
@@ -11,6 +19,8 @@ It is not a gamemode or RP framework. It provides:
11
19
 
12
20
  License: MPL-2.0
13
21
 
22
+ [Discord Community](https://discord.gg/99g3FgvkPs) | [Website](https://opencorejs.dev) | [OpenCore CLI](https://github.com/newcore-network/opencore-cli)
23
+
14
24
  ## Scope
15
25
 
16
26
  This package (`@open-core/framework`) contains transversal infrastructure only.
@@ -20,15 +20,16 @@ let NuiProcessor = class NuiProcessor {
20
20
  RegisterNuiCallbackType(metadata.eventName);
21
21
  on(`__cfx_nui:${metadata.eventName}`, async (data, cb) => {
22
22
  try {
23
- await handler(data);
24
- cb({ ok: true });
23
+ const result = await handler(data);
24
+ cb({ ok: true, data: result });
25
25
  }
26
26
  catch (error) {
27
27
  logger_1.loggers.nui.error(`NUI callback error`, {
28
28
  event: metadata.eventName,
29
29
  handler: handlerName,
30
30
  }, error);
31
- cb({ ok: false, error: String(error) });
31
+ const message = error instanceof Error ? error.message : String(error);
32
+ cb({ ok: false, error: message });
32
33
  }
33
34
  });
34
35
  logger_1.loggers.nui.debug(`Registered: ${metadata.eventName} -> ${handlerName}`);
@@ -30,20 +30,22 @@ export interface GuardOptions {
30
30
  *
31
31
  * @throws Error - If the method is invoked without a valid `Player` as the first argument.
32
32
  *
33
- * @example
34
33
  * ```ts
35
34
  * @Server.Controller()
36
- * export class FactionController {
35
+ * export class AdminController {
36
+ *
37
37
  * @Server.Guard({ permission: 'factions.manage' })
38
- * async createFaction(player: Server.Player, dto: CreateFactionDTO) {
38
+ * @Server.Command('newfaction', schema)
39
+ * async createFaction(player: Server.Player, dto: Infer<typeof schema>) {
39
40
  * return this.service.create(dto)
40
41
  * }
41
42
  *
42
43
  * @Server.Guard({ rank: 3 })
43
- * async promoteMember(player: Server.Player, memberID: string) {
44
- * return this.service.promote(player, memberID)
44
+ * @Server.Command('ban')
45
+ * async ban(player: Server.Player, targetID: string) {
46
+ * return this.service.ban(player, memberID)
45
47
  * }
46
- * }
48
+ }
47
49
  * ```
48
50
  */
49
51
  export declare function Guard(options: GuardOptions): (target: any, propertyKey: string, descriptor?: PropertyDescriptor) => PropertyDescriptor | undefined;
@@ -25,20 +25,22 @@ const principal_port_1 = require("../services/ports/principal.port");
25
25
  *
26
26
  * @throws Error - If the method is invoked without a valid `Player` as the first argument.
27
27
  *
28
- * @example
29
28
  * ```ts
30
29
  * @Server.Controller()
31
- * export class FactionController {
30
+ * export class AdminController {
31
+ *
32
32
  * @Server.Guard({ permission: 'factions.manage' })
33
- * async createFaction(player: Server.Player, dto: CreateFactionDTO) {
33
+ * @Server.Command('newfaction', schema)
34
+ * async createFaction(player: Server.Player, dto: Infer<typeof schema>) {
34
35
  * return this.service.create(dto)
35
36
  * }
36
37
  *
37
38
  * @Server.Guard({ rank: 3 })
38
- * async promoteMember(player: Server.Player, memberID: string) {
39
- * return this.service.promote(player, memberID)
39
+ * @Server.Command('ban')
40
+ * async ban(player: Server.Player, targetID: string) {
41
+ * return this.service.ban(player, memberID)
40
42
  * }
41
- * }
43
+ }
42
44
  * ```
43
45
  */
44
46
  function Guard(options) {
@@ -48,15 +48,15 @@ type ServerNetHandler<TArgs extends any[] = any[]> = (player: Player, ...args: T
48
48
  * export class ExampleController {
49
49
  * // Simple handler (no schema)
50
50
  * @Server.OnNet('example:ping')
51
- * ping(player: Player, message: string) { }
51
+ * ping(player: Server.Player, message: string) { }
52
52
  *
53
53
  * // With schema directly (recommended)
54
54
  * @Server.OnNet('example:data', PayloadSchema)
55
- * handleData(player: Player, data: Infer<typeof PayloadSchema>) { }
55
+ * handleData(player: Server.Player, data: Infer<typeof PayloadSchema>) { }
56
56
  *
57
57
  * // With options object (legacy)
58
58
  * @Server.OnNet('example:legacy', { schema: PayloadSchema })
59
- * handleLegacy(player: Player, data: Infer<typeof PayloadSchema>) { }
59
+ * handleLegacy(player: Server.Player, data: Infer<typeof PayloadSchema>) { }
60
60
  * }
61
61
  * ```
62
62
  */
@@ -1,47 +1,3 @@
1
- /**
2
- * ParallelCompute Module
3
- *
4
- * Provides parallel computation capabilities using worker threads.
5
- *
6
- * @example
7
- * ```typescript
8
- * import {
9
- * initParallelCompute,
10
- * defineTask,
11
- * filterByDistance,
12
- * } from '@open-core/framework/server'
13
- *
14
- * // Initialize the worker pool (call once at startup)
15
- * initParallelCompute({ maxWorkers: 4 })
16
- *
17
- * // Use built-in tasks
18
- * const nearby = await filterByDistance.run({
19
- * entities: allEntities,
20
- * position: player.coords,
21
- * radius: 100,
22
- * })
23
- *
24
- * // Define custom tasks
25
- * const processPlayers = defineTask({
26
- * name: 'processPlayers',
27
- * estimateCost: (input) => input.length * 10,
28
- * workerThreshold: 500,
29
- * compute: (players) => players.map(p => ({
30
- * ...p,
31
- * processed: true,
32
- * })),
33
- * })
34
- *
35
- * // Run with automatic mode selection
36
- * const result = await processPlayers.run(players)
37
- *
38
- * // Force sync execution
39
- * const resultSync = processPlayers.sync(players)
40
- *
41
- * // Force parallel execution
42
- * const resultParallel = await processPlayers.parallel(players)
43
- * ```
44
- */
45
- export { defineBatchFilter, defineBatchReduce, defineBatchTransform, defineTask, filterByDistance, findClosest, getParallelComputeService, initParallelCompute, ParallelComputeService, shutdownParallelCompute, sortByDistance, type Vector3Like, } from './parallel-compute.service';
1
+ export { defineBatchFilter, defineBatchReduce, defineBatchTransform, defineTask, filterByDistance, findClosest, getParallelComputeService, initParallelCompute, ParallelComputeService, shutdownParallelCompute, sortByDistance, } from './parallel-compute.service';
46
2
  export type { ExecutionMode, ParallelComputeMetrics, ParallelTask, ParallelTaskOptions, TaskResult, WorkerInfo, WorkerPoolConfig, WorkerStatus, } from './types';
47
3
  export { WorkerPool } from './worker-pool';
@@ -1,48 +1,4 @@
1
1
  "use strict";
2
- /**
3
- * ParallelCompute Module
4
- *
5
- * Provides parallel computation capabilities using worker threads.
6
- *
7
- * @example
8
- * ```typescript
9
- * import {
10
- * initParallelCompute,
11
- * defineTask,
12
- * filterByDistance,
13
- * } from '@open-core/framework/server'
14
- *
15
- * // Initialize the worker pool (call once at startup)
16
- * initParallelCompute({ maxWorkers: 4 })
17
- *
18
- * // Use built-in tasks
19
- * const nearby = await filterByDistance.run({
20
- * entities: allEntities,
21
- * position: player.coords,
22
- * radius: 100,
23
- * })
24
- *
25
- * // Define custom tasks
26
- * const processPlayers = defineTask({
27
- * name: 'processPlayers',
28
- * estimateCost: (input) => input.length * 10,
29
- * workerThreshold: 500,
30
- * compute: (players) => players.map(p => ({
31
- * ...p,
32
- * processed: true,
33
- * })),
34
- * })
35
- *
36
- * // Run with automatic mode selection
37
- * const result = await processPlayers.run(players)
38
- *
39
- * // Force sync execution
40
- * const resultSync = processPlayers.sync(players)
41
- *
42
- * // Force parallel execution
43
- * const resultParallel = await processPlayers.parallel(players)
44
- * ```
45
- */
46
2
  Object.defineProperty(exports, "__esModule", { value: true });
47
3
  exports.WorkerPool = exports.sortByDistance = exports.shutdownParallelCompute = exports.ParallelComputeService = exports.initParallelCompute = exports.getParallelComputeService = exports.findClosest = exports.filterByDistance = exports.defineTask = exports.defineBatchTransform = exports.defineBatchReduce = exports.defineBatchFilter = void 0;
48
4
  // Service
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const node_worker_threads_1 = require("node:worker_threads");
4
+ const worker_1 = require("./worker");
5
+ const port = node_worker_threads_1.parentPort;
6
+ if (!port) {
7
+ throw new Error('native-worker.entry.ts must be executed inside a Worker thread');
8
+ }
9
+ port.on('message', (message) => {
10
+ const response = (0, worker_1.processMessage)(message);
11
+ port.postMessage(response);
12
+ });
@@ -2,12 +2,17 @@
2
2
  * ParallelCompute Service
3
3
  *
4
4
  * Provides an ergonomic API for parallel computation.
5
- * Uses virtual workers in FiveM environment, with async execution
6
- * that yields to the event loop for better performance.
5
+ * Uses native `node:worker_threads` to run CPU-bound work off the main thread.
7
6
  *
8
- * Automatically decides whether to run synchronously or asynchronously
7
+ * Automatically decides whether to run synchronously or in a worker
9
8
  * based on estimated computational cost.
9
+ *
10
+ * Requirements/limitations:
11
+ * - The compute function is serialized with `Function#toString()`.
12
+ * - The compute function must be pure (no closures / no external references).
13
+ * - Inputs/outputs must be structured-cloneable.
10
14
  */
15
+ import { Vector3 } from '../../../../kernel';
11
16
  import { ParallelComputeMetrics, ParallelTask, ParallelTaskOptions, TaskResult, WorkerPoolConfig } from './types';
12
17
  /**
13
18
  * Service for managing parallel computation
@@ -91,33 +96,28 @@ export declare function shutdownParallelCompute(): Promise<void>;
91
96
  * Returns a task object with run, sync, parallel, and distributed methods
92
97
  */
93
98
  export declare function defineTask<TInput, TOutput>(options: ParallelTaskOptions<TInput, TOutput>): ParallelTask<TInput, TOutput>;
94
- export interface Vector3Like {
95
- x: number;
96
- y: number;
97
- z: number;
98
- }
99
99
  /**
100
100
  * Built-in task: Filter entities by distance from a position
101
101
  */
102
102
  export declare const filterByDistance: ParallelTask<{
103
- entities: Vector3Like[];
104
- position: Vector3Like;
103
+ entities: Vector3[];
104
+ position: Vector3;
105
105
  radius: number;
106
- }, Vector3Like[]>;
106
+ }, Vector3[]>;
107
107
  /**
108
108
  * Built-in task: Sort entities by distance from a position
109
109
  */
110
110
  export declare const sortByDistance: ParallelTask<{
111
- entities: Vector3Like[];
112
- position: Vector3Like;
113
- }, Vector3Like[]>;
111
+ entities: Vector3[];
112
+ position: Vector3;
113
+ }, Vector3[]>;
114
114
  /**
115
115
  * Built-in task: Find closest entity to a position
116
116
  */
117
117
  export declare const findClosest: ParallelTask<{
118
- entities: Vector3Like[];
119
- position: Vector3Like;
120
- }, Vector3Like | null>;
118
+ entities: Vector3[];
119
+ position: Vector3;
120
+ }, Vector3 | null>;
121
121
  /**
122
122
  * Built-in task: Batch transform items
123
123
  */
@@ -3,11 +3,15 @@
3
3
  * ParallelCompute Service
4
4
  *
5
5
  * Provides an ergonomic API for parallel computation.
6
- * Uses virtual workers in FiveM environment, with async execution
7
- * that yields to the event loop for better performance.
6
+ * Uses native `node:worker_threads` to run CPU-bound work off the main thread.
8
7
  *
9
- * Automatically decides whether to run synchronously or asynchronously
8
+ * Automatically decides whether to run synchronously or in a worker
10
9
  * based on estimated computational cost.
10
+ *
11
+ * Requirements/limitations:
12
+ * - The compute function is serialized with `Function#toString()`.
13
+ * - The compute function must be pure (no closures / no external references).
14
+ * - Inputs/outputs must be structured-cloneable.
11
15
  */
12
16
  var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
13
17
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -308,6 +312,9 @@ function defineTask(options) {
308
312
  },
309
313
  };
310
314
  }
315
+ // ─────────────────────────────────────────────────────────────────────────────
316
+ // Built-in Tasks
317
+ // ─────────────────────────────────────────────────────────────────────────────
311
318
  /**
312
319
  * Built-in task: Filter entities by distance from a position
313
320
  */
@@ -39,8 +39,11 @@ export interface ParallelTaskOptions<TInput, TOutput> {
39
39
  workerThreshold?: number;
40
40
  /**
41
41
  * The compute function to execute.
42
- * MUST be pure (no closures, no external dependencies).
43
- * Will be serialized and sent to worker.
42
+ *
43
+ * Requirements/limitations:
44
+ * - MUST be pure (no closures / no external references).
45
+ * - Inputs/outputs MUST be structured-cloneable.
46
+ * - The function body is serialized with `Function#toString()` and evaluated in a worker.
44
47
  */
45
48
  compute: (input: TInput) => TOutput;
46
49
  /**
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Worker Pool
3
3
  *
4
- * Abstract worker pool that supports different backends.
5
- * In FiveM environment, falls back to async execution.
6
- * Can be extended to support native workers when available.
4
+ * Native worker thread pool for CPU-bound computations.
5
+ * Tasks are executed in `node:worker_threads` and communicated through
6
+ * structured-cloned messages.
7
7
  */
8
8
  import { WorkerInfo, WorkerMessage, WorkerPoolConfig } from './types';
9
9
  type EventCallback = (...args: unknown[]) => void;
@@ -23,12 +23,8 @@ export declare class WorkerPool extends SimpleEventEmitter {
23
23
  private workerIdCounter;
24
24
  private cleanupInterval;
25
25
  private isShuttingDown;
26
- private useNativeWorkers;
26
+ private workerScriptPath;
27
27
  constructor(config?: Partial<WorkerPoolConfig>);
28
- /**
29
- * Detect if native worker threads are available
30
- */
31
- private detectWorkerSupport;
32
28
  /**
33
29
  * Check if using native workers
34
30
  */
@@ -56,7 +52,7 @@ export declare class WorkerPool extends SimpleEventEmitter {
56
52
  */
57
53
  shutdown(): Promise<void>;
58
54
  /**
59
- * Spawn a new virtual worker
55
+ * Spawn a new native worker
60
56
  */
61
57
  private spawnWorker;
62
58
  /**
@@ -2,12 +2,47 @@
2
2
  /**
3
3
  * Worker Pool
4
4
  *
5
- * Abstract worker pool that supports different backends.
6
- * In FiveM environment, falls back to async execution.
7
- * Can be extended to support native workers when available.
5
+ * Native worker thread pool for CPU-bound computations.
6
+ * Tasks are executed in `node:worker_threads` and communicated through
7
+ * structured-cloned messages.
8
8
  */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
9
42
  Object.defineProperty(exports, "__esModule", { value: true });
10
43
  exports.WorkerPool = void 0;
44
+ const path = __importStar(require("node:path"));
45
+ const node_worker_threads_1 = require("node:worker_threads");
11
46
  const DEFAULT_CONFIG = {
12
47
  minWorkers: 0,
13
48
  maxWorkers: 4,
@@ -46,16 +81,41 @@ class SimpleEventEmitter {
46
81
  }
47
82
  }
48
83
  /**
49
- * Virtual worker that executes tasks asynchronously
50
- * Used as fallback when native workers aren't available
84
+ * Native worker backed by `node:worker_threads`.
85
+ *
86
+ * Executes one task at a time and returns `WorkerResponse` coming from the
87
+ * worker entrypoint.
51
88
  */
52
- class VirtualWorker {
53
- constructor(id) {
89
+ class NativeWorker {
90
+ constructor(id, workerScriptPath, callbacks) {
54
91
  this._status = 'idle';
55
92
  this.tasksCompleted = 0;
56
93
  this.lastActiveAt = Date.now();
57
94
  this.currentTask = null;
95
+ this.pendingResponses = new Map();
58
96
  this.id = id;
97
+ this.worker = new node_worker_threads_1.Worker(workerScriptPath);
98
+ this.worker.on('message', (response) => {
99
+ const handlers = this.pendingResponses.get(response.id);
100
+ if (handlers) {
101
+ this.pendingResponses.delete(response.id);
102
+ handlers.resolve(response);
103
+ }
104
+ });
105
+ this.worker.on('error', (error) => {
106
+ this.failAllPending(error instanceof Error ? error : new Error(String(error)));
107
+ this._status = 'terminated';
108
+ this.currentTask = null;
109
+ callbacks.onError(this.id, error instanceof Error ? error : new Error(String(error)));
110
+ });
111
+ this.worker.on('exit', (code) => {
112
+ if (this._status !== 'terminated') {
113
+ this.failAllPending(new Error(`Worker exited with code ${code}`));
114
+ }
115
+ this._status = 'terminated';
116
+ this.currentTask = null;
117
+ callbacks.onExit(this.id, code);
118
+ });
59
119
  }
60
120
  get status() {
61
121
  return this._status;
@@ -71,40 +131,52 @@ class VirtualWorker {
71
131
  };
72
132
  }
73
133
  async execute(task) {
134
+ if (this._status === 'terminated') {
135
+ throw new Error('Worker is terminated');
136
+ }
74
137
  this._status = 'busy';
75
138
  this.currentTask = task;
76
- const startTime = performance.now();
77
- try {
78
- // Execute the function
79
- const fn = new Function('input', `return (${task.message.functionBody})(input)`);
80
- const result = fn(task.message.input);
81
- const executionTime = performance.now() - startTime;
82
- this.tasksCompleted++;
83
- this.lastActiveAt = Date.now();
84
- this._status = 'idle';
85
- this.currentTask = null;
86
- return {
87
- id: task.id,
88
- success: true,
89
- result,
90
- executionTime,
91
- };
92
- }
93
- catch (error) {
94
- const executionTime = performance.now() - startTime;
95
- this._status = 'idle';
96
- this.currentTask = null;
97
- return {
98
- id: task.id,
99
- success: false,
100
- error: error instanceof Error ? error.message : String(error),
101
- executionTime,
102
- };
103
- }
139
+ return new Promise((resolve, reject) => {
140
+ this.pendingResponses.set(task.id, {
141
+ resolve: (response) => {
142
+ this.tasksCompleted++;
143
+ this.lastActiveAt = Date.now();
144
+ this._status = 'idle';
145
+ this.currentTask = null;
146
+ resolve(response);
147
+ },
148
+ reject: (error) => {
149
+ this._status = 'idle';
150
+ this.currentTask = null;
151
+ reject(error);
152
+ },
153
+ });
154
+ this.worker.postMessage(task.message);
155
+ });
104
156
  }
105
157
  terminate() {
106
158
  this._status = 'terminated';
107
159
  this.currentTask = null;
160
+ void this.worker.terminate();
161
+ }
162
+ cancelTask(taskId, reason) {
163
+ const handlers = this.pendingResponses.get(taskId);
164
+ if (handlers) {
165
+ this.pendingResponses.delete(taskId);
166
+ handlers.reject(reason);
167
+ }
168
+ }
169
+ failAllPending(error) {
170
+ const handlers = Array.from(this.pendingResponses.values());
171
+ this.pendingResponses.clear();
172
+ for (const h of handlers) {
173
+ try {
174
+ h.reject(error);
175
+ }
176
+ catch (_a) {
177
+ // ignore
178
+ }
179
+ }
108
180
  }
109
181
  }
110
182
  class WorkerPool extends SimpleEventEmitter {
@@ -115,10 +187,8 @@ class WorkerPool extends SimpleEventEmitter {
115
187
  this.workerIdCounter = 0;
116
188
  this.cleanupInterval = null;
117
189
  this.isShuttingDown = false;
118
- this.useNativeWorkers = false;
119
190
  this.config = Object.assign(Object.assign({}, DEFAULT_CONFIG), config);
120
- // Check if native workers are available
121
- this.detectWorkerSupport();
191
+ this.workerScriptPath = path.join(__dirname, 'native-worker.entry.js');
122
192
  // Start cleanup interval
123
193
  this.cleanupInterval = setInterval(() => this.cleanupIdleWorkers(), 10000);
124
194
  // Spawn minimum workers
@@ -126,24 +196,11 @@ class WorkerPool extends SimpleEventEmitter {
126
196
  this.spawnWorker();
127
197
  }
128
198
  }
129
- /**
130
- * Detect if native worker threads are available
131
- */
132
- detectWorkerSupport() {
133
- try {
134
- // Try to detect worker_threads module
135
- // In FiveM this will fail, which is expected
136
- this.useNativeWorkers = false;
137
- }
138
- catch (_a) {
139
- this.useNativeWorkers = false;
140
- }
141
- }
142
199
  /**
143
200
  * Check if using native workers
144
201
  */
145
202
  get isNative() {
146
- return this.useNativeWorkers;
203
+ return true;
147
204
  }
148
205
  /**
149
206
  * Execute a task
@@ -160,6 +217,7 @@ class WorkerPool extends SimpleEventEmitter {
160
217
  reject,
161
218
  startTime: Date.now(),
162
219
  timeoutId: null,
220
+ settled: false,
163
221
  };
164
222
  // Set timeout
165
223
  task.timeoutId = setTimeout(() => {
@@ -201,7 +259,7 @@ class WorkerPool extends SimpleEventEmitter {
201
259
  idleWorkers: idle,
202
260
  busyWorkers: busy,
203
261
  queuedTasks: this.taskQueue.length,
204
- isNative: this.useNativeWorkers,
262
+ isNative: true,
205
263
  };
206
264
  }
207
265
  /**
@@ -233,12 +291,33 @@ class WorkerPool extends SimpleEventEmitter {
233
291
  this.workers.clear();
234
292
  }
235
293
  /**
236
- * Spawn a new virtual worker
294
+ * Spawn a new native worker
237
295
  */
238
296
  spawnWorker() {
239
297
  try {
240
298
  const id = this.workerIdCounter++;
241
- const worker = new VirtualWorker(id);
299
+ const worker = new NativeWorker(id, this.workerScriptPath, {
300
+ onExit: (workerId, code) => {
301
+ const existing = this.workers.get(workerId);
302
+ if (!existing)
303
+ return;
304
+ this.workers.delete(workerId);
305
+ this.emit('workerExit', { workerId, code: code !== null && code !== void 0 ? code : 0 });
306
+ if (!this.isShuttingDown && this.workers.size < this.config.minWorkers) {
307
+ this.spawnWorker();
308
+ }
309
+ },
310
+ onError: (workerId, error) => {
311
+ const existing = this.workers.get(workerId);
312
+ if (!existing)
313
+ return;
314
+ this.workers.delete(workerId);
315
+ this.emit('workerExit', { workerId, code: 1, error: error.message });
316
+ if (!this.isShuttingDown && this.workers.size < this.config.minWorkers) {
317
+ this.spawnWorker();
318
+ }
319
+ },
320
+ });
242
321
  this.workers.set(id, worker);
243
322
  this.emit('workerSpawned', id);
244
323
  return worker;
@@ -266,10 +345,17 @@ class WorkerPool extends SimpleEventEmitter {
266
345
  const response = await worker.execute(task);
267
346
  if (task.timeoutId)
268
347
  clearTimeout(task.timeoutId);
348
+ if (task.settled) {
349
+ // Task was already rejected (e.g. timeout). Ignore late responses.
350
+ this.processQueue();
351
+ return;
352
+ }
269
353
  if (response.success) {
354
+ task.settled = true;
270
355
  task.resolve(response.result);
271
356
  }
272
357
  else {
358
+ task.settled = true;
273
359
  task.reject(new Error(response.error || 'Unknown worker error'));
274
360
  }
275
361
  this.emit('taskCompleted', {
@@ -283,14 +369,38 @@ class WorkerPool extends SimpleEventEmitter {
283
369
  catch (error) {
284
370
  if (task.timeoutId)
285
371
  clearTimeout(task.timeoutId);
286
- task.reject(error instanceof Error ? error : new Error(String(error)));
372
+ if (!task.settled) {
373
+ task.settled = true;
374
+ task.reject(error instanceof Error ? error : new Error(String(error)));
375
+ }
287
376
  }
288
377
  }
289
378
  /**
290
379
  * Handle task timeout
291
380
  */
292
381
  handleTaskTimeout(task) {
293
- task.reject(new Error('Task timeout'));
382
+ const timeoutError = new Error('Task timeout');
383
+ if (task.timeoutId) {
384
+ clearTimeout(task.timeoutId);
385
+ task.timeoutId = null;
386
+ }
387
+ if (!task.settled) {
388
+ task.settled = true;
389
+ task.reject(timeoutError);
390
+ }
391
+ // Try to terminate the worker that was executing this task.
392
+ for (const [id, worker] of this.workers) {
393
+ if (worker.info.currentTaskId === task.id) {
394
+ worker.cancelTask(task.id, timeoutError);
395
+ worker.terminate();
396
+ this.workers.delete(id);
397
+ this.emit('workerExit', { workerId: id, code: 1 });
398
+ if (!this.isShuttingDown && this.workers.size < this.config.minWorkers) {
399
+ this.spawnWorker();
400
+ }
401
+ break;
402
+ }
403
+ }
294
404
  // Remove from queue if present
295
405
  const queueIndex = this.taskQueue.findIndex((t) => t.id === task.id);
296
406
  if (queueIndex !== -1) {
@@ -1,12 +1,15 @@
1
1
  /**
2
2
  * Worker Script
3
3
  *
4
- * This file is reserved for future native worker thread support.
5
- * Currently, the WorkerPool uses virtual workers that execute
6
- * tasks asynchronously on the main thread with yielding.
4
+ * Utilities used by the native worker thread entrypoint.
7
5
  *
8
- * When FiveM or the target environment supports native worker threads,
9
- * this script can be used as the worker entry point.
6
+ * The worker pool executes user-provided compute functions by serializing the
7
+ * function body (`Function#toString()`) and evaluating it with `new Function`.
8
+ *
9
+ * Requirements/limitations:
10
+ * - The compute function must be pure (no closures / no external references).
11
+ * - Inputs/outputs must be structured-cloneable.
12
+ * - Do not pass untrusted code as compute functions.
10
13
  */
11
14
  import { WorkerMessage, WorkerResponse } from './types';
12
15
  /**
@@ -2,12 +2,15 @@
2
2
  /**
3
3
  * Worker Script
4
4
  *
5
- * This file is reserved for future native worker thread support.
6
- * Currently, the WorkerPool uses virtual workers that execute
7
- * tasks asynchronously on the main thread with yielding.
5
+ * Utilities used by the native worker thread entrypoint.
8
6
  *
9
- * When FiveM or the target environment supports native worker threads,
10
- * this script can be used as the worker entry point.
7
+ * The worker pool executes user-provided compute functions by serializing the
8
+ * function body (`Function#toString()`) and evaluating it with `new Function`.
9
+ *
10
+ * Requirements/limitations:
11
+ * - The compute function must be pure (no closures / no external references).
12
+ * - Inputs/outputs must be structured-cloneable.
13
+ * - Do not pass untrusted code as compute functions.
11
14
  */
12
15
  Object.defineProperty(exports, "__esModule", { value: true });
13
16
  exports.executeCompute = executeCompute;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-core/framework",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
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",
@@ -9,7 +9,7 @@
9
9
  "type": "git",
10
10
  "url": "git+https://github.com/newcore-network/opencore.git"
11
11
  },
12
- "homepage": "https://github.com/newcore-network/opencore",
12
+ "homepage": "https://opencorejs.dev",
13
13
  "bugs": {
14
14
  "url": "https://github.com/newcore-network/opencore/issues"
15
15
  },