@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
@@ -11,6 +11,7 @@ const index_cjs_js$7 = require('../immutable-api-utils/built-in-copy/date/index.
11
11
  const index_cjs_js$8 = require('../immutable-api-utils/built-in-copy/console/index.cjs.js');
12
12
  const index_cjs_js$9 = require('../immutable-api-utils/built-in-copy/weak-set/index.cjs.js');
13
13
  const index_cjs_js$a = require('../json-utils/index.cjs.js');
14
+ const index_cjs_js$b = require('../immutable-api-utils/built-in-copy/reflect/index.cjs.js');
14
15
 
15
16
  function uuidV4() {
16
17
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char) => {
@@ -82,10 +83,10 @@ const invalidRequest = (deps) => (processId, error) => index_cjs_js$1.freeze({
82
83
  error,
83
84
  });
84
85
 
85
- const newMessage = (deps) => (data) => index_cjs_js$1.freeze({
86
+ const newMessage = (deps) => (message) => index_cjs_js$1.freeze({
86
87
  type: ACTION_TYPES.NEW_MESSAGE,
87
88
  senderId: deps.getBrokerId(),
88
- data,
89
+ data: message,
89
90
  });
90
91
 
91
92
  const openConnection = (deps) => (processId, security) => {
@@ -695,7 +696,7 @@ function send(channel, message) {
695
696
  if (!emittedTypes.includes(message.type)) {
696
697
  throw index_cjs_js$6.createError(`Cannot send message to ${state.name} channel. Message type '${message.type}' is not in the emitted actions of channel contract.`);
697
698
  }
698
- const action = channel.actions.newMessage(message.data);
699
+ const action = channel.actions.newMessage(message);
699
700
  sendAction(channel, action);
700
701
  channel.notifyMessage(message);
701
702
  }
@@ -930,6 +931,7 @@ function createChannel(config, deps) {
930
931
  getName: () => state.name,
931
932
  getTarget: () => state.target,
932
933
  isActive: () => state.active,
934
+ getAcceptedTypes: () => state.acceptedActions,
933
935
  toJSON: () => ({
934
936
  id: state.id,
935
937
  name: state.name,
@@ -1158,12 +1160,22 @@ function handleAccept(context, message) {
1158
1160
  channel.notifyEvent('open', { origin: message.origin, contract });
1159
1161
  }
1160
1162
 
1163
+ function resolveChannel(registry, message) {
1164
+ const source = message.source;
1165
+ if (source) {
1166
+ const channel = registry.getByWindow(source);
1167
+ if (channel) {
1168
+ return channel;
1169
+ }
1170
+ }
1171
+ return registry.getById(message.data.senderId);
1172
+ }
1173
+
1161
1174
  function handleCancel(context, message) {
1162
1175
  const { state, registry, processManager } = context;
1163
1176
  const action = message.data;
1164
- const senderId = action['senderId'];
1165
1177
  const processId = action['processId'];
1166
- const channel = (getById(registry, senderId) || processManager.get(processId));
1178
+ const channel = (resolveChannel(registry, message) || processManager.get(processId));
1167
1179
  if (!channel) {
1168
1180
  return;
1169
1181
  }
@@ -1192,12 +1204,11 @@ function handleCancelAcknowledged(context, message) {
1192
1204
  function handleClose(context, message) {
1193
1205
  const { state, registry, processManager } = context;
1194
1206
  const action = message.data;
1195
- const senderId = action.senderId;
1196
1207
  if (!('processId' in action)) {
1197
1208
  return;
1198
1209
  }
1199
1210
  const processId = action.processId;
1200
- const channel = getById(registry, senderId);
1211
+ const channel = resolveChannel(registry, message);
1201
1212
  if (!channel || !channel.isActive()) {
1202
1213
  return;
1203
1214
  }
@@ -1238,9 +1249,7 @@ function handleDeny(context, message) {
1238
1249
 
1239
1250
  function handleDestroy(context, message) {
1240
1251
  const { registry } = context;
1241
- const action = message.data;
1242
- const senderId = action.senderId;
1243
- const channel = getById(registry, senderId);
1252
+ const channel = resolveChannel(registry, message);
1244
1253
  if (!channel) {
1245
1254
  return;
1246
1255
  }
@@ -1313,12 +1322,11 @@ function validateMessage(message) {
1313
1322
  function handleMessage(context, message) {
1314
1323
  const { state, registry, logger } = context;
1315
1324
  const action = message.data;
1316
- const senderId = action.senderId;
1317
1325
  if (!isActionWithData(action)) {
1318
1326
  return;
1319
1327
  }
1320
1328
  const messageData = action.data;
1321
- const channel = getById(registry, senderId);
1329
+ const channel = resolveChannel(registry, message);
1322
1330
  if (!channel || !channel.isActive()) {
1323
1331
  return;
1324
1332
  }
@@ -1327,6 +1335,10 @@ function handleMessage(context, message) {
1327
1335
  logger.info(`${state.name} ignored message from ${channel.getName()}`);
1328
1336
  return;
1329
1337
  }
1338
+ if (!channel.getAcceptedTypes().includes(messageData.type)) {
1339
+ logger.info(`${state.name} dropped message type '${messageData.type}' not accepted by the ${channel.getName()} channel contract`);
1340
+ return;
1341
+ }
1330
1342
  channel.notifyMessage(messageData);
1331
1343
  }
1332
1344
 
@@ -1620,6 +1632,10 @@ function createBroker(config) {
1620
1632
  assertNoCircularRef(config.settings, 'config.settings');
1621
1633
  validateName(config.name);
1622
1634
  validateContract(config.contract);
1635
+ const brokerWindow = config.window ?? (typeof window !== 'undefined' ? window : undefined);
1636
+ if (!brokerWindow) {
1637
+ throw index_cjs_js$6.createError('Cannot create broker: no window is available. Pass an explicit `window` in the broker config when running outside a browser environment.');
1638
+ }
1623
1639
  const mergedSettings = {
1624
1640
  ...defaultBrokerSettings,
1625
1641
  ...config.settings,
@@ -1633,7 +1649,7 @@ function createBroker(config) {
1633
1649
  const state = {
1634
1650
  id: uuidV4(),
1635
1651
  name: config.name,
1636
- window: window,
1652
+ window: brokerWindow,
1637
1653
  contract: config.contract,
1638
1654
  settings: mergedSettings,
1639
1655
  logger,
@@ -1686,9 +1702,7 @@ function createBroker(config) {
1686
1702
  }
1687
1703
  routeMessage(router, routingContext, event);
1688
1704
  };
1689
- if (typeof window !== 'undefined') {
1690
- window.addEventListener('message', onMessage);
1691
- }
1705
+ brokerWindow.addEventListener('message', onMessage);
1692
1706
  const broker = {
1693
1707
  id: state.id,
1694
1708
  name: state.name,
@@ -1770,9 +1784,25 @@ const DEFAULT_CONTRACT = index_cjs_js$1.freeze({
1770
1784
  { type: 'ACK', description: 'Acknowledgment' },
1771
1785
  ]),
1772
1786
  });
1773
- const broker = createBroker({
1774
- name: 'default-broker',
1775
- contract: DEFAULT_CONTRACT,
1787
+
1788
+ let instance = null;
1789
+ function resolveBroker() {
1790
+ if (!instance) {
1791
+ instance = createBroker({
1792
+ name: 'default-broker',
1793
+ contract: DEFAULT_CONTRACT,
1794
+ });
1795
+ }
1796
+ return instance;
1797
+ }
1798
+ const broker = new Proxy({}, {
1799
+ get: (_target, property) => index_cjs_js$b.get(resolveBroker(), property),
1800
+ has: (_target, property) => index_cjs_js$b.has(resolveBroker(), property),
1801
+ ownKeys: () => index_cjs_js$b.ownKeys(resolveBroker()),
1802
+ getOwnPropertyDescriptor: (_target, property) => {
1803
+ const descriptor = index_cjs_js$b.getOwnPropertyDescriptor(resolveBroker(), property);
1804
+ return descriptor ? { ...descriptor, configurable: true } : undefined;
1805
+ },
1776
1806
  });
1777
1807
 
1778
1808
  exports.DEFAULT_CONTRACT = DEFAULT_CONTRACT;
@@ -9,6 +9,7 @@ import { dateNow } from '../immutable-api-utils/built-in-copy/date/index.esm.js'
9
9
  import { debug, info, log, warn, error } from '../immutable-api-utils/built-in-copy/console/index.esm.js';
10
10
  import { createWeakSet } from '../immutable-api-utils/built-in-copy/weak-set/index.esm.js';
11
11
  import { createValidator as createValidator$1 } from '../json-utils/index.esm.js';
12
+ import { getOwnPropertyDescriptor, ownKeys, has, get } from '../immutable-api-utils/built-in-copy/reflect/index.esm.js';
12
13
 
13
14
  function uuidV4() {
14
15
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char) => {
@@ -80,10 +81,10 @@ const invalidRequest = (deps) => (processId, error) => freeze({
80
81
  error,
81
82
  });
82
83
 
83
- const newMessage = (deps) => (data) => freeze({
84
+ const newMessage = (deps) => (message) => freeze({
84
85
  type: ACTION_TYPES.NEW_MESSAGE,
85
86
  senderId: deps.getBrokerId(),
86
- data,
87
+ data: message,
87
88
  });
88
89
 
89
90
  const openConnection = (deps) => (processId, security) => {
@@ -693,7 +694,7 @@ function send(channel, message) {
693
694
  if (!emittedTypes.includes(message.type)) {
694
695
  throw createError(`Cannot send message to ${state.name} channel. Message type '${message.type}' is not in the emitted actions of channel contract.`);
695
696
  }
696
- const action = channel.actions.newMessage(message.data);
697
+ const action = channel.actions.newMessage(message);
697
698
  sendAction(channel, action);
698
699
  channel.notifyMessage(message);
699
700
  }
@@ -928,6 +929,7 @@ function createChannel(config, deps) {
928
929
  getName: () => state.name,
929
930
  getTarget: () => state.target,
930
931
  isActive: () => state.active,
932
+ getAcceptedTypes: () => state.acceptedActions,
931
933
  toJSON: () => ({
932
934
  id: state.id,
933
935
  name: state.name,
@@ -1156,12 +1158,22 @@ function handleAccept(context, message) {
1156
1158
  channel.notifyEvent('open', { origin: message.origin, contract });
1157
1159
  }
1158
1160
 
1161
+ function resolveChannel(registry, message) {
1162
+ const source = message.source;
1163
+ if (source) {
1164
+ const channel = registry.getByWindow(source);
1165
+ if (channel) {
1166
+ return channel;
1167
+ }
1168
+ }
1169
+ return registry.getById(message.data.senderId);
1170
+ }
1171
+
1159
1172
  function handleCancel(context, message) {
1160
1173
  const { state, registry, processManager } = context;
1161
1174
  const action = message.data;
1162
- const senderId = action['senderId'];
1163
1175
  const processId = action['processId'];
1164
- const channel = (getById(registry, senderId) || processManager.get(processId));
1176
+ const channel = (resolveChannel(registry, message) || processManager.get(processId));
1165
1177
  if (!channel) {
1166
1178
  return;
1167
1179
  }
@@ -1190,12 +1202,11 @@ function handleCancelAcknowledged(context, message) {
1190
1202
  function handleClose(context, message) {
1191
1203
  const { state, registry, processManager } = context;
1192
1204
  const action = message.data;
1193
- const senderId = action.senderId;
1194
1205
  if (!('processId' in action)) {
1195
1206
  return;
1196
1207
  }
1197
1208
  const processId = action.processId;
1198
- const channel = getById(registry, senderId);
1209
+ const channel = resolveChannel(registry, message);
1199
1210
  if (!channel || !channel.isActive()) {
1200
1211
  return;
1201
1212
  }
@@ -1236,9 +1247,7 @@ function handleDeny(context, message) {
1236
1247
 
1237
1248
  function handleDestroy(context, message) {
1238
1249
  const { registry } = context;
1239
- const action = message.data;
1240
- const senderId = action.senderId;
1241
- const channel = getById(registry, senderId);
1250
+ const channel = resolveChannel(registry, message);
1242
1251
  if (!channel) {
1243
1252
  return;
1244
1253
  }
@@ -1311,12 +1320,11 @@ function validateMessage(message) {
1311
1320
  function handleMessage(context, message) {
1312
1321
  const { state, registry, logger } = context;
1313
1322
  const action = message.data;
1314
- const senderId = action.senderId;
1315
1323
  if (!isActionWithData(action)) {
1316
1324
  return;
1317
1325
  }
1318
1326
  const messageData = action.data;
1319
- const channel = getById(registry, senderId);
1327
+ const channel = resolveChannel(registry, message);
1320
1328
  if (!channel || !channel.isActive()) {
1321
1329
  return;
1322
1330
  }
@@ -1325,6 +1333,10 @@ function handleMessage(context, message) {
1325
1333
  logger.info(`${state.name} ignored message from ${channel.getName()}`);
1326
1334
  return;
1327
1335
  }
1336
+ if (!channel.getAcceptedTypes().includes(messageData.type)) {
1337
+ logger.info(`${state.name} dropped message type '${messageData.type}' not accepted by the ${channel.getName()} channel contract`);
1338
+ return;
1339
+ }
1328
1340
  channel.notifyMessage(messageData);
1329
1341
  }
1330
1342
 
@@ -1618,6 +1630,10 @@ function createBroker(config) {
1618
1630
  assertNoCircularRef(config.settings, 'config.settings');
1619
1631
  validateName(config.name);
1620
1632
  validateContract(config.contract);
1633
+ const brokerWindow = config.window ?? (typeof window !== 'undefined' ? window : undefined);
1634
+ if (!brokerWindow) {
1635
+ throw createError('Cannot create broker: no window is available. Pass an explicit `window` in the broker config when running outside a browser environment.');
1636
+ }
1621
1637
  const mergedSettings = {
1622
1638
  ...defaultBrokerSettings,
1623
1639
  ...config.settings,
@@ -1631,7 +1647,7 @@ function createBroker(config) {
1631
1647
  const state = {
1632
1648
  id: uuidV4(),
1633
1649
  name: config.name,
1634
- window: window,
1650
+ window: brokerWindow,
1635
1651
  contract: config.contract,
1636
1652
  settings: mergedSettings,
1637
1653
  logger,
@@ -1684,9 +1700,7 @@ function createBroker(config) {
1684
1700
  }
1685
1701
  routeMessage(router, routingContext, event);
1686
1702
  };
1687
- if (typeof window !== 'undefined') {
1688
- window.addEventListener('message', onMessage);
1689
- }
1703
+ brokerWindow.addEventListener('message', onMessage);
1690
1704
  const broker = {
1691
1705
  id: state.id,
1692
1706
  name: state.name,
@@ -1768,9 +1782,25 @@ const DEFAULT_CONTRACT = freeze({
1768
1782
  { type: 'ACK', description: 'Acknowledgment' },
1769
1783
  ]),
1770
1784
  });
1771
- const broker = createBroker({
1772
- name: 'default-broker',
1773
- contract: DEFAULT_CONTRACT,
1785
+
1786
+ let instance = null;
1787
+ function resolveBroker() {
1788
+ if (!instance) {
1789
+ instance = createBroker({
1790
+ name: 'default-broker',
1791
+ contract: DEFAULT_CONTRACT,
1792
+ });
1793
+ }
1794
+ return instance;
1795
+ }
1796
+ const broker = new Proxy({}, {
1797
+ get: (_target, property) => get(resolveBroker(), property),
1798
+ has: (_target, property) => has(resolveBroker(), property),
1799
+ ownKeys: () => ownKeys(resolveBroker()),
1800
+ getOwnPropertyDescriptor: (_target, property) => {
1801
+ const descriptor = getOwnPropertyDescriptor(resolveBroker(), property);
1802
+ return descriptor ? { ...descriptor, configurable: true } : undefined;
1803
+ },
1774
1804
  });
1775
1805
 
1776
1806
  export { DEFAULT_CONTRACT, createBroker, createChannel, createLogger, broker as defaultBroker, logAction, logEvent, mergeContracts };
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const node_fs = require('node:fs');
4
+ const node_path = require('node:path');
4
5
  const index_cjs_js$2 = require('../../../immutable-api-utils/built-in-copy/array/index.cjs.js');
5
6
  const index_cjs_js$4 = require('../../../immutable-api-utils/built-in-copy/console/index.cjs.js');
6
7
  const index_cjs_js$6 = require('../../../immutable-api-utils/built-in-copy/json/index.cjs.js');
@@ -271,6 +272,22 @@ function createFileSystemError(message, code, context) {
271
272
  });
272
273
  return error;
273
274
  }
275
+ function readFileContent(filePath, encoding = 'utf-8') {
276
+ if (!isSafePath(filePath)) {
277
+ throw createFileSystemError(`Unsafe file path: ${filePath}`, 'FS_READ_ERROR', { path: filePath, operation: 'read' });
278
+ }
279
+ if (!node_fs.existsSync(filePath)) {
280
+ fsLogger.debug('File not found', { path: filePath });
281
+ throw createFileSystemError(`File not found: ${filePath}`, 'FS_NOT_FOUND', { path: filePath, operation: 'read' });
282
+ }
283
+ try {
284
+ return node_fs.readFileSync(filePath, { encoding });
285
+ }
286
+ catch (error) {
287
+ fsLogger.warn('Failed to read file', { path: filePath });
288
+ throw createFileSystemError(`Failed to read file: ${filePath}`, 'FS_READ_ERROR', { path: filePath, operation: 'read', cause: error });
289
+ }
290
+ }
274
291
  function readFileBuffer(filePath) {
275
292
  if (!isSafePath(filePath)) {
276
293
  throw createFileSystemError(`Unsafe file path: ${filePath}`, 'FS_READ_ERROR', { path: filePath, operation: 'read' });
@@ -384,14 +401,59 @@ function removeDirectory(dirPath, options) {
384
401
  const fsTraversalLogger = createScopedLogger('project-scope:fs:traversal');
385
402
 
386
403
  const fsWriteLogger = createScopedLogger('project-scope:fs:write');
404
+ function ensureDir(dirPath) {
405
+ if (!node_fs.existsSync(dirPath)) {
406
+ fsWriteLogger.debug('Creating directory recursively', { path: dirPath });
407
+ node_fs.mkdirSync(dirPath, { recursive: true });
408
+ }
409
+ }
410
+ function writeFileContent(filePath, content, options) {
411
+ try {
412
+ fsWriteLogger.debug('Writing file content', { path: filePath, size: content.length, encoding: options?.encoding ?? 'utf-8' });
413
+ ensureDir(node_path.dirname(filePath));
414
+ node_fs.writeFileSync(filePath, content, {
415
+ encoding: options?.encoding ?? 'utf-8',
416
+ mode: options?.mode,
417
+ });
418
+ fsWriteLogger.debug('File written successfully', { path: filePath });
419
+ }
420
+ catch (error) {
421
+ fsWriteLogger.warn('Failed to write file', { path: filePath, error: error instanceof Error ? error.message : String(error) });
422
+ throw createFileSystemError(`Failed to write file: ${filePath}`, 'FS_WRITE_ERROR', { path: filePath, operation: 'write', cause: error });
423
+ }
424
+ }
425
+ function writeJsonFile(filePath, data, options) {
426
+ try {
427
+ fsWriteLogger.debug('Writing JSON file', { path: filePath, indent: options?.indent ?? 2 });
428
+ const indent = options?.indent ?? 2;
429
+ const content = index_cjs_js$6.stringify(data, null, indent) + '\n';
430
+ writeFileContent(filePath, content, options);
431
+ fsWriteLogger.debug('JSON file written successfully', { path: filePath });
432
+ }
433
+ catch (error) {
434
+ if (error?.code === 'FS_WRITE_ERROR') {
435
+ throw error;
436
+ }
437
+ fsWriteLogger.warn('Failed to write JSON file', { path: filePath, error: error instanceof Error ? error.message : String(error) });
438
+ throw createFileSystemError(`Failed to write JSON file: ${filePath}`, 'FS_WRITE_ERROR', {
439
+ path: filePath,
440
+ operation: 'write',
441
+ cause: error,
442
+ });
443
+ }
444
+ }
387
445
 
388
446
  exports.createDirectory = createDirectory;
389
447
  exports.createFileSystemError = createFileSystemError;
448
+ exports.ensureDir = ensureDir;
390
449
  exports.exists = exists;
391
450
  exports.getFileStat = getFileStat;
392
451
  exports.isFile = isFile;
393
452
  exports.readFileBuffer = readFileBuffer;
453
+ exports.readFileContent = readFileContent;
394
454
  exports.readFileIfExists = readFileIfExists;
395
455
  exports.readJsonFile = readJsonFile;
396
456
  exports.readJsonFileIfExists = readJsonFileIfExists;
397
457
  exports.removeDirectory = removeDirectory;
458
+ exports.writeFileContent = writeFileContent;
459
+ exports.writeJsonFile = writeJsonFile;
@@ -1,4 +1,5 @@
1
- import { existsSync, readFileSync, statSync, lstatSync, mkdirSync, rmSync } from 'node:fs';
1
+ import { existsSync, readFileSync, statSync, lstatSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
2
3
  import { isArray } from '../../../immutable-api-utils/built-in-copy/array/index.esm.js';
3
4
  import { debug, info, log, warn, error } from '../../../immutable-api-utils/built-in-copy/console/index.esm.js';
4
5
  import { stringify, parse } from '../../../immutable-api-utils/built-in-copy/json/index.esm.js';
@@ -269,6 +270,22 @@ function createFileSystemError(message, code, context) {
269
270
  });
270
271
  return error;
271
272
  }
273
+ function readFileContent(filePath, encoding = 'utf-8') {
274
+ if (!isSafePath(filePath)) {
275
+ throw createFileSystemError(`Unsafe file path: ${filePath}`, 'FS_READ_ERROR', { path: filePath, operation: 'read' });
276
+ }
277
+ if (!existsSync(filePath)) {
278
+ fsLogger.debug('File not found', { path: filePath });
279
+ throw createFileSystemError(`File not found: ${filePath}`, 'FS_NOT_FOUND', { path: filePath, operation: 'read' });
280
+ }
281
+ try {
282
+ return readFileSync(filePath, { encoding });
283
+ }
284
+ catch (error) {
285
+ fsLogger.warn('Failed to read file', { path: filePath });
286
+ throw createFileSystemError(`Failed to read file: ${filePath}`, 'FS_READ_ERROR', { path: filePath, operation: 'read', cause: error });
287
+ }
288
+ }
272
289
  function readFileBuffer(filePath) {
273
290
  if (!isSafePath(filePath)) {
274
291
  throw createFileSystemError(`Unsafe file path: ${filePath}`, 'FS_READ_ERROR', { path: filePath, operation: 'read' });
@@ -382,5 +399,46 @@ function removeDirectory(dirPath, options) {
382
399
  const fsTraversalLogger = createScopedLogger('project-scope:fs:traversal');
383
400
 
384
401
  const fsWriteLogger = createScopedLogger('project-scope:fs:write');
402
+ function ensureDir(dirPath) {
403
+ if (!existsSync(dirPath)) {
404
+ fsWriteLogger.debug('Creating directory recursively', { path: dirPath });
405
+ mkdirSync(dirPath, { recursive: true });
406
+ }
407
+ }
408
+ function writeFileContent(filePath, content, options) {
409
+ try {
410
+ fsWriteLogger.debug('Writing file content', { path: filePath, size: content.length, encoding: options?.encoding ?? 'utf-8' });
411
+ ensureDir(dirname(filePath));
412
+ writeFileSync(filePath, content, {
413
+ encoding: options?.encoding ?? 'utf-8',
414
+ mode: options?.mode,
415
+ });
416
+ fsWriteLogger.debug('File written successfully', { path: filePath });
417
+ }
418
+ catch (error) {
419
+ fsWriteLogger.warn('Failed to write file', { path: filePath, error: error instanceof Error ? error.message : String(error) });
420
+ throw createFileSystemError(`Failed to write file: ${filePath}`, 'FS_WRITE_ERROR', { path: filePath, operation: 'write', cause: error });
421
+ }
422
+ }
423
+ function writeJsonFile(filePath, data, options) {
424
+ try {
425
+ fsWriteLogger.debug('Writing JSON file', { path: filePath, indent: options?.indent ?? 2 });
426
+ const indent = options?.indent ?? 2;
427
+ const content = stringify(data, null, indent) + '\n';
428
+ writeFileContent(filePath, content, options);
429
+ fsWriteLogger.debug('JSON file written successfully', { path: filePath });
430
+ }
431
+ catch (error) {
432
+ if (error?.code === 'FS_WRITE_ERROR') {
433
+ throw error;
434
+ }
435
+ fsWriteLogger.warn('Failed to write JSON file', { path: filePath, error: error instanceof Error ? error.message : String(error) });
436
+ throw createFileSystemError(`Failed to write JSON file: ${filePath}`, 'FS_WRITE_ERROR', {
437
+ path: filePath,
438
+ operation: 'write',
439
+ cause: error,
440
+ });
441
+ }
442
+ }
385
443
 
386
- export { createDirectory, createFileSystemError, exists, getFileStat, isFile, readFileBuffer, readFileIfExists, readJsonFile, readJsonFileIfExists, removeDirectory };
444
+ export { createDirectory, createFileSystemError, ensureDir, exists, getFileStat, isFile, readFileBuffer, readFileContent, readFileIfExists, readJsonFile, readJsonFileIfExists, removeDirectory, writeFileContent, writeJsonFile };
@@ -1,13 +1,18 @@
1
1
  import { Mode } from '../../../../_dependencies/@hyperfrontend/project-scope/vfs/index.esm.js';
2
+ import { isAbsolute, join, relative, dirname } from 'node:path';
2
3
 
3
4
  const MODULE_PATH = 'src/hyperfrontend.feature.ts';
4
- function toContractImportPath(contractPath) {
5
+ function toContractImportPath(contractPath, treeRoot) {
5
6
  const withoutExtension = contractPath.replace(/\.(json|ts|js)$/, '');
6
- return /^[./]/.test(withoutExtension) ? withoutExtension : `./${withoutExtension}`;
7
+ const target = isAbsolute(withoutExtension) ? withoutExtension : join(treeRoot, withoutExtension);
8
+ const specifier = relative(join(treeRoot, dirname(MODULE_PATH)), target)
9
+ .split('\\')
10
+ .join('/');
11
+ return specifier.startsWith('.') ? specifier : `./${specifier}`;
7
12
  }
8
- function buildFeatureModule(config, contract) {
13
+ function buildFeatureModule(config, contract, treeRoot) {
9
14
  const handlers = contract.accepted
10
- .map((action) => `feature.on('${action.type}', (data) => {\n // todo: handle ${action.type}\n})`)
15
+ .map((action) => `feature.on('${action.type}', (_data: unknown) => {\n // todo: handle ${action.type}\n})`)
11
16
  .join('\n\n');
12
17
  const emits = contract.emitted.map((action) => `// feature.send('${action.type}', undefined)`).join('\n');
13
18
  return `/**
@@ -20,7 +25,7 @@ function buildFeatureModule(config, contract) {
20
25
  * @module ${config.name}.feature
21
26
  */
22
27
  import { createFeature } from '@hyperfrontend/features/hostee'
23
- import contract from '${toContractImportPath(config.contract)}'
28
+ import contract from '${toContractImportPath(config.contract, treeRoot)}'
24
29
 
25
30
  /** The ${config.name} feature handle; use it to send and receive contract actions. */
26
31
  export const feature = createFeature({ name: '${config.name}', contract })
@@ -32,7 +37,7 @@ ${emits}
32
37
  `;
33
38
  }
34
39
  function generateFeatureModule(config, contract, tree) {
35
- tree.write(MODULE_PATH, buildFeatureModule(config, contract), { mode: Mode.SkipIfExists });
40
+ tree.write(MODULE_PATH, buildFeatureModule(config, contract, tree.root), { mode: Mode.SkipIfExists });
36
41
  }
37
42
 
38
43
  export { MODULE_PATH, toContractImportPath, buildFeatureModule, generateFeatureModule };
@@ -9,6 +9,7 @@ function generateMetadata(config, contract, tree) {
9
9
  version: format(parseVersionStrict(config.version)),
10
10
  url: config.url,
11
11
  contract,
12
+ ...(config.protocol !== undefined && { protocol: config.protocol }),
12
13
  generatedBy: '@hyperfrontend/features',
13
14
  };
14
15
  tree.write(METADATA_PATH, `${stringify(metadata, null, 2)}\n`);
@@ -8,15 +8,25 @@ const ControlType = index_cjs_js.freeze({
8
8
  Beat: '__hf:beat',
9
9
  /** Hostee-to-host content-size announcement. */
10
10
  Size: '__hf:size',
11
+ /** Correlated request envelope carrying a consumer request in either direction. */
12
+ Request: '__hf:request',
13
+ /** Correlated response envelope answering a request. */
14
+ Response: '__hf:response',
11
15
  });
12
16
  function isControlType(type) {
13
17
  return type.startsWith(CONTROL_PREFIX);
14
18
  }
15
19
  function withControlContract(contract) {
16
20
  // note: Fresh action literals per direction — sharing references across emitted/accepted trips nexus's circular-reference guard, which flags any repeated object.
21
+ const controlActions = () => [
22
+ { type: ControlType.Beat },
23
+ { type: ControlType.Size },
24
+ { type: ControlType.Request },
25
+ { type: ControlType.Response },
26
+ ];
17
27
  return {
18
- emitted: [...contract.emitted, { type: ControlType.Beat }, { type: ControlType.Size }],
19
- accepted: [...contract.accepted, { type: ControlType.Beat }, { type: ControlType.Size }],
28
+ emitted: [...contract.emitted, ...controlActions()],
29
+ accepted: [...contract.accepted, ...controlActions()],
20
30
  };
21
31
  }
22
32
 
@@ -6,15 +6,25 @@ const ControlType = freeze({
6
6
  Beat: '__hf:beat',
7
7
  /** Hostee-to-host content-size announcement. */
8
8
  Size: '__hf:size',
9
+ /** Correlated request envelope carrying a consumer request in either direction. */
10
+ Request: '__hf:request',
11
+ /** Correlated response envelope answering a request. */
12
+ Response: '__hf:response',
9
13
  });
10
14
  function isControlType(type) {
11
15
  return type.startsWith(CONTROL_PREFIX);
12
16
  }
13
17
  function withControlContract(contract) {
14
18
  // note: Fresh action literals per direction — sharing references across emitted/accepted trips nexus's circular-reference guard, which flags any repeated object.
19
+ const controlActions = () => [
20
+ { type: ControlType.Beat },
21
+ { type: ControlType.Size },
22
+ { type: ControlType.Request },
23
+ { type: ControlType.Response },
24
+ ];
15
25
  return {
16
- emitted: [...contract.emitted, { type: ControlType.Beat }, { type: ControlType.Size }],
17
- accepted: [...contract.accepted, { type: ControlType.Beat }, { type: ControlType.Size }],
26
+ emitted: [...contract.emitted, ...controlActions()],
27
+ accepted: [...contract.accepted, ...controlActions()],
18
28
  };
19
29
  }
20
30