@hyperfrontend/features 0.1.0 → 0.2.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 (159) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/_dependencies/@hyperfrontend/builder/bundle/dependencies/index.cjs.js +1 -0
  3. package/_dependencies/@hyperfrontend/builder/bundle/dependencies/index.esm.js +1 -0
  4. package/_dependencies/@hyperfrontend/builder/bundle/dependencies/worker/index.cjs.js +1 -0
  5. package/_dependencies/@hyperfrontend/builder/bundle/dependencies/worker/index.esm.js +1 -0
  6. package/_dependencies/@hyperfrontend/builder/bundle/index.cjs.js +12 -10
  7. package/_dependencies/@hyperfrontend/builder/bundle/index.esm.js +14 -12
  8. package/_dependencies/@hyperfrontend/builder/bundle/rollup/index.cjs.js +2 -0
  9. package/_dependencies/@hyperfrontend/builder/bundle/rollup/index.esm.js +2 -0
  10. package/_dependencies/@hyperfrontend/builder/bundle/rollup/worker/index.cjs.js +2 -0
  11. package/_dependencies/@hyperfrontend/builder/bundle/rollup/worker/index.esm.js +2 -0
  12. package/_dependencies/@hyperfrontend/builder/index.cjs.js +87 -53
  13. package/_dependencies/@hyperfrontend/builder/index.esm.js +89 -55
  14. package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/promise/index.cjs.js +4 -0
  15. package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/promise/index.esm.js +3 -1
  16. package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/reflect/index.cjs.js +10 -0
  17. package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/reflect/index.esm.js +6 -0
  18. package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/timers/index.cjs.js +5 -0
  19. package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/timers/index.esm.js +5 -1
  20. package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/typed-arrays/index.cjs.js +2 -2
  21. package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/typed-arrays/index.esm.js +2 -2
  22. package/_dependencies/@hyperfrontend/network-protocol/browser/channel/index.cjs.js +5 -19
  23. package/_dependencies/@hyperfrontend/network-protocol/browser/channel/index.esm.js +1 -15
  24. package/_dependencies/@hyperfrontend/network-protocol/browser/data/index.cjs.js +15 -23
  25. package/_dependencies/@hyperfrontend/network-protocol/browser/data/index.esm.js +7 -15
  26. package/_dependencies/@hyperfrontend/network-protocol/browser/packet/index.cjs.js +6 -14
  27. package/_dependencies/@hyperfrontend/network-protocol/browser/packet/index.esm.js +7 -15
  28. package/_dependencies/@hyperfrontend/network-protocol/browser/receiver/index.cjs.js +4 -18
  29. package/_dependencies/@hyperfrontend/network-protocol/browser/receiver/index.esm.js +1 -15
  30. package/_dependencies/@hyperfrontend/network-protocol/browser/sender/index.cjs.js +5 -19
  31. package/_dependencies/@hyperfrontend/network-protocol/browser/sender/index.esm.js +2 -16
  32. package/_dependencies/@hyperfrontend/network-protocol/browser/v1/index.cjs.js +16 -24
  33. package/_dependencies/@hyperfrontend/network-protocol/browser/v1/index.esm.js +7 -15
  34. package/_dependencies/@hyperfrontend/network-protocol/browser/v2/index.cjs.js +16 -24
  35. package/_dependencies/@hyperfrontend/network-protocol/browser/v2/index.esm.js +7 -15
  36. package/_dependencies/@hyperfrontend/network-protocol/node/channel/index.cjs.js +3 -17
  37. package/_dependencies/@hyperfrontend/network-protocol/node/channel/index.esm.js +1 -15
  38. package/_dependencies/@hyperfrontend/network-protocol/node/data/index.cjs.js +6 -14
  39. package/_dependencies/@hyperfrontend/network-protocol/node/data/index.esm.js +7 -15
  40. package/_dependencies/@hyperfrontend/network-protocol/node/packet/index.cjs.js +6 -14
  41. package/_dependencies/@hyperfrontend/network-protocol/node/packet/index.esm.js +7 -15
  42. package/_dependencies/@hyperfrontend/network-protocol/node/receiver/index.cjs.js +3 -17
  43. package/_dependencies/@hyperfrontend/network-protocol/node/receiver/index.esm.js +1 -15
  44. package/_dependencies/@hyperfrontend/network-protocol/node/sender/index.cjs.js +2 -16
  45. package/_dependencies/@hyperfrontend/network-protocol/node/sender/index.esm.js +2 -16
  46. package/_dependencies/@hyperfrontend/network-protocol/node/v1/index.cjs.js +6 -14
  47. package/_dependencies/@hyperfrontend/network-protocol/node/v1/index.esm.js +7 -15
  48. package/_dependencies/@hyperfrontend/network-protocol/node/v2/index.cjs.js +6 -14
  49. package/_dependencies/@hyperfrontend/network-protocol/node/v2/index.esm.js +7 -15
  50. package/_dependencies/@hyperfrontend/nexus/index.cjs.js +49 -19
  51. package/_dependencies/@hyperfrontend/nexus/index.esm.js +49 -19
  52. package/_dependencies/@hyperfrontend/project-scope/core/fs/index.cjs.js +62 -0
  53. package/_dependencies/@hyperfrontend/project-scope/core/fs/index.esm.js +60 -2
  54. package/_shared/generators/feature/generate-feature-module/index.esm.js +11 -6
  55. package/_shared/generators/metadata/generate-metadata/index.esm.js +1 -0
  56. package/_shared/shared/control/index.cjs.js +12 -2
  57. package/_shared/shared/control/index.esm.js +12 -2
  58. package/_shared/shared/request/index.cjs.js +91 -0
  59. package/_shared/shared/request/index.esm.js +88 -0
  60. package/_shared/shared/shutdown/index.esm.js +12 -0
  61. package/bin/hf.js +643 -70
  62. package/bundle/host/index.iife.js +290 -4041
  63. package/bundle/host/index.iife.min.js +1 -1
  64. package/bundle/host/index.umd.js +290 -4041
  65. package/bundle/host/index.umd.min.js +1 -1
  66. package/bundle/hostee/index.iife.js +215 -2893
  67. package/bundle/hostee/index.iife.min.js +1 -1
  68. package/bundle/hostee/index.umd.js +215 -2893
  69. package/bundle/hostee/index.umd.min.js +1 -1
  70. package/cli/args.d.ts +2 -0
  71. package/cli/args.d.ts.map +1 -1
  72. package/cli/commands/build.d.ts +8 -5
  73. package/cli/commands/build.d.ts.map +1 -1
  74. package/cli/commands/dev.d.ts +7 -2
  75. package/cli/commands/dev.d.ts.map +1 -1
  76. package/cli/config/resolve.d.ts +3 -1
  77. package/cli/config/resolve.d.ts.map +1 -1
  78. package/cli/index.cjs.js +643 -70
  79. package/cli/index.d.ts +21 -10
  80. package/cli/index.esm.js +591 -60
  81. package/cli/usage.d.ts +1 -1
  82. package/cli/usage.d.ts.map +1 -1
  83. package/generators/feature/generate-feature-module.d.ts.map +1 -1
  84. package/generators/index.cjs.js +435 -42
  85. package/generators/index.d.ts +9 -8
  86. package/generators/index.esm.js +404 -30
  87. package/generators/metadata/generate-metadata.d.ts +4 -4
  88. package/generators/metadata/generate-metadata.d.ts.map +1 -1
  89. package/generators/shell/connector-types.d.ts +19 -0
  90. package/generators/shell/connector-types.d.ts.map +1 -0
  91. package/generators/shell/generate-shell.d.ts +5 -4
  92. package/generators/shell/generate-shell.d.ts.map +1 -1
  93. package/generators/shell/schema-type.d.ts +20 -0
  94. package/generators/shell/schema-type.d.ts.map +1 -0
  95. package/generators/shell/source-literal.d.ts +28 -0
  96. package/generators/shell/source-literal.d.ts.map +1 -1
  97. package/host/create-shell.d.ts +4 -1
  98. package/host/create-shell.d.ts.map +1 -1
  99. package/host/display-modes/dialog.d.ts +1 -1
  100. package/host/display-modes/dialog.d.ts.map +1 -1
  101. package/host/display-modes/embedded.d.ts +1 -1
  102. package/host/display-modes/embedded.d.ts.map +1 -1
  103. package/host/index.cjs.js +150 -30
  104. package/host/index.d.ts +53 -38
  105. package/host/index.d.ts.map +1 -1
  106. package/host/index.esm.js +129 -9
  107. package/host/lifecycle.d.ts.map +1 -1
  108. package/host/plugins.d.ts +1 -34
  109. package/host/plugins.d.ts.map +1 -1
  110. package/host/types.d.ts +49 -0
  111. package/host/types.d.ts.map +1 -1
  112. package/hostee/index.cjs.js +54 -9
  113. package/hostee/index.d.ts +41 -1
  114. package/hostee/index.d.ts.map +1 -1
  115. package/hostee/index.esm.js +51 -6
  116. package/hostee/lifecycle.d.ts.map +1 -1
  117. package/hostee/types.d.ts +40 -0
  118. package/hostee/types.d.ts.map +1 -1
  119. package/index.cjs.js +32 -1
  120. package/index.d.ts +89 -3
  121. package/index.d.ts.map +1 -1
  122. package/index.esm.js +32 -1
  123. package/nx/executors/build/index.cjs.js +14975 -137
  124. package/nx/executors/build/index.esm.js +14935 -115
  125. package/nx/executors/serve/executor.d.ts.map +1 -1
  126. package/nx/executors/serve/index.cjs.js +6594 -80
  127. package/nx/executors/serve/index.esm.js +6529 -44
  128. package/nx/generators/feature/index.cjs.js +8751 -108
  129. package/nx/generators/feature/index.esm.js +8711 -81
  130. package/package.json +15 -5
  131. package/server/debug-ui/index.d.ts +2 -0
  132. package/server/debug-ui/index.d.ts.map +1 -0
  133. package/server/debug-ui/index.html +15 -0
  134. package/server/debug-ui/index.iife.js +427 -0
  135. package/server/debug-ui/index.iife.min.js +1 -0
  136. package/server/dev-server.d.ts.map +1 -1
  137. package/server/index.cjs.js +78 -10
  138. package/server/index.esm.js +78 -11
  139. package/server/module-dir.d.ts +17 -0
  140. package/server/module-dir.d.ts.map +1 -0
  141. package/server/module-dir.stub.d.ts +15 -0
  142. package/server/module-dir.stub.d.ts.map +1 -0
  143. package/shared/contract.d.ts +1 -1
  144. package/shared/contract.d.ts.map +1 -1
  145. package/shared/control.d.ts +4 -0
  146. package/shared/control.d.ts.map +1 -1
  147. package/shared/invert-contract.d.ts +20 -0
  148. package/shared/invert-contract.d.ts.map +1 -0
  149. package/shared/request.d.ts +68 -0
  150. package/shared/request.d.ts.map +1 -0
  151. package/{nx/shared → shared}/shutdown.d.ts +3 -2
  152. package/shared/shutdown.d.ts.map +1 -0
  153. package/shared/types.d.ts +72 -1
  154. package/shared/types.d.ts.map +1 -1
  155. package/_shared/nx/shared/context/index.cjs.js +0 -18
  156. package/_shared/nx/shared/context/index.esm.js +0 -16
  157. package/nx/shared/shutdown.d.ts.map +0 -1
  158. package/server/debug-ui/bootstrap.d.ts +0 -2
  159. package/server/debug-ui/bootstrap.d.ts.map +0 -1
