@open-core/framework 1.0.8 → 1.0.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
@@ -141,6 +141,44 @@ export class ExampleNetController {
141
141
  - `@Throttle(limit, windowMs)`
142
142
  - `@RequiresState({ missing: [...] })`
143
143
 
144
+ ### Exports
145
+
146
+ `@Export()` defines a public resource API. Adapters may expose both direct/local access through `getResource()` and an optional explicit async helper layer through `getRemoteResource()` / `waitForRemoteResource()`.
147
+
148
+ ```ts
149
+ import { Controller, Export } from '@open-core/framework/server'
150
+ import { IExports } from '@open-core/framework/contracts/server'
151
+
152
+ @Controller()
153
+ export class DatabaseController {
154
+ @Export('pingDatabase')
155
+ async pingDatabase() {
156
+ return { success: true }
157
+ }
158
+ }
159
+
160
+ interface DatabaseExports {
161
+ pingDatabase(): Promise<{ success: boolean }>
162
+ }
163
+
164
+ class ExampleConsumer {
165
+ constructor(private readonly exportsService: IExports) {}
166
+
167
+ async ping() {
168
+ const database = await this.exportsService.waitForRemoteResource<DatabaseExports>('database', {
169
+ exportName: 'pingDatabase',
170
+ })
171
+
172
+ return database.pingDatabase()
173
+ }
174
+ }
175
+ ```
176
+
177
+ Guidance:
178
+
179
+ - `getResource()` is for local/synchronous resolution used by framework internals.
180
+ - `waitForRemoteResource()` / `getRemoteResource()` are optional adapter utilities for explicit async resource-to-resource calls.
181
+
144
182
  ### Library events
145
183
 
146
184
  Use library wrappers to emit domain events and `@OnLibraryEvent()` to observe them.
@@ -1,4 +1,42 @@
1
1
  export declare abstract class IExports {
2
+ /**
3
+ * Registers a local export handler for the current resource.
4
+ *
5
+ * @remarks
6
+ * This is called by the framework during metadata processing when it discovers
7
+ * methods decorated with `@Export()`.
8
+ */
2
9
  abstract register(exportName: string, handler: (...args: unknown[]) => unknown): void;
10
+ /**
11
+ * Resolves exports for a resource using the adapter's direct/local mechanism.
12
+ *
13
+ * @remarks
14
+ * Framework internals rely on this method remaining synchronous and side-effect free.
15
+ * Adapters should return `undefined` when the resource is not directly resolvable.
16
+ */
3
17
  abstract getResource<T = unknown>(resourceName: string): T | undefined;
18
+ /**
19
+ * Returns an async proxy for resource exports when the adapter provides a remote helper layer.
20
+ *
21
+ * @remarks
22
+ * This is optional and should not change the semantics of `getResource()`.
23
+ * Consumers should treat methods on the returned proxy as async.
24
+ */
25
+ getRemoteResource<T = unknown>(_resourceName: string): T;
26
+ /**
27
+ * Calls a single exported method through the adapter's optional remote helper layer.
28
+ */
29
+ callRemoteExport<TResult = unknown>(_resourceName: string, _exportName: string, ..._args: unknown[]): Promise<TResult>;
30
+ /**
31
+ * Waits until a resource exposes exports compatible with the adapter's remote helper layer.
32
+ *
33
+ * @param _options.exportName Optional export name that must be present before resolving.
34
+ * @param _options.timeoutMs Maximum time to wait before failing.
35
+ * @param _options.intervalMs Polling interval used by adapters that implement polling.
36
+ */
37
+ waitForRemoteResource<T = unknown>(_resourceName: string, _options?: {
38
+ exportName?: string;
39
+ timeoutMs?: number;
40
+ intervalMs?: number;
41
+ }): Promise<T>;
4
42
  }
@@ -1,2 +1,28 @@
1
1
  export class IExports {
2
+ /**
3
+ * Returns an async proxy for resource exports when the adapter provides a remote helper layer.
4
+ *
5
+ * @remarks
6
+ * This is optional and should not change the semantics of `getResource()`.
7
+ * Consumers should treat methods on the returned proxy as async.
8
+ */
9
+ getRemoteResource(_resourceName) {
10
+ throw new Error('[OpenCore] Remote exports are not supported by the active adapter.');
11
+ }
12
+ /**
13
+ * Calls a single exported method through the adapter's optional remote helper layer.
14
+ */
15
+ callRemoteExport(_resourceName, _exportName, ..._args) {
16
+ return Promise.reject(new Error('[OpenCore] Remote exports are not supported by the active adapter.'));
17
+ }
18
+ /**
19
+ * Waits until a resource exposes exports compatible with the adapter's remote helper layer.
20
+ *
21
+ * @param _options.exportName Optional export name that must be present before resolving.
22
+ * @param _options.timeoutMs Maximum time to wait before failing.
23
+ * @param _options.intervalMs Polling interval used by adapters that implement polling.
24
+ */
25
+ waitForRemoteResource(_resourceName, _options) {
26
+ return Promise.reject(new Error('[OpenCore] Remote exports are not supported by the active adapter.'));
27
+ }
2
28
  }
@@ -7,6 +7,13 @@ export declare class NodeExports implements IExports {
7
7
  private exports;
8
8
  register(exportName: string, handler: (...args: any[]) => any): void;
9
9
  getResource(resourceName: string): any;
10
+ getRemoteResource<T = unknown>(_resourceName: string): T;
11
+ callRemoteExport<TResult = unknown>(_resourceName: string, _exportName: string, ..._args: unknown[]): Promise<TResult>;
12
+ waitForRemoteResource<T = unknown>(_resourceName: string, _options?: {
13
+ exportName?: string;
14
+ timeoutMs?: number;
15
+ intervalMs?: number;
16
+ }): Promise<T>;
10
17
  /**
11
18
  * Get all registered exports as an object
12
19
  */
@@ -23,6 +23,15 @@ let NodeExports = class NodeExports {
23
23
  throw new Error(`Cross-resource exports not supported in Node.js runtime. ` +
24
24
  `Attempted to access resource: ${resourceName}`);
25
25
  }
26
+ getRemoteResource(_resourceName) {
27
+ throw new Error('[OpenCore] Remote exports are not supported in Node.js runtime.');
28
+ }
29
+ callRemoteExport(_resourceName, _exportName, ..._args) {
30
+ return Promise.reject(new Error('[OpenCore] Remote exports are not supported in Node.js runtime.'));
31
+ }
32
+ waitForRemoteResource(_resourceName, _options) {
33
+ return Promise.reject(new Error('[OpenCore] Remote exports are not supported in Node.js runtime.'));
34
+ }
26
35
  /**
27
36
  * Get all registered exports as an object
28
37
  */
@@ -278,13 +278,32 @@ async function tryImportAutoLoad() {
278
278
  await import('./.opencore/autoload.server.controllers');
279
279
  }
280
280
  catch (err) {
281
- if (err instanceof Error && err.message.includes('Cannot find module')) {
281
+ if (isAutoloadModuleNotFound(err)) {
282
282
  loggers.bootstrap.warn(`[Bootstrap] No server controllers autoload file found, skipping.`);
283
283
  return;
284
284
  }
285
+ const message = err instanceof Error ? err.message : String(err);
286
+ loggers.bootstrap.error(`[Bootstrap] Failed to import server controllers autoload file.`, {
287
+ error: message,
288
+ });
285
289
  throw err;
286
290
  }
287
291
  }
292
+ function isAutoloadModuleNotFound(err) {
293
+ if (!err || typeof err !== 'object') {
294
+ return false;
295
+ }
296
+ const error = err;
297
+ const message = typeof error.message === 'string' ? error.message : '';
298
+ const requireStack = Array.isArray(error.requireStack) ? error.requireStack : [];
299
+ if (error.code !== 'MODULE_NOT_FOUND' && !message.includes('Cannot find module')) {
300
+ return false;
301
+ }
302
+ if (message.includes('autoload.server.controllers')) {
303
+ return true;
304
+ }
305
+ return requireStack.some((entry) => entry.includes('autoload.server.controllers'));
306
+ }
288
307
  /**
289
308
  * Runs session recovery to restore sessions for players already connected.
290
309
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-core/framework",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "Secure, event-driven TypeScript Framework & Runtime engine for CitizenFX (Cfx).",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",