@feasibleone/blong 1.16.1 → 1.18.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.
package/types.ts CHANGED
@@ -24,12 +24,15 @@ import {
24
24
  type TUnknown,
25
25
  } from 'typebox';
26
26
  // import type {client} from 'node-vault';
27
- import type {Dirent} from 'node:fs';
27
+ import type {ChokidarOptions, FSWatcherEventMap} from 'chokidar';
28
+ import type {EventEmitter} from 'events';
29
+ import type {Dirent, StatSyncFn} from 'node:fs';
28
30
  import type {Duplex} from 'node:stream';
29
31
  import type {OpenAPI, OpenAPIV2, OpenAPIV3_1} from 'openapi-types';
30
32
  import type {Level, LogFn, Logger as PinoLogger} from 'pino';
31
33
  import merge from 'ut-function.merge';
32
34
  import type {Knex} from './knex.js';
35
+ import type {IMock, IModelSpec} from './model.ts';
33
36
 
34
37
  // export {
35
38
  // AppsV1Api,
@@ -45,6 +48,7 @@ export type * from 'mongodb';
45
48
  export type {IJsonSchema, OpenAPI, OpenAPIV2, OpenAPIV3, OpenAPIV3_1} from 'openapi-types';
46
49
  // export type {Level, LogFn, Logger as PinoLogger} from 'pino';
47
50
  export type {Knex} from './knex.js';
51
+ export type * from './model.ts';
48
52
 