package/host/index.esm.js CHANGED
@@ -1,15 +1,50 @@
1
- import { freeze } from '../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/object/index.esm.js';
1
+ import { freeze, values } from '../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/object/index.esm.js';
2
2
  import { DEFAULT_CONTRACT, createBroker } from '../_dependencies/@hyperfrontend/nexus/index.esm.js';
3
+ import { parse, stringify } from '../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/json/index.esm.js';
3
4
  import { createElement, div, button } from '../_dependencies/@hyperfrontend/ui-utils/element/index.esm.js';
4
5
  import { createError } from '../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/error/index.esm.js';
5
6
  import { round } from '../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/math/index.esm.js';
6
7
  import { dateNow } from '../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/date/index.esm.js';
7
- import { setInterval, clearInterval } from '../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/timers/index.esm.js';
8
+ import { setInterval, clearInterval, clearTimeout, setTimeout } from '../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/timers/index.esm.js';
9
+ import { createPromise, promiseResolve, promiseReject } from '../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/promise/index.esm.js';
8
10
  import { createProtocol } from '../_dependencies/@hyperfrontend/network-protocol/browser/v1/index.esm.js';
9
11
  import { createProtocol as createProtocol$1 } from '../_dependencies/@hyperfrontend/network-protocol/browser/v2/index.esm.js';
