dreaction-client-core 1.2.2 → 1.4.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.
Files changed (49) hide show
  1. package/lib/core.d.ts +30 -0
  2. package/lib/core.d.ts.map +1 -0
  3. package/lib/core.js +317 -0
  4. package/lib/index.d.ts +16 -195
  5. package/lib/index.d.ts.map +1 -1
  6. package/lib/index.js +22 -461
  7. package/lib/plugins/api-response.d.ts +15 -4
  8. package/lib/plugins/api-response.d.ts.map +1 -1
  9. package/lib/plugins/api-response.js +16 -4
  10. package/lib/plugins/benchmark.d.ts +1 -1
  11. package/lib/plugins/benchmark.d.ts.map +1 -1
  12. package/lib/plugins/clear.d.ts +1 -1
  13. package/lib/plugins/clear.d.ts.map +1 -1
  14. package/lib/plugins/image.d.ts +1 -1
  15. package/lib/plugins/image.d.ts.map +1 -1
  16. package/lib/plugins/index.d.ts +52 -0
  17. package/lib/plugins/index.d.ts.map +1 -0
  18. package/lib/plugins/index.js +36 -0
  19. package/lib/plugins/issue.d.ts +19 -0
  20. package/lib/plugins/issue.d.ts.map +1 -0
  21. package/lib/plugins/issue.js +20 -0
  22. package/lib/plugins/logger.d.ts +20 -5
  23. package/lib/plugins/logger.d.ts.map +1 -1
  24. package/lib/plugins/logger.js +8 -26
  25. package/lib/plugins/repl.d.ts +1 -1
  26. package/lib/plugins/repl.d.ts.map +1 -1
  27. package/lib/plugins/state-responses.d.ts +17 -5
  28. package/lib/plugins/state-responses.d.ts.map +1 -1
  29. package/lib/plugins/state-responses.js +10 -19
  30. package/lib/types.d.ts +73 -0
  31. package/lib/types.d.ts.map +1 -0
  32. package/lib/types.js +3 -0
  33. package/lib/utils/plugin-guard.d.ts +12 -0
  34. package/lib/utils/plugin-guard.d.ts.map +1 -0
  35. package/lib/utils/plugin-guard.js +19 -0
  36. package/package.json +2 -2
  37. package/src/core.ts +428 -0
  38. package/src/index.ts +38 -720
  39. package/src/plugins/api-response.ts +39 -6
  40. package/src/plugins/benchmark.ts +1 -1
  41. package/src/plugins/clear.ts +1 -1
  42. package/src/plugins/image.ts +1 -1
  43. package/src/plugins/index.ts +26 -0
  44. package/src/plugins/issue.ts +25 -0
  45. package/src/plugins/logger.ts +17 -39
  46. package/src/plugins/state-responses.ts +19 -29
  47. package/src/types.ts +127 -0
  48. package/src/utils/plugin-guard.ts +31 -0
  49. package/src/plugins/repl.ts +0 -63
package/src/index.ts CHANGED
@@ -1,728 +1,46 @@
1
- import WebSocket from 'ws';
2
- import type {
3
- Command,
4
- CommandMap,
5
- CommandTypeKey,
6
- CustomCommandArg,
7
- CustomCommandRegisterPayload,
8
- } from 'dreaction-protocol';
9
- import validate from './validate';
10
- import logger from './plugins/logger';
11
- import image from './plugins/image';
12
- import benchmark from './plugins/benchmark';
13
- import stateResponses from './plugins/state-responses';
14
- import apiResponse from './plugins/api-response';
15
- import clear from './plugins/clear';
16
- import repl from './plugins/repl';
17
- import serialize from './serialize';
18
- import { start } from './stopwatch';
19
- import { ClientOptions } from './client-options';
20
-
21
- export type { ClientOptions };
22
- export { assertHasLoggerPlugin } from './plugins/logger';
23
- export type { LoggerPlugin } from './plugins/logger';
1
+ // Types
2
+ export type { ClientOptions } from './client-options';
3
+ export type {
4
+ CustomCommand,
5
+ CustomCommandArgs,
6
+ DisplayConfig,
7
+ DReactionCore,
8
+ InferFeatures,
9
+ InferFeaturesFromPlugins,
10
+ LifeCycleMethods,
11
+ Plugin,
12
+ PluginCreator,
13
+ } from './types';
14
+
15
+ // Core
16
+ export { DReactionImpl, createClient } from './core';
17
+
18
+ // Plugins
19
+ export { corePlugins } from './plugins';
20
+ export { assertHasLoggerPlugin, hasLoggerPlugin } from './plugins/logger';
21
+ export type { LoggerPlugin, LoggerFeatures } from './plugins/logger';
24
22
  export {
25
23
  assertHasStateResponsePlugin,
26
24
  hasStateResponsePlugin,
27
25
  } from './plugins/state-responses';
