@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/cli/usage.d.ts CHANGED
@@ -4,5 +4,5 @@
4
4
  * Documents the three commands and the shared flag surface so the headless
5
5
  * (`--ci`) path is discoverable without reading the docs.
6
6
  */
7
- export declare const USAGE = "@hyperfrontend/features \u2014 build, embed, and orchestrate micro-frontend features\n\nUsage: hf <command> [options]\n\nCommands:\n init Scaffold the feature glue module and wire it into your app\n build Generate the host connector, bundle it, and pack a tarball\n dev Resolve the dev-server config and start the debug UI\n\nOptions:\n --name <name> Feature name\n --version <version> Feature version\n --contract <path> Path to the *.contract.{json,ts,js} file\n --entry <path> Entry file to wire the glue import into (init)\n --url <url> URL the connector loads the feature from (build)\n --protocol <none|v1|v2> Security envelope enforced at build time\n --out <dir> Output directory for the built connector (build)\n --apps <path> Path to the dev-server apps array (dev)\n --port <number> Port the dev-server debug UI listens on (dev)\n --config <path> Path to the whole feature.config.* / hf-dev.config.* file\n --cwd <dir> Working-directory override\n --ci, --yes Run headlessly; error on any unresolved required key\n --dry-run Preview file changes without writing them\n -h, --help Print this help\n";
7
+ export declare const USAGE = "@hyperfrontend/features \u2014 build, embed, and orchestrate micro-frontend features\n\nUsage: hf <command> [options]\n\nCommands:\n init Scaffold the feature glue module and wire it into your app\n build Generate the host connector, bundle it, and pack a tarball\n dev Start the app servers and debug UI, serving until Ctrl-C\n\nOptions:\n --name <name> Feature name\n --version <version> Feature version\n --contract <path> Path to the *.contract.{json,ts,js} file\n --entry <path> Entry file to wire the glue import into (init)\n --url <url> URL the connector loads the feature from (build)\n --protocol <none|v1|v2> Security envelope enforced at build time\n --allow-open Acknowledge an explicit '--protocol none' and build an open, unauthenticated connector (build)\n --out <dir> Output directory for the built connector (build)\n --apps <path> Path to the dev-server apps array (dev)\n --port <number> Port the dev-server debug UI listens on (dev)\n --config <path> Path to the whole feature.config.* / hf-dev.config.* file\n --cwd <dir> Working-directory override\n --ci, --yes Run headlessly; error on any unresolved required key\n --dry-run Preview file changes without writing them\n -h, --help Print this help\n";
8
8
  //# sourceMappingURL=usage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../../../../../../../libs/features/src/cli/usage.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,KAAK,+tCAwBjB,CAAA"}