10
12
  import { DisplayMode } from '../_shared/shared/types/index.esm.js';
11
13
  import { ControlType, isControlType, withControlContract } from '../_shared/shared/control/index.esm.js';
12
14
  import { createEventEmitter } from '../_shared/shared/event-emitter/index.esm.js';
15
+ import { createRequestPeer } from '../_shared/shared/request/index.esm.js';
16
+
17
+ /**
18
+ * Deep-copies an action list so the returned contract shares no object
19
+ * references with its source.
20
+ *
21
+ * @param actions - The action descriptions to copy.
22
+ * @returns Fresh action objects with identical contents.
23
+ */
24
+ function copyActions(actions) {
25
+ // why: The messaging layer rejects configs containing repeated object references, so the inverted contract must be built from fresh literals rather than aliasing the caller's action objects.
26
+ return actions.map((action) => parse(stringify(action)));
27
+ }
28
+ /**
29
+ * Inverts a feature-authored contract into the host's perspective.
30
+ *
31
+ * A feature contract is written from the feature's point of view: `emitted` is
32
+ * what the feature sends, `accepted` is what the feature handles. The host
33
+ * channel needs the mirror image — it sends what the feature accepts and
34
+ * accepts what the feature emits — so the two lists are swapped.
35
+ *
36
+ * @param contract - The contract as authored by the feature.
37
+ * @returns A fresh contract oriented to the host side.
38
+ *
39
+ * @example Deriving the host-side contract
40
+ * ```typescript
41
+ * const host = invertFeatureContract({ emitted: [{ type: 'tick' }], accepted: [{ type: 'setTimezone' }] })
42
+ * // => { emitted: [{ type: 'setTimezone' }], accepted: [{ type: 'tick' }] }
43
+ * ```
44
+ */
45
+ function invertFeatureContract(contract) {
46
+ return { emitted: copyActions(contract.accepted), accepted: copyActions(contract.emitted) };
47
+ }
13
48
 
14
49
  // note: Borderless and transparent so the embedded feature blends into the host page.
