@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
@@ -2,13 +2,14 @@ import { createBroker } from '../_dependencies/@hyperfrontend/nexus/index.esm.js
2
2
  import { isArray } from '../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/array/index.esm.js';
3
3
  import { createError } from '../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/error/index.esm.js';
4
4
  import '../_dependencies/@hyperfrontend/json-utils/index.esm.js';
5
- import { freeze } from '../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/object/index.esm.js';
6
- import { createPromise } from '../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/promise/index.esm.js';
7
- import { clearInterval, setInterval } from '../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/timers/index.esm.js';
5
+ import { freeze, values } from '../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/object/index.esm.js';
6
+ import { createPromise, promiseResolve, promiseReject } from '../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/promise/index.esm.js';
7
+ import { clearTimeout, setTimeout, clearInterval, setInterval } from '../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/timers/index.esm.js';
8
8
  import { onElementResize } from '../_dependencies/@hyperfrontend/ui-utils/element/index.esm.js';
9
9
  import { addStylesheet } from '../_dependencies/@hyperfrontend/ui-utils/style/index.esm.js';
10
- import { ControlType, withControlContract } from '../_shared/shared/control/index.esm.js';
10
+ import { ControlType, isControlType, withControlContract } from '../_shared/shared/control/index.esm.js';
11
11
  import { createEventEmitter } from '../_shared/shared/event-emitter/index.esm.js';
12
+ import { createRequestPeer } from '../_shared/shared/request/index.esm.js';
12
13
 
13
14
  // note: Runtime validation shared by the host/hostee factories and the config loader.
14
15
  /**
@@ -43,6 +44,31 @@ function collectActionListIssues(actions, field, issues) {
43
44
  if (typeof action['type'] !== 'string' || action['type'].length === 0) {
44
45
  issues.push(`"${field}[${index}]" must have a non-empty string "type".`);
45
46
  }
47
+ if (action['respondsWith'] !== undefined && (typeof action['respondsWith'] !== 'string' || action['respondsWith'].length === 0)) {
48
+ issues.push(`"${field}[${index}]" has a "respondsWith" that must be a non-empty string.`);
49
+ }
50
+ });
51
+ }
52
+ /**
53
+ * Collects every `respondsWith` reference that does not name an action in the other direction.
54
+ *
55
+ * A request emitted by one side is answered by an action the same side accepts (and
56
+ * vice versa), so each `respondsWith` must resolve across the contract's directions.
57
+ *
58
+ * @param actions - The already well-formed action list to check.
59
+ * @param field - The field name of `actions`, used to locate problems in messages.
60
+ * @param other - The action list of the opposite direction.
61
+ * @param otherField - The field name of `other`, used in messages.
62
+ * @param issues - The running list of human-readable problems, appended to in place.
63
+ */
64
+ function collectRespondsWithIssues(actions, field, other, otherField, issues) {
65
+ actions.forEach((action, index) => {
66
+ if (action.respondsWith === undefined) {
67
+ return;
68
+ }
69
+ if (!other.some((candidate) => candidate.type === action.respondsWith)) {
70
+ issues.push(`"${field}[${index}]" responds with "${action.respondsWith}", but "${otherField}" has no action of that type.`);
71
+ }
46
72
  });
47
73
  }