49
53
  export type ServerContext = {
50
54
  queryBuilder?: Knex;
@@ -75,6 +79,89 @@ export interface ILog {
75
79
  child: PinoLogger['child'];
76
80
  }
77
81
 
82
+ // ---------------------------------------------------------------------------
83
+ // Types
84
+ // ---------------------------------------------------------------------------
85
+
86
+ /** A flat map of dotted-path → [prev, next] pairs that represent changed keys */
87
+ export type ConfigDiff = Map<string, {prev: unknown; next: unknown}>;
88
+
89
+ /** Subscriber callback invoked after a successful reload */
90
+ export type ConfigSubscriber = (
91
+ diff: ConfigDiff,
92
+ next: object,
93
+ prev: object,
94
+ ) => void | Promise<void>;
95
+
96
+ /**
97
+ * Mode used when the config proxy is queried during handler factory initialisation.
98
+ *
99
+ * - `'throw'` — throw immediately (default; keeps misuse from going unnoticed)
100
+ * - `'collect'` — accumulate errors and return them from exitConfigFactoryPhase()
101
+ * (useful in tests that explicitly verify the anti-pattern is caught)
102
+ */
103
+ export type FactoryPhaseMode = 'throw' | 'collect';
104
+ export interface IConfigRuntime {
105
+ /** Current effective config, exposed as a live proxy */
106
+ readonly snapshot: object;
107
+ /** Raw (non-proxy) snapshot of the current effective config */
108
+ readonly rawSnapshot: object;
109
+ /** Load (or reload) config from all sources; returns the updated snapshot */
110
+ load(params?: object): Promise<object>;
111
+ /**
112
+ * Reload config in-place. The backing store of the proxy is updated so all
113
+ * existing proxy references automatically reflect the new values.
114
+ * Returns the computed diff.
115
+ */
116
+ reload(): Promise<ConfigDiff>;
117
+ /** Compute the diff between two plain config objects without modifying state */
118
+ diff(prev: object, next: object): ConfigDiff;
119
+ /** Register a subscriber to be called after every successful reload */
120
+ subscribe(fn: ConfigSubscriber): () => void;
121
+ /** Enter the config factory phase */
122
+ enterConfig(mode?: FactoryPhaseMode): void;
123
+ /** Exit the config factory phase */
124
+ exitConfig(): Error[];
125
+ }
126
+
127
+ export interface IWatcher extends EventEmitter<FSWatcherEventMap> {
128
+ close(): Promise<void>;
129
+ }
130
+
131
+ export type HRTime = [number, number];
132
+
133
+ export interface IPlatformApi {
134
+ platform: 'server' | 'browser';
135
+ loadConfig: (
136
+ config: string | object,
137
+ ) => Promise<{loadedConfig?: object; configRuntime?: IConfigRuntime}>;
138
+ readdir: (path: string) => Promise<Dirent[]>;
139
+ scan: (...path: string[]) => Promise<Dirent[]>;
140
+ existsSync: (path: string) => boolean;
141
+ createRequire?: (path: string | URL) => NodeJS.Require;
142
+ join: (...paths: string[]) => string;
143
+ dirname: (path: string) => string;
144
+ basename: (path: string, ext?: string) => string;
145
+ relative: (from: string, to: string) => string;
146
+ extname: (path: string) => string;
147
+ resolve: (...paths: string[]) => string;
148
+ readFileSync: (path: string, options?: {encoding: BufferEncoding}) => string | Buffer;
149
+ writeFileSync: (
150
+ path: string,
151
+ data: string | Buffer,
152
+ options?: {encoding: BufferEncoding},
153
+ ) => void;
154
+ statSync: StatSyncFn;
155
+ watch?: (path: string | string[], options?: ChokidarOptions) => IWatcher;
156
+ timing: {
157
+ diff: (time: HRTime, newTime: HRTime) => number;
158
+ after: (milliseconds: number) => HRTime;
159
+ now: (previous?: HRTime) => HRTime;
160
+ isAfter: (time: HRTime, timeout: HRTime) => boolean;
161
+ spare: (time: HRTime, latency?: number) => number;
162
+ };
163
+ }
164
+
78
165
  export interface IErrorFactory {
79
166
  get(type?: string): unknown;
80
167
  fetch(type: string): object;
@@ -160,6 +247,10 @@ export interface IApiSchema {
160
247
  ): Promise<Record<string, GatewaySchema>>;
161
248
  generateFile(file: string): Promise<boolean>;
162
249
  generateDir(dir: string, files: Dirent[]): Promise<boolean>;
250
+ loadApi(
251
+ locations: string | string[] | object | object[] | {assets: object},
252
+ source: string,
253
+ ): unknown;
163
254
  }
164
255
 
165
256
  export interface IGateway {
@@ -175,28 +266,30 @@ export interface IGateway {
175
266
  export type Handlers = ((params: {
176
267
  remote: unknown;
177
268
  lib: object;
178
- port: object;
269
+ port: object | undefined;
179
270
  local: object;
180
271
  literals: object[];
181
272
  gateway: IGateway;
273
+ apiSchema: IApiSchema;
274
+ attachCheckpoint?: (meta: IMeta) => void;
182
275
  }) => void)[];
183
276
 
184
277
  export interface IRegistry {
185
- start: (configOverride?: object) => Promise<IRegistry>;
278
+ start: (configOverride: object) => Promise<IRegistry>;
186
279
  test: (tester?: unknown) => Promise<void>;
187
280
  stop: () => Promise<IRegistry>;
188
- ports: Map<string, IAdapterFactory>;
281
+ ports: Map<string, IAdapterRegistry>;
189
282
  methods: Map<string, Handlers>;
190
283
  modules: Map<string | symbol, IRegistry[]>;
191
- createPort: (id: string) => Promise<ReturnType<IAdapterFactory>>;
192
- getPort: (id: string) => ReturnType<IAdapterFactory> | undefined;
193
- replaceHandlers: (id: string, handlers: object) => Promise<void>;
284
+ createPort: (id: string) => Promise<Adapter | undefined>;
285
+ getPort: (id: string) => Adapter | undefined;
286
+ replaceHandlers: (id: string, handlers: Handlers) => Promise<void>;
194
287
  loadApi: (
195
288
  id: string,
196
289
  def: {
197
290
  namespace: Record<string, string | string[]>;
198
291
  },
199
- source?: string,
292
+ source: string,
200
293
  ) => Promise<void>;
201
294
  connected: () => Promise<boolean>;
202
295
  }
@@ -204,15 +297,7 @@ export interface IRegistry {
204
297
  export interface IApi {
205
298
  id?: string;
206
299
  type: typeof Type;
207
- adapter: (
208
- id: string,
209
- ) => (api: {
210
- utError: IError;
211
- remote: IRemote;
212
- rpc: IRpcServer;
213
- local: ILocal;
214
- registry: IRegistry;
215
- }) => object;
300
+ adapter: (id: string) => IAdapterRegistry | undefined;
216
301
  utError: IError;
217
302
  errors: IErrorFactory;
218
303
  gateway: unknown;
@@ -220,24 +305,27 @@ export interface IApi {
220
305
  rpc: IRpcServer;
221
306
  local: ILocal;
222
307
  registry: IRegistry;
223
- utBus: {
224
- config: object;
225
- register: (methods: object, namespace: string, id: string, pkg: {version: string}) => void;
226
- unregister: (methods: string[], namespace: string) => void;
227
- subscribe: (methods: object, namespace: string, id: string, pkg: {version: string}) => void;
228
- unsubscribe: (methods: string[], namespace: string) => void;
229
- dispatch: (...params: unknown[]) => boolean | Promise<unknown>;
230
- methodId: (name: string) => string;
231
- getPath: (name: string) => string;
232
- importMethod: (
233
- methodName: string,
234
- options?: object,
235
- ) => (...params: unknown[]) => Promise<unknown>;
236
- attachHandlers: (target: object, patterns: unknown, adapter?: boolean) => unknown;
237
- };
238
- utLog: {
239
- createLog: ILog['logger'];
240
- };
308
+ register: (methods: object, namespace: string, id: string, pkg: {version: string}) => void;
309
+ unregister: (methods: string[], namespace: string) => void;
310
+ subscribe: (methods: object, namespace: string, id: string, pkg: {version: string}) => void;
311
+ unsubscribe: (methods: string[], namespace: string) => void;
312
+ dispatch: (...params: unknown[]) => boolean | Promise<unknown>;
313
+ methodId: (name: string) => string;
314
+ getPath: (name: string) => string;
315
+ importMethod: (
316
+ methodName: string,
317
+ options?: object,
318
+ ) => (...params: unknown[]) => Promise<unknown>;
319
+ attachHandlers: (
320
+ target: {
321
+ importedMap?: Map<string, object>;
322
+ imported: object;
323
+ config: {namespace?: string | string[]};
324
+ },
325
+ patterns: (string | RegExp)[] | string | RegExp,
326
+ adapter?: boolean,
327
+ ) => unknown;
328
+ createLog: ILog['logger'];
241
329
  attachCheckpoint?: (meta: IMeta) => void;
242
330
  handlers?: (api: {utError: IError; remote: IRemote; type: typeof Type}) => {
243
331
  extends?:
@@ -262,6 +350,21 @@ export interface IErrorMap {
262
350
  };
263
351
  }
264
352
 
353
+ export type Adapter<T = Record<string, unknown>, C = Record<string, unknown>> = IAdapter<T, C> &
354
+ Pick<
355
+ Required<IAdapter<T, C>>,
356
+ | 'init'
357
+ | 'start'
358
+ | 'stop'
359
+ | 'ready'
360
+ | 'config'
361
+ | 'imported'
362
+ | 'errors'
363
+ | 'error'
364
+ | 'findValidation'
365
+ | 'getConversion'
366
+ | 'dispatch'
367
+ >;
265
368
  export interface IAdapter<T, C> {
266
369
  validation?: TSchema;
267
370
  config?: Config<T, C>;
@@ -269,59 +372,63 @@ export interface IAdapter<T, C> {
269
372
  configBase?: string;
270
373
  log?: ILogger;
271
374
  errors?: Errors<IErrorMap>;
272
- imported?: ReturnType<IAdapterFactory<T, C>>;
375
+ imported?: Record<string, PortHandlerBound>;
376
+ importedMap?: Map<string, IRemoteHandler>;
273
377
  extends?: object | `adapter.${string}` | `orchestrator.${string}`;
274
- activeConfig?: (this: ReturnType<IAdapterFactory<T, C>>) => Partial<Config<T, C>>;
275
- init?: (
276
- this: ReturnType<IAdapterFactory<T, C>>,
277
- ...config: Partial<Config<T, C>>[]
278
- ) => Promise<void>;
279
- start?: (this: ReturnType<IAdapterFactory<T, C>>, configOverride: object) => Promise<object>;
280
- ready?: (this: ReturnType<IAdapterFactory<T, C>>) => Promise<object>;
281
- stop?: (this: ReturnType<IAdapterFactory<T, C>>) => Promise<object>;
282
- connected?: (this: ReturnType<IAdapterFactory<T, C>>) => Promise<boolean>;
283
- error?: (error: Error, $meta: IMeta) => void;
284
- pack?: (
285
- this: ReturnType<IAdapterFactory<T, C>>,
286
- packet: {size: number; data: Buffer},
287
- ) => Buffer;
288
- unpackSize?: (
289
- this: ReturnType<IAdapterFactory<T, C>>,
290
- buffer: Buffer,
291
- ) => {size: number; data: Buffer};
292
- unpack?: (
293
- this: ReturnType<IAdapterFactory<T, C>>,
294
- buffer: Buffer,
295
- options?: {size: number},
296
- ) => Buffer;
297
- encode?: (data: unknown, $meta: IMeta, context: object, log: ILogger) => string | Buffer;
298
- decode?: (buff: string | Buffer, $meta: IMeta, context: object, log: ILogger) => object[];
299
- request?: () => Promise<unknown>;
300
- publish?: () => Promise<unknown>;
301
- drain?: () => void;
302
- findValidation?: (this: ReturnType<IAdapterFactory<T, C>>, $meta: IMeta) => () => object;
303
- getConversion?: (
304
- this: ReturnType<IAdapterFactory<T, C>>,
305
- $meta: IMeta,
306
- type: 'send' | 'receive',
307
- ) => {name: string; fn: () => object};
308
- findHandler?: (this: ReturnType<IAdapterFactory<T, C>>, name: string) => () => unknown;
309
- handles?: (this: ReturnType<IAdapterFactory<T, C>>, name: string) => boolean;
310
- forNamespaces?: <T>(reducer: (prev: T, current: unknown) => T, initial: T) => T;
311
- methodPath?: (name: string) => string;
312
- dispatch?: (...params: unknown[]) => Promise<unknown>;
313
- exec?: (this: ReturnType<IAdapterFactory<T, C>>, ...params: unknown[]) => Promise<unknown>;
314
- bytesSent?: (count: number) => void;
315
- bytesReceived?: (count: number) => void;
316
- msgSent?: (count: number) => void;
317
- msgReceived?: (count: number) => void;
378
+ activeConfig?(this: Adapter<T, C>): Partial<Config<T, C>>;
379
+ init?(this: Adapter<T, C>, ...config: unknown[]): Promise<unknown>;
380
+ start?(this: Adapter<T, C>, ...params: unknown[]): Promise<unknown>;
381
+ ready?(this: Adapter<T, C>): Promise<unknown>;
382
+ stop?(this: Adapter<T, C>, ...params: unknown[]): Promise<unknown>;
383
+ link?(
384
+ this: Adapter<T, C>,
385
+ patterns: (string | RegExp)[] | string | RegExp,
386
+ target: object,
387
+ ): Promise<{
388
+ importedMap?: Map<string, object>;
389
+ imported?: object;
390
+ config?: {namespace?: string | string[]};
391
+ }>;
392
+ connected?(this: Adapter<T, C>): Promise<boolean>;
393
+ error?(error: unknown, $meta: unknown): void;
394
+ pack?(this: Adapter<T, C>, ...params: unknown[]): unknown;
395
+ unpackSize?(this: Adapter<T, C>, ...params: unknown[]): unknown;
396
+ unpack?(this: Adapter<T, C>, ...params: unknown[]): unknown;
397
+ encode?(
398
+ data: unknown,
399
+ $meta: unknown,
400
+ context: unknown,
401
+ log: unknown,
402
+ ): Promise<string | Buffer>;
403
+ decode?(
404
+ buff: string | Buffer,
405
+ $meta: unknown,
406
+ context: unknown,
407
+ log: unknown,
408
+ ): Promise<object[]>;
409
+ request?(...params: unknown[]): Promise<unknown>;
410
+ publish?(): Promise<unknown>;
411
+ drain?(): void;
412
+ findValidation?(this: Adapter<T, C>, $meta: unknown): (...params: unknown[]) => object;
413
+ getConversion?(
414
+ this: Adapter<T, C>,
415
+ $meta: unknown,
416
+ type: string,
417
+ ): {name: string; fn: (...params: unknown[]) => Promise<object>};
418
+ findHandler?(this: Adapter<T, C>, name: string): () => unknown;
419
+ handles?(this: Adapter<T, C>, name: string): boolean;
420
+ forNamespaces?<U>(reducer: (prev: U, current: unknown) => U, initial: U): U;
421
+ methodPath?(name: string): string;
422
+ dispatch?(...params: unknown[]): Promise<unknown>;
423
+ exec?(this: Adapter<T, C>, ...params: unknown[]): Promise<unknown>;
424
+ bytesSent?(count: number): void;
425
+ bytesReceived?(count: number): void;
426
+ msgSent?(count: number): void;
427
+ msgReceived?(count: number): void;
318
428
  isConnected?: Promise<boolean>;
319
- event?: (name: string, params?: unknown) => Promise<object>;
320
- handle?: (...params: unknown[]) => Promise<unknown>;
321
- connect?: (
322
- what: unknown,
323
- context: {requests: unknown; waiting: unknown; buffer: unknown},
324
- ) => void;
429
+ event?(name: string, params?: unknown): Promise<object>;
430
+ handle?(...params: unknown[]): Promise<unknown>;
431
+ connect?(what: unknown, context: unknown): void;
325
432
  /**
326
433
  * Optional lifecycle hook called when configuration changes.
327
434
  * When present, the framework calls this instead of a full stop+start cycle.
@@ -333,11 +440,14 @@ export interface IAdapter<T, C> {
333
440
  * @param next The full new effective config snapshot (via proxy)
334
441
  * @param prev The full previous effective config snapshot
335
442
  */
336
- configChanged?: (
337
- diff: Map<string, {prev: unknown; next: unknown}>,
338
- next: object,
339
- prev: object,
340
- ) => Promise<void>;
443
+ configChanged?(
444
+ this: Adapter<T, C>,
445
+ diff: unknown,
446
+ next: unknown,
447
+ prev?: unknown,
448
+ ): Promise<void>;
449
+ /** Allow arbitrary extra methods on adapter definitions (e.g. authenticate) */
450
+ [key: string]: unknown;
341
451
  }
342
452
 
343
453
  export interface IAdapterFactory<T = Record<string, unknown>, C = Record<string, unknown>> {
@@ -345,6 +455,17 @@ export interface IAdapterFactory<T = Record<string, unknown>, C = Record<string,
345
455
  (api: IApi): IAdapter<T, C>;
346
456
  }
347
457
 
458
+ export interface IAdapterRegistry {
459
+ config: unknown;
460
+ (api: {
461
+ utError: IError;
462
+ remote: IRemote;
463
+ rpc: IRpcServer;
464
+ local: ILocal;
465
+ registry: IRegistry;
466
+ }): Promise<Adapter>;
467
+ }
468
+
348
469
  export interface IMeta {
349
470
  mtid?: 'request' | 'response' | 'error' | 'notification' | 'discard' | 'event';
350
471
  request?: IMeta;
@@ -356,7 +477,7 @@ export interface IMeta {
356
477
  expect?: string[] | string;
357
478
  opcode?: string;
358
479
  source?: string;
359
- forward?: object;
480
+ forward?: Record<string, string>;
360
481
  httpResponse?: {
361
482
  type?: string;
362
483
  redirect?: string;
@@ -402,16 +523,16 @@ export interface IMeta {
402
523
  deviceId?: string | string[];
403
524
  latitude?: string | string[];
404
525
  longitude?: string | string[];
405
- conId?: number;
526
+ conId?: string | number;
406
527
  dispatch?: (
407
528
  msg?: object,
408
529
  $meta?: IMeta,
409
530
  ) => [msg: object, $meta: IMeta] | boolean | void | Promise<boolean>;
410
531
  reply?: unknown;
411
- timeout?: number;
532
+ timeout?: HRTime;
412
533
  timer?: (
413
534
  name?: string,
414
- newTime?: HRTime | false,
535
+ newTime?: HRTime | undefined,
415
536
  ) => {
416
537
  [name: string]: number;
417
538
  };
@@ -422,19 +543,18 @@ export interface IMeta {
422
543
  checkpoints?: Array<{name: string; data?: unknown; timestamp: number}>;
423
544
  }
424
545
 
425
- export type HRTime = [number, number];
426
-
427
546
  export interface IContext {
428
- trace: number;
547
+ // trace: number;
429
548
  session?: {
430
549
  [name: string]: unknown;
431
550
  };
432
- conId?: string;
551
+ conId?: string | number;
433
552
  requests: Map<
434
553
  string,
435
- {$meta: IMeta; end: (error: Error) => {local: object; literals: object[]}}
554
+ {$meta: IMeta; end?: (error: Error) => {local: object; literals: object[]}}
436
555
  >;
437
556
  waiting: Set<(error: Error) => void>;
557
+ buffer?: Buffer;
438
558
  }
439
559
 
440
560
  export interface ITypedError extends Error {
@@ -492,7 +612,11 @@ export interface IModuleConfig<T extends TSchema = TNever> {
492
612
  url: string;
493
613
  config?: IActivationConfig<Partial<Static<T>> & Partial<Static<IBaseConfig>>>;
494
614
  validation?: T;
495
- children?: (string | (() => Promise<object>))[] | ((layer: ModuleApi) => unknown)[];
615
+ children?:
616
+ | (string | (() => Promise<object>))[]
617
+ | ((layer: ModuleApi) => unknown)[]
618
+ | Record<string, () => Promise<unknown>>;
619
+ glob?: Record<string, () => Promise<object>>;
496
620
  }
497
621
 
498
622
  export interface ILogger {
@@ -567,9 +691,16 @@ export interface ILib {
567
691
  /** @deprecated The framework now auto-names step arrays from handler names. */
568
692
  group: (name: string) => (handlers: ChainStep[]) => ChainStep[] & {name: string};
569
693
  assert: typeof Assert | undefined;
694
+ yaml: {
695
+ parse: <T>(source: string, options?: unknown) => T;
696
+ parseAllDocuments: <T>(source: string, options?: unknown) => T;
697
+ parseDocument: <T>(source: string, options?: unknown) => T;
698
+ stringify: (value: unknown, options?: unknown) => string;
699
+ };
570
700
  ulid: () => string;
571
701
  uuid4: () => string;
572
702
  uuid7: () => string;
703
+ timing: IPlatformApi['timing'];
573
704
  setProperty: (obj: Record<string, unknown>, path: string, value: unknown) => void;
574
705
  merge<T, S1>(target: T, source: S1): T & S1;
575
706
  merge<T, S1, S2>(target: T, source1: S1, source2: S2): T & S1 & S2;
@@ -608,17 +739,24 @@ export type ApiDefinition = (blong: IValidationProxy) =>
608
739
  };
609
740
 
610
741
  export type PortHandler<T, C> = <R>(
611
- this: ReturnType<IAdapterFactory<T, C>>,
612
- params: {},
742
+ this: Adapter<T, C>,
743
+ params: object,
613
744
  $meta: IMeta,
614
745
  context?: IContext,
615
746
  ) => Promise<R> | R;
616
- export type PortHandlerBound = <T>(params: {}, $meta: IMeta, context?: IContext) => Promise<T> | T;
747
+ export type PortHandlerBound = (<T>(
748
+ params: object,
749
+ $meta: IMeta,
750
+ context?: IContext,
751
+ ) => Promise<T> | T) & {
752
+ [name: string]: PortHandlerBound;
753
+ };
617
754
  export type LibFn = <T>(...params: unknown[]) => T;
618
755
  export interface IRemoteHandler {
619
756
  [name: string]: PortHandlerBound;
620
757
  }
621
- export interface ISchema {}
758
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
759
+ export interface ISchema {} // this is being extended via ambient declarations in ~.schema.ts
622
760
  export interface IHandlerProxy<T> {
623
761
  config: T;
624
762
  handler: {
@@ -639,6 +777,7 @@ export interface IHandlerProxy<T> {
639
777
  gateway: {
640
778
  config: () => {public: {sign: object; encrypt: object}};
641
779
  };
780
+ apiSchema: IApiSchema;
642
781
  }
643
782
 
644
783
  export type ImportProxyCallback<T, C> = (
@@ -674,9 +813,9 @@ const Kind: symbol = Symbol.for('blong:kind');
674
813
  export type Kind = typeof Kind;
675
814
 
676
815
  export abstract class Internal {
677
- #log?: ILog;
816
+ #log?: ILog | undefined;
678
817
  protected log?: ReturnType<ILog['logger']>;
679
- public constructor(api?: {log: ILog}) {
818
+ public constructor(api?: {log?: ILog}) {
680
819
  this.#log = api?.log;
681
820
  }
682
821
  protected merge: ILib['merge'] = (...args: Parameters<ILib['merge']>) => {
@@ -688,7 +827,8 @@ export abstract class Internal {
688
827
  public async stop(): Promise<unknown> {
689
828
  return this;
690
829
  }
691
- public async start(...params: unknown[]): Promise<unknown> {
830
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
831
+ public async start(..._args: unknown[]): Promise<unknown> {
692
832
  return this;
693
833
  }
694
834
  }
@@ -705,23 +845,13 @@ export const handler = <T = Record<string, unknown>, C = AdapterContext>(
705
845
  * The inner function should return a map whose keys are dot-notation method names
706
846
  * (e.g. `'coral.browse'`) and values are async functions that return component
707
847
  * metadata `{title, permission, icon, component: async () => ReactComponent}`.
708
- *
709
- * @example
710
- * ```ts
711
- * export default componentHandler(blong => function coralBrowse() {
712
- * return {
713
- * 'coral.browse': async () => ({
714
- * title: 'Browse Corals',
715
- * permission: 'marine.coral.browse',
716
- * component: async () => (await import('./CoralBrowse.js')).default,
717
- * }),
718
- * };
719
- * });
720
- * ```
721
848
  */
722
- export const componentHandler = <T = Record<string, unknown>, C = AdapterContext>(
723
- definition: Definition<T, C>,
724
- ): Definition<T, C> => Object.defineProperty(definition, Kind, {value: 'handler'});
849
+ export interface IComponent {
850
+ title?: string;
851
+ permission?: string;
852
+ icon?: string;
853
+ component: (params?: Record<string, unknown>) => Promise<unknown>;
854
+ }
725
855
 
726
856
  /** Action definition for use with `defineActions`. */
727
857
  export interface IActionDef {
@@ -737,7 +867,9 @@ export interface IActionDef {
737
867
  /** Action names whose query caches should be invalidated on success. */
738
868
  invalidates?: string[];
739
869
  /** Static params merged into every invocation. */
740
- params?: Record<string, unknown> | ((params: Record<string, unknown>) => Record<string, unknown>);
870
+ params?:
871
+ | Record<string, unknown>
872
+ | ((params: Record<string, unknown>) => Record<string, unknown>);
741
873
  }
742
874
 
743
875
  /**
@@ -763,10 +895,7 @@ export const defineActions = (
763
895
  actions: Record<string, IActionDef>,
764
896
  ): ((_blong: unknown) => Record<string, () => IActionDef>) =>
765
897
  Object.defineProperty(
766
- (_blong: unknown) =>
767
- Object.fromEntries(
768
- Object.entries(actions).map(([key, value]) => [key, () => value]),
769
- ),
898
+ () => Object.fromEntries(Object.entries(actions).map(([key, value]) => [key, () => value])),
770
899
  Kind,
771
900
  {value: 'handler'},
772
901
  );
@@ -777,6 +906,11 @@ export const validation = (validation: ValidationDefinition): ValidationDefiniti
777
906
  Object.defineProperty(validation, Kind, {value: 'validation'});
778
907
  export const api = (api: ApiDefinition): ApiDefinition =>
779
908
  Object.defineProperty(api, Kind, {value: 'api'});
909
+ export const model = <T extends IModelSpec>(
910
+ definition: () => () => Promise<T>,
911
+ ): (() => () => Promise<T>) => Object.defineProperty(definition, Kind, {value: 'model'});
912
+ export const fixture = <T extends IMock>(definition: () => T): (() => T) =>
913
+ Object.defineProperty(definition, Kind, {value: 'fixture'});
780
914
 
781
915
  export const validationHandlers: (
782
916
  handlers: Record<string, TFunction<[ApiSchema]>>,
@@ -798,21 +932,41 @@ export const validationHandlers: (
798
932
  ),
799
933
  );
800
934
 
801
- export const realm = <T extends TObject>(definition: SolutionFactory<T>): SolutionFactory<T> =>
802
- Object.defineProperty(definition, Kind, {value: 'solution'});
803
- export const server = <T extends TObject>(definition: SolutionFactory<T>): SolutionFactory<T> =>
804
- Object.defineProperty(definition, Kind, {value: 'server'});
805
- export const browser = <T extends TObject>(definition: SolutionFactory<T>): SolutionFactory<T> =>
806
- Object.defineProperty(definition, Kind, {value: 'browser'});
935
+ export const realm = <T extends TObject>(
936
+ definition: SolutionFactory<T>,
937
+ ): SolutionFactory<T> & {[Kind]: 'solution'} =>
938
+ Object.defineProperty(definition as SolutionFactory<T> & {[Kind]: 'solution'}, Kind, {
939
+ value: 'solution',
940
+ });
941
+ export const server = <T extends TObject>(
942
+ definition: SolutionFactory<T>,
943
+ ): SolutionFactory<T> & {[Kind]: 'server'} =>
944
+ Object.defineProperty(definition as SolutionFactory<T> & {[Kind]: 'server'}, Kind, {
945
+ value: 'server',
946
+ });
947
+ export const browser = <T extends TObject>(
948
+ definition: SolutionFactory<T>,
949
+ ): SolutionFactory<T> & {[Kind]: 'browser'} =>
950
+ Object.defineProperty(definition as SolutionFactory<T> & {[Kind]: 'browser'}, Kind, {
951
+ value: 'browser',
952
+ });
807
953
  export const layer = (
808
954
  activation: Record<string, boolean | object>,
809
- ): Record<string, boolean | object> => Object.defineProperty(activation, Kind, {value: 'layer'});
955
+ ): Record<string, boolean | object> & {[Kind]: 'layer'} =>
956
+ Object.defineProperty(activation, Kind, {value: 'layer'});
810
957
  export const adapter = <T, C = AdapterContext>(
811
958
  definition: IAdapterFactory<T, C>,
812
- ): IAdapterFactory<T, C> => Object.defineProperty(definition, Kind, {value: 'adapter'});
959
+ ): IAdapterFactory<T, C> & {[Kind]: 'adapter'} =>
960
+ Object.defineProperty(definition as IAdapterFactory<T, C> & {[Kind]: 'adapter'}, Kind, {
961
+ value: 'adapter',
962
+ });
813
963
  export const orchestrator = <T, C = AdapterContext>(
814
964
  definition: IAdapterFactory<T, C>,
815
- ): IAdapterFactory<T, C> => Object.defineProperty(definition, Kind, {value: 'orchestrator'});
965
+ ): IAdapterFactory<T, C> & {[Kind]: 'orchestrator'} =>
966
+ Object.defineProperty(definition as IAdapterFactory<T, C> & {[Kind]: 'orchestrator'}, Kind, {
967
+ value: 'orchestrator',
968
+ });
969
+
816
970
  export type Kinds =
817
971
  | 'lib'
818
972
  | 'validation'
@@ -822,12 +976,14 @@ export type Kinds =
822
976
  | 'browser'
823
977
  | 'adapter'
824
978
  | 'orchestrator'
825
- | 'handler';
826
- export const kind = (what: {[Kind]: Kinds | undefined}): Kinds | undefined => what[Kind];
979
+ | 'handler'
980
+ | 'model'
981
+ | 'fixture'
982
+ | '';
983
+ export const kind = (what: {[Kind]: Kinds | undefined}): Kinds => what[Kind] || '';
827
984
 
828
985
  export default {
829
986
  handler,
830
- componentHandler,
831
987
  defineActions,
832
988
  library,
833
989
  validation,
@@ -837,5 +993,6 @@ export default {
837
993
  browser,
838
994
  adapter,
839
995
  orchestrator,
996
+ fixture,
840
997
  kind,
841
998
  };