28
- export type { StateResponsePlugin } from './plugins/state-responses';
29
- export { runFPSMeter } from './utils/fps';
30
-
31
- // #region Plugin Types
32
- export interface LifeCycleMethods {
33
- onCommand?: (command: Command) => void;
34
- onConnect?: () => void;
35
- onDisconnect?: () => void;
36
- }
37
-
38
- type AnyFunction = (...args: any[]) => any;
39
- export interface Plugin<Client> extends LifeCycleMethods {
40
- features?: {
41
- [key: string]: AnyFunction;
42
- };
43
- onPlugin?: (client: Client) => void;
44
- }
45
-
46
- export type PluginCreator<Client> = (client: Client) => Plugin<Client>;
47
-
48
- interface DisplayConfig {
49
- name: string;
50
- value?: object | string | number | boolean | null | undefined;
51
- preview?: string;
52
- image?: string | { uri: string };
53
- important?: boolean;
54
- }
55
-
56
- interface ArgTypeMap {
57
- string: string;
58
- }
59
-
60
- type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
61
- k: infer I
62
- ) => void
63
- ? I
64
- : never;
65
-
66
- export type CustomCommandArgs<Args extends CustomCommandArg[]> =
67
- UnionToIntersection<
68
- Args extends Array<infer U>
69
- ? U extends CustomCommandArg
70
- ? { [K in U as U['name']]: ArgTypeMap[U['type']] }
71
- : never
72
- : never
73
- >;
74
-
75
- export interface CustomCommand<
76
- Args extends CustomCommandArg[] = CustomCommandArg[]
77
- > extends Omit<CustomCommandRegisterPayload, 'id' | 'args'> {
78
- id?: number;
79
- handler: (args: CustomCommandArgs<Args>) => any | Promise<any>;
80
- args?: Args;
81
- }
82
-
83
- type ExtractFeatures<T> = T extends { features: infer U } ? U : never;
84
- type PluginFeatures<Client, P extends PluginCreator<Client>> = ExtractFeatures<
85
- ReturnType<P>
86
- >;
87
-
88
- export type InferFeaturesFromPlugins<
89
- Client,
90
- Plugins extends PluginCreator<Client>[]
91
- > = UnionToIntersection<PluginFeatures<Client, Plugins[number]>>;
92
-
93
- type InferFeaturesFromPlugin<
94
- Client,
95
- P extends PluginCreator<Client>
96
- > = UnionToIntersection<PluginFeatures<Client, P>>;
97
-
98
- export interface DReactionCore {
99
- connected: boolean;
100
- isReady: boolean;
101
- options: ClientOptions<this>;
102
- plugins: Plugin<this>[];
103
- startTimer: () => () => number;
104
- close: () => this;
105
- send: <Type extends keyof CommandMap>(
106
- type: Type,
107
- payload?: CommandMap[Type],
108
- important?: boolean
109
- ) => void;
110
- display: (config: DisplayConfig) => void;
111
- registerCustomCommand: <
112
- Args extends CustomCommandArg[] = Exclude<CustomCommand['args'], undefined>
113
- >(
114
- config: CustomCommand<Args>
115
- ) => () => void | ((config: string, optHandler?: () => void) => () => void);
116
- /**
117
- * Set the configuration options.
118
- */
119
- configure: (
120
- options?: ClientOptions<this>
121
- ) => ClientOptions<this>['plugins'] extends PluginCreator<this>[]
122
- ? this & InferFeaturesFromPlugins<this, ClientOptions<this>['plugins']>
123
- : this;
124
-
125
- use: <P extends PluginCreator<this>>(
126
- pluginCreator: P
127
- ) => this & InferFeaturesFromPlugin<this, P>;
128
-
129
- connect: () => this;
130
-
131
- /**
132
- * Wait for connection to be established
133
- */
134
- waitForConnect: () => Promise<void>;
135
- }
136
-
137
- export type InferFeatures<
138
- Client = DReactionCore,
139
- PC extends PluginCreator<Client> = PluginCreator<Client>
140
- > = PC extends (client: Client) => { features: infer U } ? U : never;
141
-
142
- export const corePlugins = [
143
- image(),
144
- logger(),
145
- benchmark(),
146
- stateResponses(),
147
- apiResponse(),
148
- clear(),
149
- repl(),
150
- ] satisfies PluginCreator<DReactionCore>[];
151
-
152
- export type InferPluginsFromCreators<
153
- Client,
154
- PC extends PluginCreator<Client>[]
155
- > = PC extends Array<infer P extends PluginCreator<Client>>
156
- ? ReturnType<P>[]
157
- : never;
158
- // #endregion
159
-
160
- export type CorePluginFeatures = InferFeaturesFromPlugins<
161
- DReactionCore,
162
- typeof corePlugins
163
- >;
164
-
165
- export interface DReaction extends DReactionCore, CorePluginFeatures {}
166
-
167
- // these are not for you.
168
- const reservedFeatures = [
169
- 'configure',
170
- 'connect',
171
- 'connected',
172
- 'options',
173
- 'plugins',
174
- 'send',
175
- 'socket',
176
- 'startTimer',
177
- 'use',
178
- ] as const;
179
- type ReservedKeys = (typeof reservedFeatures)[number];
180
- const isReservedFeature = (value: string): value is ReservedKeys =>
181
- reservedFeatures.some((res) => res === value);
182
-
183
- function emptyPromise() {
184
- return Promise.resolve('');
185
- }
186
-
187
- export class DReactionImpl
188
- implements
189
- Omit<
190
- DReactionCore,
191
- 'options' | 'plugins' | 'configure' | 'connect' | 'use' | 'close'
192
- >
193
- {
194
- // the configuration options
195
- options!: ClientOptions<DReactionCore>;
196
-
197
- /**
198
- * Are we connected to a server?
199
- */
200
- connected = false;
201
-
202
- /**
203
- * The socket we're using.
204
- */
205
- socket: WebSocket = null as never;
206
-
207
- /**
208
- * Available plugins.
209
- */
210
- plugins: Plugin<this>[] = [];
211
-
212
- /**
213
- * Messages that need to be sent.
214
- */
215
- sendQueue: string[] = [];
216
-
217
- /**
218
- * Are we ready to start communicating?
219
- */
220
- isReady = false;
221
-
222
- /**
223
- * The last time we sent a message.
224
- */
225
- lastMessageDate = new Date();
226
-
227
- /**
228
- * The registered custom commands
229
- */
230
- customCommands: CustomCommand[] = [];
231
-
232
- /**
233
- * The current ID for custom commands
234
- */
235
- customCommandLatestId = 1;
236
-
237
- /**
238
- * Promise resolvers for connection wait
239
- */
240
- private connectPromiseResolve: (() => void) | null = null;
241
- private connectPromiseReject: ((error: Error) => void) | null = null;
242
- private connectPromise: Promise<void> | null = null;
243
-
244
- /**
245
- * Starts a timer and returns a function you can call to stop it and return the elapsed time.
246
- */
247
- startTimer = () => start();
248
-
249
- /**
250
- * Set the configuration options.
251
- */
252
- configure(
253
- options: ClientOptions<this> = {}
254
- ): ClientOptions<this>['plugins'] extends PluginCreator<this>[]
255
- ? this & InferFeaturesFromPlugins<this, ClientOptions<this>['plugins']>
256
- : this {
257
- // options get merged & validated before getting set
258
- const newOptions = {
259
- createSocket: null as never,
260
- host: 'localhost',
261
- port: 9600,
262
- name: 'dreaction-core-client',
263
- secure: false,
264
- plugins: corePlugins as any,
265
- safeRecursion: true,
266
- onCommand: () => null,
267
- onConnect: () => null,
268
- onDisconnect: () => null,
269
-
270
- ...this.options,
271
- ...options,
272
- } satisfies ClientOptions<DReactionCore>;
273
-
274
- validate(newOptions);
275
- this.options = newOptions;
276
-
277
- // if we have plugins, let's add them here
278
- if (Array.isArray(this.options.plugins)) {
279
- this.options.plugins.forEach((p) => this.use(p as never));
280
- }
281
-
282
- return this as this &
283
- InferFeaturesFromPlugins<
284
- this,
285
- Exclude<ClientOptions<this>['plugins'], undefined>
286
- >;
287
- }
288
-
289
- close() {
290
- this.connected = false;
291
- this.socket && this.socket.close && this.socket.close();
292
-
293
- // Reject any pending connection promise
294
- if (this.connectPromiseReject) {
295
- this.connectPromiseReject(new Error('Connection closed'));
296
- this.connectPromiseResolve = null;
297
- this.connectPromiseReject = null;
298
- }
299
-
300
- return this;
301
- }
302
-
303
- /**
304
- * Connect to the DReaction server.
305
- */
306
- connect() {
307
- this.connected = true;
308
-
309
- // Create a new promise for this connection attempt
310
- this.connectPromise = new Promise<void>((resolve, reject) => {
311
- this.connectPromiseResolve = resolve;
312
- this.connectPromiseReject = reject;
313
- });
314
-
315
- const {
316
- createSocket,
317
- secure,
318
- host,
319
- environment,
320
- port,
321
- name,
322
- client = {},
323
- info = {},
324
- getClientId,
325
- } = this.options;
326
- const { onCommand, onConnect, onDisconnect } = this.options;
327
-
328
- if (!host) {
329
- console.log('host is not config, skip connect.');
330
- if (this.connectPromiseReject) {
331
- this.connectPromiseReject(new Error('Host is not configured'));
332
- this.connectPromiseResolve = null;
333
- this.connectPromiseReject = null;
334
- }
335
- return this;
336
- }
337
-
338
- // establish a connection to the server
339
- const protocol = secure ? 'wss' : 'ws';
340
- const socket = createSocket!(`${protocol}://${host}:${port}`);
341
-
342
- // fires when we talk to the server
343
- const onOpen = () => {
344
- // fire our optional onConnect handler
345
- onConnect && onConnect();
346
-
347
- // trigger our plugins onConnect
348
- this.plugins.forEach((p) => p.onConnect && p.onConnect());
349
-
350
- // Resolve the connection promise
351
- if (this.connectPromiseResolve) {
352
- this.connectPromiseResolve();
353
- this.connectPromiseResolve = null;
354
- this.connectPromiseReject = null;
355
- }
356
-
357
- const getClientIdPromise = getClientId || emptyPromise;
358
- getClientIdPromise(name!).then((clientId) => {
359
- this.isReady = true;
360
- // introduce ourselves
361
- this.send('client.intro', {
362
- environment,
363
- ...client,
364
- ...info,
365
- name,
366
- clientId,
367
- dreactionCoreClientVersion: 'DREACTION_CORE_CLIENT_VERSION',
368
- });
369
-
370
- // flush the send queue
371
- while (this.sendQueue.length > 0) {
372
- const h = this.sendQueue[0];
373
- this.sendQueue = this.sendQueue.slice(1);
374
- this.socket.send(h);
375
- }
376
- });
377
- };
378
-
379
- // fires when we disconnect
380
- const onClose = () => {
381
- this.isReady = false;
382
-
383
- // Reject connection promise if still pending
384
- if (this.connectPromiseReject) {
385
- this.connectPromiseReject(new Error('Connection failed or closed'));
386
- this.connectPromiseResolve = null;
387
- this.connectPromiseReject = null;
388
- }
389
-
390
- // trigger our disconnect handler
391
- onDisconnect && onDisconnect();
392
-
393
- // as well as the plugin's onDisconnect
394
- this.plugins.forEach((p) => p.onDisconnect && p.onDisconnect());
395
- };
396
-
397
- const decodeCommandData = (data: unknown) => {
398
- if (typeof data === 'string') {
399
- return JSON.parse(data);
400
- }
401
-
402
- if (Buffer.isBuffer(data)) {
403
- return JSON.parse(data.toString());
404
- }
405
-
406
- return data;
407
- };
408
-
409
- // fires when we receive a command, just forward it off
410
- const onMessage = (data: any) => {
411
- const command = decodeCommandData(data);
412
- // trigger our own command handler
413
- onCommand && onCommand(command);
414
-
415
- // trigger our plugins onCommand
416
- this.plugins.forEach((p) => p.onCommand && p.onCommand(command));
417
-
418
- // trigger our registered custom commands
419
- if (command.type === 'custom') {
420
- this.customCommands
421
- .filter((cc) => {
422
- if (typeof command.payload === 'string') {
423
- return cc.command === command.payload;
424
- }
425
-
426
- return cc.command === command.payload.command;
427
- })
428
- .forEach(async (cc) => {
429
- const res = await cc.handler(
430
- typeof command.payload === 'object' ? command.payload.args : {}
431
- );
432
-
433
- if (res) {
434
- // has return value
435
- this.send('customCommand.response', {
436
- command: cc.command,
437
- payload: res,
438
- });
439
- }
440
- });
441
- } else if (command.type === 'setClientId') {
442
- this.options.setClientId && this.options.setClientId(command.payload);
443
- }
444
- };
445
-
446
- // this is ws style from require('ws') on node js
447
- if ('on' in socket && socket.on!) {
448
- const nodeWebSocket = socket as WebSocket;
449
- nodeWebSocket.on('open', onOpen);
450
- nodeWebSocket.on('close', onClose);
451
- nodeWebSocket.on('message', onMessage);
452
- // assign the socket to the instance
453
- this.socket = socket;
454
- } else {
455
- // this is a browser
456
- const browserWebSocket = socket as WebSocket;
457
- socket.onopen = onOpen;
458
- socket.onclose = onClose;
459
- socket.onmessage = (evt: WebSocket.MessageEvent) => onMessage(evt.data);
460
- // assign the socket to the instance
461
- this.socket = browserWebSocket;
462
- }
463
-
464
- return this;
465
- }
466
-
467
- /**
468
- * Sends a command to the server
469
- */
470
- send = <Type extends CommandTypeKey>(
471
- type: Type,
472
- payload?: CommandMap[Type]['payload'],
473
- important?: boolean
474
- ) => {
475
- // set the timing info
476
- const date = new Date();
477
- let deltaTime = date.getTime() - this.lastMessageDate.getTime();
478
- // glitches in the matrix
479
- if (deltaTime < 0) {
480
- deltaTime = 0;
481
- }
482
- this.lastMessageDate = date;
483
-
484
- const fullMessage = {
485
- type,
486
- payload,
487
- important: !!important,
488
- date: date.toISOString(),
489
- deltaTime,
490
- };
491
-
492
- const serializedMessage = serialize(fullMessage, this.options.proxyHack);
493
-
494
- if (this.isReady) {
495
- // send this command
496
- try {
497
- this.socket.send(serializedMessage);
498
- } catch {
499
- this.isReady = false;
500
- console.log(
501
- 'An error occurred communicating with dreaction. Please reload your app'
502
- );
503
- }
504
- } else {
505
- // queue it up until we can connect
506
- this.sendQueue.push(serializedMessage);
507
- }
508
- };
509
-
510
- /**
511
- * Sends a custom command to the server to displays nicely.
512
- */
513
- display(config: DisplayConfig) {
514
- const { name, value, preview, image: img, important = false } = config;
515
- const payload = {
516
- name,
517
- value: value || null,
518
- preview: preview || null,
519
- image: img || null,
520
- };
521
- this.send('display', payload, important);
522
- }
523
-
524
- /**
525
- * Client libraries can hijack this to report errors.
526
- */
527
- reportError(this: any, error: Error) {
528
- this.error(error);
529
- }
530
-
531
- /**
532
- * Adds a plugin to the system
533
- */
534
- use(
535
- pluginCreator: PluginCreator<this>
536
- ): this & PluginFeatures<this, typeof pluginCreator> {
537
- // we're supposed to be given a function
538
- if (typeof pluginCreator !== 'function') {
539
- throw new Error('plugins must be a function');
540
- }
541
-
542
- // execute it immediately passing the send function
543
- const plugin = pluginCreator.bind(this)(this) as ReturnType<
544
- typeof pluginCreator
545
- >;
546
-
547
- // ensure we get an Object-like creature back
548
- if (typeof plugin !== 'object') {
549
- throw new Error('plugins must return an object');
550
- }
551
-
552
- // do we have features to mixin?
553
- if (plugin.features) {
554
- // validate
555
- if (typeof plugin.features !== 'object') {
556
- throw new Error('features must be an object');
557
- }
558
-
559
- // here's how we're going to inject these in
560
- const inject = (key: string) => {
561
- // grab the function
562
- const featureFunction = plugin.features![key];
563
-
564
- // only functions may pass
565
- if (typeof featureFunction !== 'function') {
566
- throw new Error(`feature ${key} is not a function`);
567
- }
568
-
569
- // ditch reserved names
570
- if (isReservedFeature(key)) {
571
- throw new Error(`feature ${key} is a reserved name`);
572
- }
573
-
574
- // ok, let's glue it up... and lose all respect from elite JS champions.
575
- (this as any)[key] = featureFunction;
576
- };
577
-
578
- // let's inject
579
- Object.keys(plugin.features).forEach((key) => inject(key));
580
- }
581
-
582
- // add it to the list
583
- this.plugins.push(plugin);
584
-
585
- // call the plugins onPlugin
586
- plugin.onPlugin &&
587
- typeof plugin.onPlugin === 'function' &&
588
- plugin.onPlugin.bind(this)(this);
589
-
590
- // chain-friendly
591
- return this as this & PluginFeatures<this, typeof pluginCreator>;
592
- }
593
-
594
- registerCustomCommand(
595
- config: CustomCommand,
596
- optHandler?: () => void
597
- ): () => void {
598
- let command: string;
599
- let handler: (args: Record<string, any>) => void;
600
- let title!: string;
601
- let description!: string;
602
- let args!: CustomCommandArg[];
603
-
604
- if (typeof config === 'string') {
605
- command = config;
606
- handler = optHandler!;
607
- } else {
608
- command = config.command;
609
- handler = config.handler;
610
-
611
- title = config.title!;
612
- description = config.description!;
613
- args = config.args!;
614
- }
615
-
616
- // Validations
617
- // Make sure there is a command
618
- if (!command) {
619
- throw new Error('A command is required');
620
- }
621
-
622
- // Make sure there is a handler
623
- if (!handler) {
624
- throw new Error(`A handler is required for command "${command}"`);
625
- }
626
-
627
- // Make sure the command doesn't already exist
628
- const existingCommands = this.customCommands.filter(
629
- (cc) => cc.command === command
630
- );
631
- if (existingCommands.length > 0) {
632
- existingCommands.forEach((command) => {
633
- this.customCommands = this.customCommands.filter(
634
- (cc) => cc.id !== command.id
635
- );
636
-
637
- this.send('customCommand.unregister', {
638
- id: command.id,
639
- command: command.command,
640
- });
641
- });
642
- }
643
-
644
- if (args) {
645
- const argNames = [] as string[];
646
-
647
- args.forEach((arg) => {
648
- if (!arg.name) {
649
- throw new Error(
650
- `A arg on the command "${command}" is missing a name`
651
- );
652
- }
653
-
654
- if (argNames.indexOf(arg.name) > -1) {
655
- throw new Error(
656
- `A arg with the name "${arg.name}" already exists in the command "${command}"`
657
- );
658
- }
659
-
660
- argNames.push(arg.name);
661
- });
662
- }
663
-
664
- // Create this command handlers object
665
- const customHandler: CustomCommand = {
666
- id: this.customCommandLatestId,
667
- command,
668
- handler,
669
- title,
670
- description,
671
- args,
672
- responseViewType: config.responseViewType,
673
- };
674
-
675
- // Increment our id counter
676
- this.customCommandLatestId += 1;
677
-
678
- // Add it to our array
679
- this.customCommands.push(customHandler);
680
-
681
- this.send('customCommand.register', {
682
- id: customHandler.id,
683
- command: customHandler.command,
684
- title: customHandler.title,
685
- description: customHandler.description,
686
- args: customHandler.args,
687
- responseViewType: customHandler.responseViewType,
688
- });
689
-
690
- return () => {
691
- this.customCommands = this.customCommands.filter(
692
- (cc) => cc.id !== customHandler.id
693
- );
694
-
695
- this.send('customCommand.unregister', {
696
- id: customHandler.id,
697
- command: customHandler.command,
698
- });
699
- };
700
- }
26
+ export type {
27
+ StateResponsePlugin,
28
+ StateResponseFeatures,
29
+ } from './plugins/state-responses';
30
+ export { assertHasIssuePlugin, hasIssuePlugin } from './plugins/issue';
31
+ export type { IssuePlugin, IssueFeatures } from './plugins/issue';
32
+ export { generateRequestId } from './plugins/api-response';
33
+ export type { ApiResponsePlugin, ApiResponseFeatures } from './plugins/api-response';
701
34
 
702
- /**
703
- * Wait for connection to be established.
704
- * Returns a promise that resolves when the connection is ready.
705
- */
706
- waitForConnect(): Promise<void> {
707
- if (this.isReady) {
708
- // Already connected, resolve immediately
709
- return Promise.resolve();
710
- }
35
+ // Utils
36
+ export { runFPSMeter } from './utils/fps';
37
+ export { createPluginGuard } from './utils/plugin-guard';
711
38
 
712
- if (this.connectPromise) {
713
- // Return existing promise
714
- return this.connectPromise;
715
- }
39
+ // Derived Types
40
+ import type { InferFeatures } from './types';
41
+ import type { corePlugins } from './plugins';
716
42
 
717
- // No connection attempt in progress
718
- return Promise.reject(new Error('Not connected. Call connect() first.'));
719
- }
720
- }
43
+ export type CorePluginFeatures = InferFeatures<typeof corePlugins>;
721
44
 
722
- // convenience factory function
723
- export function createClient<Client extends DReactionCore = DReactionCore>(
724
- options?: ClientOptions<Client>
725
- ) {
726
- const client = new DReactionImpl();
727
- return client.configure(options as never) as unknown as Client;
728
- }
45
+ import type { DReactionCore } from './types';
46
+ export interface DReaction extends DReactionCore, CorePluginFeatures { }