48
74
  /**
@@ -68,7 +94,7 @@ function describeType(value) {
68
94
  *
69
95
  * @param contract - The candidate contract, typically parsed from disk.
70
96
  * @returns The validated contract, typed.
71
- * @throws {Error} When the value is not an object, or any action is malformed.
97
+ * @throws {Error} When the value is not an object, any action is malformed, or a `respondsWith` names no action in the other direction.
72
98
  *
73
99
  * @example Validating a parsed contract file
74
100
  * ```typescript
@@ -83,6 +109,12 @@ function validateContract(contract) {
83
109
  const issues = [];
84
110
  collectActionListIssues(contract['emitted'], 'emitted', issues);
85
111
  collectActionListIssues(contract['accepted'], 'accepted', issues);
112
+ if (issues.length === 0) {
113
+ const emitted = contract['emitted'];
114
+ const accepted = contract['accepted'];
115
+ collectRespondsWithIssues(emitted, 'emitted', accepted, 'accepted', issues);
116
+ collectRespondsWithIssues(accepted, 'accepted', emitted, 'emitted', issues);
117
+ }
86
118
  if (issues.length > 0) {
87
119
  throw createError(`Invalid contract:\n${issues.map((issue) => ` - ${issue}`).join('\n')}`);
88
120
  }
@@ -205,6 +237,7 @@ function resolveHostWindow(win) {
205
237
  function createFeatureHandle(broker, hostWindow, emitter) {
206
238
  let channel = null;
207
239
  let opened = false;
240
+ const requests = createRequestPeer('feature', (type, data) => channel?.send(type, data));
208
241
  if (hostWindow) {
209
242
  const activeChannel = broker.addChannel('host', hostWindow);
210
243
  channel = activeChannel;
@@ -221,14 +254,26 @@ function createFeatureHandle(broker, hostWindow, emitter) {
221
254
  emitter.emit('close');
222
255
  heartbeat.stop();
223
256
  announcer.stop();
257
+ requests.rejectAll('The host channel closed before the host responded.');
224
258
  });
225
259
  activeChannel.on('deny', (data) => emitter.emit('error', data));
226
260
  activeChannel.on('invalid', (data) => emitter.emit('error', data));
227
- activeChannel.onMessage((message) => emitter.emit(message.type, message.data));
261
+ activeChannel.onMessage((message) => {
262
+ // why: Control traffic (heartbeat/size echoes, request/response envelopes) is SDK-internal; forwarding it would leak reserved __hf: types into consumer handlers.
263
+ if (isControlType(message.type)) {
264
+ requests.dispatch(message.type, message.data);
265
+ return;
266
+ }
267
+ emitter.emit(message.type, message.data);
268
+ });
228
269
  activeChannel.connect();
229
270
  }
230
271
  return freeze({
231
272
  send: (type, data) => channel?.send(type, data),
273
+ request: (type, data, options) => channel
274
+ ? requests.request(type, data, options)
275
+ : promiseReject(createError(`Cannot send request '${type}': the feature is not connected to a host.`)),
276
+ handle: requests.handle,
232
277
  on: emitter.on,
233
278
  ready: () => createPromise((resolve) => {
234
279
  if (opened) {
@@ -1 +1 @@
1
- {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../../../../../../libs/features/src/hostee/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,aAAa,EAAE,MAAM,SAAS,CAAA;AAQ5C;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQ5D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,YAAY,GAAG,aAAa,CAwCzH"}
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../../../../../../libs/features/src/hostee/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,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAW5C;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQ5D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,YAAY,GAAG,aAAa,CAsDzH"}
package/hostee/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
  /**
3
4
  * Public handle returned by the hostee-side feature factory.
4
5
  */