15
50
  /**
@@ -107,7 +142,7 @@ const dialogDefaults = freeze({
107
142
  * @param context - Inputs the shell passes to this display mode.
108
143
  * @param context.options - The merged shell options.
109
144
  * @param context.requestClose - Requests the shell close itself.
110
- * @returns The iframe content window and a teardown that unmounts the dialog.
145
+ * @returns The iframe content window, the dialog container as the mounted element, and a teardown that unmounts the dialog.
111
146
  *
112
147
  * @example Mounting a dialog
113
148
  * ```typescript
@@ -151,6 +186,7 @@ const mountDialog = ({ options, requestClose }) => {
151
186
  document.addEventListener('keydown', onKeydown);
152
187
  return {
153
188
  target: iframe.contentWindow,
189
+ element: container.ref,
154
190
  cleanup: () => {
155
191
  document.removeEventListener('keydown', onKeydown);
156
192
  container.ref.remove();
@@ -163,7 +199,7 @@ const mountDialog = ({ options, requestClose }) => {
163
199
  *
164
200
  * @param context - Inputs the shell passes to this display mode.
165
201
  * @param context.options - The merged shell options.
166
- * @returns The iframe content window and a teardown that removes the iframe.
202
+ * @returns The iframe content window, the iframe as the mounted element, and a teardown that removes the iframe.
167
203
  *
168
204
  * @example Mounting embedded
169
205
  * ```typescript
@@ -176,6 +212,7 @@ const mountEmbedded = ({ options }) => {
176
212
  container.appendChild(iframe);
177
213
  return {
178
214
  target: iframe.contentWindow,
215
+ element: iframe,
179
216
  frame: iframe,
180
217
  cleanup: () => iframe.remove(),
181
218
  };
@@ -348,6 +385,12 @@ function createShellHandle(broker, baseOptions, emitter, wiring) {
348
385
  let opened = false;
349
386
  let openCount = 0;
350
387
  let monitor = null;
388
+ let plugins = null;
389
+ let pendingUnmount = null;
390
+ let queuedOpen = null;
391
+ // why: One peer outlives every open/close cycle so handlers registered before the first open (or across reopens) keep answering feature requests.
392
+ const requests = createRequestPeer('host', (type, data) => channel?.send(type, data));
393
+ const emitError = (error) => emitter.emit('error', error);
351
394
  const runCleanup = () => {
352
395
  if (cleanup) {
353
396
  cleanup();
@@ -360,8 +403,45 @@ function createShellHandle(broker, baseOptions, emitter, wiring) {
360
403
  monitor = null;
361
404
  }
362
405
  };
363
- const destroy = () => {
364
- stopMonitor();
406
+ const mountPlugins = (registered, element, displayMode) => {
407
+ const context = freeze({ element, displayMode });
408
+ const teardowns = [];
409
+ for (const plugin of registered) {
410
+ try {
411
+ const teardown = plugin.onMount?.(context);
412
+ if (teardown) {
413
+ teardowns.push(teardown);
414
+ }
415
+ }
416
+ catch (error) {
417
+ emitError(error);
418
+ }
419
+ }
420
+ plugins = { registered, context, teardowns };
421
+ };
422
+ const startUnmount = (state, finish) => {
423
+ plugins = null;
424
+ let chain = promiseResolve();
425
+ for (const plugin of [...state.registered].reverse()) {
426
+ chain = chain.then(() => plugin.onUnmount?.(state.context)).catch(emitError);
427
+ }
428
+ pendingUnmount = chain.then(() => {
429
+ for (const teardown of [...state.teardowns].reverse()) {
430
+ try {
431
+ teardown();
432
+ }
433
+ catch (error) {
434
+ emitError(error);
435
+ }
436
+ }
437
+ finish();
438
+ pendingUnmount = null;
439
+ const reopen = queuedOpen;
440
+ queuedOpen = null;
441
+ reopen?.();
442
+ });
443
+ };
444
+ const releaseChannelAndCleanup = () => {
365
445
  if (channel) {
366
446
  channel.destroy();
367
447
  channel = null;
@@ -369,6 +449,20 @@ function createShellHandle(broker, baseOptions, emitter, wiring) {
369
449
  opened = false;
370
450
  runCleanup();
371
451
  };
452
+ const destroy = () => {
453
+ requests.rejectAll('The shell was destroyed before the feature responded.');
454
+ queuedOpen = null;
455
+ if (pendingUnmount) {
456
+ return;
457
+ }
458
+ stopMonitor();
459
+ const state = plugins;
460
+ if (state) {
461
+ startUnmount(state, releaseChannelAndCleanup);
462
+ return;
463
+ }
464
+ releaseChannelAndCleanup();
465
+ };
372
466
  const close = () => {
373
467
  if (channel) {
374
468
  channel.disconnect();
@@ -389,13 +483,21 @@ function createShellHandle(broker, baseOptions, emitter, wiring) {
389
483
  };
390
484
  const open = (overrides) => {
391
485
  destroy();
486
+ if (pendingUnmount) {
487
+ queuedOpen = () => open(overrides);
488
+ return;
489
+ }
392
490
  const options = { ...baseOptions, ...overrides };
393
- const result = wiring.selectMount(options.displayMode ?? DisplayMode.Embedded)({ options, requestClose: close });
491
+ const displayMode = options.displayMode ?? DisplayMode.Embedded;
492
+ const result = wiring.selectMount(displayMode)({ options, requestClose: close });
394
493
  cleanup = result.cleanup;
395
494
  if (result.target === null) {
396
495
  emitter.emit('error', createError('Feature window could not be opened.'));
397
496
  return;
398
497
  }
498
+ if (options.plugins && options.plugins.length > 0) {
499
+ mountPlugins(options.plugins, result.element ?? null, displayMode);
500
+ }
399
501
  channel = broker.addChannel(`feature-${(openCount += 1)}`, result.target, wiring.registerSecurity(broker, options.protocol, options.sharedKey));
400
502
  const sizeFrame = options.embedSizing === 'content' ? result.frame : undefined;
401
503
  const activeMonitor = wiring.createHeartbeatMonitor((missedBeats, lastBeatAt) => applyUnresponsive(options, missedBeats, lastBeatAt));
@@ -409,6 +511,15 @@ function createShellHandle(broker, baseOptions, emitter, wiring) {
409
511
  opened = false;
410
512
  emitter.emit('close');
411
513
  stopMonitor();
514
+ requests.rejectAll('The feature channel closed before the feature responded.');
515
+ if (pendingUnmount) {
516
+ return;
517
+ }
518
+ const state = plugins;
519
+ if (state) {
520
+ startUnmount(state, runCleanup);
521
+ return;
522
+ }
412
523
  runCleanup();
413
524
  });
414
525
  channel.on('deny', (data) => emitter.emit('error', data));
@@ -421,6 +532,9 @@ function createShellHandle(broker, baseOptions, emitter, wiring) {
421
532
  else if (message.type === ControlType.Size && sizeFrame) {
422
533
  applyContentSize(sizeFrame, message.data);
423
534
  }
535
+ else {
536
+ requests.dispatch(message.type, message.data);
537
+ }
424
538
  return;
425
539
  }
426
540
  emitter.emit(message.type, message.data);
@@ -432,6 +546,8 @@ function createShellHandle(broker, baseOptions, emitter, wiring) {
432
546
  close,
433
547
  destroy,
434
548
  send: (type, data) => channel?.send(type, data),
549
+ request: (type, data, options) => channel ? requests.request(type, data, options) : promiseReject(createError(`Cannot send request '${type}': the shell is not open.`)),
550
+ handle: requests.handle,
435
551
  on: emitter.on,
436
552
  get isOpen() {
437
553
  return opened;
@@ -556,7 +672,10 @@ function deriveShellName(options, sequence) {
556
672
  * Creates a host-side shell for embedding a feature.
557
673
  *
558
674
  * Provisions a nexus broker and returns a handle whose `open` mounts the feature
559
- * in the requested display mode.
675
+ * in the requested display mode. The `contract` option takes the feature's
676
+ * contract exactly as the feature authored it; the shell derives the host-side
677
+ * orientation itself, so the handle sends what the feature accepts and receives
678
+ * what the feature emits.
560
679
  *
561
680
  * @param options - Create-time shell options, overridable per `open` call.
562
681
  * @returns A handle exposing `open`, `close`, `destroy`, `send`, `on`, and `isOpen`.
@@ -570,7 +689,8 @@ function deriveShellName(options, sequence) {
570
689
  */