1
+ {"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../../../../../../../libs/features/src/cli/usage.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,KAAK,21CAyBjB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"generate-feature-module.d.ts","sourceRoot":"","sources":["../../../../../../../../libs/features/src/generators/feature/generate-feature-module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kCAAkC,CAAA;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAmDhF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,EAAE,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAEhH"}
1
+ {"version":3,"file":"generate-feature-module.d.ts","sourceRoot":"","sources":["../../../../../../../../libs/features/src/generators/feature/generate-feature-module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kCAAkC,CAAA;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AA2DhF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,EAAE,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAEhH"}
@@ -3,6 +3,7 @@
3
3
  const index_cjs_js = require('../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/array/index.cjs.js');
4
4
  const index_cjs_js$2 = require('../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/json/index.cjs.js');
5
5
  const index_cjs_js$1 = require('../_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/object/index.cjs.js');
6
+ const node_path = require('node:path');
6
7
  const index_cjs_js$3 = require('../_dependencies/@hyperfrontend/project-scope/vfs/index.cjs.js');
7
8
  const index_cjs_js$4 = require('../_dependencies/@hyperfrontend/versioning/semver/format/index.cjs.js');
8
9
  const index_cjs_js$5 = require('../_dependencies/@hyperfrontend/versioning/semver/parse/index.cjs.js');
@@ -83,25 +84,32 @@ export default contract
83
84
  // note: Write-once target; re-runs never clobber handlers the author has filled in.
84
85
  const MODULE_PATH = 'src/hyperfrontend.feature.ts';
85
86
  /**
86
- * Derives the extensionless, relative import specifier for the contract module.
87
+ * Derives the extensionless import specifier for the contract module, computed
88
+ * relative to the directory the integration module is written into.
87
89
  *
88
- * @param contractPath - The config's contract path (e.g. `contracts/clock.contract.json`).
89
- * @returns A relative specifier suitable for an `import` statement.
90
+ * @param contractPath - The config's contract path, tree-root-relative or absolute.
91
+ * @param treeRoot - Absolute root of the tree the integration module is staged into.
92
+ * @returns A POSIX-style relative specifier suitable for an `import` statement.
90
93
  */
91
- function toContractImportPath(contractPath) {
94
+ function toContractImportPath(contractPath, treeRoot) {
92
95
  const withoutExtension = contractPath.replace(/\.(json|ts|js)$/, '');
93
- return /^[./]/.test(withoutExtension) ? withoutExtension : `./${withoutExtension}`;
96
+ const target = node_path.isAbsolute(withoutExtension) ? withoutExtension : node_path.join(treeRoot, withoutExtension);
97
+ const specifier = node_path.relative(node_path.join(treeRoot, node_path.dirname(MODULE_PATH)), target)
98
+ .split('\\')
99
+ .join('/');
100
+ return specifier.startsWith('.') ? specifier : `./${specifier}`;
94
101
  }
95
102
  /**
96
103
  * Builds the feature integration module source from the contract.
97
104
  *
98
105
  * @param config - The resolved feature config naming the feature and contract path.
99
106
  * @param contract - The validated contract whose actions drive the scaffolded stubs.
107
+ * @param treeRoot - Absolute root the config's contract path is relative to.
100
108
  * @returns The integration module source as a string.
101
109
  */
102
- function buildFeatureModule(config, contract) {
110
+ function buildFeatureModule(config, contract, treeRoot) {
103
111
  const handlers = contract.accepted
104
- .map((action) => `feature.on('${action.type}', (data) => {\n // todo: handle ${action.type}\n})`)
112
+ .map((action) => `feature.on('${action.type}', (_data: unknown) => {\n // todo: handle ${action.type}\n})`)
105
113
  .join('\n\n');
106
114
  const emits = contract.emitted.map((action) => `// feature.send('${action.type}', undefined)`).join('\n');
107
115
  return `/**
@@ -114,7 +122,7 @@ function buildFeatureModule(config, contract) {
114
122
  * @module ${config.name}.feature
115
123
  */
116
124
  import { createFeature } from '@hyperfrontend/features/hostee'
117
- import contract from '${toContractImportPath(config.contract)}'
125
+ import contract from '${toContractImportPath(config.contract, treeRoot)}'
118
126
 
119
127
  /** The ${config.name} feature handle; use it to send and receive contract actions. */
120
128
  export const feature = createFeature({ name: '${config.name}', contract })
@@ -143,7 +151,7 @@ ${emits}
143
151
  * ```
144
152
  */
145
153
  function generateFeatureModule(config, contract, tree) {
146
- tree.write(MODULE_PATH, buildFeatureModule(config, contract), { mode: index_cjs_js$3.Mode.SkipIfExists });
154
+ tree.write(MODULE_PATH, buildFeatureModule(config, contract, tree.root), { mode: index_cjs_js$3.Mode.SkipIfExists });
147
155
  }
148
156
 
149
157
  // note: Human- and registry-facing sidecar; the bundled connector inlines its own copy of the contract.
@@ -152,16 +160,16 @@ const METADATA_PATH = 'metadata.json';
152
160
  * Stages the connector's `metadata.json` describing the feature and its contract.
153
161
  *
154
162
  * Stamps a canonical version string via `@hyperfrontend/versioning` and embeds
155
- * the contract so humans and the registry can inspect the feature without
156
- * unpacking the bundle.
163
+ * the contract and the baked security protocol so humans and the registry can
164
+ * inspect the feature without unpacking the bundle.
157
165
  *
158
- * @param config - The resolved feature config supplying name, version, and URL.
166
+ * @param config - The resolved feature config supplying name, version, URL, and protocol.
159
167
  * @param contract - The validated contract embedded for inspection.
160
168
  * @param tree - The VFS tree the metadata file is staged into.
161
169
  *
162
170
  * @example Staging metadata for the clock feature
163
171
  * ```typescript
164
- * generateMetadata({ name: 'clock', version: '1.0.0', contract: './clock.contract.json', url: '/clock' }, contract, tree)
172
+ * generateMetadata({ name: 'clock', version: '1.0.0', contract: './clock.contract.json', url: '/clock', protocol: 'v2' }, contract, tree)
165
173
  * ```
166
174
  */
167
175
  function generateMetadata(config, contract, tree) {
@@ -170,6 +178,7 @@ function generateMetadata(config, contract, tree) {
170
178
  version: index_cjs_js$4.format(index_cjs_js$5.parseVersionStrict(config.version)),
171
179
  url: config.url,
172
180
  contract,
181
+ ...(config.protocol !== undefined && { protocol: config.protocol }),
173
182
  generatedBy: '@hyperfrontend/features',
174
183
  };
175
184
  tree.write(METADATA_PATH, `${index_cjs_js$2.stringify(metadata, null, 2)}\n`);
@@ -218,6 +227,11 @@ function isIdentifier(key) {
218
227
  *
219
228
  * @param value - The string to render.
220
229
  * @returns A single-quoted, safely escaped string literal.
230
+ *
231
+ * @example Quoting a string containing a single quote
232
+ * ```typescript
233
+ * quoteString("it's") // => "'it\\'s'"
234
+ * ```
221
235
  */
222
236
  function quoteString(value) {
223
237
  // how: JSON encoding already escapes backslashes and control chars; we scan it to swap quote conventions (unescape \" to ", escape ' to \') while copying every other escape verbatim, so an input backslash stays intact.
@@ -244,6 +258,12 @@ function quoteString(value) {
244
258
  *
245
259
  * @param key - The property name.
246
260
  * @returns The key as written in an object literal.
261
+ *
262
+ * @example Formatting identifier and non-identifier keys
263
+ * ```typescript
264
+ * formatKey('url') // => 'url'
265
+ * formatKey('time-zone') // => "'time-zone'"
266
+ * ```
247
267
  */
248
268
  function formatKey(key) {
249
269
  return isIdentifier(key) ? key : quoteString(key);
@@ -291,43 +311,357 @@ function toSourceLiteral(value, indent = '') {
291
311
  return index_cjs_js$2.stringify(value);
292
312
  }
293
313
 
314
+ // note: JSON-schema scalar types mapped straight to their TypeScript equivalents; `integer` narrows to `number`.
315
+ const PRIMITIVES = {
316
+ string: 'string',
317
+ number: 'number',
318
+ integer: 'number',
319
+ boolean: 'boolean',
320
+ null: 'null',
321
+ };
322
+ /**
323
+ * Narrows an unknown value to a plain record.
324
+ *
325
+ * @param value - The value to test.
326
+ * @returns `true` when the value is a non-null, non-array object.
327
+ */
328
+ function isRecord(value) {
329
+ return typeof value === 'object' && value !== null && !index_cjs_js.isArray(value);
330
+ }
331
+ /**
332
+ * Renders a JSON scalar as a TypeScript literal type.
333
+ *
334
+ * @param value - The candidate literal.
335
+ * @returns The literal type source, or `null` for a non-scalar value.
336
+ */
337
+ function literalType(value) {
338
+ if (value === null) {
339
+ return 'null';
340
+ }
341
+ if (typeof value === 'string') {
342
+ return quoteString(value);
343
+ }
344
+ if (typeof value === 'number' || typeof value === 'boolean') {
345
+ return index_cjs_js$2.stringify(value);
346
+ }
347
+ return null;
348
+ }
349
+ /**
350
+ * Renders an `enum` member list as a union of literal types.
351
+ *
352
+ * @param members - The allowed values listed by the schema.
353
+ * @returns The union source, or `unknown` when any member is not a scalar.
354
+ */
355
+ function enumType(members) {
356
+ const literals = members.map(literalType);
357
+ // why: One non-scalar member would poison the union with `unknown`, which silently absorbs the useful literals — better to fall back for the whole enum.
358
+ return literals.every((literal) => literal !== null) ? literals.join(' | ') : 'unknown';
359
+ }
360
+ /**
361
+ * Renders an `object` schema as an inline type literal.
362
+ *
363
+ * @param schema - The schema record with optional `properties` and `required`.
364
+ * @param indent - The current indentation prefix.
365
+ * @returns The object type source; `Record<string, unknown>` without properties.
366
+ */
367
+ function objectType(schema, indent) {
368
+ const properties = isRecord(schema['properties']) ? index_cjs_js$1.entries(schema['properties']) : [];
369
+ if (properties.length === 0) {
370
+ return 'Record<string, unknown>';
371
+ }
372
+ const required = index_cjs_js.isArray(schema['required']) ? schema['required'] : [];
373
+ const inner = `${indent} `;
374
+ const members = properties.map(([key, member]) => `${inner}${formatKey(key)}${required.includes(key) ? '' : '?'}: ${schemaToType(member, inner)}`);
375
+ return `{\n${members.join('\n')}\n${indent}}`;
376
+ }
377
+ /**
378
+ * Renders an `array` schema as an element-type array.
379
+ *
380
+ * @param schema - The schema record with an optional single `items` schema.
381
+ * @param indent - The current indentation prefix.
382
+ * @returns The array type source; `unknown[]` without a single `items` schema.
383
+ */
384
+ function arrayType(schema, indent) {
385
+ const items = schema['items'];
386
+ if (!isRecord(items)) {
387
+ return 'unknown[]';
388
+ }
389
+ const element = schemaToType(items, indent);
390
+ return element.includes('|') ? `(${element})[]` : `${element}[]`;
391
+ }
392
+ /**
393
+ * Projects a JSON-schema-like payload description into TypeScript type source.
394
+ *
395
+ * Bounded mapping: scalar `type`s, `object` with `properties`/`required`,
396
+ * `array` with a single `items` schema, `enum`, and `const` are projected;
397
+ * anything else (including a missing schema) falls back to `unknown`, so a
398
+ * generated type is never wrong — at worst it is loose.
399
+ *
400
+ * @param schema - The schema value to project.
401
+ * @param indent - Indentation prefix applied to nested object members.
402
+ * @returns The TypeScript type as source text.
403
+ *
404
+ * @example Projecting an action payload schema
405
+ * ```typescript
406
+ * schemaToType({ type: 'object', properties: { tz: { type: 'string' } }, required: ['tz'] })
407
+ * // => '{\n tz: string\n}'
408
+ * ```
409
+ */
410
+ function schemaToType(schema, indent = '') {
411
+ if (!isRecord(schema)) {
412
+ return 'unknown';
413
+ }
414
+ const enumMembers = schema['enum'];
415
+ if (index_cjs_js.isArray(enumMembers) && enumMembers.length > 0) {
416
+ return enumType(enumMembers);
417
+ }
418
+ if ('const' in schema) {
419
+ return literalType(schema['const']) ?? 'unknown';
420
+ }
421
+ const type = schema['type'];
422
+ if (typeof type !== 'string') {
423
+ return 'unknown';
424
+ }
425
+ const primitive = PRIMITIVES[type];
426
+ if (primitive !== undefined) {
427
+ return primitive;
428
+ }
429
+ if (type === 'object') {
430
+ return objectType(schema, indent);
431
+ }
432
+ if (type === 'array') {
433
+ return arrayType(schema, indent);
434
+ }
435
+ return 'unknown';
436
+ }
437
+
438
+ // note: The generated declarations are structural (no type imports from the SDK), so the connector's d.ts stays fully usable for consumers who install nothing but the packed tarball.
439
+ const STATIC_TYPES = `/** How the feature is surfaced by the host. */
440
+ export type FeatureDisplayMode = 'embedded' | 'dialog' | 'popup' | 'standalone'
441
+
442
+ /** Security envelope selector negotiated between host and feature. */
443
+ export type FeatureSecurityProtocol = 'none' | 'v1' | 'v2'
444
+
445
+ /** Context handed to an experience plugin around the feature's mount lifecycle. */
446
+ export interface FeaturePluginContext {
447
+ /** In-document root the display mode mounted, or \`null\` for windowed modes. */
448
+ element: HTMLElement | null
449
+ /** The display mode the feature was surfaced in. */
450
+ displayMode: FeatureDisplayMode
451
+ }
452
+
453
+ /** Opt-in extension that decorates the feature's mount lifecycle (e.g. transitions). */
454
+ export interface FeatureExperiencePlugin {
455
+ /** Unique plugin name, surfaced in debug logs. */
456
+ name: string
457
+ /**
458
+ * Runs after the feature mounts; may animate it in and return a teardown.
459
+ *
460
+ * @param context - The mounted element and its display mode.
461
+ * @returns An optional teardown invoked on unmount.
462
+ */
463
+ onMount?(context: FeaturePluginContext): void | (() => void)
464
+ /**
465
+ * Runs before the feature unmounts; may defer teardown until an exit animation finishes.
466
+ *
467
+ * @param context - The mounted element and its display mode.
468
+ * @returns Optionally a promise the shell awaits before tearing down.
469
+ */
470
+ onUnmount?(context: FeaturePluginContext): void | Promise<void>
471
+ }
472
+
473
+ /** Details handed to an unresponsive-feature callback when the feature stops responding. */
474
+ export interface FeatureUnresponsiveInfo {
475
+ /** Consecutive missed heartbeats that tripped the watchdog. */
476
+ missedBeats: number
477
+ /** Timestamp (ms) of the last heartbeat received, or \`null\` if none ever arrived. */
478
+ lastBeatAt: number | null
479
+ /** The display mode the unresponsive feature was using. */
480
+ displayMode: FeatureDisplayMode
481
+ /** Closes the feature gracefully. */
482
+ close(): void
483
+ /** Closes the feature and releases all resources. */
484
+ destroy(): void
485
+ }
486
+
487
+ /**
488
+ * Options accepted by \`createFeatureShell\`; anything omitted falls back to the
489
+ * defaults baked in from the feature's build.
490
+ */
491
+ export interface FeatureShellOptions {
492
+ /** Target element (or CSS selector) the embedded feature mounts into. */
493
+ container: string | HTMLElement
494
+ /** Stable identifier for the feature; seeds the broker name surfaced in debug logs. */
495
+ name?: string
496
+ /** How the feature should be surfaced; defaults to \`embedded\`. */
497
+ displayMode?: FeatureDisplayMode
498
+ /** URL of the feature app to load; defaults to the URL baked in from the feature config. */
499
+ url?: string
500
+ /** How an embedded feature is sized; defaults to \`fill\` (the iframe fills its container). */
501
+ embedSizing?: 'fill' | 'content'
502
+ /** How the host reacts when the feature stops responding; defaults to \`emit\`. */
503
+ onUnresponsive?: 'emit' | 'unmount' | ((info: FeatureUnresponsiveInfo) => void)
504
+ /** Whether pressing Escape closes the shell; defaults to \`true\`. */
505
+ closeOnEscape?: boolean
506
+ /** Dialog width in pixels (dialog mode only). */
507
+ dialogWidth?: number
508
+ /** Dialog height in pixels (dialog mode only). */
509
+ dialogHeight?: number
510
+ /** Whether the dialog renders a dimmed backdrop; defaults to \`true\`. */
511
+ dialogOverlay?: boolean
512
+ /** Security envelope to negotiate; defaults to the protocol baked in from the feature's build. */
513
+ protocol?: FeatureSecurityProtocol
514
+ /** Pre-shared key used by the \`v2\` protocol; always supplied by the host, never baked into the connector. */
515
+ sharedKey?: string
516
+ /** Experience plugins wrapped around each mount/unmount. */
517
+ plugins?: readonly FeatureExperiencePlugin[]
518
+ }
519
+
520
+ /** Handle returned by \`createFeatureShell\`, narrowed to the feature's contract. */
521
+ export interface FeatureShellHandle {
522
+ /**
523
+ * Mounts the feature using the merged baked defaults and call-time options.
524
+ *
525
+ * @param options - Per-open overrides layered over the baked and create-time options.
526
+ */
527
+ open(options?: Partial<FeatureShellOptions>): void
528
+ /** Closes the feature gracefully, disconnecting the messaging channel. */
529
+ close(): void
530
+ /** Closes the feature and releases all resources (channel and DOM). */
531
+ destroy(): void
532
+ /**
533
+ * Sends a contract action to the feature.
534
+ *
535
+ * @param type - Action type from the feature contract's accepted list.
536
+ * @param data - Payload matching the action's schema.
537
+ */
538
+ send<T extends HostSendType>(type: T, data?: HostSendPayloads[T]): void
539
+ /**
540
+ * Subscribes to a feature event with its contract-typed payload.
541
+ *
542
+ * @param event - Event type from the feature contract's emitted list.
543
+ * @param handler - Callback invoked with the typed event payload.
544
+ * @returns A function that removes this subscription.
545
+ */
546
+ on<T extends HostEventType>(event: T, handler: (data: HostEventPayloads[T]) => void): () => void
547
+ /**
548
+ * Subscribes to a shell lifecycle event.
549
+ *
550
+ * @param event - Lifecycle event name.
551
+ * @param handler - Callback invoked when the event fires.
552
+ * @returns A function that removes this subscription.
553
+ */
554
+ on(event: 'open' | 'close' | 'error', handler: (data?: unknown) => void): () => void
555
+ /** Whether the feature channel is currently open (\`true\` while connected). */
556
+ readonly isOpen: boolean
557
+ }
558
+ `;
559
+ /**
560
+ * Escapes a contract description for safe embedding inside a JSDoc comment.
561
+ *
562
+ * @param description - The raw description text.
563
+ * @returns The text with any comment terminator defused.
564
+ */
565
+ function escapeDoc(description) {
566
+ return description.split('*/').join('*\\/');
567
+ }
568
+ /**
569
+ * Renders one payload-map member for an action, with its description as JSDoc.
570
+ *
571
+ * @param action - The contract action to render.
572
+ * @returns The interface member source.
573
+ */
574
+ function buildMember(action) {
575
+ const doc = action.description === undefined ? '' : ` /** ${escapeDoc(action.description)} */\n`;
576
+ return `${doc} ${formatKey(action.type)}: ${schemaToType(action.schema, ' ')}`;
577
+ }
578
+ /**
579
+ * Renders a payload-map interface keyed by action type.
580
+ *
581
+ * @param name - The interface identifier.
582
+ * @param doc - The single-line JSDoc summary.
583
+ * @param actions - The contract actions projected into members.
584
+ * @returns The interface declaration source.
585
+ */
586
+ function buildPayloadInterface(name, doc, actions) {
587
+ const body = actions.length === 0 ? '' : `\n${actions.map(buildMember).join('\n')}\n`;
588
+ return `/** ${doc} */\nexport interface ${name} {${body}}`;
589
+ }
590
+ /**
591
+ * Builds the connector's full generated type surface for a feature contract.
592
+ *
593
+ * Emits payload maps and literal action-name unions projected from the
594
+ * contract, plus structural shell option and handle types, so the connector's
595
+ * declarations resolve with no dependencies beyond the DOM lib.
596
+ *
597
+ * @param contract - The feature contract driving the projected types.
598
+ * @returns The type declaration block for the connector entry.
599
+ *
600
+ * @example Projecting the clock contract
601
+ * ```typescript
602
+ * buildConnectorTypes({ emitted: [{ type: 'timeUpdated' }], accepted: [{ type: 'setTimezone' }] })
603
+ * // => source containing "export interface HostSendPayloads { setTimezone: unknown }"
604
+ * ```
605
+ */
606
+ function buildConnectorTypes(contract) {
607
+ return `${buildPayloadInterface('HostSendPayloads', 'Payloads for actions the host can send, keyed by action type from the feature contract.', contract.accepted)}
608
+
609
+ ${buildPayloadInterface('HostEventPayloads', 'Payloads for events the feature emits to the host, keyed by event type from the feature contract.', contract.emitted)}
610
+
611
+ /** Action types the host can send to the feature. */
612
+ export type HostSendType = keyof HostSendPayloads
613
+
614
+ /** Event types the feature emits to the host. */
615
+ export type HostEventType = keyof HostEventPayloads
616
+
617
+ ${STATIC_TYPES}`;
618
+ }
619
+
294
620
  const ENTRY_PATH = 'src/index.ts';
295
621
  const PACKAGE_PATH = 'package.json';
296
622
  const README_PATH = 'README.md';
297
623
  /**
298
624
  * Builds the baked-in default shell options from the resolved config.
299
625
  *
300
- * @param config - The resolved feature config supplying the URL and display defaults.
626
+ * @param config - The resolved feature config supplying the URL, protocol, and display defaults.
301
627
  * @returns A record of options the connector merges under host-supplied overrides.
302
628
  */
303
629
  function buildDefaults(config) {
304
- return { url: config.url, ...(config.display ?? {}) };
630
+ return {
631
+ url: config.url,
632
+ ...(config.protocol !== undefined && { protocol: config.protocol }),
633
+ ...(config.display ?? {}),
634
+ };
305
635
  }
306
636
  /**
307
- * Builds the connector's TypeScript entry source with the contract inlined.
637
+ * Builds the connector's TypeScript entry source with the contract inlined and
638
+ * the contract-projected types exported.
308
639
  *
309
640
  * @param config - The resolved feature config naming the feature and its URL.
310
641
  * @param contract - The validated contract inlined into the connector.
311
642
  * @returns The entry module source as a string.
312
643
  */
313
644
  function buildConnectorEntry(config, contract) {
314
- return `import type { ShellHandle, ShellOptions } from '@hyperfrontend/features/host'
315
- import { createShell } from '@hyperfrontend/features/host'
645
+ return `import { createShell } from '@hyperfrontend/features/host'
316
646
 
317
- /** Inlined contract describing the ${config.name} feature's actions. */
647
+ ${buildConnectorTypes(contract)}
648
+ /** Inlined contract describing the ${config.name} feature's actions, exactly as the feature authored it. */
318
649
  const contract = ${toSourceLiteral(contract)}
319
650
 
320
- /** Default shell options baked in from the feature config. */
321
- const defaults = ${toSourceLiteral(buildDefaults(config))}
651
+ /** Default shell options baked in from the feature's build. */
652
+ const defaults = <const>${toSourceLiteral(buildDefaults(config))}
322
653
 
323
654
  /**
324
655
  * Creates a host-side shell for the ${config.name} feature.
325
656
  *
657
+ * The feature's contract and build-time defaults are baked in, and \`send\`/\`on\`
658
+ * are typed from the contract's actions.
659
+ *
326
660
  * @param options - Host-supplied options (at minimum a \`container\`); these override the baked defaults.
327
- * @returns A shell handle exposing \`open\`, \`close\`, \`destroy\`, \`send\`, \`on\`, and \`isOpen\`.
661
+ * @returns A typed shell handle exposing \`open\`, \`close\`, \`destroy\`, \`send\`, \`on\`, and \`isOpen\`.
328
662
  */
329
- export function createFeatureShell(options: ShellOptions): ShellHandle {
330
- return createShell({ ...defaults, ...options, contract })
663
+ export function createFeatureShell(options: FeatureShellOptions): FeatureShellHandle {
664
+ return <FeatureShellHandle>createShell({ ...defaults, ...options, contract })
331
665
  }
332
666
  `;
333
667
  }
@@ -350,49 +684,108 @@ function buildConnectorPackageJson(config) {
350
684
  };
351
685
  return `${index_cjs_js$2.stringify(manifest, null, 2)}\n`;
352
686
  }
687
+ /**
688
+ * Builds the open-connector warning block for a protocol-`none` build.
689
+ *
690
+ * @param config - The resolved feature config carrying the baked protocol.
691
+ * @returns The warning block, or an empty string for secured builds.
692
+ */
693
+ function buildReadmeWarning(config) {
694
+ if (config.protocol !== 'none') {
695
+ return '';
696
+ }
697
+ return `> **Warning: open connector.** This build uses protocol \`none\`: messages between host and feature travel with no security envelope, so any page that can reach the feature URL can embed and drive it. For production, rebuild the feature with \`--protocol v1\` or \`--protocol v2\`.
698
+
699
+ `;
700
+ }
701
+ /**
702
+ * Builds the security paragraph matching the baked protocol.
703
+ *
704
+ * @param config - The resolved feature config carrying the baked protocol.
705
+ * @returns The security guidance paragraph.
706
+ */
707
+ function buildReadmeSecurity(config) {
708
+ if (config.protocol === 'v2') {
709
+ return "The `v2` security envelope is baked in from the feature's build — do not pass `protocol` yourself. Supply your own pre-shared key via `sharedKey`; a key is never baked into the artifact.";
710
+ }
711
+ if (config.protocol === 'v1') {
712
+ return "The `v1` security envelope is baked in from the feature's build — do not pass `protocol` yourself.";
713
+ }
714
+ if (config.protocol === 'none') {
715
+ return 'This connector was deliberately built open (see the warning above); harden it by rebuilding the feature with a security protocol.';
716
+ }
717
+ return "No security envelope is baked into this connector; pass `protocol: 'v1'` or `protocol: 'v2'` (with your own `sharedKey` for `v2`) when creating the shell.";
718
+ }
719
+ /**
720
+ * Builds the option lines shown inside the quick-start `createFeatureShell` call.
721
+ *
722
+ * @param config - The resolved feature config carrying the baked protocol.
723
+ * @returns Extra option lines, each newline-prefixed, or an empty string.
724
+ */
725
+ function buildReadmeOptions(config) {
726
+ if (config.protocol === 'v2') {
727
+ return "\n sharedKey: 'your-pre-shared-key',";
728
+ }
729
+ if (config.protocol === undefined) {
730
+ return "\n protocol: 'v2',\n sharedKey: 'your-pre-shared-key',";
731
+ }
732
+ return '';
733
+ }
734
+ /**
735
+ * Builds the typed messaging example lines from the contract's first actions.
736
+ *
737
+ * @param contract - The validated feature contract.
738
+ * @returns Messaging example lines ending in a newline, or an empty string.
739
+ */
740
+ function buildReadmeMessaging(contract) {
741
+ const sent = contract.accepted[0];
742
+ const received = contract.emitted[0];
743
+ const sendLine = sent === undefined ? '' : `shell.send('${sent.type}', data) // typed: the payload shape comes from the feature contract\n`;
744
+ const onLine = received === undefined ? '' : `shell.on('${received.type}', (data) => console.log(data)) // typed: data follows the contract\n`;
745
+ return `${sendLine}${onLine}`;
746
+ }
353
747
  /**
354
748
  * Builds the connector's `README.md` documenting the generated install + usage.
355
749
  *
356
750
  * @param config - The resolved feature config naming the feature.
751
+ * @param contract - The validated feature contract driving the typed examples.
357
752
  * @returns The README contents as a string.
358
753
  */
359
- function buildConnectorReadme(config) {
754
+ function buildConnectorReadme(config, contract) {
360
755
  return `# ${config.name}-shell
361
756
 
362
- Generated host connector for the **${config.name}** feature. Self-contained — install it and embed the feature with one call.
757
+ Generated host connector for the **${config.name}** feature. Self-contained — install it and embed the feature with one call. \`send\` and \`on\` are typed from the feature's contract.
363
758
 
364
- \`\`\`typescript
759
+ ${buildReadmeWarning(config)}\`\`\`typescript
365
760
  import { createFeatureShell } from '${config.name}-shell'
366
761
 
367
762
  const shell = createFeatureShell({
368
- container: '#${config.name}',
369
- // Security envelope: 'none' (default) | 'v1' | 'v2'.
370
- // 'v2' authenticates the channel with a pre-shared key.
371
- protocol: 'v2',
372
- sharedKey: 'your-pre-shared-key',
763
+ container: '#${config.name}',${buildReadmeOptions(config)}
373
764
  })
374
765
 
375
766
  // Lifecycle events ('open', 'close', 'error'); on() returns an unsubscribe fn.
376
767
  const unsubscribe = shell.on('open', () => console.log('connected'))
377
768
 
378
- shell.open() // mount the feature in its display mode
379
- shell.send('setTimezone', { tz: 'UTC' }) // send an action the feature accepts
380
- console.log(shell.isOpen) // current connection state
769
+ shell.open() // mount the feature in its display mode
770
+ ${buildReadmeMessaging(contract)}console.log(shell.isOpen) // current connection state
381
771
 
382
772
  unsubscribe()
383
- shell.close() // disconnect gracefully
384
- shell.destroy() // disconnect and release all resources
773
+ shell.close() // disconnect gracefully
774
+ shell.destroy() // disconnect and release all resources
385
775
  \`\`\`
386
776
 
777
+ ${buildReadmeSecurity(config)}
778
+
387
779
  > Regenerated from scratch on every build; do not edit by hand.
388
780
  `;
389
781
  }
390
782
  /**
391
783
  * Stages the complete host connector package into the supplied VFS tree.
392
784
  *
393
- * Emits the entry source, source-level `package.json`, `README.md`, and (via
394
- * {@link generateMetadata}) `metadata.json`. Pure: stages only into `tree` — the
395
- * CLI owns temp-dir creation, bundling, and commit.
785
+ * Emits the entry source (with contract-projected types), source-level
786
+ * `package.json`, `README.md`, and (via {@link generateMetadata})
787
+ * `metadata.json`. Pure: stages only into `tree` — the CLI owns temp-dir
788
+ * creation, bundling, and commit.
396
789
  *
397
790
  * @param config - The resolved feature config.
398
791
  * @param contract - The validated feature contract, inlined into the connector.
@@ -400,13 +793,13 @@ shell.destroy() // disconnect and release all resource
400
793
  *
401
794
  * @example Staging a connector for the clock feature
402
795
  * ```typescript
403
- * generateShell({ name: 'clock', version: '1.0.0', contract: './clock.contract.json', url: '/clock' }, contract, tree)
796
+ * generateShell({ name: 'clock', version: '1.0.0', contract: './clock.contract.json', url: '/clock', protocol: 'v2' }, contract, tree)
404
797
  * ```
405
798
  */
406
799
  function generateShell(config, contract, tree) {
407
800
  tree.write(ENTRY_PATH, buildConnectorEntry(config, contract));
408
801
  tree.write(PACKAGE_PATH, buildConnectorPackageJson(config));
409
- tree.write(README_PATH, buildConnectorReadme(config));
802
+ tree.write(README_PATH, buildConnectorReadme(config, contract));
410
803
  generateMetadata(config, contract, tree);
411
804
  }
412
805