@@ -10,6 +11,45 @@ export interface FeatureHandle {
10
11
  * @param data - Optional payload for the action.
11
12
  */
12
13
  send(type: string, data?: unknown): void;
14
+ /**
15
+ * Sends a request to the host and resolves with its response.
16
+ *
17
+ * The host answers through a handler it registered with its own
18
+ * `handle(type, handler)`. The promise rejects when the host's handler
19
+ * throws, when the host has no handler for the type, when no response
20
+ * arrives within the timeout (30 seconds by default), when the feature is
21
+ * not connected to a host, or when the host channel closes while the
22
+ * request is pending.
23
+ *
24
+ * @param type - Request action type, drawn from the feature contract.
25
+ * @param data - Optional payload for the request.
26
+ * @param options - Per-request settings such as `timeoutMs`.
27
+ * @returns A promise settling with the host's response payload.
28
+ *
29
+ * @example Asking the host for its settings
30
+ * ```typescript
31
+ * const settings = await feature.request('getSettings', undefined, { timeoutMs: 5000 })
32
+ * ```
33
+ */
34
+ request(type: string, data?: unknown, options?: RequestOptions): Promise<unknown>;
35
+ /**
36
+ * Registers the handler that answers host requests of a given type.
37
+ *
38
+ * One handler per type: registering a second handler for a type that already
39
+ * has one throws. The handler may return the response value directly or a
40
+ * promise of it; a thrown error or rejected promise reaches the host as a
41
+ * failed response carrying the error's message.
42
+ *
43
+ * @param type - Request type to answer.
44
+ * @param handler - Receives the request payload and returns the response.
45
+ * @returns A function that unregisters this handler.
46
+ *
47
+ * @example Answering the host's request for the feature's state
48
+ * ```typescript
49
+ * feature.handle('getTime', (data) => formatTime(data))
50
+ * ```
51
+ */
52
+ handle(type: string, handler: RequestHandler): () => void;
13
53
  /**
14
54
  * Subscribes to host messages or lifecycle events (`open`, `close`, `error`).
15
55
  *
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../../../libs/features/src/hostee/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAE3D;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;;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;;;;OAIG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB;;OAEG;IACH,KAAK,IAAI,IAAI,CAAA;CACd"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../../../libs/features/src/hostee/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAEvE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;;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;;;;OAIG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB;;OAEG;IACH,KAAK,IAAI,IAAI,CAAA;CACd"}
package/index.cjs.js CHANGED
@@ -39,6 +39,31 @@ function collectActionListIssues(actions, field, issues) {
39
39
  if (typeof action['type'] !== 'string' || action['type'].length === 0) {
40
40
  issues.push(`"${field}[${index}]" must have a non-empty string "type".`);
41
41
  }
42
+ if (action['respondsWith'] !== undefined && (typeof action['respondsWith'] !== 'string' || action['respondsWith'].length === 0)) {
43
+ issues.push(`"${field}[${index}]" has a "respondsWith" that must be a non-empty string.`);
44
+ }
45
+ });
46
+ }
47
+ /**
48
+ * Collects every `respondsWith` reference that does not name an action in the other direction.
49
+ *
50
+ * A request emitted by one side is answered by an action the same side accepts (and
51
+ * vice versa), so each `respondsWith` must resolve across the contract's directions.
52
+ *
53
+ * @param actions - The already well-formed action list to check.
54
+ * @param field - The field name of `actions`, used to locate problems in messages.
55
+ * @param other - The action list of the opposite direction.
56
+ * @param otherField - The field name of `other`, used in messages.
57
+ * @param issues - The running list of human-readable problems, appended to in place.
58
+ */
59
+ function collectRespondsWithIssues(actions, field, other, otherField, issues) {
60
+ actions.forEach((action, index) => {
61
+ if (action.respondsWith === undefined) {
62
+ return;
63
+ }
64
+ if (!other.some((candidate) => candidate.type === action.respondsWith)) {
65
+ issues.push(`"${field}[${index}]" responds with "${action.respondsWith}", but "${otherField}" has no action of that type.`);
66
+ }
42
67
  });
43
68
  }
44
69
  /**
@@ -64,7 +89,7 @@ function describeType(value) {
64
89
  *
65
90
  * @param contract - The candidate contract, typically parsed from disk.
66
91
  * @returns The validated contract, typed.
67
- * @throws {Error} When the value is not an object, or any action is malformed.
92
+ * @throws {Error} When the value is not an object, any action is malformed, or a `respondsWith` names no action in the other direction.
68
93
  *
69
94
  * @example Validating a parsed contract file
70
95
  * ```typescript
@@ -79,6 +104,12 @@ function validateContract(contract) {
79
104
  const issues = [];
80
105
  collectActionListIssues(contract['emitted'], 'emitted', issues);
81
106
  collectActionListIssues(contract['accepted'], 'accepted', issues);
107
+ if (issues.length === 0) {
108
+ const emitted = contract['emitted'];
109
+ const accepted = contract['accepted'];
110
+ collectRespondsWithIssues(emitted, 'emitted', accepted, 'accepted', issues);
111
+ collectRespondsWithIssues(accepted, 'accepted', emitted, 'emitted', issues);
112
+ }
82
113
  if (issues.length > 0) {
83
114
  throw index_cjs_js.createError(`Invalid contract:\n${issues.map((issue) => ` - ${issue}`).join('\n')}`);
84
115
  }
package/index.d.ts CHANGED
@@ -1,5 +1,20 @@
1
1
  import { ValidationResult } from './_dependencies/@hyperfrontend/json-utils/index.js';
2
2
 
3
+ /**
4
+ * Per-request settings accepted by `request`.
5
+ */
6
+ interface RequestOptions {
7
+ /** Milliseconds to wait for the response before rejecting; defaults to 30000. */
8
+ timeoutMs?: number;
9
+ }
10
+ /**
11
+ * Answers one request type; may return the response value directly or a promise of it.
12
+ *
13
+ * @param data - The request payload sent by the other side.
14
+ * @returns The response value, or a promise that resolves to it.
15
+ */
16
+ type RequestHandler = (data: unknown) => unknown;
17
+
3
18
  /**
4
19
  * Supported ways a host can surface an embedded feature.
5
20
  *
@@ -65,6 +80,8 @@ interface ActionDescription {
65
80
  description?: string;
66
81
  /** Optional JSON-schema-like shape describing the action payload. */
67
82
  schema?: object;
83
+ /** When this action is used as a request, the type of the action in the other direction that answers it. */
84
+ respondsWith?: string;
68
85
  }