571
690
  function createShell(options) {
572
691
  const emitter = createEventEmitter();
573
- const contract = withControlContract((options.contract ?? DEFAULT_CONTRACT));
692
+ // how: A feature-authored contract is inverted into the host's perspective; the generic default contract is already channel-oriented and is used as-is.
693
+ const contract = withControlContract(options.contract ? invertFeatureContract(options.contract) : DEFAULT_CONTRACT);
574
694
  const broker = createBroker({ name: deriveShellName(options, (shellCount += 1)), contract });
575
695
  return createShellHandle(broker, options, emitter, { selectMount, registerSecurity, createHeartbeatMonitor });
576
696
  }
@@ -1 +1 @@
1
- {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../../../../../../libs/features/src/host/lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAA2B,MAAM,sBAAsB,CAAA;AACjF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAI5D,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAO7C;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;;OAKG;IACH,WAAW,CAAC,IAAI,EAAE,WAAW,GAAG,gBAAgB,CAAA;IAChD;;;;;;;OAOG;IACH,gBAAgB,CACd,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,gBAAgB,GAAG,SAAS,EACtC,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;IACtC;;;;;OAKG;IACH,sBAAsB,CAAC,cAAc,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,GAAG,gBAAgB,CAAA;CACnH;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,YAAY,EACpB,WAAW,EAAE,YAAY,EACzB,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,WAAW,GAClB,WAAW,CAyGb"}
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../../../../../../libs/features/src/host/lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAA2B,MAAM,sBAAsB,CAAA;AACjF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAE3D,OAAO,KAAK,EAA6C,gBAAgB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAChH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAM5D,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAO7C;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;;OAKG;IACH,WAAW,CAAC,IAAI,EAAE,WAAW,GAAG,gBAAgB,CAAA;IAChD;;;;;;;OAOG;IACH,gBAAgB,CACd,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,gBAAgB,GAAG,SAAS,EACtC,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;IACtC;;;;;OAKG;IACH,sBAAsB,CAAC,cAAc,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,GAAG,gBAAgB,CAAA;CACnH;AAcD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,YAAY,EACpB,WAAW,EAAE,YAAY,EACzB,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,WAAW,GAClB,WAAW,CA0Lb"}
package/host/plugins.d.ts CHANGED
@@ -1,35 +1,2 @@
1
- import type { DisplayMode } from '../shared/types';
2
- /**
3
- * Context handed to an {@link ExperiencePlugin} around a feature's mount lifecycle.
4
- */
5
- export interface ExperiencePluginContext {
6
- /** The root element the display mode mounted (dialog container or embedded frame). */
7
- element: HTMLElement;
8
- /** The display mode the feature was surfaced in. */
9
- displayMode: DisplayMode;
10
- }
11
- /**
12
- * Opt-in extension that decorates a feature's mount lifecycle (e.g. transitions, animations).
13
- *
14
- * Implement this interface to layer experiences onto the built-in display modes,
15
- * then pass the plugin to the host shell. The SDK ships no built-in plugins.
16
- */
17
- export interface ExperiencePlugin {
18
- /** Unique plugin name, surfaced in debug logs. */
19
- name: string;
20
- /**
21
- * Runs after the feature mounts; may animate it in and return a teardown.
22
- *
23
- * @param context - The mounted element and its display mode.
24
- * @returns An optional teardown invoked on unmount.
25
- */
26
- onMount?(context: ExperiencePluginContext): void | (() => void);
27
- /**
28
- * Runs before the feature unmounts; may return a promise to defer teardown until an exit animation finishes.
29
- *
30
- * @param context - The mounted element and its display mode.
31
- * @returns Optionally a promise the shell awaits before tearing down.
32
- */
33
- onUnmount?(context: ExperiencePluginContext): void | Promise<void>;
34
- }
1
+ export type { ExperiencePlugin, ExperiencePluginContext } from '../shared/types';
35
2
  //# sourceMappingURL=plugins.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugins.d.ts","sourceRoot":"","sources":["../../../../../../../libs/features/src/host/plugins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAIlD;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,sFAAsF;IACtF,OAAO,EAAE,WAAW,CAAA;IACpB,oDAAoD;IACpD,WAAW,EAAE,WAAW,CAAA;CACzB;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;OAKG;IACH,OAAO,CAAC,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,CAAA;IAC/D;;;;;OAKG;IACH,SAAS,CAAC,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACnE"}
1
+ {"version":3,"file":"plugins.d.ts","sourceRoot":"","sources":["../../../../../../../libs/features/src/host/plugins.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAA"}
package/host/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { EventHandler } from '../shared/event-emitter';
2
+ import type { RequestHandler, RequestOptions } from '../shared/request';
2
3
  import type { ShellOptions } from '../shared/types';
3
4
  /**
4
5
  * Public handle returned by {@link createShell}.
@@ -7,6 +8,9 @@ export interface ShellHandle {
7
8
  /**
8
9
  * Mounts the feature using the merged create-time and call-time options.
9
10
  *
11
+ * When a plugin teardown from the previous mount is still in flight, the
12
+ * remount is queued until it settles; the latest `open` call wins.
13
+ *
10
14
  * @param options - Per-open overrides layered over the create-time options.
11
15
  */
12
16
  open(options?: Partial<ShellOptions>): void;
@@ -16,6 +20,10 @@ export interface ShellHandle {
16
20
  close(): void;
17
21
  /**
18
22
  * Closes the feature and releases all resources (channel and DOM).
23
+ *
24
+ * When experience plugins are registered, their `onUnmount` hooks are
25
+ * awaited before the channel and DOM are released; calling `destroy` again
26
+ * while that teardown is in flight is a no-op.
19
27
  */
20
28
  destroy(): void;
21
29
  /**
@@ -25,6 +33,45 @@ export interface ShellHandle {
25
33
  * @param data - Optional payload for the action.
26
34
  */
27
35
  send(type: string, data?: unknown): void;
36
+ /**
37
+ * Sends a request to the feature and resolves with its response.
38
+ *
39
+ * The feature answers through a handler it registered with its own
40
+ * `handle(type, handler)`. The promise rejects when the feature's handler
41
+ * throws, when the feature has no handler for the type, when no response
42
+ * arrives within the timeout (30 seconds by default), when the shell is not
43
+ * open, or when the channel closes or the shell is destroyed while the
44
+ * request is pending.
45
+ *
46
+ * @param type - Request action type, drawn from the feature contract.
47
+ * @param data - Optional payload for the request.
48
+ * @param options - Per-request settings such as `timeoutMs`.
49
+ * @returns A promise settling with the feature's response payload.
50
+ *
51
+ * @example Querying the feature for its current state
52
+ * ```typescript
53
+ * const time = await shell.request('getTime', { timezone: 'UTC' }, { timeoutMs: 5000 })
54
+ * ```
55
+ */
56
+ request(type: string, data?: unknown, options?: RequestOptions): Promise<unknown>;
57
+ /**
58
+ * Registers the handler that answers feature requests of a given type.
59
+ *
60
+ * One handler per type: registering a second handler for a type that already
61
+ * has one throws. The handler may return the response value directly or a
62
+ * promise of it; a thrown error or rejected promise reaches the feature as a
63
+ * failed response carrying the error's message.
64
+ *
65
+ * @param type - Request type to answer.
66
+ * @param handler - Receives the request payload and returns the response.
67
+ * @returns A function that unregisters this handler.
68
+ *
69
+ * @example Answering a feature's request for host settings
70
+ * ```typescript
71
+ * shell.handle('getSettings', () => ({ locale: 'en-US' }))
72
+ * ```
73
+ */
74
+ handle(type: string, handler: RequestHandler): () => void;
28
75
  /**
29
76
  * Subscribes to feature messages or lifecycle events (`open`, `close`, `error`).
30
77
  *
@@ -44,6 +91,8 @@ export interface ShellHandle {
44
91
  export interface MountResult {
45
92
  /** Window the host messages, or `null` when a popup/standalone was blocked. */
46
93
  target: Window | null;
94
+ /** In-document root the mode mounted (embedded iframe or dialog container); unset when the feature opens in a separate window. */
95
+ element?: HTMLElement;
47
96
  /** Resizable element for content-driven sizing, when the mode embeds an iframe inline. */
48
97
  frame?: HTMLElement;
49
98
  /** Removes any DOM or closes any window created by the mount. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../../../libs/features/src/host/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAEnD;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,IAAI,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,IAAI,CAAA;IAC3C;;OAEG;IACH,KAAK,IAAI,IAAI,CAAA;IACb;;OAEG;IACH,OAAO,IAAI,IAAI,CAAA;IACf;;;;;OAKG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;IACxC;;;;;;OAMG;IACH,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,MAAM,IAAI,CAAA;IACpD;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,+EAA+E;IAC/E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,0FAA0F;IAC1F,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,iEAAiE;IACjE,OAAO,IAAI,IAAI,CAAA;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,oDAAoD;IACpD,OAAO,EAAE,YAAY,CAAA;IACrB,6EAA6E;IAC7E,YAAY,IAAI,IAAI,CAAA;CACrB;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,EAAE,YAAY,KAAK,WAAW,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../../../libs/features/src/host/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAEnD;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;;;;OAOG;IACH,IAAI,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,IAAI,CAAA;IAC3C;;OAEG;IACH,KAAK,IAAI,IAAI,CAAA;IACb;;;;;;OAMG;IACH,OAAO,IAAI,IAAI,CAAA;IACf;;;;;OAKG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;IACxC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACjF;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,MAAM,IAAI,CAAA;IACzD;;;;;;OAMG;IACH,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,MAAM,IAAI,CAAA;IACpD;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,+EAA+E;IAC/E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,kIAAkI;IAClI,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,0FAA0F;IAC1F,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,iEAAiE;IACjE,OAAO,IAAI,IAAI,CAAA;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,oDAAoD;IACpD,OAAO,EAAE,YAAY,CAAA;IACrB,6EAA6E;IAC7E,YAAY,IAAI,IAAI,CAAA;CACrB;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,EAAE,YAAY,KAAK,WAAW,CAAA"}
@@ -3,14 +3,15 @@
3
3
  const index_cjs_js$7 = require('../_dependencies/@hyperfrontend/nexus/index.cjs.js');
4
4
  const index_cjs_js$1 = require('../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/array/index.cjs.js');
5
5
  const index_cjs_js = require('../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/error/index.cjs.js');
6
- const { ControlType, withControlContract } = require('../_shared/shared/control/index.cjs.js');
6
+ const { ControlType, isControlType, withControlContract } = require('../_shared/shared/control/index.cjs.js');
7
7
  const { createEventEmitter } = require('../_shared/shared/event-emitter/index.cjs.js');
8
+ const { createRequestPeer } = require('../_shared/shared/request/index.cjs.js');
8
9
  require('../_dependencies/@hyperfrontend/json-utils/index.cjs.js');
9
10
  const index_cjs_js$2 = require('../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/object/index.cjs.js');
10
- const index_cjs_js$6 = require('../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/promise/index.cjs.js');
11
+ const index_cjs_js$4 = require('../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/promise/index.cjs.js');
11
12
  const index_cjs_js$3 = require('../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/timers/index.cjs.js');
12
- const index_cjs_js$5 = require('../_dependencies/@hyperfrontend/ui-utils/element/index.cjs.js');
13
- const index_cjs_js$4 = require('../_dependencies/@hyperfrontend/ui-utils/style/index.cjs.js');
13
+ const index_cjs_js$6 = require('../_dependencies/@hyperfrontend/ui-utils/element/index.cjs.js');
14
+ const index_cjs_js$5 = require('../_dependencies/@hyperfrontend/ui-utils/style/index.cjs.js');
14
15
 
15
16
  // note: Runtime validation shared by the host/hostee factories and the config loader.
16
17
  /**
@@ -45,6 +46,31 @@ function collectActionListIssues(actions, field, issues) {
45
46
  if (typeof action['type'] !== 'string' || action['type'].length === 0) {
46
47
  issues.push(`"${field}[${index}]" must have a non-empty string "type".`);
47
48
  }
49
+ if (action['respondsWith'] !== undefined && (typeof action['respondsWith'] !== 'string' || action['respondsWith'].length === 0)) {
50
+ issues.push(`"${field}[${index}]" has a "respondsWith" that must be a non-empty string.`);
51
+ }
52
+ });
53
+ }
54
+ /**
55
+ * Collects every `respondsWith` reference that does not name an action in the other direction.
56
+ *
57
+ * A request emitted by one side is answered by an action the same side accepts (and
58
+ * vice versa), so each `respondsWith` must resolve across the contract's directions.
59
+ *
60
+ * @param actions - The already well-formed action list to check.
61
+ * @param field - The field name of `actions`, used to locate problems in messages.
62
+ * @param other - The action list of the opposite direction.
63
+ * @param otherField - The field name of `other`, used in messages.
64
+ * @param issues - The running list of human-readable problems, appended to in place.
65
+ */
66
+ function collectRespondsWithIssues(actions, field, other, otherField, issues) {
67
+ actions.forEach((action, index) => {
68
+ if (action.respondsWith === undefined) {
69
+ return;
70
+ }
71
+ if (!other.some((candidate) => candidate.type === action.respondsWith)) {
72
+ issues.push(`"${field}[${index}]" responds with "${action.respondsWith}", but "${otherField}" has no action of that type.`);
73
+ }
48
74
  });
49
75
  }
50
76
  /**
@@ -70,7 +96,7 @@ function describeType(value) {
70
96
  *
71
97
  * @param contract - The candidate contract, typically parsed from disk.
72
98
  * @returns The validated contract, typed.
73
- * @throws {Error} When the value is not an object, or any action is malformed.
99
+ * @throws {Error} When the value is not an object, any action is malformed, or a `respondsWith` names no action in the other direction.
74
100
  *
75
101
  * @example Validating a parsed contract file
76
102
  * ```typescript
@@ -85,6 +111,12 @@ function validateContract(contract) {
85
111
  const issues = [];
86
112
  collectActionListIssues(contract['emitted'], 'emitted', issues);
87
113
  collectActionListIssues(contract['accepted'], 'accepted', issues);
114
+ if (issues.length === 0) {
115
+ const emitted = contract['emitted'];
116
+ const accepted = contract['accepted'];
117
+ collectRespondsWithIssues(emitted, 'emitted', accepted, 'accepted', issues);
118
+ collectRespondsWithIssues(accepted, 'accepted', emitted, 'emitted', issues);
119
+ }
88
120
  if (issues.length > 0) {
89
121
  throw index_cjs_js.createError(`Invalid contract:\n${issues.map((issue) => ` - ${issue}`).join('\n')}`);
90
122
  }
@@ -135,7 +167,7 @@ const BODY_RESET_CSS = 'html,body{margin:0;padding:0;background:transparent}';
135
167
  * ```
136
168
  */
137
169
  function applyBodyReset() {
138
- index_cjs_js$4.addStylesheet(BODY_RESET_CSS);
170
+ index_cjs_js$5.addStylesheet(BODY_RESET_CSS);
139
171
  }
140
172
  /**
141
173
  * Creates the hostee-side content-size announcer.
@@ -158,7 +190,7 @@ function createSizeAnnouncer(send) {
158
190
  return;
159
191
  }
160
192
  announce();
161
- cleanup = index_cjs_js$5.onElementResize(document.body, announce);
193
+ cleanup = index_cjs_js$6.onElementResize(document.body, announce);
162
194
  },
163
195
  stop() {
164
196
  if (cleanup) {
@@ -207,6 +239,7 @@ function resolveHostWindow(win) {
207
239
  function createFeatureHandle(broker, hostWindow, emitter) {
208
240
  let channel = null;
209
241
  let opened = false;
242
+ const requests = createRequestPeer('feature', (type, data) => channel?.send(type, data));
210
243
  if (hostWindow) {
211
244
  const activeChannel = broker.addChannel('host', hostWindow);
212
245
  channel = activeChannel;
@@ -223,16 +256,28 @@ function createFeatureHandle(broker, hostWindow, emitter) {
223
256
  emitter.emit('close');
224
257
  heartbeat.stop();
225
258
  announcer.stop();
259
+ requests.rejectAll('The host channel closed before the host responded.');
226
260
  });
227
261
  activeChannel.on('deny', (data) => emitter.emit('error', data));
228
262
  activeChannel.on('invalid', (data) => emitter.emit('error', data));
229
- activeChannel.onMessage((message) => emitter.emit(message.type, message.data));
263
+ activeChannel.onMessage((message) => {
264
+ // why: Control traffic (heartbeat/size echoes, request/response envelopes) is SDK-internal; forwarding it would leak reserved __hf: types into consumer handlers.
265
+ if (isControlType(message.type)) {
266
+ requests.dispatch(message.type, message.data);
267
+ return;
268
+ }
269
+ emitter.emit(message.type, message.data);
270
+ });
230
271
  activeChannel.connect();
231
272
  }
232
273
  return index_cjs_js$2.freeze({
233
274
  send: (type, data) => channel?.send(type, data),
275
+ request: (type, data, options) => channel
276
+ ? requests.request(type, data, options)
277
+ : index_cjs_js$4.promiseReject(index_cjs_js.createError(`Cannot send request '${type}': the feature is not connected to a host.`)),
278
+ handle: requests.handle,
234
279
  on: emitter.on,
235
- ready: () => index_cjs_js$6.createPromise((resolve) => {
280
+ ready: () => index_cjs_js$4.createPromise((resolve) => {
236
281
  if (opened) {
237
282
  resolve();
238
283
  return;
package/hostee/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { EventHandler, FeatureOptions } from '..';
1
+ import { RequestOptions, RequestHandler, EventHandler, FeatureOptions } from '..';
2
+ export { ActionDescription, FeatureContract, FeatureOptions, RequestHandler, RequestOptions } from '..';
2
3
 
3
4
  /**
4
5
  * Public handle returned by the hostee-side feature factory.
@@ -11,6 +12,45 @@ interface FeatureHandle {
11
12
  * @param data - Optional payload for the action.
12
13
  */
13
14
  send(type: string, data?: unknown): void;
15
+ /**
16
+ * Sends a request to the host and resolves with its response.
17
+ *
18
+ * The host answers through a handler it registered with its own
19
+ * `handle(type, handler)`. The promise rejects when the host's handler
20
+ * throws, when the host has no handler for the type, when no response
21
+ * arrives within the timeout (30 seconds by default), when the feature is
22
+ * not connected to a host, or when the host channel closes while the
23
+ * request is pending.
24
+ *
25
+ * @param type - Request action type, drawn from the feature contract.
26
+ * @param data - Optional payload for the request.
27
+ * @param options - Per-request settings such as `timeoutMs`.
28
+ * @returns A promise settling with the host's response payload.
29
+ *
30
+ * @example Asking the host for its settings
31
+ * ```typescript
32
+ * const settings = await feature.request('getSettings', undefined, { timeoutMs: 5000 })
33
+ * ```
34
+ */
35
+ request(type: string, data?: unknown, options?: RequestOptions): Promise<unknown>;
36
+ /**
37
+ * Registers the handler that answers host requests of a given type.
38
+ *
39
+ * One handler per type: registering a second handler for a type that already
40
+ * has one throws. The handler may return the response value directly or a
41
+ * promise of it; a thrown error or rejected promise reaches the host as a
42
+ * failed response carrying the error's message.
43
+ *
44
+ * @param type - Request type to answer.
45
+ * @param handler - Receives the request payload and returns the response.
46
+ * @returns A function that unregisters this handler.
47
+ *
48
+ * @example Answering the host's request for the feature's state
49
+ * ```typescript
50
+ * feature.handle('getTime', (data) => formatTime(data))
51
+ * ```
52
+ */
53
+ handle(type: string, handler: RequestHandler): () => void;
14
54
  /**
15
55
  * Subscribes to host messages or lifecycle events (`open`, `close`, `error`).
16
56
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../libs/features/src/hostee/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../libs/features/src/hostee/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACvE,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACzF,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA"}