69
86
  /**
70
87
  * The set of actions a feature emits to, and accepts from, its counterpart.
@@ -78,6 +95,66 @@ interface FeatureContract {
78
95
  /** Actions this side handles from the other side. */
79
96
  accepted: ActionDescription[];
80
97
  }
98
+ /**
99
+ * Context handed to an {@link ExperiencePlugin} around a feature's mount lifecycle.
100
+ */
101
+ interface ExperiencePluginContext {
102
+ /**
103
+ * The in-document root the display mode mounted: the iframe for `embedded`,
104
+ * the dialog container for `dialog`, and `null` for `popup` and
105
+ * `standalone`, which open a separate window with no in-document element.
106
+ */
107
+ element: HTMLElement | null;
108
+ /** The display mode the feature was surfaced in. */
109
+ displayMode: DisplayMode;
110
+ }
111
+ /**
112
+ * Opt-in extension that decorates a feature's mount lifecycle (e.g. transitions, animations).
113
+ *
114
+ * Register plugins through {@link ShellOptions.plugins}. After each successful
115
+ * mount the shell calls `onMount` on every plugin in registration order; before
116
+ * each unmount it calls `onUnmount` one plugin at a time in reverse
117
+ * registration order, awaiting any returned promise, then runs the teardowns
118
+ * returned by `onMount` (also in reverse registration order) and finally
119
+ * removes the feature. The SDK ships no built-in plugins.
120
+ *
121
+ * @example Registering a fade-out plugin on a shell
122
+ * ```typescript
123
+ * const shell = createShell({
124
+ * container: '#shell',
125
+ * plugins: [
126
+ * {
127
+ * name: 'fade',
128
+ * onUnmount: ({ element }) => (element ? animateFadeOut(element) : undefined),
129
+ * },
130
+ * ],
131
+ * })
132
+ * ```
133
+ */
134
+ interface ExperiencePlugin {
135
+ /** Unique plugin name, surfaced in debug logs. */
136
+ name: string;
137
+ /**
138
+ * Runs after the feature mounts; may animate it in and return a teardown.
139
+ *
140
+ * A thrown error surfaces as an `error` event on the shell and does not
141
+ * stop the remaining plugins from mounting.
142
+ *
143
+ * @param context - The mounted element and its display mode.
144
+ * @returns An optional teardown invoked on unmount, after every `onUnmount` has settled.
145
+ */
146
+ onMount?(context: ExperiencePluginContext): void | (() => void);
147
+ /**
148
+ * Runs before the feature unmounts; may return a promise to defer teardown until an exit animation finishes.
149
+ *
150
+ * A rejected promise (or thrown error) surfaces as an `error` event on the
151
+ * shell and teardown continues with the remaining plugins.
152
+ *
153
+ * @param context - The mounted element and its display mode.
154
+ * @returns Optionally a promise the shell awaits before tearing down.
155
+ */
156
+ onUnmount?(context: ExperiencePluginContext): void | Promise<void>;
157
+ }
81
158
  /**
82
159
  * Options accepted by the host-side {@link FeatureContract} consumer when
83
160
  * creating or opening a shell.
@@ -87,7 +164,12 @@ interface ShellOptions {
87
164
  container: string | HTMLElement;
88
165
  /** Stable identifier for the feature; seeds the broker name surfaced in debug logs. */
89
166
  name?: string;
90
- /** Contract describing the feature's actions; replaces the generic default when provided. */
167
+ /**
168
+ * The feature's contract exactly as the feature authored it (`emitted` = what
169
+ * the feature sends, `accepted` = what the feature handles). The shell
170
+ * derives the host-side orientation itself — hand it the feature's contract,
171
+ * never a pre-swapped copy. Replaces the generic default when provided.
172
+ */
91
173
  contract?: FeatureContract;
92
174
  /** How the feature should be surfaced; defaults to {@link DisplayMode.Embedded}. */
93
175
  displayMode?: DisplayMode;
@@ -109,6 +191,8 @@ interface ShellOptions {
109
191
  protocol?: SecurityProtocol;
110
192
  /** Pre-shared key used by the `v2` protocol. */
111
193
  sharedKey?: string;
194
+ /** Experience plugins wrapped around each mount/unmount; `onMount` runs in registration order, `onUnmount` in reverse. */
195
+ plugins?: readonly ExperiencePlugin[];
112
196
  }
113
197
  /**
114
198
  * Options accepted by the hostee-side feature factory.
@@ -163,6 +247,8 @@ interface ResolvedFeatureConfig extends FeatureConfig {
163
247
  url: string;
164
248
  /** Default display options baked into the connector as the feature's defaults. */
165
249
  display?: DisplayDefaults;
250
+ /** Security envelope the build resolved; baked into the generated connector as its default. */
251
+ protocol?: SecurityProtocol;
166
252
  }
167
253
  /**
168
254
  * A single feature app entry served by the dev server.
@@ -230,7 +316,7 @@ declare const defineDevConfig: (config: DevConfig) => DevConfig;
230
316
  *
231
317
  * @param contract - The candidate contract, typically parsed from disk.
232
318
  * @returns The validated contract, typed.
233
- * @throws {Error} When the value is not an object, or any action is malformed.
319
+ * @throws {Error} When the value is not an object, any action is malformed, or a `respondsWith` names no action in the other direction.
234
320
  *
235
321
  * @example Validating a parsed contract file
236
322
  * ```typescript
@@ -279,4 +365,4 @@ declare const sdkInfo: {
279
365
  };
280
366
 
281
367
  export { DisplayMode, defineConfig, defineDevConfig, sdkInfo, validateContract, validateFeatureConfig, validatePayload };
282
- export type { ActionDescription, DevConfig, DisplayDefaults, EmbedSizing, FeatureConfig, FeatureContract, FeatureOptions, ResolvedFeatureConfig, SecurityProtocol, ShellOptions, UnresponsiveInfo, UnresponsivePolicy };
368
+ export type { ActionDescription, DevConfig, DisplayDefaults, EmbedSizing, ExperiencePlugin, ExperiencePluginContext, FeatureConfig, FeatureContract, FeatureOptions, RequestHandler, RequestOptions, ResolvedFeatureConfig, SecurityProtocol, ShellOptions, UnresponsiveInfo, UnresponsivePolicy };
package/index.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../libs/features/src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,YAAY,EACV,iBAAiB,EACjB,SAAS,EACT,eAAe,EACf,WAAW,EACX,aAAa,EACb,eAAe,EACf,cAAc,EACd,qBAAqB,EACrB,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAC5F,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../libs/features/src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AACtE,YAAY,EACV,iBAAiB,EACjB,SAAS,EACT,eAAe,EACf,WAAW,EACX,gBAAgB,EAChB,uBAAuB,EACvB,aAAa,EACb,eAAe,EACf,cAAc,EACd,qBAAqB,EACrB,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAC5F,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA"}
package/index.esm.js CHANGED
@@ -37,6 +37,31 @@ function collectActionListIssues(actions, field, issues) {
37
37
  if (typeof action['type'] !== 'string' || action['type'].length === 0) {
38
38
  issues.push(`"${field}[${index}]" must have a non-empty string "type".`);
39
39
  }
40
+ if (action['respondsWith'] !== undefined && (typeof action['respondsWith'] !== 'string' || action['respondsWith'].length === 0)) {
41
+ issues.push(`"${field}[${index}]" has a "respondsWith" that must be a non-empty string.`);
42
+ }
43
+ });
44
+ }
45
+ /**
46
+ * Collects every `respondsWith` reference that does not name an action in the other direction.
47
+ *
48
+ * A request emitted by one side is answered by an action the same side accepts (and
49
+ * vice versa), so each `respondsWith` must resolve across the contract's directions.
50
+ *
51
+ * @param actions - The already well-formed action list to check.
52
+ * @param field - The field name of `actions`, used to locate problems in messages.
53
+ * @param other - The action list of the opposite direction.
54
+ * @param otherField - The field name of `other`, used in messages.
55
+ * @param issues - The running list of human-readable problems, appended to in place.
56
+ */
57
+ function collectRespondsWithIssues(actions, field, other, otherField, issues) {
58
+ actions.forEach((action, index) => {
59
+ if (action.respondsWith === undefined) {
60
+ return;
61
+ }
62
+ if (!other.some((candidate) => candidate.type === action.respondsWith)) {
63
+ issues.push(`"${field}[${index}]" responds with "${action.respondsWith}", but "${otherField}" has no action of that type.`);
64
+ }
40
65
  });
41
66
  }
42
67
  /**
@@ -62,7 +87,7 @@ function describeType(value) {
62
87
  *
63
88
  * @param contract - The candidate contract, typically parsed from disk.
64
89
  * @returns The validated contract, typed.
65
- * @throws {Error} When the value is not an object, or any action is malformed.
90
+ * @throws {Error} When the value is not an object, any action is malformed, or a `respondsWith` names no action in the other direction.
66
91
  *
67
92
  * @example Validating a parsed contract file
68
93
  * ```typescript
@@ -77,6 +102,12 @@ function validateContract(contract) {
77
102
  const issues = [];
78
103
  collectActionListIssues(contract['emitted'], 'emitted', issues);
79
104
  collectActionListIssues(contract['accepted'], 'accepted', issues);
105
+ if (issues.length === 0) {
106
+ const emitted = contract['emitted'];
107
+ const accepted = contract['accepted'];
108
+ collectRespondsWithIssues(emitted, 'emitted', accepted, 'accepted', issues);
109
+ collectRespondsWithIssues(accepted, 'accepted', emitted, 'emitted', issues);
110
+ }
80
111
  if (issues.length > 0) {
81
112
  throw createError(`Invalid contract:\n${issues.map((issue) => ` - ${issue}`).join('\n')}`);
82
113
  }