@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
@@ -4,120 +4,26 @@
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.HyperfrontendFeaturesHostee = {}));
5
5
  })(this, (function (exports) { 'use strict';
6
6
 
7
- /**
8
- * Safe copies of Error built-ins via factory functions.
9
- *
10
- * Since constructors cannot be safely captured via Object.assign, this module
11
- * provides factory functions that use Reflect.construct internally.
12
- *
13
- * These references are captured at module initialization time to protect against
14
- * prototype pollution attacks. Import only what you need for tree-shaking.
15
- *
16
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/error
17
- */
18
7
  const _Error = globalThis.Error;
19
- const _Reflect$9 = globalThis.Reflect;
20
- /**
21
- * (Safe copy) Creates a new Error using the captured Error constructor.
22
- * Use this instead of `new Error()`.
23
- *
24
- * @param message - Optional error message.
25
- * @param options - Optional error options.
26
- * @returns A new Error instance.
27
- *
28
- * @example Creating Error instances
29
- * ```typescript
30
- * const error = createError('Operation failed')
31
- * // With cause for error chaining
32
- * const wrapped = createError('Request failed', { cause: originalError })
33
- * ```
34
- */
35
- const createError = (message, options) => _Reflect$9.construct(_Error, [message, options]);
36
-
37
- /**
38
- * Safe copies of Object built-in methods.
39
- *
40
- * These references are captured at module initialization time to protect against
41
- * prototype pollution attacks. Import only what you need for tree-shaking.
42
- *
43
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/object
44
- */
8
+ const _Reflect$a = globalThis.Reflect;
9
+ const createError = (message, options) => _Reflect$a.construct(_Error, [message, options]);
10
+
45
11
  const _Object = globalThis.Object;
46
- const _Reflect$8 = globalThis.Reflect;
12
+ const _Reflect$9 = globalThis.Reflect;
47
13
  const _ObjectPrototype = _Object.prototype;
48
14
  const _hasOwnProperty = _ObjectPrototype.hasOwnProperty;
49
- /**
50
- * (Safe copy) Prevents modification of existing property attributes and values,
51
- * and prevents the addition of new properties.
52
- */
53
15
  const freeze = _Object.freeze;
54
- /**
55
- * (Safe copy) Returns the names of the enumerable string properties and methods of an object.
56
- */
57
16
  const keys = _Object.keys;
58
- /**
59
- * (Safe copy) Returns an array of key/values of the enumerable own properties of an object.
60
- */
61
17
  const entries = _Object.entries;
62
- /**
63
- * (Safe copy) Adds a property to an object, or modifies attributes of an existing property.
64
- */
18
+ const values = _Object.values;
65
19
  const defineProperty = _Object.defineProperty;
66
- /**
67
- * (Safe copy) Sets the prototype of a specified object o to object proto or null.
68
- */
69
20
  const setPrototypeOf = _Object.setPrototypeOf;
70
- /**
71
- * (Safe copy) Safe wrapper for Object.prototype.hasOwnProperty.call().
72
- * Checks if an object has a property as its own (not inherited) property.
73
- *
74
- * @param obj - The object to check.
75
- * @param key - The property key to check.
76
- * @returns True if the object has the property as its own property.
77
- *
78
- * @example Checking own properties
79
- * ```typescript
80
- * const user = { name: 'Alice' }
81
- * hasOwn(user, 'name') // => true
82
- * hasOwn(user, 'toString') // => false (inherited from prototype)
83
- * ```
84
- */
85
- const hasOwn = (obj, key) => _Reflect$8.apply(_hasOwnProperty, obj, [key]);
86
-
87
- /**
88
- * Safe copies of Math built-in methods.
89
- *
90
- * These references are captured at module initialization time to protect against
91
- * prototype pollution attacks. Import only what you need for tree-shaking.
92
- *
93
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/math
94
- */
21
+ const hasOwn = (obj, key) => _Reflect$9.apply(_hasOwnProperty, obj, [key]);
22
+
95
23
  const _Math = globalThis.Math;
96
- /**
97
- * (Safe copy) Returns the absolute value of a number.
98
- */
99
24
  const abs = _Math.abs;
100
- /**
101
- * (Safe copy) Returns a pseudo-random number between 0 and 1.
102
- * Note: This is NOT cryptographically secure. For secure random values,
103
- * use crypto.getRandomValues().
104
- */
105
25
  const random = _Math.random;
106
26
 
107
- /**
108
- * Generates a version 4 UUID.
109
- *
110
- * @returns a version 4 UUID.
111
- *
112
- * @example Creating unique identifiers for entities
113
- * ```typescript
114
- * const userId = uuidV4()
115
- * // => 'a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d'
116
- *
117
- * const sessionId = uuidV4()
118
- * // => '9f8e7d6c-5b4a-4321-8765-4321fedcba98'
119
- * ```
120
- */
121
27
  function uuidV4() {
122
28
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char) => {
123
29
  const randomHex = (random() * 16) | 0;
@@ -126,17 +32,7 @@
126
32
  });
127
33
  }
128
34
 
129
- /**
130
- * Protocol prefix for all action types.
131
- * This is the Nexus protocol identifier.
132
- */
133
35
  const PROTOCOL = 'nexus';
134
- /**
135
- * Action type string literals for the Nexus protocol.
136
- *
137
- * These define the wire format for all connection lifecycle actions.
138
- * The connection flow behavior is 1:1 with the proven legacy implementation.
139
- */
140
36
  const ACTION_TYPES = {
141
37
  INVALID_REQUEST: `[${PROTOCOL}] invalid-request`,
142
38
  REQUEST_CONNECTION: `[${PROTOCOL}] connection-request`,
@@ -150,66 +46,10 @@
150
46
  OPEN_CONNECTION: `[${PROTOCOL}] connection-opened`,
151
47
  NEW_MESSAGE: `[${PROTOCOL}] new-message`,
152
48
  };
153
- /**
154
- * Type guards for specific action types
155
- *
156
- * @param action - The action to check
157
- * @returns True if action contains a contract property
158
- *
159
- * @example Checking for contract property
160
- * ```typescript
161
- * if (isActionWithContract(action)) {
162
- * console.log(action.contract)
163
- * }
164
- * ```
165
- */
166
49
  const isActionWithContract = (action) => 'contract' in action;
167
- /**
168
- * Type guard for actions with data property.
169
- *
170
- * @param action - The action to check
171
- * @returns True if action contains a data property
172
- *
173
- * @example Checking for data property
174
- * ```typescript
175
- * if (isActionWithData(action)) {
176
- * processData(action.data)
177
- * }
178
- * ```
179
- */
180
50
  const isActionWithData = (action) => 'data' in action;
181
- /**
182
- * Type guard for actions with processId property.
183
- *
184
- * @param action - The action to check
185
- * @returns True if action contains a processId property
186
- *
187
- * @example Checking for processId property
188
- * ```typescript
189
- * if (isActionWithProcess(action)) {
190
- * trackProcess(action.processId)
191
- * }
192
- * ```
193
- */
194
51
  const isActionWithProcess = (action) => 'processId' in action;
195
52
 
196
- /**
197
- * Creates ACCEPT_CONNECTION action
198
- *
199
- * @param deps - Action dependencies (getBrokerId, getContract)
200
- * @returns Function that takes processId and optional security response, returns frozen action
201
- *
202
- * @example Creating accept connection actions
203
- * ```typescript
204
- * // Without security
205
- * const action = acceptConnection(deps)('process-123')
206
- *
207
- * // With security negotiation response
208
- * const secureAction = acceptConnection(deps)('process-123', {
209
- * negotiated: 'v2'
210
- * })
211
- * ```
212
- */
213
53
  const acceptConnection = (deps) => (processId, security) => {
214
54
  const base = {
215
55
  type: ACTION_TYPES.ACCEPT_CONNECTION,
@@ -223,57 +63,18 @@
223
63
  return freeze(base);
224
64
  };
225
65
 
226
- /**
227
- * Creates a cancel connection action.
228
- *
229
- * @param deps - Action dependencies containing broker ID
230
- * @returns A function that creates a cancel connection action for a process
231
- *
232
- * @example Creating cancel connection actions
233
- * ```typescript
234
- * const createCancelAction = cancelConnection({ getBrokerId: () => 'broker-1' })
235
- * const action = createCancelAction('process-123')
236
- * // => { type: 'CANCEL_CONNECTION', processId: 'process-123', senderId: 'broker-1' }
237
- * ```
238
- */
239
66
  const cancelConnection = (deps) => (processId) => freeze({
240
67
  type: ACTION_TYPES.CANCEL_CONNECTION,
241
68
  processId,
242
69
  senderId: deps.getBrokerId(),
243
70
  });
244
71
 
245
- /**
246
- * Creates a close connection action.
247
- *
248
- * @param deps - Action dependencies containing broker ID
249
- * @returns A function that creates a close connection action for a process
250
- *
251
- * @example Creating close connection actions
252
- * ```typescript
253
- * const createCloseAction = closeConnection({ getBrokerId: () => 'broker-1' })
254
- * const action = createCloseAction('process-123')
255
- * // => { type: 'CLOSE_CONNECTION', processId: 'process-123', senderId: 'broker-1' }
256
- * ```
257
- */
258
72
  const closeConnection = (deps) => (processId) => freeze({
259
73
  type: ACTION_TYPES.CLOSE_CONNECTION,
260
74
  processId,
261
75
  senderId: deps.getBrokerId(),
262
76
  });
263
77
 
264
- /**
265
- * Creates a deny connection action with an error message.
266
- *
267
- * @param deps - Action dependencies containing broker ID
268
- * @returns A function that creates a deny connection action for a process
269
- *
270
- * @example Creating deny connection actions
271
- * ```typescript
272
- * const createDenyAction = denyConnection({ getBrokerId: () => 'broker-1' })
273
- * const action = createDenyAction('process-123', 'Origin not allowed')
274
- * // => { type: 'DENY_CONNECTION', processId: 'process-123', senderId: 'broker-1', error: 'Origin not allowed' }
275
- * ```
276
- */
277
78
  const denyConnection = (deps) => (processId, error) => freeze({
278
79
  type: ACTION_TYPES.DENY_CONNECTION,
279
80
  processId,
@@ -281,37 +82,11 @@
281
82
  error,
282
83
  });
283
84
 
284
- /**
285
- * Creates a destroy connection action.
286
- *
287
- * @param deps - Action dependencies containing broker ID
288
- * @returns A function that creates a destroy connection action
289
- *
290
- * @example Creating destroy connection actions
291
- * ```typescript
292
- * const createDestroyAction = destroyConnection({ getBrokerId: () => 'broker-1' })
293
- * const action = createDestroyAction()
294
- * // => { type: 'DESTROY_CONNECTION', senderId: 'broker-1' }
295
- * ```
296
- */
297
85
  const destroyConnection = (deps) => () => freeze({
298
86
  type: ACTION_TYPES.DESTROY_CONNECTION,
299
87
  senderId: deps.getBrokerId(),
300
88
  });
301
89
 
302
- /**
303
- * Creates an invalid request action with an error message.
304
- *
305
- * @param deps - Action dependencies containing broker ID
306
- * @returns A function that creates an invalid request action for a process
307
- *
308
- * @example Creating invalid request actions
309
- * ```typescript
310
- * const createInvalidAction = invalidRequest({ getBrokerId: () => 'broker-1' })
311
- * const action = createInvalidAction('process-123', 'Malformed payload')
312
- * // => { type: 'INVALID_REQUEST', processId: 'process-123', senderId: 'broker-1', error: 'Malformed payload' }
313
- * ```
314
- */
315
90
  const invalidRequest = (deps) => (processId, error) => freeze({
316
91
  type: ACTION_TYPES.INVALID_REQUEST,
317
92
  processId,
@@ -319,43 +94,12 @@
319
94
  error,
320
95
  });
321
96
 
322
- /**
323
- * Creates a new message action with data payload.
324
- *
325
- * @param deps - Action dependencies containing broker ID
326
- * @returns A function that creates a new message action with data
327
- *
328
- * @example Creating message actions with data
329
- * ```typescript
330
- * const createMessageAction = newMessage({ getBrokerId: () => 'broker-1' })
331
- * const action = createMessageAction({ userId: 123, event: 'login' })
332
- * // => { type: 'NEW_MESSAGE', senderId: 'broker-1', data: { userId: 123, event: 'login' } }
333
- * ```
334
- */
335
- const newMessage = (deps) => (data) => freeze({
97
+ const newMessage = (deps) => (message) => freeze({
336
98
  type: ACTION_TYPES.NEW_MESSAGE,
337
99
  senderId: deps.getBrokerId(),
338
- data,
100
+ data: message,
339
101
  });
340
102
 
341
- /**
342
- * Creates OPEN_CONNECTION action
343
- *
344
- * @param deps - Action dependencies (getBrokerId, getContract)
345
- * @returns Function that takes processId and optional security confirmation, returns frozen action
346
- *
347
- * @example Creating open connection actions
348
- * ```typescript
349
- * // Without security
350
- * const action = openConnection(deps)('process-123')
351
- *
352
- * // With security confirmation
353
- * const secureAction = openConnection(deps)('process-123', {
354
- * active: true,
355
- * protocol: 'v2'
356
- * })
357
- * ```
358
- */
359
103
  const openConnection = (deps) => (processId, security) => {
360
104
  const base = {
361
105
  type: ACTION_TYPES.OPEN_CONNECTION,
@@ -368,24 +112,6 @@
368
112
  return freeze(base);
369
113
  };
370
114
 
371
- /**
372
- * Creates REQUEST_CONNECTION action
373
- *
374
- * @param deps - Action dependencies (getBrokerId, getContract)
375
- * @returns Function that takes processId and optional security request, returns frozen action
376
- *
377
- * @example Creating request connection actions
378
- * ```typescript
379
- * // Without security
380
- * const action = requestConnection(deps)('process-123')
381
- *
382
- * // With security negotiation
383
- * const secureAction = requestConnection(deps)('process-123', {
384
- * supported: ['v2', 'v1', 'none'],
385
- * preferred: 'v2'
386
- * })
387
- * ```
388
- */
389
115
  const requestConnection = (deps) => (processId, security) => {
390
116
  const base = {
391
117
  type: ACTION_TYPES.REQUEST_CONNECTION,
@@ -399,21 +125,6 @@
399
125
  return freeze(base);
400
126
  };
401
127
 
402
- /**
403
- * Creates all action creators bound to the provided dependencies
404
- *
405
- * @param deps - Action dependencies (getBrokerId, getContract)
406
- * @returns Frozen object containing all action creator functions
407
- *
408
- * @example Creating action creators
409
- * ```typescript
410
- * const actions = createActionCreators({
411
- * getBrokerId: () => 'broker-123',
412
- * getContract: () => myContract
413
- * })
414
- * const action = actions.requestConnection('process-456')
415
- * ```
416
- */
417
128
  const createActionCreators = (deps) => freeze({
418
129
  requestConnection: requestConnection(deps),
419
130
  acceptConnection: acceptConnection(deps),
@@ -426,102 +137,26 @@
426
137
  invalidRequest: invalidRequest(deps),
427
138
  });
428
139
 
429
- /**
430
- * Safe Map factory with optional groupBy for ES2024+.
431
- *
432
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/map
433
- */
434
- /* eslint-disable workspace/lib-require-jsdoc-example */
435
140
  const _Map = globalThis.Map;
436
- const _Reflect$7 = globalThis.Reflect;
437
- /**
438
- * (Safe copy) Creates a new Map using the captured Map constructor.
439
- * Use this instead of `new Map()`.
440
- *
441
- * @param iterable - Optional iterable of key-value pairs.
442
- * @returns A new Map instance.
443
- */
444
- const createMap = (iterable) => _Reflect$7.construct(_Map, iterable ? [iterable] : []);
445
-
446
- /**
447
- * Clears all processes from the registry
448
- *
449
- * @param processes - Map storing process to channel mappings
450
- * @returns Function that clears all processes
451
- *
452
- * @example Clearing all processes
453
- * ```typescript
454
- * const clear = clearProcesses(processMap)
455
- * clear() // All processes removed
456
- * ```
457
- */
141
+ const _Reflect$8 = globalThis.Reflect;
142
+ const createMap = (iterable) => _Reflect$8.construct(_Map, iterable ? [iterable] : []);
143
+
458
144
  const clearProcesses = (processes) => () => {
459
145
  processes.clear();
460
146
  };
461
147
 
462
- /**
463
- * Creates a process ID and registers the channel
464
- *
465
- * @param processes - Map storing process to channel mappings
466
- * @returns Function that takes a channel and returns new process ID
467
- *
468
- * @example Registering a channel process
469
- * ```typescript
470
- * const create = createProcess(processMap)
471
- * const processId = create(myChannel)
472
- * ```
473
- */
474
148
  const createProcess = (processes) => (channel) => {
475
149
  const processId = uuidV4();
476
150
  processes.set(processId, channel);
477
151
  return processId;
478
152
  };
479
153
 
480
- /**
481
- * Gets a channel by its process ID
482
- *
483
- * @param processes - Map storing process to channel mappings
484
- * @returns Function that takes processId and returns channel or undefined
485
- *
486
- * @example Looking up channel by process ID
487
- * ```typescript
488
- * const processes = new Map([['proc-1', channelHandle]])
489
- * const findChannel = getChannel(processes)
490
- * const channel = findChannel('proc-1')
491
- * // => channelHandle or undefined
492
- * ```
493
- */
494
154
  const getChannel$1 = (processes) => (processId) => processes.get(processId);
495
155
 
496
- /**
497
- * Removes a process ID from the registry
498
- *
499
- * @param processes - Map storing process to channel mappings
500
- * @returns Function that takes processId and removes it
501
- *
502
- * @example Removing a process
503
- * ```typescript
504
- * const remove = removeProcess(processMap)
505
- * remove('some-process-id')
506
- * ```
507
- */
508
156
  const removeProcess = (processes) => (processId) => {
509
157
  processes.delete(processId);
510
158
  };
511
159
 
512
- /**
513
- * Creates a process manager for tracking process IDs to channel mappings
514
- *
515
- * @returns Object with process management methods
516
- *
517
- * @example Managing channel processes
518
- * ```typescript
519
- * const processManager = createProcessManager()
520
- * const processId = processManager.create(channel)
521
- * const found = processManager.get(processId)
522
- * processManager.remove(processId)
523
- * ```
524
- */
525
160
  const createProcessManager = () => {
526
161
  const processes = createMap();
527
162
  return freeze({
@@ -529,98 +164,27 @@
529
164
  get: getChannel$1(processes),
530
165
  remove: removeProcess(processes),
531
166
  clear: clearProcesses(processes),
532
- /**
533
- * Track an existing process ID with a channel.
534
- * Useful when receiving a process ID from the remote side that needs to be associated
535
- * with a local channel instance.
536
- *
537
- * @param processId - The process ID to track
538
- * @param channel - The channel handle to associate with the process ID
539
- */
540
167
  track: (processId, channel) => {
541
168
  processes.set(processId, channel);
542
169
  },
543
- /**
544
- * Check if a process ID exists in the manager.
545
- *
546
- * @param processId - The process ID to check
547
- * @returns True if the process ID exists, false otherwise
548
- */
549
170
  has: (processId) => {
550
171
  return processes.has(processId);
551
172
  },
552
173
  });
553
174
  };
554
175
 
555
- /**
556
- * Safe copies of Array built-in static methods.
557
- *
558
- * These references are captured at module initialization time to protect against
559
- * prototype pollution attacks. Import only what you need for tree-shaking.
560
- *
561
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/array
562
- */
563
176
  const _Array = globalThis.Array;
564
- /**
565
- * (Safe copy) Determines whether the passed value is an Array.
566
- */
567
177
  const isArray = _Array.isArray;
568
- /**
569
- * (Safe copy) Creates an array from an array-like or iterable object.
570
- */
571
178
  const from = _Array.from;
572
179
 
573
- /**
574
- * Safe Set factory for protected set construction.
575
- *
576
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/set
577
- */
578
- /* eslint-disable workspace/lib-require-jsdoc-example */
579
180
  const _Set = globalThis.Set;
580
- const _Reflect$6 = globalThis.Reflect;
581
- /**
582
- * (Safe copy) Creates a new Set using the captured Set constructor.
583
- * Use this instead of `new Set()`.
584
- *
585
- * @param iterable - Optional iterable of values.
586
- * @returns A new Set instance.
587
- */
588
- const createSet = (iterable) => _Reflect$6.construct(_Set, iterable ? [iterable] : []);
589
-
590
- /**
591
- * Safe WeakMap factory for protected weak map construction.
592
- *
593
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/weak-map
594
- */
595
- /* eslint-disable workspace/lib-require-jsdoc-example */
181
+ const _Reflect$7 = globalThis.Reflect;
182
+ const createSet = (iterable) => _Reflect$7.construct(_Set, iterable ? [iterable] : []);
183
+
596
184
  const _WeakMap = globalThis.WeakMap;
597
- const _Reflect$5 = globalThis.Reflect;
598
- /**
599
- * (Safe copy) Creates a new WeakMap using the captured WeakMap constructor.
600
- * Use this instead of `new WeakMap()`.
601
- *
602
- * @param iterable - Optional iterable of key-value pairs.
603
- * @returns A new WeakMap instance.
604
- */
605
- const createWeakMap = (iterable) => _Reflect$5.construct(_WeakMap, iterable ? [iterable] : []);
606
-
607
- /**
608
- * Minimal channel structure required for registry operations.
609
- * Extended with optional methods for use with full ChannelHandle objects.
610
- */
611
- /**
612
- * Creates a new channel registry with isolated state.
613
- * All lookup operations are O(1) using WeakMap/Map.
614
- *
615
- * @returns Registry functions for managing channels
616
- *
617
- * @example Creating and using a registry
618
- * ```typescript
619
- * const registry = createRegistry()
620
- * registry.add({ id: 'ch-1', name: 'main', target: iframe.contentWindow })
621
- * const channel = registry.getById('ch-1')
622
- * ```
623
- */
185
+ const _Reflect$6 = globalThis.Reflect;
186
+ const createWeakMap = (iterable) => _Reflect$6.construct(_WeakMap, iterable ? [iterable] : []);
187
+
624
188
  function createRegistry() {
625
189
  const windowMap = createWeakMap();
626
190
  const idMap = createMap();
@@ -667,31 +231,10 @@
667
231
  });
668
232
  }
669
233
 
670
- /* eslint-disable workspace/lib-require-jsdoc-example */
671
- /**
672
- * Check if value is an object (not null, not array)
673
- *
674
- * @param value - The value to check
675
- * @returns True if value is a plain object
676
- */
677
234
  function isObject(value) {
678
235
  return value !== null && typeof value === 'object' && !isArray(value);
679
236
  }
680
237
 
681
- /**
682
- * Validates a channel contract structure.
683
- *
684
- * @param contract - The contract to validate
685
- * @throws {Error} Error if contract is invalid
686
- *
687
- * @example Validating contract structure
688
- * ```typescript
689
- * validateContract({
690
- * emitted: [{ type: 'ping' }],
691
- * accepted: [{ type: 'pong' }]
692
- * })
693
- * ```
694
- */
695
238
  function validateContract$1(contract) {
696
239
  if (!contract) {
697
240
  throw createError('Contract cannot be null or undefined');
@@ -725,27 +268,9 @@
725
268
  }
726
269
  }
727
270
 
728
- /**
729
- * Helper to check if a string is empty after trimming
730
- *
731
- * @param str - The string to check
732
- * @returns True if string is empty after trimming
733
- */
734
271
  function isEmpty(str) {
735
272
  return str.trim().length === 0;
736
273
  }
737
- /**
738
- * Validates a channel or broker name.
739
- *
740
- * @param name - The name to validate
741
- * @throws {Error} Error if name is invalid
742
- *
743
- * @example Validating channel name
744
- * ```typescript
745
- * validateName('my-channel') // valid
746
- * validateName('') // throws Error
747
- * ```
748
- */
749
274
  function validateName(name) {
750
275
  if (name === null || name === undefined) {
751
276
  throw createError('Name cannot be null or undefined');
@@ -758,98 +283,29 @@
758
283
  }
759
284
  }
760
285
 
761
- /**
762
- * Protocol registry factory.
763
- *
764
- * Creates a registry for managing protocol providers at the broker level.
765
- *
766
- * @module security/registry/factory
767
- */
768
- /**
769
- * Creates a protocol registry for managing security protocol providers.
770
- *
771
- * The registry provides a centralized store for protocol providers,
772
- * allowing channels to retrieve the appropriate provider based on
773
- * the negotiated protocol version.
774
- *
775
- * The 'none' protocol is always considered supported (it requires
776
- * no provider) and cannot be registered or unregistered.
777
- *
778
- * @returns A new protocol registry instance
779
- *
780
- * @example Managing protocol providers
781
- * ```typescript
782
- * const registry = createProtocolRegistry()
783
- *
784
- * // Register v1 protocol
785
- * registry.register('v1', createProtocol(logger, 60))
786
- *
787
- * // Check availability
788
- * registry.has('v1') // true
789
- * registry.has('v2') // false
790
- * registry.has('none') // always true
791
- *
792
- * // Get supported versions
793
- * registry.getSupportedVersions() // ['v1', 'none']
794
- * ```
795
- */
796
286
  function createProtocolRegistry() {
797
287
  const providers = createMap();
798
- /**
799
- * Register a protocol provider.
800
- *
801
- * @param version - The protocol version ('v1' or 'v2')
802
- * @param provider - The protocol provider instance
803
- */
804
288
  const register = (version, provider) => {
805
289
  if (!provider) {
806
290
  throw createError(`Cannot register null/undefined provider for ${version}`);
807
291
  }
808
292
  providers.set(version, provider);
809
293
  };
810
- /**
811
- * Unregister a protocol provider.
812
- *
813
- * @param version - The protocol version to unregister
814
- */
815
294
  const unregister = (version) => {
816
295
  providers.delete(version);
817
296
  };
818
- /**
819
- * Get a registered protocol provider.
820
- *
821
- * Returns undefined for 'none' since it requires no provider.
822
- *
823
- * @param version - The protocol version to retrieve
824
- * @returns The provider if registered, otherwise undefined
825
- */
826
297
  const get = (version) => {
827
298
  if (version === 'none') {
828
299
  return undefined;
829
300
  }
830
301
  return providers.get(version);
831
302
  };
832
- /**
833
- * Check if a protocol provider is registered.
834
- *
835
- * The 'none' protocol is always considered available.
836
- *
837
- * @param version - The protocol version to check
838
- * @returns True if the provider is registered (or 'none')
839
- */
840
303
  const has = (version) => {
841
304
  if (version === 'none') {
842
305
  return true;
843
306
  }
844
307
  return providers.has(version);
845
308
  };
846
- /**
847
- * Get all supported protocol versions.
848
- *
849
- * Returns versions that have registered providers plus 'none'.
850
- *
851
- * @returns Array of supported protocol versions
852
- */
853
309
  const getSupportedVersions = () => {
854
310
  const versions = ['none'];
855
311
  if (providers.has('v1')) {
@@ -869,20 +325,6 @@
869
325
  });
870
326
  }
871
327
 
872
- /**
873
- * Merges multiple channel contracts into a single contract
874
- *
875
- * @param contracts - The contracts to merge
876
- * @returns A single merged contract containing all accepted and provided actions
877
- *
878
- * @example Merging channel contracts
879
- * ```typescript
880
- * const contract1 = { accepted: [{ type: 'a' }], provided: [{ type: 'b' }] }
881
- * const contract2 = { accepted: [{ type: 'c' }], provided: [{ type: 'd' }] }
882
- * const merged = mergeContracts(contract1, contract2)
883
- * // merged = { accepted: [{ type: 'a' }, { type: 'c' }], emitted: [{ type: 'b' }, { type: 'd' }] }
884
- * ```
885
- */
886
328
  function mergeContracts(...contracts) {
887
329
  const mergedContract = {
888
330
  accepted: [],
@@ -919,38 +361,9 @@
919
361
  info: 1,
920
362
  debug: 0,
921
363
  };
922
- /**
923
- * Validates whether a given string is a valid log level.
924
- *
925
- * @param level - The log level to validate
926
- * @returns True if the level is valid, false otherwise
927
- *
928
- * @example Validating log levels
929
- * ```typescript
930
- * isValidLogLevel('error') // => true
931
- * isValidLogLevel('verbose') // => false
932
- * ```
933
- */
934
364
  function isValidLogLevel(level) {
935
365
  return logLevels.includes(level);
936
366
  }
937
- /**
938
- * Creates a log level configuration manager for controlling logging behavior.
939
- * Provides methods to get, set, and evaluate log levels based on priority.
940
- *
941
- * @param level - The initial log level (defaults to 'error')
942
- * @returns A configuration object with log level management methods
943
- * @throws {Error} When the provided level is not a valid log level
944
- *
945
- * @example Managing log levels with priority checks
946
- * ```typescript
947
- * const config = createLogLevelConfig('warn')
948
- * config.shouldLog('error') // => true (error >= warn)
949
- * config.shouldLog('debug') // => false (debug < warn)
950
- * config.setLogLevel('debug')
951
- * config.shouldLog('debug') // => true
952
- * ```
953
- */
954
367
  function createLogLevelConfig(level = 'error') {
955
368
  if (!isValidLogLevel(level)) {
956
369
  throw createError('Cannot create log level configuration with a valid default log level');
@@ -1002,18 +415,6 @@
1002
415
  },
1003
416
  ];
1004
417
 
1005
- /**
1006
- * Gets the keys from an iterable target based on its data type.
1007
- *
1008
- * @param target - The target to get the keys from.
1009
- * @param dataType - The data type of the target.
1010
- * @returns The keys from the iterable target.
1011
- *
1012
- * @example Extracting keys from object
1013
- * ```typescript
1014
- * getKeysFromIterable({ a: 1, b: 2 }, 'object') // ['a', 'b']
1015
- * ```
1016
- */
1017
418
  const getKeysFromIterable = (target, dataType) => {
1018
419
  if (dataType === 'array')
1019
420
  dataType = Array.name;
@@ -1025,21 +426,6 @@
1025
426
  return iterableClass.getKeys(target);
1026
427
  };
1027
428
 
1028
- /**
1029
- * Returns the data type of the target.
1030
- * Uses native `typeof` operator, however, makes distinction between `null`, `array`, and `object`.
1031
- * Also, when classes are registered via `registerClass`, it checks if objects are instance of any known registered class.
1032
- *
1033
- * @param target - The target to get the data type of.
1034
- * @returns The data type of the target.
1035
- *
1036
- * @example Determining data types
1037
- * ```typescript
1038
- * getType([1, 2]) // 'array'
1039
- * getType({ a: 1 }) // 'object'
1040
- * getType(null) // 'null'
1041
- * ```
1042
- */
1043
429
  const getType = (target) => {
1044
430
  if (target === null)
1045
431
  return 'null';
@@ -1055,17 +441,6 @@
1055
441
  return nativeDataType;
1056
442
  };
1057
443
 
1058
- /**
1059
- * Returns a list of iterable data types. By default 'array' and 'object' are included.,
1060
- * but can be extended by using `registerIterableClass`.
1061
- *
1062
- * @returns Array of iterable data types.
1063
- *
1064
- * @example Listing registered iterable types
1065
- * ```typescript
1066
- * getIterableTypes() // ['array', 'object', ...registered types]
1067
- * ```
1068
- */
1069
444
  const getIterableTypes = () => registeredIterableClasses.map(({ classRef }) => {
1070
445
  const name = classRef.name;
1071
446
  if (name === Object.name)
@@ -1075,109 +450,19 @@
1075
450
  return name;
1076
451
  });
1077
452
 
1078
- /**
1079
- * Checks if the provided data type is registered as an iterable type.
1080
- *
1081
- * @param dataType - The data type to check
1082
- * @returns `true` if the data type is iterable, otherwise `false`
1083
- *
1084
- * @example Checking iterable types
1085
- * ```typescript
1086
- * isIterableType('array') // true
1087
- * isIterableType('object') // true
1088
- * isIterableType('string') // false
1089
- * ```
1090
- */
1091
453
  const isIterableType = (dataType) => getIterableTypes().includes(dataType);
1092
454
 
1093
- /**
1094
- * Checks if the target is iterable.
1095
- *
1096
- * @param target - The target to check.
1097
- * @returns `true` if the target is iterable, `false` otherwise.
1098
- *
1099
- * @example Checking if value is iterable
1100
- * ```typescript
1101
- * isIterable([1, 2]) // true
1102
- * isIterable({ a: 1 }) // true
1103
- * isIterable('string') // false
1104
- * ```
1105
- */
1106
455
  const isIterable = (target) => isIterableType(getType(target));
1107
456
 
1108
- /**
1109
- * Safe copies of Date built-in via factory function and static methods.
1110
- *
1111
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/date
1112
- */
1113
- /* eslint-disable jsdoc/require-param */
1114
457
  const _Date = globalThis.Date;
1115
- const _Reflect$4 = globalThis.Reflect;
1116
- /**
1117
- * (Safe copy) Creates a new Date using the captured Date constructor.
1118
- * Use this instead of `new Date()`. Accepts all standard Date constructor signatures.
1119
- *
1120
- * @returns A new Date instance.
1121
- *
1122
- * @example Creating Date instances
1123
- * ```typescript
1124
- * const now = createDate()
1125
- * const fromTimestamp = createDate(1704067200000)
1126
- * const fromString = createDate('2024-01-01T00:00:00Z')
1127
- * const fromParts = createDate(2024, 0, 1, 12, 30, 0) // Jan 1, 2024 12:30:00
1128
- * ```
1129
- */
458
+ const _Reflect$5 = globalThis.Reflect;
1130
459
  function createDate(...args) {
1131
- return _Reflect$4.construct(_Date, args);
1132
- }
1133
- /**
1134
- * (Safe copy) Returns the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC.
1135
- *
1136
- * @example
1137
- * ```typescript
1138
- * const timestamp = dateNow()
1139
- * // => 1704067200000 (example timestamp)
1140
- * ```
1141
- */
460
+ return _Reflect$5.construct(_Date, args);
461
+ }
1142
462
  const dateNow = _Date.now;
1143
- /**
1144
- * (Safe copy) Parses a string representation of a date.
1145
- *
1146
- * @example
1147
- * ```typescript
1148
- * const timestamp = dateParse('2024-01-01T00:00:00Z')
1149
- * // => 1704067200000
1150
- * ```
1151
- */
1152
463
  const dateParse = _Date.parse;
1153
- /**
1154
- * (Safe copy) Returns the number of milliseconds in a Date object since January 1, 1970 UTC.
1155
- *
1156
- * @example
1157
- * ```typescript
1158
- * const timestamp = dateUTC(2024, 0, 1, 12, 0, 0)
1159
- * // => 1704110400000 (Jan 1, 2024 12:00:00 UTC)
1160
- * ```
1161
- */
1162
464
  const dateUTC = _Date.UTC;
1163
465
 
1164
- /* eslint-disable @typescript-eslint/no-explicit-any */
1165
- /**
1166
- * Creates a wrapper function that only executes the wrapped function if the condition function returns true.
1167
- *
1168
- * @param func - The function to be conditionally executed.
1169
- * @param conditionFunc - A function that returns a boolean, determining if `func` should be executed.
1170
- * @returns A wrapped version of `func` that executes conditionally.
1171
- *
1172
- * @example Conditional logging based on flag
1173
- * ```typescript
1174
- * let enabled = false
1175
- * const conditionalLog = createConditionalExecutionFunction(console.log, () => enabled)
1176
- * conditionalLog('test') // does nothing
1177
- * enabled = true
1178
- * conditionalLog('test') // logs 'test'
1179
- * ```
1180
- */
1181
466
  function createConditionalExecutionFunction(func, conditionFunc) {
1182
467
  return function (...args) {
1183
468
  if (conditionFunc()) {
@@ -1186,77 +471,19 @@
1186
471
  };
1187
472
  }
1188
473
 
1189
- /* eslint-disable @typescript-eslint/no-explicit-any */
1190
- /**
1191
- * Creates a wrapper function that silently ignores any errors thrown by the wrapped void function.
1192
- * This function is specifically for wrapping functions that do not return a value (void functions).
1193
- * Exceptions are swallowed without any logging or handling.
1194
- *
1195
- * @param func - The void function to be wrapped.
1196
- * @returns A wrapped version of the input function that ignores errors.
1197
- *
1198
- * @example Safely parsing invalid JSON
1199
- * ```typescript
1200
- * const safeParse = createErrorIgnoringFunction(() => JSON.parse('invalid'))
1201
- * safeParse() // silently fails without throwing
1202
- * ```
1203
- */
1204
474
  function createErrorIgnoringFunction(func) {
1205
475
  return function (...args) {
1206
476
  try {
1207
477
  func(...args);
1208
478
  }
1209
479
  catch {
1210
- // Deliberately swallowing/ignoring the exception
1211
480
  }
1212
481
  };
1213
482
  }
1214
483
 
1215
- /* eslint-disable @typescript-eslint/no-unused-vars */
1216
- /**
1217
- * A no-operation function (noop) that does nothing regardless of the arguments passed.
1218
- * It is designed to be as permissive as possible in its typing without using the `Function` keyword.
1219
- *
1220
- * @param args - Any arguments passed to the function (ignored)
1221
- *
1222
- * @example Using noop as fallback callback
1223
- * ```typescript
1224
- * const callback = condition ? handleEvent : noop
1225
- * callback() // safely does nothing if condition is false
1226
- * ```
1227
- */
1228
484
  const noop = (...args) => {
1229
- // Intentionally does nothing
1230
485
  };
1231
486
 
1232
- /**
1233
- * Creates a logger instance with configurable log level filtering.
1234
- * Each log function is wrapped to respect the current log level setting.
1235
- *
1236
- * @param error - Function to handle error-level logs (required)
1237
- * @param warn - Function to handle warning-level logs (optional, defaults to noop)
1238
- * @param log - Function to handle standard logs (optional, defaults to noop)
1239
- * @param info - Function to handle info-level logs (optional, defaults to noop)
1240
- * @param debug - Function to handle debug-level logs (optional, defaults to noop)
1241
- * @returns A frozen logger object with log methods, level control, channel, and timing helpers
1242
- * @throws {ErrorLevelFn} When any provided log function is invalid
1243
- *
1244
- * @example Creating a logger with log level filtering
1245
- * ```typescript
1246
- * const logger = createLogger(console.error, console.warn, console.log)
1247
- * logger.setLogLevel('warn')
1248
- * logger.warn('Connection timeout') // logs
1249
- * logger.info('Request complete') // suppressed (below 'warn' level)
1250
- * ```
1251
- *
1252
- * @example Channeling and timing
1253
- * ```typescript
1254
- * const logger = createLogger(console.error, console.warn, console.log, console.info, console.debug)
1255
- * logger.setLogLevel('debug')
1256
- * const build = logger.channel('build')
1257
- * await build.timedAsync('bundle', async () => bundle())
1258
- * ```
1259
- */
1260
487
  function createLogger$1(error, warn = noop, log = noop, info = noop, debug = noop) {
1261
488
  if (notValidLogFn(error)) {
1262
489
  throw createError(notFnMsg('error'));
@@ -1291,20 +518,11 @@
1291
518
  };
1292
519
  return createPrefixedLogger(core, '');
1293
520
  }
1294
- /**
1295
- * Creates a Logger that prepends `[fullPrefix]` to every log call. When `fullPrefix`
1296
- * is empty, returns a Logger that delegates directly to the core functions.
1297
- *
1298
- * @param core - Shared level-aware log functions and level controls.
1299
- * @param fullPrefix - Channel chain joined with `:`, or `''` for the root logger.
1300
- * @returns A frozen Logger that emits with the resolved prefix.
1301
- */
1302
521
  function createPrefixedLogger(core, fullPrefix) {
1303
522
  const tag = fullPrefix ? `[${fullPrefix}]` : '';
1304
523
  const prefixed = (fn) => {
1305
524
  if (!tag)
1306
525
  return fn;
1307
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1308
526
  return (...data) => fn(tag, ...data);
1309
527
  };
1310
528
  const instance = freeze({
@@ -1325,14 +543,6 @@
1325
543
  });
1326
544
  return instance;
1327
545
  }
1328
- /**
1329
- * Times a sync call, logging completion at debug level or failure at error level.
1330
- *
1331
- * @param logger - The logger used to emit the timing message.
1332
- * @param label - Human-readable label for the timed operation.
1333
- * @param fn - The synchronous operation to invoke.
1334
- * @returns The value returned by `fn`.
1335
- */
1336
546
  function runTimed(logger, label, fn) {
1337
547
  const start = dateNow();
1338
548
  try {
@@ -1347,15 +557,6 @@
1347
557
  throw error;
1348
558
  }
1349
559
  }
1350
- /**
1351
- * Times an async call, logging completion at debug level or failure at error level.
1352
- * On rejection with an `Error` carrying a stack, the stack is also dumped at debug level.
1353
- *
1354
- * @param logger - The logger used to emit the timing message.
1355
- * @param label - Human-readable label for the timed operation.
1356
- * @param fn - The async operation to invoke.
1357
- * @returns The value resolved by `fn`'s promise.
1358
- */
1359
560
  async function runTimedAsync(logger, label, fn) {
1360
561
  const start = dateNow();
1361
562
  try {
@@ -1373,144 +574,42 @@
1373
574
  throw error;
1374
575
  }
1375
576
  }
1376
- /**
1377
- * Extracts a human-readable message from an unknown thrown value.
1378
- *
1379
- * @param error - The unknown value caught from a try/catch or rejected promise.
1380
- * @returns The error's `message` when it is an `Error`, otherwise its string coercion.
1381
- */
1382
577
  function describeError(error) {
1383
578
  return error instanceof Error ? error.message : String(error);
1384
579
  }
1385
- /**
1386
- * Validates whether a given value is a valid log function.
1387
- *
1388
- * @param fn - The value to validate
1389
- * @returns True if the value is not a function (invalid), false if it is valid
1390
- */
1391
580
  function notValidLogFn(fn) {
1392
581
  return getType(fn) !== 'function' && fn !== noop;
1393
582
  }
1394
- /**
1395
- * Generates an error message for invalid log function parameters.
1396
- *
1397
- * @param label - The name of the log function that failed validation
1398
- * @returns A formatted error message string
1399
- */
1400
583
  function notFnMsg(label) {
1401
584
  return `Cannot create a logger when ${label} is not a function`;
1402
585
  }
1403
586
 
1404
- /**
1405
- * Safe copies of Console built-in methods.
1406
- *
1407
- * These references are captured at module initialization time to protect against
1408
- * prototype pollution attacks. Import only what you need for tree-shaking.
1409
- *
1410
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/console
1411
- */
1412
587
  const _console = globalThis.console;
1413
- /**
1414
- * (Safe copy) Outputs a message to the console.
1415
- */
1416
588
  const log = _console.log.bind(_console);
1417
- /**
1418
- * (Safe copy) Outputs a warning message to the console.
1419
- */
1420
589
  const warn = _console.warn.bind(_console);
1421
- /**
1422
- * (Safe copy) Outputs an error message to the console.
1423
- */
1424
590
  const error = _console.error.bind(_console);
1425
- /**
1426
- * (Safe copy) Outputs an informational message to the console.
1427
- */
1428
591
  const info = _console.info.bind(_console);
1429
- /**
1430
- * (Safe copy) Outputs a debug message to the console.
1431
- */
1432
592
  const debug = _console.debug.bind(_console);
1433
- /**
1434
- * (Safe copy) Outputs a stack trace to the console.
1435
- */
1436
593
  _console.trace.bind(_console);
1437
- /**
1438
- * (Safe copy) Displays an interactive listing of the properties of a specified object.
1439
- */
1440
594
  _console.dir.bind(_console);
1441
- /**
1442
- * (Safe copy) Displays tabular data as a table.
1443
- */
1444
595
  _console.table.bind(_console);
1445
- /**
1446
- * (Safe copy) Writes an error message to the console if the assertion is false.
1447
- */
1448
596
  _console.assert.bind(_console);
1449
- /**
1450
- * (Safe copy) Clears the console.
1451
- */
1452
597
  _console.clear.bind(_console);
1453
- /**
1454
- * (Safe copy) Logs the number of times that this particular call to count() has been called.
1455
- */
1456
598
  _console.count.bind(_console);
1457
- /**
1458
- * (Safe copy) Resets the counter used with console.count().
1459
- */
1460
599
  _console.countReset.bind(_console);
1461
- /**
1462
- * (Safe copy) Creates a new inline group in the console.
1463
- */
1464
600
  _console.group.bind(_console);
1465
- /**
1466
- * (Safe copy) Creates a new inline group in the console that is initially collapsed.
1467
- */
1468
601
  _console.groupCollapsed.bind(_console);
1469
- /**
1470
- * (Safe copy) Exits the current inline group.
1471
- */
1472
602
  _console.groupEnd.bind(_console);
1473
- /**
1474
- * (Safe copy) Starts a timer with a name specified as an input parameter.
1475
- */
1476
603
  _console.time.bind(_console);
1477
- /**
1478
- * (Safe copy) Stops a timer that was previously started.
1479
- */
1480
604
  _console.timeEnd.bind(_console);
1481
- /**
1482
- * (Safe copy) Logs the current value of a timer that was previously started.
1483
- */
1484
605
  _console.timeLog.bind(_console);
1485
606
 
1486
607
  const logger = createLogger$1(error, warn, log, info, debug);
1487
608
 
1488
609
  const DEFAULT_PREFIX = '[nexus]';
1489
- /**
1490
- * Creates a logger instance configured for nexus.
1491
- *
1492
- * If a custom logger is provided, it will be used directly.
1493
- * Otherwise, a new logger will be created using the logging library.
1494
- *
1495
- * @param options - Logger configuration options
1496
- * @returns Logger instance
1497
- *
1498
- * @example Configuring logger options
1499
- * ```typescript
1500
- * const logger = createLogger({ level: 'debug', prefix: '[my-channel]' })
1501
- * logger.debug('Channel initialized')
1502
- * ```
1503
- */
1504
610
  function createLogger(options = {}) {
1505
611
  return createLoggerInternal(options);
1506
612
  }
1507
- /**
1508
- * Internal helper to create a logger with given options.
1509
- *
1510
- * @param options - Logger configuration options
1511
- * @returns Configured logger instance
1512
- * @internal
1513
- */
1514
613
  const createLoggerInternal = (options) => {
1515
614
  const { level = 'error', prefix = DEFAULT_PREFIX, customLogger } = options;
1516
615
  if (customLogger)
@@ -1521,31 +620,10 @@
1521
620
  return nexusLogger;
1522
621
  };
1523
622
 
1524
- /**
1525
- * Safe WeakSet factory for protected weak set construction.
1526
- *
1527
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/weak-set
1528
- */
1529
- /* eslint-disable workspace/lib-require-jsdoc-example */
1530
623
  const _WeakSet = globalThis.WeakSet;
1531
- const _Reflect$3 = globalThis.Reflect;
1532
- /**
1533
- * (Safe copy) Creates a new WeakSet using the captured WeakSet constructor.
1534
- * Use this instead of `new WeakSet()`.
1535
- *
1536
- * @param iterable - Optional iterable of values.
1537
- * @returns A new WeakSet instance.
1538
- */
1539
- const createWeakSet = (iterable) => _Reflect$3.construct(_WeakSet, iterable ? [iterable] : []);
1540
-
1541
- /**
1542
- * Checks whether an object has circular references using WeakSet.
1543
- * Safe for use with frozen objects since it only reads, never mutates.
1544
- *
1545
- * @param value - The value to check
1546
- * @param seen - WeakSet of already-seen references
1547
- * @returns True if circular reference detected
1548
- */
624
+ const _Reflect$4 = globalThis.Reflect;
625
+ const createWeakSet = (iterable) => _Reflect$4.construct(_WeakSet, iterable ? [iterable] : []);
626
+
1549
627
  function hasCircular(value, seen) {
1550
628
  if (!isIterable(value))
1551
629
  return false;
@@ -1562,36 +640,12 @@
1562
640
  }
1563
641
  return false;
1564
642
  }
1565
- /**
1566
- * Asserts that the given value does not contain circular references.
1567
- * Throws an Error if a circular reference is detected.
1568
- *
1569
- * Uses WeakSet-based cycle detection that works safely with frozen objects.
1570
- *
1571
- * @param value - The value to check for circular references
1572
- * @param paramName - Name of the parameter for error messaging
1573
- * @throws {Error} if circular reference is detected
1574
- *
1575
- * @example Checking for circular references
1576
- * ```typescript
1577
- * const config = { a: 1, b: 2 }
1578
- * assertNoCircularRef(config, 'config') // OK
1579
- *
1580
- * const circular: Record<string, unknown> = { a: 1 }
1581
- * circular.self = circular
1582
- * assertNoCircularRef(circular, 'config') // Throws Error
1583
- * ```
1584
- */
1585
643
  function assertNoCircularRef(value, paramName) {
1586
644
  if (hasCircular(value, createWeakSet())) {
1587
645
  throw createError(`Circular reference detected in parameter "${paramName}"`);
1588
646
  }
1589
647
  }
1590
648
 
1591
- /**
1592
- * Default channel settings.
1593
- * Accept any origin, queue messages, inherit contract from broker.
1594
- */
1595
649
  const DEFAULT_CHANNEL_SETTINGS = freeze({
1596
650
  origin: '*',
1597
651
  queueMessages: true,
@@ -1599,23 +653,6 @@
1599
653
  contract: undefined,
1600
654
  });
1601
655
 
1602
- /**
1603
- * Gracefully closes an active channel connection.
1604
- *
1605
- * - Only works if channel is currently active
1606
- * - Sets channel state to inactive
1607
- * - Optionally notifies the target window
1608
- * - Fires 'close' event to subscribers
1609
- *
1610
- * @param channel - Channel internals with state and dependencies
1611
- * @param notify - Whether to notify target window (default: true)
1612
- *
1613
- * @example Gracefully closing a connection
1614
- * ```typescript
1615
- * disconnect(channel, true) // Close and notify target
1616
- * disconnect(channel, false) // Close silently
1617
- * ```
1618
- */
1619
656
  function disconnect(channel, notify = true) {
1620
657
  const state = channel.getState();
1621
658
  if (!state.active) {
@@ -1630,22 +667,6 @@
1630
667
  channel.notifyEvent('close');
1631
668
  }
1632
669
 
1633
- /**
1634
- * Cancels a pending connection request.
1635
- *
1636
- * - If channel is closed, sends CANCEL_CONNECTION
1637
- * - If channel is already open, calls disconnect instead
1638
- * - Fires 'cancel' event to subscribers
1639
- *
1640
- * @param channel - Channel internals with state and dependencies
1641
- * @param notify - Whether to notify target window (default: true)
1642
- *
1643
- * @example Canceling a pending connection
1644
- * ```typescript
1645
- * cancel(channel, true) // Cancel and notify target
1646
- * cancel(channel, false) // Cancel silently
1647
- * ```
1648
- */
1649
670
  function cancel(channel, notify = true) {
1650
671
  const state = channel.getState();
1651
672
  if (state.active) {
@@ -1660,19 +681,6 @@
1660
681
  channel.notifyEvent('cancel');
1661
682
  }
1662
683
 
1663
- /**
1664
- * Clears all queued messages from the channel.
1665
- * Returns a new state object with empty queue (immutable update).
1666
- *
1667
- * @param state - Current channel state
1668
- * @returns New state with cleared message queue
1669
- *
1670
- * @example Clearing all queued messages
1671
- * ```typescript
1672
- * const clearedState = clearQueue(channelState)
1673
- * // => { ...channelState, queuedMessages: [] }
1674
- * ```
1675
- */
1676
684
  function clearQueue(state) {
1677
685
  return freeze({
1678
686
  ...state,
@@ -1680,21 +688,6 @@
1680
688
  });
1681
689
  }
1682
690
 
1683
- /**
1684
- * Adds a message to the channel's queue.
1685
- * Returns a new state object with the message appended (immutable update).
1686
- *
1687
- * @param state - Current channel state
1688
- * @param message - Message to queue
1689
- * @returns New state with message added to queue
1690
- *
1691
- * @example Adding a message to the queue
1692
- * ```typescript
1693
- * const message = { type: 'data', payload: { userId: 123 } }
1694
- * const updatedState = queueMessage(channelState, message)
1695
- * // => { ...channelState, queuedMessages: [...existingMessages, message] }
1696
- * ```
1697
- */
1698
691
  function queueMessage(state, message) {
1699
692
  return freeze({
1700
693
  ...state,
@@ -1702,33 +695,12 @@
1702
695
  });
1703
696
  }
1704
697
 
1705
- /**
1706
- * Queues a message for delivery when the channel opens.
1707
- *
1708
- * Messages are stored in the channel state and will be sent
1709
- * automatically when the channel becomes active via the flush operation.
1710
- *
1711
- * @param channel - Channel internals with state and dependencies
1712
- * @param message - Message to queue
1713
- *
1714
- * @example Queueing a message
1715
- * ```typescript
1716
- * queue(channel, { type: 'GREETING', data: 'Hello' })
1717
- * ```
1718
- */
1719
698
  function queue(channel, message) {
1720
699
  const state = channel.getState();
1721
700
  const newState = queueMessage(state, message);
1722
701
  channel.updateState(newState);
1723
702
  }
1724
703
 
1725
- /**
1726
- * Action types that should always be sent in plaintext.
1727
- *
1728
- * Handshake actions must remain unencrypted because:
1729
- * - Security negotiation happens during handshake
1730
- * - Both parties need to read handshake messages before security is established
1731
- */
1732
704
  const PLAINTEXT_ACTION_TYPES = createSet([
1733
705
  ACTION_TYPES.REQUEST_CONNECTION,
1734
706
  ACTION_TYPES.ACCEPT_CONNECTION,
@@ -1737,26 +709,6 @@
1737
709
  ACTION_TYPES.CANCEL_CONNECTION_ACKNOWLEDGED,
1738
710
  ACTION_TYPES.OPEN_CONNECTION,
1739
711
  ]);
1740
- /**
1741
- * Sends a raw action to the channel's target window.
1742
- *
1743
- * This function routes actions through the security transport when:
1744
- * - The channel has a security transport configured
1745
- * - The security transport is ready
1746
- * - The action type is not a handshake action (handshakes are always plaintext)
1747
- *
1748
- * For secure protocols (v1/v2), the action is encrypted and sent as Uint8Array.
1749
- * For 'none' protocol or handshake actions, the action is sent as plain object.
1750
- *
1751
- * @param channel - Channel internals with state and dependencies
1752
- * @param action - Action to send
1753
- *
1754
- * @example Sending an action to the target window
1755
- * ```typescript
1756
- * const action = channel.actions.requestConnection(processId)
1757
- * sendAction(channel, action)
1758
- * ```
1759
- */
1760
712
  function sendAction(channel, action) {
1761
713
  const state = channel.getState();
1762
714
  if (!action || typeof action.type !== 'string') {
@@ -1771,29 +723,6 @@
1771
723
  state.target.postMessage(action, '*');
1772
724
  }
1773
725
 
1774
- /**
1775
- * Sends a typed message through an active channel.
1776
- *
1777
- * Message routing behavior:
1778
- * - If channel is closed and queueMessages is enabled, queues the message
1779
- * - If channel is closed and queueMessages is disabled, throws error
1780
- * - If security transport exists but is not ready, queues the message
1781
- * - If channel is open and security is ready (or protocol is 'none'), sends message
1782
- *
1783
- * For secure protocols (v1/v2), the message is routed through the security
1784
- * transport which encrypts and sends as Uint8Array via postMessage.
1785
- *
1786
- * @param channel - Channel internals with state and dependencies
1787
- * @param message - Message to send with type and data
1788
- *
1789
- * @throws {Error} If channel is closed and queueing is disabled
1790
- * @throws {Error} If message type is not accepted in channel contract
1791
- *
1792
- * @example Sending a typed message
1793
- * ```typescript
1794
- * send(channel, { type: 'USER_ACTION', data: { userId: 123 } })
1795
- * ```
1796
- */
1797
726
  function send(channel, message) {
1798
727
  const state = channel.getState();
1799
728
  if (!state.active) {
@@ -1816,24 +745,11 @@
1816
745
  if (!emittedTypes.includes(message.type)) {
1817
746
  throw createError(`Cannot send message to ${state.name} channel. Message type '${message.type}' is not in the emitted actions of channel contract.`);
1818
747
  }
1819
- const action = channel.actions.newMessage(message.data);
748
+ const action = channel.actions.newMessage(message);
1820
749
  sendAction(channel, action);
1821
750
  channel.notifyMessage(message);
1822
751
  }
1823
752
 
1824
- /**
1825
- * Sends all queued messages and clears the queue.
1826
- *
1827
- * Called automatically when a channel opens if queueMessages is enabled.
1828
- * Messages are sent in FIFO order.
1829
- *
1830
- * @param channel - Channel internals with state and dependencies
1831
- *
1832
- * @example Flushing queued messages
1833
- * ```typescript
1834
- * flush(channel) // Sends all queued messages
1835
- * ```
1836
- */
1837
753
  function flush(channel) {
1838
754
  const state = channel.getState();
1839
755
  for (const message of state.queuedMessages) {
@@ -1850,20 +766,6 @@
1850
766
  channel.updateState(newState);
1851
767
  }
1852
768
 
1853
- /**
1854
- * Initiates the connection handshake for a channel.
1855
- *
1856
- * - If channel is already open, does nothing
1857
- * - If channel has a scheduled activation (pending connection), accepts it
1858
- * - Otherwise, sends REQUEST_CONNECTION to initiate handshake
1859
- *
1860
- * @param channel - Channel internals with state and dependencies
1861
- *
1862
- * @example Initiating a connection
1863
- * ```typescript
1864
- * connect(channel) // Sends REQUEST_CONNECTION or accepts pending
1865
- * ```
1866
- */
1867
769
  function connect(channel) {
1868
770
  const state = channel.getState();
1869
771
  if (state.active) {
@@ -1905,24 +807,6 @@
1905
807
  channel.sendAction(requestAction);
1906
808
  }
1907
809
 
1908
- /**
1909
- * Immediately destroys a channel and removes it from the broker.
1910
- *
1911
- * - Sets channel to inactive immediately
1912
- * - Optionally notifies the target window
1913
- * - Removes channel from all registries
1914
- * - Fires 'destroy' event to subscribers
1915
- * - This is irreversible - channel cannot be reconnected
1916
- *
1917
- * @param channel - Channel internals with state and dependencies
1918
- * @param notify - Whether to notify target window (default: true)
1919
- *
1920
- * @example Destroying a channel
1921
- * ```typescript
1922
- * destroy(channel, true) // Destroy and notify target
1923
- * destroy(channel, false) // Destroy silently
1924
- * ```
1925
- */
1926
810
  function destroy(channel, notify = true) {
1927
811
  channel.updateState({ active: false });
1928
812
  if (notify) {
@@ -1934,22 +818,6 @@
1934
818
  }
1935
819
  }
1936
820
 
1937
- /**
1938
- * Activates a channel by setting it as active and recording connection details.
1939
- * Returns a new state object (immutable update).
1940
- *
1941
- * @param state - Current channel state
1942
- * @param origin - Origin of the connected channel
1943
- * @param contract - Negotiated channel contract
1944
- * @returns New state with channel activated
1945
- *
1946
- * @example Activating a channel with contract
1947
- * ```typescript
1948
- * const contract = { accepted: [{ type: 'message' }] }
1949
- * const activeState = activate(channelState, 'https://example.com', contract)
1950
- * // => { ...channelState, active: true, origin: 'https://example.com', ... }
1951
- * ```
1952
- */
1953
821
  function activate(state, origin, contract) {
1954
822
  const acceptedActions = (contract.accepted || []).map((action) => action.type);
1955
823
  return freeze({
@@ -1963,21 +831,6 @@
1963
831
  });
1964
832
  }
1965
833
 
1966
- /**
1967
- * Creates the initial state for a new channel.
1968
- * All collections are empty, all optional fields are null.
1969
- *
1970
- * @param name - Channel name/identifier
1971
- * @param target - Target window for communication
1972
- * @param settings - Channel settings (queueMessages, debug, logger, etc.)
1973
- * @returns Fresh channel state object
1974
- *
1975
- * @example Creating initial channel state
1976
- * ```typescript
1977
- * const state = createInitialState('my-channel', targetWindow, { queueMessages: true })
1978
- * // => { id: '...', name: 'my-channel', active: false, queuedMessages: [], ... }
1979
- * ```
1980
- */
1981
834
  function createInitialState(name, target, settings) {
1982
835
  return freeze({
1983
836
  id: uuidV4(),
@@ -2003,26 +856,6 @@
2003
856
  });
2004
857
  }
2005
858
 
2006
- /**
2007
- * Implementation of subscribeToEvents that handles both overload signatures.
2008
- *
2009
- * @param channel - The channel internals object
2010
- * @param eventOrHandler - Either an event name or a handler function
2011
- * @param handler - Handler function when event name is provided
2012
- * @returns Unsubscribe function to remove the handler
2013
- *
2014
- * @example Subscribing to all events with cleanup
2015
- * ```typescript
2016
- * const unsubscribe = subscribeToEvents(channel, (event, data) => {
2017
- * if (event === 'open') {
2018
- * console.log('Channel opened:', data)
2019
- * }
2020
- * })
2021
- *
2022
- * // Cleanup when no longer needed
2023
- * unsubscribe()
2024
- * ```
2025
- */
2026
859
  function subscribeToEvents(channel, eventOrHandler, handler) {
2027
860
  const isEventSpecific = typeof eventOrHandler === 'string' && typeof handler === 'function';
2028
861
  let wrappedHandler;
@@ -2054,26 +887,6 @@
2054
887
  };
2055
888
  }
2056
889
 
2057
- /**
2058
- * Subscribes to incoming messages on the channel.
2059
- *
2060
- * Handler will be called with each message received from the target window.
2061
- *
2062
- * @param channel - Channel internals with state and dependencies
2063
- * @param handler - Message handler function
2064
- * @returns Unsubscribe function to remove the handler
2065
- *
2066
- * @throws {Error} If handler is not a function
2067
- *
2068
- * @example Subscribing to channel messages
2069
- * ```typescript
2070
- * const unsubscribe = subscribeToMessages(channel, (message) => {
2071
- * console.log('Message:', message.type, message.data)
2072
- * })
2073
- *
2074
- * // Later: unsubscribe()
2075
- * ```
2076
- */
2077
890
  function subscribeToMessages(channel, handler) {
2078
891
  if (typeof handler !== 'function') {
2079
892
  throw createError('Expected callback function.');
@@ -2088,32 +901,10 @@
2088
901
  };
2089
902
  }
2090
903
 
2091
- /**
2092
- * Logs a channel event in a structured format.
2093
- *
2094
- * @param logger - Logger instance to use
2095
- * @param event - Type of channel event that occurred
2096
- * @param data - Additional data associated with the event
2097
- */
2098
904
  function logEvent(logger, event, data) {
2099
905
  logger.debug(`Channel event:`, event, data);
2100
906
  }
2101
907
 
2102
- /**
2103
- * Notifies all event subscribers of a channel event.
2104
- *
2105
- * Calls each subscribed event handler with the event type, optional data, and channel JSON.
2106
- * Errors in handlers are caught and logged to prevent breaking other handlers.
2107
- *
2108
- * @param channel - Channel internals with state and dependencies
2109
- * @param event - Event type that occurred
2110
- * @param data - Optional event data
2111
- *
2112
- * @example Notifying subscribers of an event
2113
- * ```typescript
2114
- * notifyEvent(channel, 'open', { timestamp: Date.now() })
2115
- * ```
2116
- */
2117
908
  function notifyEvent(channel, event, data) {
2118
909
  const state = channel.getState();
2119
910
  if (state.logger) {
@@ -2140,20 +931,6 @@
2140
931
  }
2141
932
  }
2142
933
 
2143
- /**
2144
- * Notifies all message subscribers of an incoming message.
2145
- *
2146
- * Calls each subscribed message handler with the message data.
2147
- * Errors in handlers are caught and logged to prevent breaking other handlers.
2148
- *
2149
- * @param channel - Channel internals with state and dependencies
2150
- * @param message - Message that was received
2151
- *
2152
- * @example Notifying subscribers of a message
2153
- * ```typescript
2154
- * notifyMessage(channel, { type: 'USER_ACTION', data: { userId: 123 } })
2155
- * ```
2156
- */
2157
934
  function notifyMessage(channel, message) {
2158
935
  const state = channel.getState();
2159
936
  for (const handler of state.messageSubscriptions) {
@@ -2168,26 +945,6 @@
2168
945
  }
2169
946
  }
2170
947
 
2171
- /**
2172
- * Creates a new message channel.
2173
- *
2174
- * Uses functional programming with closures for encapsulation.
2175
- * Returns a public handle with methods while keeping state private.
2176
- *
2177
- * @param config - Channel configuration (name, target, settings)
2178
- * @param deps - Dependencies (action creators, process manager, cleanup)
2179
- * @returns Channel handle with public API
2180
- *
2181
- * @example Creating and using a channel
2182
- * ```typescript
2183
- * const channel = createChannel(
2184
- * { name: 'my-channel', target: childWindow },
2185
- * { actions, processManager, cleanup }
2186
- * )
2187
- * channel.connect()
2188
- * channel.send('greet', { message: 'Hello!' })
2189
- * ```
2190
- */
2191
948
  function createChannel(config, deps) {
2192
949
  assertNoCircularRef(config.settings, 'config.settings');
2193
950
  const settings = { ...DEFAULT_CHANNEL_SETTINGS, ...config.settings };
@@ -2223,6 +980,7 @@
2223
980
  getName: () => state.name,
2224
981
  getTarget: () => state.target,
2225
982
  isActive: () => state.active,
983
+ getAcceptedTypes: () => state.acceptedActions,
2226
984
  toJSON: () => ({
2227
985
  id: state.id,
2228
986
  name: state.name,
@@ -2291,72 +1049,18 @@
2291
1049
  return freeze(handle);
2292
1050
  }
2293
1051
 
2294
- /**
2295
- * Adds a channel to the registry, making it available for lookup
2296
- * by window, ID, and name.
2297
- *
2298
- * @param registry - The channel registry instance
2299
- * @param channel - Channel to register (must have id, name, target)
2300
- * @throws {Error} Error if channel is invalid
2301
- *
2302
- * @example Registering a channel
2303
- * ```typescript
2304
- * const registry = createRegistry()
2305
- * add(registry, { id: 'ch-1', name: 'main', target: iframe.contentWindow })
2306
- * ```
2307
- */
2308
1052
  function add(registry, channel) {
2309
1053
  registry.add(channel);
2310
1054
  }
2311
1055
 
2312
- /**
2313
- * Finds a channel by its target window.
2314
- * Uses WeakMap for O(1) lookup that doesn't prevent garbage collection.
2315
- *
2316
- * @param registry - The channel registry instance
2317
- * @param target - The window to look up
2318
- * @returns Channel if found, undefined otherwise
2319
- */
2320
1056
  function getByWindow(registry, target) {
2321
1057
  return registry.getByWindow(target);
2322
1058
  }
2323
1059
 
2324
- /**
2325
- * Removes a channel from the registry, making it unavailable
2326
- * for all lookup methods.
2327
- *
2328
- * @param registry - The channel registry instance
2329
- * @param channel - Channel to unregister
2330
- */
2331
1060
  function remove(registry, channel) {
2332
1061
  registry.remove(channel);
2333
1062
  }
2334
1063
 
2335
- /**
2336
- * Adds a channel to the broker.
2337
- *
2338
- * @param state - Current broker state
2339
- * @param registry - Channel registry for storing and retrieving channels
2340
- * @param processManager - Process ID manager for tracking communication processes
2341
- * @param actions - Action creators from broker for managing channel lifecycle
2342
- * @param name - Unique identifier for the channel
2343
- * @param target - Target window to communicate with
2344
- * @param settings - Optional configuration settings for the channel
2345
- * @returns The created or existing channel
2346
- *
2347
- * @example Registering a channel with the broker
2348
- * ```typescript
2349
- * const channel = addChannel(
2350
- * brokerState,
2351
- * registry,
2352
- * processManager,
2353
- * actions,
2354
- * 'widget-channel',
2355
- * iframe.contentWindow,
2356
- * { timeout: 5000 }
2357
- * )
2358
- * ```
2359
- */
2360
1064
  function addChannel(state, registry, processManager, actions, name, target, settings = {}) {
2361
1065
  assertNoCircularRef(settings, 'settings');
2362
1066
  const existing = getByWindow(registry, target);
@@ -2383,43 +1087,14 @@
2383
1087
  return channel;
2384
1088
  }
2385
1089
 
2386
- /**
2387
- * Finds a channel by its unique ID.
2388
- * Uses Map for O(1) lookup.
2389
- *
2390
- * @param registry - The channel registry instance
2391
- * @param id - The channel ID (UUID) to look up
2392
- * @returns Channel if found, undefined otherwise
2393
- */
2394
1090
  function getById(registry, id) {
2395
1091
  return registry.getById(id);
2396
1092
  }
2397
1093
 
2398
- /**
2399
- * Finds a channel by its name.
2400
- * Uses Map for O(1) lookup.
2401
- *
2402
- * @param registry - The channel registry instance
2403
- * @param name - The channel name to look up
2404
- * @returns Channel if found, undefined otherwise
2405
- */
2406
1094
  function getByName(registry, name) {
2407
1095
  return registry.getByName(name);
2408
1096
  }
2409
1097
 
2410
- /**
2411
- * Gets a channel by reference (id, name, or window)
2412
- *
2413
- * @param registry - Channel registry containing all registered channels
2414
- * @param reference - Channel identifier (id, name, or window object)
2415
- * @returns The channel if found, null otherwise
2416
- *
2417
- * @example Retrieving channels by name or window
2418
- * ```typescript
2419
- * const channelByName = getChannel(registry, 'widget-channel')
2420
- * const channelByWindow = getChannel(registry, iframe.contentWindow)
2421
- * ```
2422
- */
2423
1098
  function getChannel(registry, reference) {
2424
1099
  if (typeof reference === 'object' && reference !== null) {
2425
1100
  const channel = getByWindow(registry, reference);
@@ -2432,64 +1107,20 @@
2432
1107
  return null;
2433
1108
  }
2434
1109
 
2435
- /**
2436
- * Returns all registered channels as an array.
2437
- * The order is not guaranteed.
2438
- *
2439
- * @param registry - The channel registry instance
2440
- * @returns Array of all registered channels
2441
- *
2442
- * @example Listing registered channels
2443
- * ```typescript
2444
- * const registry = createRegistry()
2445
- * const channels = getAll(registry)
2446
- * channels.forEach(ch => console.log(ch.name))
2447
- * ```
2448
- */
2449
1110
  function getAll(registry) {
2450
1111
  return registry.getAll();
2451
1112
  }
2452
1113
 
2453
- /**
2454
- * Lists all channels in JSON format
2455
- *
2456
- * @param registry - Channel registry containing all registered channels
2457
- * @returns Array of channel JSON representations
2458
- *
2459
- * @example Listing all channels as JSON
2460
- * ```typescript
2461
- * const channels = listChannels(registry)
2462
- * // => [{ id: 'abc-123', name: 'widget', state: 'connected' }, ...]
2463
- * ```
2464
- */
2465
1114
  function listChannels(registry) {
2466
1115
  const channels = getAll(registry);
2467
1116
  return channels.map((channel) => channel.toJSON());
2468
1117
  }
2469
1118
 
2470
- /**
2471
- * Removes a channel from the broker
2472
- *
2473
- * @param registry - Channel registry from which to remove the channel
2474
- * @param channel - The channel instance to cleanup and remove
2475
- *
2476
- * @example Removing a channel from the broker
2477
- * ```typescript
2478
- * const channel = getChannel(registry, 'widget-channel')
2479
- * if (channel) {
2480
- * removeChannel(registry, channel)
2481
- * }
2482
- * ```
2483
- */
2484
1119
  function removeChannel(registry, channel) {
2485
1120
  channel.destroy(false);
2486
1121
  remove(registry, channel);
2487
1122
  }
2488
1123
 
2489
- /**
2490
- * Default broker settings
2491
- * Used when settings are partially provided
2492
- */
2493
1124
  const defaultBrokerSettings = freeze({
2494
1125
  whitelist: freeze([]),
2495
1126
  blacklist: freeze([]),
@@ -2497,21 +1128,6 @@
2497
1128
  logLevel: 'error',
2498
1129
  });
2499
1130
 
2500
- /**
2501
- * Creates a router map for action types to handlers.
2502
- *
2503
- * @param handlers - Map of action types to handler functions
2504
- * @returns Router map
2505
- *
2506
- * @example Creating a router for action handlers
2507
- * ```typescript
2508
- * const router = createRouter({
2509
- * 'OPEN': handleOpen,
2510
- * 'CLOSE': handleClose,
2511
- * 'MESSAGE': handleMessage,
2512
- * })
2513
- * ```
2514
- */
2515
1131
  function createRouter(handlers) {
2516
1132
  const router = createMap();
2517
1133
  entries(handlers).forEach(([type, handler]) => {
@@ -2520,21 +1136,6 @@
2520
1136
  return router;
2521
1137
  }
2522
1138
 
2523
- /**
2524
- * Applies a security policy to a connection request.
2525
- *
2526
- * @param policy - The security policy function
2527
- * @param event - The MessageEvent to validate
2528
- * @param logger - Logger instance for error reporting
2529
- * @returns true if policy allows connection, false otherwise
2530
- *
2531
- * @example Validating connection requests with security policy
2532
- * ```typescript
2533
- * const policy = (event) => event.origin === 'https://trusted.example.com'
2534
- * const allowed = applyPolicy(policy, messageEvent, logger)
2535
- * // => true or false
2536
- * ```
2537
- */
2538
1139
  function applyPolicy(policy, event, logger) {
2539
1140
  try {
2540
1141
  const result = policy(event);
@@ -2546,27 +1147,6 @@
2546
1147
  }
2547
1148
  }
2548
1149
 
2549
- /**
2550
- * Handles ACCEPT_CONNECTION action.
2551
- * Completes connection handshake from the initiator's side.
2552
- *
2553
- * @param context - Routing context with state, registry, actions, and logger
2554
- * @param message - Message event containing the ACCEPT_CONNECTION action
2555
- *
2556
- * @remarks
2557
- * Side Effects:
2558
- * - Activates the channel
2559
- * - Extracts negotiated security protocol (if present)
2560
- * - Stores negotiated protocol in channel state
2561
- * - Sends OPEN_CONNECTION to complete handshake (with security confirmation)
2562
- * - Terminates process after activation
2563
- * - Fires 'open' lifecycle event
2564
- *
2565
- * @example Three-way handshake acceptance
2566
- * Second step of three-way handshake:
2567
- * Initiator <- ACCEPT (this handler) <- Responder
2568
- * Initiator -> OPEN -> Responder
2569
- */
2570
1150
  function handleAccept(context, message) {
2571
1151
  const { state, processManager, logger } = context;
2572
1152
  const action = message.data;
@@ -2629,33 +1209,22 @@
2629
1209
  channel.notifyEvent('open', { origin: message.origin, contract });
2630
1210
  }
2631
1211
 
2632
- /**
2633
- * Handles CANCEL_CONNECTION action.
2634
- * Processes connection cancellation request.
2635
- *
2636
- * @param context - Routing context with state, registry, actions, and logger
2637
- * @param message - Message event containing the CANCEL_CONNECTION action
2638
- *
2639
- * @remarks
2640
- * Side Effects:
2641
- * - Cancels pending connection
2642
- * - Sends CANCEL_CONNECTION_ACKNOWLEDGED response
2643
- * - Terminates process
2644
- * - Fires 'cancel' lifecycle event
2645
- *
2646
- * @example Cancellation flow during connection
2647
- * Cancel flow (before connection completes):
2648
- * Side A -> CANCEL_CONNECTION
2649
- * Side B <- CANCEL (this handler)
2650
- * Side B -> CANCEL_ACKNOWLEDGED
2651
- * Both sides fire 'cancel' event
2652
- */
1212
+ function resolveChannel(registry, message) {
1213
+ const source = message.source;
1214
+ if (source) {
1215
+ const channel = registry.getByWindow(source);
1216
+ if (channel) {
1217
+ return channel;
1218
+ }
1219
+ }
1220
+ return registry.getById(message.data.senderId);
1221
+ }
1222
+
2653
1223
  function handleCancel(context, message) {
2654
1224
  const { state, registry, processManager } = context;
2655
1225
  const action = message.data;
2656
- const senderId = action['senderId'];
2657
1226
  const processId = action['processId'];
2658
- const channel = (getById(registry, senderId) || processManager.get(processId));
1227
+ const channel = (resolveChannel(registry, message) || processManager.get(processId));
2659
1228
  if (!channel) {
2660
1229
  return;
2661
1230
  }
@@ -2669,24 +1238,6 @@
2669
1238
  channel.notifyEvent('cancel', { notify: true });
2670
1239
  }
2671
1240
 
2672
- /**
2673
- * Handles CANCEL_CONNECTION_ACKNOWLEDGED action.
2674
- * Completes cancellation on initiator's side and notifies cancel event.
2675
- *
2676
- * @param context - Routing context with state, registry, actions, and logger
2677
- * @param message - Message event containing the CANCEL_CONNECTION_ACKNOWLEDGED action
2678
- *
2679
- * @remarks
2680
- * Side Effects:
2681
- * - Terminates the connection process
2682
- * - Fires 'cancel' lifecycle event on initiator's side
2683
- *
2684
- * @example Initiator-side cancellation acknowledgment
2685
- * Cancellation acknowledgment (initiator side):
2686
- * Initiator -> CANCEL_CONNECTION
2687
- * Initiator <- CANCEL_ACKNOWLEDGED (this handler)
2688
- * Initiator fires 'cancel' event
2689
- */
2690
1241
  function handleCancelAcknowledged(context, message) {
2691
1242
  const { processManager } = context;
2692
1243
  const action = message.data;
@@ -2699,36 +1250,14 @@
2699
1250
  channel.notifyEvent('cancel', { notify: false });
2700
1251
  }
2701
1252
 
2702
- /**
2703
- * Handles CLOSE_CONNECTION action.
2704
- * Gracefully closes an open connection.
2705
- *
2706
- * @param context - Routing context with state, registry, actions, and logger
2707
- * @param message - Message event containing the CLOSE_CONNECTION action
2708
- *
2709
- * @remarks
2710
- * Side Effects:
2711
- * - Deactivates the channel
2712
- * - Sends CLOSE_CONNECTION_ACKNOWLEDGED response
2713
- * - Terminates process
2714
- * - Fires 'close' lifecycle event
2715
- *
2716
- * @example Graceful disconnect flow
2717
- * Disconnect flow:
2718
- * Side A -> CLOSE_CONNECTION (initiates)
2719
- * Side B <- CLOSE_CONNECTION (this handler)
2720
- * Side B -> CLOSE_ACKNOWLEDGED
2721
- * Both sides fire 'close' event
2722
- */
2723
1253
  function handleClose(context, message) {
2724
1254
  const { state, registry, processManager } = context;
2725
1255
  const action = message.data;
2726
- const senderId = action.senderId;
2727
1256
  if (!('processId' in action)) {
2728
1257
  return;
2729
1258
  }
2730
1259
  const processId = action.processId;
2731
- const channel = getById(registry, senderId);
1260
+ const channel = resolveChannel(registry, message);
2732
1261
  if (!channel || !channel.isActive()) {
2733
1262
  return;
2734
1263
  }
@@ -2742,18 +1271,6 @@
2742
1271
  channel.notifyEvent('close', { notify: true });
2743
1272
  }
2744
1273
 
2745
- /**
2746
- * Handles CLOSE_CONNECTION_ACKNOWLEDGED action.
2747
- * Completes close on initiator's side and notifies close event.
2748
- *
2749
- * @param context - Routing context with state, registry, actions, and logger
2750
- * @param message - Message event containing the CLOSE_CONNECTION_ACKNOWLEDGED action
2751
- *
2752
- * @example Handling close acknowledgment
2753
- * ```typescript
2754
- * handleCloseAcknowledged(routingContext, closeAcknowledgedEvent)
2755
- * ```
2756
- */
2757
1274
  function handleCloseAcknowledged(context, message) {
2758
1275
  const { processManager } = context;
2759
1276
  const action = message.data;
@@ -2766,25 +1283,6 @@
2766
1283
  channel.notifyEvent('close', { notify: false });
2767
1284
  }
2768
1285
 
2769
- /**
2770
- * Handles DENY_CONNECTION action.
2771
- * Processes connection denial from remote broker.
2772
- *
2773
- * @param context - Routing context with state, registry, actions, and logger
2774
- * @param message - Message event containing the DENY_CONNECTION action
2775
- *
2776
- * @remarks
2777
- * Side Effects:
2778
- * - Terminates the connection process
2779
- * - Fires 'deny' lifecycle event with error details
2780
- *
2781
- * @example Connection denial during handshake
2782
- * Denial flow (during handshake):
2783
- * Initiator -> REQUEST_CONNECTION
2784
- * Responder validates and rejects
2785
- * Initiator <- DENY_CONNECTION (this handler)
2786
- * Initiator fires 'deny' event
2787
- */
2788
1286
  function handleDeny(context, message) {
2789
1287
  const { processManager } = context;
2790
1288
  const action = message.data;
@@ -2798,55 +1296,15 @@
2798
1296
  channel.notifyEvent('deny', { error, origin: message.origin });
2799
1297
  }
2800
1298
 
2801
- /**
2802
- * Handles DESTROY_CONNECTION action.
2803
- * Immediately destroys a connection without handshake.
2804
- *
2805
- * @param context - Routing context with state, registry, actions, and logger
2806
- * @param message - Message event containing the DESTROY_CONNECTION action
2807
- *
2808
- * @remarks
2809
- * Side Effects:
2810
- * - Immediately destroys channel (no acknowledgment)
2811
- * - Removes channel from registry
2812
- * - No lifecycle event fired (forceful termination)
2813
- *
2814
- * @example Forceful connection termination
2815
- * Forceful termination (e.g., window unload):
2816
- * channel.destroy()
2817
- * -> DESTROY_CONNECTION sent
2818
- * -> Remote receives (this handler)
2819
- * -> Channel immediately removed
2820
- */
2821
1299
  function handleDestroy(context, message) {
2822
1300
  const { registry } = context;
2823
- const action = message.data;
2824
- const senderId = action.senderId;
2825
- const channel = getById(registry, senderId);
1301
+ const channel = resolveChannel(registry, message);
2826
1302
  if (!channel) {
2827
1303
  return;
2828
1304
  }
2829
1305
  channel.destroy(false);
2830
1306
  }
2831
1307
 
2832
- /**
2833
- * Handles INVALID_REQUEST action.
2834
- * Processes error responses from remote broker.
2835
- *
2836
- * @param context - Routing context with state, registry, actions, and logger
2837
- * @param message - Message event containing the INVALID_REQUEST action
2838
- *
2839
- * @remarks
2840
- * Side Effects:
2841
- * - Fires 'invalid' lifecycle event with error details
2842
- *
2843
- * @example Handling protocol violations
2844
- * Protocol violation detected:
2845
- * Initiator sends malformed action
2846
- * Responder detects violation
2847
- * Initiator <- INVALID_REQUEST (this handler)
2848
- * Initiator fires 'invalid' event with reason
2849
- */
2850
1308
  function handleInvalid(context, message) {
2851
1309
  const { processManager } = context;
2852
1310
  const action = message.data;
@@ -2890,67 +1348,18 @@
2890
1348
  additionalProperties: additionalProperties
2891
1349
  };
2892
1350
 
2893
- /**
2894
- * Safe copies of JSON built-in methods.
2895
- *
2896
- * These references are captured at module initialization time to protect against
2897
- * prototype pollution attacks. Import only what you need for tree-shaking.
2898
- *
2899
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/json
2900
- */
2901
1351
  const _JSON = globalThis.JSON;
2902
- /**
2903
- * (Safe copy) Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
2904
- */
2905
1352
  const stringify = _JSON.stringify;
2906
1353
 
2907
- /**
2908
- * Safe copies of Number built-in methods and constants.
2909
- *
2910
- * These references are captured at module initialization time to protect against
2911
- * prototype pollution attacks. Import only what you need for tree-shaking.
2912
- *
2913
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/number
2914
- */
2915
1354
  const _Number = globalThis.Number;
2916
1355
  const _parseInt = globalThis.parseInt;
2917
1356
  const _isNaN = globalThis.isNaN;
2918
1357
  const _isFinite = globalThis.isFinite;
2919
- /**
2920
- * (Safe copy) Determines whether the passed value is an integer.
2921
- */
2922
1358
  const isInteger = _Number.isInteger;
2923
- /**
2924
- * (Safe copy) Parses a string and returns an integer.
2925
- */
2926
1359
  const parseInt = _parseInt;
2927
- /**
2928
- * (Safe copy) Global isNaN function (coerces to number first, less strict than Number.isNaN).
2929
- */
2930
1360
  const globalIsNaN = _isNaN;
2931
- /**
2932
- * (Safe copy) Global isFinite function (coerces to number first, less strict than Number.isFinite).
2933
- */
2934
1361
  const globalIsFinite = _isFinite;
2935
1362
 
2936
- /**
2937
- * Creates a new validation context.
2938
- *
2939
- * @param rootSchema - The root schema being validated against
2940
- * @param validator - The schema validator function
2941
- * @param collectAllErrors - Whether to collect all errors (default: true)
2942
- * @param strictPatterns - Whether to report errors for invalid regex patterns (default: false)
2943
- * @param patternSafetyChecker - Optional pattern safety checker for ReDoS detection
2944
- * @returns A new validation context
2945
- * @example Creating validation context
2946
- * ```typescript
2947
- * const schema = { type: 'string' }
2948
- * const ctx = createValidationContext(schema, validateSchema)
2949
- * // ctx.path === ''
2950
- * // ctx.errors === []
2951
- * // ctx.collectAllErrors === true
2952
- * ```
2953
- */
2954
1363
  function createValidationContext(rootSchema, validator, collectAllErrors = true, strictPatterns = false, patternSafetyChecker) {
2955
1364
  const definitions = createMap();
2956
1365
  if (rootSchema.definitions) {
@@ -2969,21 +1378,6 @@
2969
1378
  validate: validator,
2970
1379
  };
2971
1380
  }
2972
- /**
2973
- * Creates a child context with updated path.
2974
- *
2975
- * @param ctx - Parent context
2976
- * @param segment - Path segment to append
2977
- * @returns New context with updated path
2978
- * @example Creating child context with path
2979
- * ```typescript
2980
- * const ctx = createValidationContext(schema, validate)
2981
- * const childCtx = pushPath(ctx, 'items')
2982
- * // childCtx.path === '/items'
2983
- * const nestedCtx = pushPath(childCtx, 0)
2984
- * // nestedCtx.path === '/items/0'
2985
- * ```
2986
- */
2987
1381
  function pushPath(ctx, segment) {
2988
1382
  const escapedSegment = String(segment).replace(/~/g, '~0').replace(/\//g, '~1');
2989
1383
  return {
@@ -2991,22 +1385,6 @@
2991
1385
  path: `${ctx.path}/${escapedSegment}`,
2992
1386
  };
2993
1387
  }
2994
- /**
2995
- * Adds a validation error to the context.
2996
- *
2997
- * @param ctx - Validation context
2998
- * @param message - Human-readable error message
2999
- * @param instance - The failing value
3000
- * @param code - Optional error code for programmatic handling
3001
- * @param params - Optional additional parameters
3002
- * @example Adding validation error
3003
- * ```typescript
3004
- * const ctx = createValidationContext(schema, validate)
3005
- * addError(ctx, 'Value must be a string', 42, 'type', { expected: 'string' })
3006
- * // ctx.errors[0].message === 'Value must be a string'
3007
- * // ctx.errors[0].code === 'type'
3008
- * ```
3009
- */
3010
1388
  function addError(ctx, message, instance, code, params) {
3011
1389
  ctx.errors.push({
3012
1390
  message,
@@ -3016,42 +1394,10 @@
3016
1394
  params,
3017
1395
  });
3018
1396
  }
3019
- /**
3020
- * Checks if we should continue validation after an error.
3021
- *
3022
- * @param ctx - Validation context
3023
- * @returns true if we should continue, false if we should stop
3024
- * @example Checking error collection mode
3025
- * ```typescript
3026
- * const ctx = createValidationContext(schema, validate, true) // collectAllErrors: true
3027
- * addError(ctx, 'First error', 'value', 'error')
3028
- * shouldContinue(ctx) // => true (keep collecting errors)
3029
- *
3030
- * const ctx2 = createValidationContext(schema, validate, false) // collectAllErrors: false
3031
- * addError(ctx2, 'First error', 'value', 'error')
3032
- * shouldContinue(ctx2) // => false (stop at first error)
3033
- * ```
3034
- */
3035
1397
  function shouldContinue(ctx) {
3036
1398
  return ctx.collectAllErrors || ctx.errors.length === 0;
3037
1399
  }
3038
1400
 
3039
- /**
3040
- * Performs deep equality check for JSON values.
3041
- *
3042
- * Used for enum validation and uniqueItems validation.
3043
- *
3044
- * @param a - First value to compare
3045
- * @param b - Second value to compare
3046
- * @returns true if values are deeply equal, false otherwise
3047
- * @example Comparing values for equality
3048
- * ```typescript
3049
- * isEqual({ name: 'Alice' }, { name: 'Alice' }) // => true
3050
- * isEqual([1, 2, 3], [1, 2, 3]) // => true
3051
- * isEqual({ a: 1 }, { a: 2 }) // => false
3052
- * isEqual([1, 2], [2, 1]) // => false (order matters)
3053
- * ```
3054
- */
3055
1401
  function isEqual(a, b) {
3056
1402
  if (a === b)
3057
1403
  return true;
@@ -3086,21 +1432,6 @@
3086
1432
  return false;
3087
1433
  }
3088
1434
 
3089
- /**
3090
- * Validates array length and uniqueItems constraints.
3091
- *
3092
- * @param instance - Array being validated
3093
- * @param schema - Schema containing array bounds
3094
- * @param ctx - Validation context
3095
- * @returns true if validation passes, false otherwise
3096
- * @example Validating array constraints
3097
- * ```typescript
3098
- * const schema = { minItems: 2, maxItems: 5, uniqueItems: true }
3099
- * validateArrayBounds([1, 2, 3], schema, ctx) // => true
3100
- * validateArrayBounds([1], schema, ctx) // => false (too few items)
3101
- * validateArrayBounds([1, 1, 2], schema, ctx) // => false (duplicates)
3102
- * ```
3103
- */
3104
1435
  function validateArrayBounds(instance, schema, ctx) {
3105
1436
  let valid = true;
3106
1437
  if (schema.minItems !== undefined && instance.length < schema.minItems) {
@@ -3136,23 +1467,6 @@
3136
1467
  return valid;
3137
1468
  }
3138
1469
 
3139
- /**
3140
- * Validates 'allOf' keyword - all schemas must match.
3141
- *
3142
- * @param instance - Value being validated
3143
- * @param schema - Schema containing the allOf constraint
3144
- * @param ctx - Validation context
3145
- * @returns true if validation passes, false otherwise
3146
- * @example Validating allOf composition
3147
- * ```typescript
3148
- * { type: 'object', required: ['name'] },
3149
- * { type: 'object', required: ['email'] }
3150
- * ]
3151
- * }
3152
- * validateAllOf({ name: 'Alice', email: 'alice@example.com' }, schema, ctx) // => true
3153
- * validateAllOf({ name: 'Alice' }, schema, ctx) // => false (missing email)
3154
- * ```
3155
- */
3156
1470
  function validateAllOf(instance, schema, ctx) {
3157
1471
  const allOf = schema.allOf;
3158
1472
  if (!allOf || allOf.length === 0) {
@@ -3161,7 +1475,6 @@
3161
1475
  let valid = true;
3162
1476
  for (let i = 0; i < allOf.length; i++) {
3163
1477
  const subSchema = allOf[i];
3164
- /* istanbul ignore if -- defensive null check for sparse arrays */
3165
1478
  if (!subSchema)
3166
1479
  continue;
3167
1480
  if (!ctx.validate(instance, subSchema, ctx)) {
@@ -3172,26 +1485,6 @@
3172
1485
  }
3173
1486
  return valid;
3174
1487
  }
3175
- /**
3176
- * Validates 'anyOf' keyword - at least one schema must match.
3177
- *
3178
- * @param instance - Value being validated
3179
- * @param schema - Schema containing the anyOf constraint
3180
- * @param ctx - Validation context
3181
- * @returns true if validation passes, false otherwise
3182
- * @example Validating anyOf composition
3183
- * ```typescript
3184
- * const schema = {
3185
- * anyOf: [
3186
- * { type: 'string' },
3187
- * { type: 'number' }
3188
- * ]
3189
- * }
3190
- * validateAnyOf('hello', schema, ctx) // => true
3191
- * validateAnyOf(42, schema, ctx) // => true
3192
- * validateAnyOf(true, schema, ctx) // => false (neither string nor number)
3193
- * ```
3194
- */
3195
1488
  function validateAnyOf(instance, schema, ctx) {
3196
1489
  const anyOf = schema.anyOf;
3197
1490
  if (!anyOf || anyOf.length === 0) {
@@ -3207,26 +1500,6 @@
3207
1500
  addError(ctx, 'Value does not match any of the allowed schemas (anyOf)', instance, 'anyOf');
3208
1501
  return false;
3209
1502
  }
3210
- /**
3211
- * Validates 'oneOf' keyword - exactly one schema must match.
3212
- *
3213
- * @param instance - Value being validated
3214
- * @param schema - Schema containing the oneOf constraint
3215
- * @param ctx - Validation context
3216
- * @returns true if validation passes, false otherwise
3217
- * @example Validating oneOf composition
3218
- * ```typescript
3219
- * const schema = {
3220
- * oneOf: [
3221
- * { type: 'integer' },
3222
- * { minimum: 5 }
3223
- * ]
3224
- * }
3225
- * validateOneOf(3, schema, ctx) // => true (matches first only)
3226
- * validateOneOf(10, schema, ctx) // => false (matches both)
3227
- * validateOneOf('hi', schema, ctx) // => false (matches neither)
3228
- * ```
3229
- */
3230
1503
  function validateOneOf(instance, schema, ctx) {
3231
1504
  const oneOf = schema.oneOf;
3232
1505
  if (!oneOf || oneOf.length === 0) {
@@ -3255,20 +1528,6 @@
3255
1528
  }
3256
1529
  return false;
3257
1530
  }
3258
- /**
3259
- * Validates 'not' keyword - schema must NOT match.
3260
- *
3261
- * @param instance - Value being validated
3262
- * @param schema - Schema containing the not constraint
3263
- * @param ctx - Validation context
3264
- * @returns true if validation passes, false otherwise
3265
- * @example Validating not constraint
3266
- * ```typescript
3267
- * const schema = { not: { type: 'string' } }
3268
- * validateNot(42, schema, ctx) // => true (not a string)
3269
- * validateNot('hello', schema, ctx) // => false (is a string)
3270
- * ```
3271
- */
3272
1531
  function validateNot(instance, schema, ctx) {
3273
1532
  const not = schema.not;
3274
1533
  if (!not) {
@@ -3283,58 +1542,31 @@
3283
1542
  return true;
3284
1543
  }
3285
1544
 
3286
- /**
3287
- * Validates object 'dependencies' keyword.
3288
- *
3289
- * @param instance - Object being validated
3290
- * @param schema - Schema containing the dependencies constraint
3291
- * @param ctx - Validation context
3292
- * @returns true if validation passes, false otherwise
3293
- * @example Validating dependent properties
3294
- * ```typescript
3295
- * const schema = {
3296
- * dependencies: {
3297
- * creditCard: ['billingAddress'], // if creditCard present, billingAddress required
3298
- * name: { required: ['email'] } // if name present, email required via schema
3299
- * }
3300
- * }
3301
- * validateDependencies({ creditCard: '1234', billingAddress: '123 Main St' }, schema, ctx) // => true
3302
- * validateDependencies({ creditCard: '1234' }, schema, ctx) // => false (missing billingAddress)
3303
- * ```
3304
- */
3305
1545
  function validateDependencies(instance, schema, ctx) {
3306
1546
  if (!schema.dependencies) {
3307
1547
  return true;
3308
1548
  }
3309
1549
  let valid = true;
3310
1550
  for (const [key, dependency] of entries(schema.dependencies)) {
3311
- /* istanbul ignore next -- key presence check */
3312
1551
  if (!hasOwn(instance, key)) {
3313
1552
  continue;
3314
1553
  }
3315
- /* istanbul ignore next -- dependency type check */
3316
1554
  if (isArray(dependency)) {
3317
1555
  for (const requiredKey of dependency) {
3318
- /* istanbul ignore next -- required key check */
3319
1556
  if (!hasOwn(instance, requiredKey)) {
3320
1557
  addError(ctx, `Property '${key}' requires property '${requiredKey}' to also be present`, instance, 'dependencies', {
3321
1558
  property: key,
3322
- /* istanbul ignore next -- required key assignment */
3323
1559
  required: requiredKey,
3324
1560
  });
3325
1561
  valid = false;
3326
- /* istanbul ignore if -- early exit tested in validate.spec.ts */
3327
1562
  if (!shouldContinue(ctx))
3328
1563
  return false;
3329
1564
  }
3330
1565
  }
3331
1566
  }
3332
1567
  else {
3333
- /* istanbul ignore next -- schema dependency validation */
3334
1568
  if (!ctx.validate(instance, dependency, ctx)) {
3335
- /* istanbul ignore next -- failure path */
3336
1569
  valid = false;
3337
- /* istanbul ignore if -- early exit tested in validate.spec.ts */
3338
1570
  if (!shouldContinue(ctx))
3339
1571
  return false;
3340
1572
  }
@@ -3343,20 +1575,6 @@
3343
1575
  return valid;
3344
1576
  }
3345
1577
 
3346
- /**
3347
- * Validates enum constraint.
3348
- *
3349
- * @param instance - Value being validated
3350
- * @param schema - Schema containing the enum constraint
3351
- * @param ctx - Validation context
3352
- * @returns true if validation passes, false otherwise
3353
- * @example Validating enum values
3354
- * ```typescript
3355
- * const schema = { enum: ['draft', 'published', 'archived'] }
3356
- * validateEnum('published', schema, ctx) // => true
3357
- * validateEnum('deleted', schema, ctx) // => false (not in enum)
3358
- * ```
3359
- */
3360
1578
  function validateEnum(instance, schema, ctx) {
3361
1579
  if (!schema.enum) {
3362
1580
  return true;
@@ -3373,77 +1591,24 @@
3373
1591
  return false;
3374
1592
  }
3375
1593
 
3376
- /**
3377
- * Safe RegExp factory for protected regex construction.
3378
- *
3379
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/regexp
3380
- */
3381
- /* eslint-disable workspace/lib-require-jsdoc-example */
3382
1594
  const _RegExp = globalThis.RegExp;
3383
- const _Reflect$2 = globalThis.Reflect;
3384
- /**
3385
- * (Safe copy) Creates a new RegExp using the captured RegExp constructor.
3386
- * Use this instead of `new RegExp()`.
3387
- *
3388
- * @param pattern - The pattern string or RegExp to copy.
3389
- * @param flags - Optional flags string.
3390
- * @returns A new RegExp instance.
3391
- */
3392
- const createRegExp = (pattern, flags) => _Reflect$2.construct(_RegExp, [pattern, flags]);
3393
-
3394
- /**
3395
- * Safe copies of URL built-ins via factory functions.
3396
- *
3397
- * Provides safe references to URL and URLSearchParams.
3398
- * These references are captured at module initialization time to protect against
3399
- * prototype pollution attacks. Import only what you need for tree-shaking.
3400
- *
3401
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/url
3402
- */
1595
+ const _Reflect$3 = globalThis.Reflect;
1596
+ const createRegExp = (pattern, flags) => _Reflect$3.construct(_RegExp, [pattern, flags]);
1597
+
3403
1598
  const _URL = globalThis.URL;
3404
- const _Reflect$1 = globalThis.Reflect;
3405
- /**
3406
- * (Safe copy) Creates a new URL using the captured URL constructor.
3407
- * Use this instead of `new URL()`.
3408
- *
3409
- * @param url - The URL string to parse.
3410
- * @param base - Optional base URL for relative URLs.
3411
- * @returns A new URL instance.
3412
- *
3413
- * @example Creating URL instances
3414
- * ```typescript
3415
- * const absolute = createURL('https://example.com/path?query=1')
3416
- * const relative = createURL('/api/users', 'https://example.com')
3417
- * // => URL { href: 'https://example.com/api/users' }
3418
- * ```
3419
- */
3420
- const createURL = (url, base) => _Reflect$1.construct(_URL, [url, base]);
3421
- /**
3422
- * (Safe copy) Creates an object URL for the given object.
3423
- * Use this instead of `URL.createObjectURL()`.
3424
- *
3425
- * Note: This is a browser-only API. In Node.js environments, this will throw.
3426
- */
1599
+ const _Reflect$2 = globalThis.Reflect;
1600
+ const createURL = (url, base) => _Reflect$2.construct(_URL, [url, base]);
3427
1601
  typeof _URL.createObjectURL === 'function'
3428
1602
  ? _URL.createObjectURL.bind(_URL)
3429
1603
  : () => {
3430
1604
  throw new Error('URL.createObjectURL is not available in this environment');
3431
1605
  };
3432
- /**
3433
- * (Safe copy) Revokes an object URL previously created with createObjectURL.
3434
- * Use this instead of `URL.revokeObjectURL()`.
3435
- *
3436
- * Note: This is a browser-only API. In Node.js environments, this will throw.
3437
- */
3438
1606
  typeof _URL.revokeObjectURL === 'function'
3439
1607
  ? _URL.revokeObjectURL.bind(_URL)
3440
1608
  : () => {
3441
1609
  throw new Error('URL.revokeObjectURL is not available in this environment');
3442
1610
  };
3443
1611
 
3444
- /**
3445
- * Format validators for common string formats.
3446
- */
3447
1612
  const formatValidators = {
3448
1613
  'date-time': (v) => {
3449
1614
  if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(v))
@@ -3533,7 +1698,6 @@
3533
1698
  try {
3534
1699
  createURL(v);
3535
1700
  return true;
3536
- /* istanbul ignore next -- URL constructor always throws for invalid URI */
3537
1701
  }
3538
1702
  catch {
3539
1703
  return false;
@@ -3542,11 +1706,9 @@
3542
1706
  'uri-reference': (v) => {
3543
1707
  try {
3544
1708
  createURL(v, 'http://example.com');
3545
- /* istanbul ignore next -- success path just returns true */
3546
1709
  return true;
3547
1710
  }
3548
1711
  catch {
3549
- /* istanbul ignore next -- URL constructor is very permissive with base URL */
3550
1712
  return false;
3551
1713
  }
3552
1714
  },
@@ -3555,7 +1717,6 @@
3555
1717
  },
3556
1718
  regex: (v) => {
3557
1719
  try {
3558
- // eslint-disable-next-line workspace/no-unsafe-regex -- intentionally validating user-provided regex patterns
3559
1720
  createRegExp(v);
3560
1721
  return true;
3561
1722
  }
@@ -3573,21 +1734,6 @@
3573
1734
  return segments.every((seg) => validSegment.test(seg));
3574
1735
  },
3575
1736
  };
3576
- /**
3577
- * Validates string format constraint.
3578
- *
3579
- * @param instance - String being validated
3580
- * @param schema - Schema containing the format constraint
3581
- * @param ctx - Validation context
3582
- * @returns true if validation passes, false otherwise
3583
- * @example Validating string formats
3584
- * ```typescript
3585
- * validateFormat('user@example.com', { format: 'email' }, ctx) // => true
3586
- * validateFormat('invalid-email', { format: 'email' }, ctx) // => false
3587
- * validateFormat('2024-01-15', { format: 'date' }, ctx) // => true
3588
- * validateFormat('192.168.1.1', { format: 'ipv4' }, ctx) // => true
3589
- * ```
3590
- */
3591
1737
  function validateFormat(instance, schema, ctx) {
3592
1738
  if (!schema.format) {
3593
1739
  return true;
@@ -3605,27 +1751,6 @@
3605
1751
  return true;
3606
1752
  }
3607
1753
 
3608
- /**
3609
- * Validates array 'items' keyword.
3610
- *
3611
- * @param instance - Array being validated
3612
- * @param schema - Schema containing the items constraint
3613
- * @param ctx - Validation context
3614
- * @returns true if validation passes, false otherwise
3615
- * @example Single schema for all items
3616
- * ```typescript
3617
- * const schema = { items: { type: 'string' } }
3618
- * validateItems(['a', 'b', 'c'], schema, ctx) // => true
3619
- * validateItems(['a', 1, 'c'], schema, ctx) // => false (1 is not a string)
3620
- * ```
3621
- *
3622
- * @example Tuple validation
3623
- * ```typescript
3624
- * const schema = { items: [{ type: 'string' }, { type: 'number' }] }
3625
- * validateItems(['name', 42], schema, ctx) // => true
3626
- * validateItems([42, 'name'], schema, ctx) // => false (wrong types)
3627
- * ```
3628
- */
3629
1754
  function validateItems(instance, schema, ctx) {
3630
1755
  const items = schema.items;
3631
1756
  if (items === undefined) {
@@ -3635,13 +1760,11 @@
3635
1760
  if (isArray(items)) {
3636
1761
  for (let i = 0; i < items.length && i < instance.length; i++) {
3637
1762
  const itemSchema = items[i];
3638
- /* istanbul ignore if -- defensive null check for sparse arrays */
3639
1763
  if (!itemSchema)
3640
1764
  continue;
3641
1765
  const itemCtx = pushPath(ctx, i);
3642
1766
  if (!ctx.validate(instance[i], itemSchema, itemCtx)) {
3643
1767
  valid = false;
3644
- /* istanbul ignore if -- early exit already tested in validate.spec.ts */
3645
1768
  if (!shouldContinue(ctx))
3646
1769
  return false;
3647
1770
  }
@@ -3664,24 +1787,12 @@
3664
1787
  }
3665
1788
  return valid;
3666
1789
  }
3667
- /**
3668
- * Validates 'additionalItems' keyword for tuple arrays.
3669
- *
3670
- * @param instance - Array being validated
3671
- * @param schema - Schema containing the additionalItems constraint
3672
- * @param ctx - Validation context
3673
- * @param startIndex - Index from which to start checking additional items
3674
- * @returns true if validation passes, false otherwise
3675
- */
3676
1790
  function validateAdditionalItems(instance, schema, ctx, startIndex) {
3677
1791
  const additionalItems = schema.additionalItems;
3678
- /* istanbul ignore if -- default case handled in items.spec.ts */
3679
1792
  if (additionalItems === undefined) {
3680
1793
  return true;
3681
1794
  }
3682
- /* istanbul ignore next -- additionalItems initialization and branching */
3683
1795
  let valid = true;
3684
- /* istanbul ignore next -- additionalItems branching */
3685
1796
  if (additionalItems === false) {
3686
1797
  if (instance.length > startIndex) {
3687
1798
  addError(ctx, `Array has too many items. Expected at most ${startIndex}, got ${instance.length}`, instance, 'additionalItems', {
@@ -3694,10 +1805,8 @@
3694
1805
  else if (typeof additionalItems === 'object') {
3695
1806
  for (let i = startIndex; i < instance.length; i++) {
3696
1807
  const itemCtx = pushPath(ctx, i);
3697
- /* istanbul ignore else -- validation failure tested in items.spec.ts */
3698
1808
  if (!ctx.validate(instance[i], additionalItems, itemCtx)) {
3699
1809
  valid = false;
3700
- /* istanbul ignore if -- early exit tested in validate.spec.ts */
3701
1810
  if (!shouldContinue(ctx))
3702
1811
  return false;
3703
1812
  }
@@ -3706,22 +1815,6 @@
3706
1815
  return valid;
3707
1816
  }
3708
1817
 
3709
- /**
3710
- * Validates number range and multipleOf constraints.
3711
- *
3712
- * @param instance - Number being validated
3713
- * @param schema - Schema containing number constraints
3714
- * @param ctx - Validation context
3715
- * @returns true if validation passes, false otherwise
3716
- * @example Validating number constraints
3717
- * ```typescript
3718
- * const schema = { minimum: 0, maximum: 100, multipleOf: 5 }
3719
- * validateNumberBounds(50, schema, ctx) // => true
3720
- * validateNumberBounds(-1, schema, ctx) // => false (below minimum)
3721
- * validateNumberBounds(101, schema, ctx) // => false (above maximum)
3722
- * validateNumberBounds(17, schema, ctx) // => false (not multiple of 5)
3723
- * ```
3724
- */
3725
1818
  function validateNumberBounds(instance, schema, ctx) {
3726
1819
  let valid = true;
3727
1820
  if (schema.minimum !== undefined) {
@@ -3768,21 +1861,6 @@
3768
1861
  return valid;
3769
1862
  }
3770
1863
 
3771
- /**
3772
- * Validates object bounds constraints (minProperties, maxProperties).
3773
- *
3774
- * @param instance - Object being validated
3775
- * @param schema - Schema containing object bounds
3776
- * @param ctx - Validation context
3777
- * @returns true if validation passes, false otherwise
3778
- * @example Validating object property counts
3779
- * ```typescript
3780
- * const schema = { minProperties: 1, maxProperties: 3 }
3781
- * validateObjectBounds({ a: 1, b: 2 }, schema, ctx) // => true
3782
- * validateObjectBounds({}, schema, ctx) // => false (no properties)
3783
- * validateObjectBounds({ a: 1, b: 2, c: 3, d: 4 }, schema, ctx) // => false (too many)
3784
- * ```
3785
- */
3786
1864
  function validateObjectBounds(instance, schema, ctx) {
3787
1865
  let valid = true;
3788
1866
  const propertyCount = keys(instance).length;
@@ -3807,25 +1885,6 @@
3807
1885
  return valid;
3808
1886
  }
3809
1887
 
3810
- /**
3811
- * Validates object 'patternProperties' keyword.
3812
- *
3813
- * @param instance - Object being validated
3814
- * @param schema - Schema containing the patternProperties constraint
3815
- * @param ctx - Validation context
3816
- * @returns true if validation passes, false otherwise
3817
- * @example Validating pattern properties
3818
- * ```typescript
3819
- * const schema = {
3820
- * patternProperties: {
3821
- * '^x-': { type: 'string' }, // extension properties must be strings
3822
- * '^\\d+$': { type: 'number' } // numeric keys must have number values
3823
- * }
3824
- * }
3825
- * validatePatternProperties({ 'x-custom': 'value', '42': 100 }, schema, ctx) // => true
3826
- * validatePatternProperties({ 'x-custom': 123 }, schema, ctx) // => false (should be string)
3827
- * ```
3828
- */
3829
1888
  function validatePatternProperties(instance, schema, ctx) {
3830
1889
  if (!schema.patternProperties) {
3831
1890
  return true;
@@ -3833,7 +1892,6 @@
3833
1892
  let valid = true;
3834
1893
  const patterns = [];
3835
1894
  for (const [pattern, patternSchema] of entries(schema.patternProperties)) {
3836
- /* istanbul ignore if -- patternSafetyChecker branch tested in validate.spec.ts */
3837
1895
  if (ctx.patternSafetyChecker) {
3838
1896
  const safetyResult = ctx.patternSafetyChecker(pattern);
3839
1897
  if (!safetyResult.safe) {
@@ -3848,21 +1906,15 @@
3848
1906
  }
3849
1907
  }
3850
1908
  try {
3851
- // eslint-disable-next-line workspace/no-unsafe-regex -- Pattern safety validated above when safePatterns enabled
3852
1909
  patterns.push({ regex: createRegExp(pattern), schema: patternSchema });
3853
1910
  }
3854
1911
  catch (e) {
3855
- /* istanbul ignore next -- strictPatterns mode verified in validate.spec.ts */
3856
1912
  if (ctx.strictPatterns) {
3857
- /* istanbul ignore next -- error reporting for invalid regex */
3858
1913
  addError(ctx, `Invalid regex pattern in patternProperties: ${pattern}`, instance, 'patternProperties', {
3859
- /* istanbul ignore next -- error message extraction */
3860
1914
  pattern,
3861
- /* istanbul ignore next -- ternary expression */
3862
1915
  error: e instanceof Error ? e.message : 'Invalid regex',
3863
1916
  });
3864
1917
  valid = false;
3865
- /* istanbul ignore if -- early exit tested in validate.spec.ts */
3866
1918
  if (!shouldContinue(ctx))
3867
1919
  return false;
3868
1920
  }
@@ -3883,25 +1935,6 @@
3883
1935
  return valid;
3884
1936
  }
3885
1937
 
3886
- /**
3887
- * Validates object 'properties' keyword.
3888
- *
3889
- * @param instance - Object being validated
3890
- * @param schema - Schema containing the properties constraint
3891
- * @param ctx - Validation context
3892
- * @returns true if validation passes, false otherwise
3893
- * @example Validating object properties
3894
- * ```typescript
3895
- * const schema = {
3896
- * properties: {
3897
- * name: { type: 'string' },
3898
- * age: { type: 'integer' }
3899
- * }
3900
- * }
3901
- * validateProperties({ name: 'Alice', age: 30 }, schema, ctx) // => true
3902
- * validateProperties({ name: 123 }, schema, ctx) // => false (name should be string)
3903
- * ```
3904
- */
3905
1938
  function validateProperties(instance, schema, ctx) {
3906
1939
  if (!schema.properties) {
3907
1940
  return true;
@@ -3919,20 +1952,6 @@
3919
1952
  }
3920
1953
  return valid;
3921
1954
  }
3922
- /**
3923
- * Validates object 'required' keyword.
3924
- *
3925
- * @param instance - Object being validated
3926
- * @param schema - Schema containing the required constraint
3927
- * @param ctx - Validation context
3928
- * @returns true if validation passes, false otherwise
3929
- * @example Validating required properties
3930
- * ```typescript
3931
- * const schema = { required: ['name', 'email'] }
3932
- * validateRequired({ name: 'Alice', email: 'alice@example.com' }, schema, ctx) // => true
3933
- * validateRequired({ name: 'Alice' }, schema, ctx) // => false (missing email)
3934
- * ```
3935
- */
3936
1955
  function validateRequired(instance, schema, ctx) {
3937
1956
  if (!schema.required) {
3938
1957
  return true;
@@ -3948,38 +1967,18 @@
3948
1967
  }
3949
1968
  return valid;
3950
1969
  }
3951
- /**
3952
- * Validates object 'additionalProperties' keyword.
3953
- *
3954
- * @param instance - Object being validated
3955
- * @param schema - Schema containing the additionalProperties constraint
3956
- * @param ctx - Validation context
3957
- * @returns true if validation passes, false otherwise
3958
- * @example Validating additional properties
3959
- * ```typescript
3960
- * const schema = {
3961
- * properties: { name: { type: 'string' } },
3962
- * additionalProperties: false
3963
- * }
3964
- * validateAdditionalProperties({ name: 'Alice' }, schema, ctx) // => true
3965
- * validateAdditionalProperties({ name: 'Alice', extra: 1 }, schema, ctx) // => false
3966
- * ```
3967
- */
3968
1970
  function validateAdditionalProperties(instance, schema, ctx) {
3969
1971
  const additionalProperties = schema.additionalProperties;
3970
1972
  if (additionalProperties === undefined) {
3971
1973
  return true;
3972
1974
  }
3973
- /* istanbul ignore next -- definedKeys initialization */
3974
1975
  const definedKeys = createSet();
3975
- /* istanbul ignore next -- schema.properties may not exist */
3976
1976
  if (schema.properties) {
3977
1977
  for (const key of keys(schema.properties)) {
3978
1978
  definedKeys.add(key);
3979
1979
  }
3980
1980
  }
3981
1981
  const patterns = [];
3982
- /* istanbul ignore next -- patternProperties may not always be present */
3983
1982
  if (schema.patternProperties) {
3984
1983
  for (const pattern of keys(schema.patternProperties)) {
3985
1984
  if (ctx.patternSafetyChecker) {
@@ -3989,12 +1988,9 @@
3989
1988
  }
3990
1989
  }
3991
1990
  try {
3992
- // eslint-disable-next-line workspace/no-unsafe-regex -- Pattern safety validated above when safePatterns enabled
3993
1991
  patterns.push(createRegExp(pattern));
3994
- /* istanbul ignore next -- invalid regex patterns handled in patternProperties validator */
3995
1992
  }
3996
1993
  catch {
3997
- // Invalid regex, skip
3998
1994
  }
3999
1995
  }
4000
1996
  }
@@ -4011,7 +2007,6 @@
4011
2007
  property: key,
4012
2008
  });
4013
2009
  valid = false;
4014
- /* istanbul ignore if -- early exit tested in validate.spec.ts */
4015
2010
  if (!shouldContinue(ctx))
4016
2011
  return false;
4017
2012
  }
@@ -4019,7 +2014,6 @@
4019
2014
  const propCtx = pushPath(ctx, key);
4020
2015
  if (!ctx.validate(instance[key], additionalProperties, propCtx)) {
4021
2016
  valid = false;
4022
- /* istanbul ignore if -- early exit tested in validate.spec.ts */
4023
2017
  if (!shouldContinue(ctx))
4024
2018
  return false;
4025
2019
  }
@@ -4028,21 +2022,6 @@
4028
2022
  return valid;
4029
2023
  }
4030
2024
 
4031
- /**
4032
- * Validates string length and pattern constraints.
4033
- *
4034
- * @param instance - String being validated
4035
- * @param schema - Schema containing string constraints
4036
- * @param ctx - Validation context
4037
- * @returns true if validation passes, false otherwise
4038
- * @example Validating string constraints
4039
- * ```typescript
4040
- * const schema = { minLength: 3, maxLength: 10, pattern: '^[a-z]+$' }
4041
- * validateStringBounds('hello', schema, ctx) // => true
4042
- * validateStringBounds('hi', schema, ctx) // => false (too short)
4043
- * validateStringBounds('Hello', schema, ctx) // => false (contains uppercase)
4044
- * ```
4045
- */
4046
2025
  function validateStringBounds(instance, schema, ctx) {
4047
2026
  let valid = true;
4048
2027
  if (schema.minLength !== undefined && instance.length < schema.minLength) {
@@ -4078,7 +2057,6 @@
4078
2057
  }
4079
2058
  }
4080
2059
  try {
4081
- // eslint-disable-next-line workspace/no-unsafe-regex -- Pattern safety validated above when safePatterns enabled
4082
2060
  const regex = createRegExp(schema.pattern);
4083
2061
  if (!regex.test(instance)) {
4084
2062
  addError(ctx, `String does not match pattern: ${schema.pattern}`, instance, 'pattern', {
@@ -4090,16 +2068,12 @@
4090
2068
  }
4091
2069
  }
4092
2070
  catch (e) {
4093
- /* istanbul ignore next -- strictPatterns mode verified in validate.spec.ts */
4094
2071
  if (ctx.strictPatterns) {
4095
- /* istanbul ignore next -- error reporting for invalid regex */
4096
2072
  addError(ctx, `Invalid regex pattern: ${schema.pattern}`, instance, 'pattern', {
4097
2073
  pattern: schema.pattern,
4098
- /* istanbul ignore next -- error message extraction ternary */
4099
2074
  error: e instanceof Error ? e.message : 'Invalid regex',
4100
2075
  });
4101
2076
  valid = false;
4102
- /* istanbul ignore if -- early exit tested in validate.spec.ts */
4103
2077
  if (!shouldContinue(ctx))
4104
2078
  return false;
4105
2079
  }
@@ -4108,9 +2082,6 @@
4108
2082
  return valid;
4109
2083
  }
4110
2084
 
4111
- /**
4112
- * Type checking functions for JSON Schema types.
4113
- */
4114
2085
  const typeCheckers = {
4115
2086
  string: (v) => typeof v === 'string',
4116
2087
  number: (v) => typeof v === 'number' && globalIsFinite(v),
@@ -4120,12 +2091,6 @@
4120
2091
  object: (v) => v !== null && typeof v === 'object' && !isArray(v),
4121
2092
  null: (v) => v === null,
4122
2093
  };
4123
- /**
4124
- * Gets the actual JSON type of a value for error messages.
4125
- *
4126
- * @param value - The value to get the type of
4127
- * @returns The JSON type as a string
4128
- */
4129
2094
  function getActualType(value) {
4130
2095
  if (value === null)
4131
2096
  return 'null';
@@ -4134,28 +2099,12 @@
4134
2099
  const t = typeof value;
4135
2100
  if (t === 'number') {
4136
2101
  const num = value;
4137
- /* istanbul ignore next -- NaN/Infinity edge case */
4138
2102
  if (!globalIsFinite(num))
4139
2103
  return 'number';
4140
2104
  return isInteger(num) ? 'integer' : 'number';
4141
2105
  }
4142
2106
  return t;
4143
2107
  }
4144
- /**
4145
- * Validates the 'type' keyword.
4146
- *
4147
- * @param instance - Value being validated
4148
- * @param schema - Schema containing the type constraint
4149
- * @param ctx - Validation context
4150
- * @returns true if validation passes, false otherwise
4151
- * @example Validating type constraints
4152
- * ```typescript
4153
- * validateType('hello', { type: 'string' }, ctx) // => true
4154
- * validateType(42, { type: 'integer' }, ctx) // => true
4155
- * validateType('42', { type: 'integer' }, ctx) // => false
4156
- * validateType(null, { type: ['string', 'null'] }, ctx) // => true (union type)
4157
- * ```
4158
- */
4159
2108
  function validateType(instance, schema, ctx) {
4160
2109
  const schemaType = schema.type;
4161
2110
  if (schemaType === undefined) {
@@ -4164,11 +2113,9 @@
4164
2113
  const types = isArray(schemaType) ? schemaType : [schemaType];
4165
2114
  for (const type of types) {
4166
2115
  const checker = typeCheckers[type];
4167
- /* istanbul ignore if -- defensive check for unknown type */
4168
2116
  if (checker && checker(instance)) {
4169
2117
  return true;
4170
2118
  }
4171
- /* istanbul ignore if -- defensive fallback for integer/number coercion */
4172
2119
  if (type === 'number' && typeCheckers['integer']?.(instance)) {
4173
2120
  return true;
4174
2121
  }
@@ -4182,24 +2129,6 @@
4182
2129
  return false;
4183
2130
  }
4184
2131
 
4185
- /**
4186
- * Resolves a $ref JSON Pointer to its target schema.
4187
- *
4188
- * @param ref - The $ref string (e.g., '#/definitions/Address')
4189
- * @param ctx - Validation context containing root schema and definitions
4190
- * @returns The resolved schema, or undefined if not found
4191
- * @example Resolving schema references
4192
- * ```typescript
4193
- * const rootSchema = {
4194
- * definitions: {
4195
- * Address: { type: 'object', properties: { street: { type: 'string' } } }
4196
- * }
4197
- * }
4198
- * const ctx = createValidationContext(rootSchema, validate)
4199
- * resolveRef('#/definitions/Address', ctx)
4200
- * // => { type: 'object', properties: { street: { type: 'string' } } }
4201
- * ```
4202
- */
4203
2132
  function resolveRef(ref, ctx) {
4204
2133
  const cached = ctx.definitions.get(ref);
4205
2134
  if (cached) {
@@ -4234,21 +2163,6 @@
4234
2163
  return undefined;
4235
2164
  }
4236
2165
 
4237
- /**
4238
- * Validates a value against a JSON Schema.
4239
- *
4240
- * @param instance - The value to validate
4241
- * @param schema - The JSON Schema to validate against
4242
- * @param options - Validation options
4243
- * @returns Validation result with valid flag and any errors
4244
- *
4245
- * @example Basic validation
4246
- * ```typescript
4247
- * const schema = { type: 'string', minLength: 1 }
4248
- * const result = validate('hello', schema)
4249
- * console.log(result.valid) // true
4250
- * ```
4251
- */
4252
2166
  function validate(instance, schema, options) {
4253
2167
  let patternSafetyChecker;
4254
2168
  const ctx = createValidationContext(schema, validateSchema, true, false, patternSafetyChecker);
@@ -4258,25 +2172,9 @@
4258
2172
  errors: ctx.errors,
4259
2173
  };
4260
2174
  }
4261
- /**
4262
- * Internal recursive validation function.
4263
- *
4264
- * @param instance - Value being validated
4265
- * @param schema - Schema to validate against
4266
- * @param ctx - Validation context
4267
- * @returns true if validation passes, false otherwise
4268
- * @example Internal recursive validation
4269
- * ```typescript
4270
- * const schema = { type: 'object', properties: { count: { type: 'integer' } } }
4271
- * const ctx = createValidationContext(schema, validateSchema)
4272
- * validateSchema({ count: 5 }, schema, ctx) // => true
4273
- * validateSchema({ count: 'five' }, schema, ctx) // => false
4274
- * ```
4275
- */
4276
2175
  function validateSchema(instance, schema, ctx) {
4277
2176
  if (schema.$ref) {
4278
2177
  const resolved = resolveRef(schema.$ref, ctx);
4279
- /* istanbul ignore if -- $ref resolution failures are tested in resolve-ref.spec.ts */
4280
2178
  if (!resolved) {
4281
2179
  return true;
4282
2180
  }
@@ -4333,35 +2231,26 @@
4333
2231
  }
4334
2232
  if (!validateRequired(obj, schema, ctx)) {
4335
2233
  valid = false;
4336
- /* istanbul ignore if -- early exit tested elsewhere */
4337
2234
  if (!shouldContinue(ctx))
4338
2235
  return false;
4339
2236
  }
4340
- /* istanbul ignore next -- patternProperties validation */
4341
2237
  if (!validatePatternProperties(obj, schema, ctx)) {
4342
2238
  valid = false;
4343
- /* istanbul ignore next -- early exit tested elsewhere */
4344
2239
  if (!shouldContinue(ctx))
4345
2240
  return false;
4346
2241
  }
4347
- /* istanbul ignore next -- additionalProperties validation */
4348
2242
  if (!validateAdditionalProperties(obj, schema, ctx)) {
4349
2243
  valid = false;
4350
- /* istanbul ignore next -- early exit tested elsewhere */
4351
2244
  if (!shouldContinue(ctx))
4352
2245
  return false;
4353
2246
  }
4354
- /* istanbul ignore next -- objectBounds validation */
4355
2247
  if (!validateObjectBounds(obj, schema, ctx)) {
4356
2248
  valid = false;
4357
- /* istanbul ignore next -- early exit tested elsewhere */
4358
2249
  if (!shouldContinue(ctx))
4359
2250
  return false;
4360
2251
  }
4361
- /* istanbul ignore next -- dependencies validation */
4362
2252
  if (!validateDependencies(obj, schema, ctx)) {
4363
2253
  valid = false;
4364
- /* istanbul ignore next -- early exit tested elsewhere */
4365
2254
  if (!shouldContinue(ctx))
4366
2255
  return false;
4367
2256
  }
@@ -4389,51 +2278,10 @@
4389
2278
  return valid;
4390
2279
  }
4391
2280
 
4392
- /**
4393
- * Creates a reusable validator function from a JSON Schema.
4394
- *
4395
- * @param schema - JSON Schema to validate against
4396
- * @param options - Validation options
4397
- * @returns A function that validates data against the schema
4398
- *
4399
- * @example Creating reusable validator
4400
- * ```typescript
4401
- * const schema = {
4402
- * type: 'object',
4403
- * properties: {
4404
- * name: { type: 'string' },
4405
- * age: { type: 'integer', minimum: 0 }
4406
- * },
4407
- * required: ['name']
4408
- * }
4409
- *
4410
- * const validateUser = createValidator(schema)
4411
- * const result = validateUser({ name: 'Alice', age: 30 })
4412
- * console.log(result.valid) // true
4413
- * ```
4414
- */
4415
2281
  function createValidator$1(schema, options) {
4416
2282
  return (data) => validate(data, schema);
4417
2283
  }
4418
2284
 
4419
- /**
4420
- * Creates a validator function from a JSON schema.
4421
- * Returns a function that validates data against the schema.
4422
- *
4423
- * @param schema - JSON Schema to validate against
4424
- * @returns Validator function that returns validation results
4425
- *
4426
- * @example Creating a schema validator
4427
- * ```typescript
4428
- * const validateUser = createValidator({
4429
- * type: 'object',
4430
- * properties: { name: { type: 'string' } },
4431
- * required: ['name']
4432
- * })
4433
- * const result = validateUser({ name: 'Alice' })
4434
- * // => { valid: true, errors: [] }
4435
- * ```
4436
- */
4437
2285
  function createValidator(schema) {
4438
2286
  const validator = createValidator$1(schema);
4439
2287
  return (data) => {
@@ -4449,47 +2297,19 @@
4449
2297
  };
4450
2298
  }
4451
2299
 
4452
- /* istanbul ignore next -- validator initialization happens at module load */
4453
2300
  const validateMessageData = createValidator(messageSchema);
4454
- /**
4455
- * Validates a user message against the message schema.
4456
- *
4457
- * @param message - The message to validate
4458
- * @returns Validation result with any errors
4459
- */
4460
2301
  function validateMessage(message) {
4461
2302
  return validateMessageData(message);
4462
2303
  }
4463
2304
 
4464
- /**
4465
- * Handles NEW_MESSAGE action.
4466
- * Routes messages to appropriate channel.
4467
- *
4468
- * @param context - Routing context with state, registry, actions, and logger
4469
- * @param message - Message event containing the NEW_MESSAGE action
4470
- *
4471
- * @remarks
4472
- * Side Effects:
4473
- * - Validates message type against contract
4474
- * - Invokes channel message handlers if validation passes
4475
- * - Logs and ignores invalid messages
4476
- *
4477
- * @example Routing user messages
4478
- * User message flow:
4479
- * channel.send('USER_LOGIN', {userId: 123})
4480
- * -> NEW_MESSAGE action sent
4481
- * -> Received by remote broker (this handler)
4482
- * -> Routed to channel's onMessage handlers
4483
- */
4484
2305
  function handleMessage(context, message) {
4485
2306
  const { state, registry, logger } = context;
4486
2307
  const action = message.data;
4487
- const senderId = action.senderId;
4488
2308
  if (!isActionWithData(action)) {
4489
2309
  return;
4490
2310
  }
4491
2311
  const messageData = action.data;
4492
- const channel = getById(registry, senderId);
2312
+ const channel = resolveChannel(registry, message);
4493
2313
  if (!channel || !channel.isActive()) {
4494
2314
  return;
4495
2315
  }
@@ -4498,29 +2318,13 @@
4498
2318
  logger.info(`${state.name} ignored message from ${channel.getName()}`);
4499
2319
  return;
4500
2320
  }
2321
+ if (!channel.getAcceptedTypes().includes(messageData.type)) {
2322
+ logger.info(`${state.name} dropped message type '${messageData.type}' not accepted by the ${channel.getName()} channel contract`);
2323
+ return;
2324
+ }
4501
2325
  channel.notifyMessage(messageData);
4502
2326
  }
4503
2327
 
4504
- /**
4505
- * Handles OPEN_CONNECTION action.
4506
- * Completes handshake on responder's side and notifies open event.
4507
- *
4508
- * @param context - Routing context with state, registry, actions, and logger
4509
- * @param message - Message event containing the OPEN_CONNECTION action
4510
- *
4511
- * @remarks
4512
- * Side Effects:
4513
- * - Terminates the connection process
4514
- * - Extracts security confirmation (if present)
4515
- * - Marks security as ready if security is active
4516
- * - Fires 'open' lifecycle event on responder's side
4517
- * - Fires 'security-ready' event if security transport is active
4518
- *
4519
- * @example Completing the three-way handshake
4520
- * Final step of three-way handshake:
4521
- * Responder receives OPEN (this handler) from Initiator
4522
- * Both sides now have active connection
4523
- */
4524
2328
  function handleOpen(context, message) {
4525
2329
  const { state, processManager, logger } = context;
4526
2330
  const action = message.data;
@@ -4548,33 +2352,6 @@
4548
2352
  channel.notifyEvent('open', { origin: message.origin });
4549
2353
  }
4550
2354
 
4551
- /**
4552
- * Protocol negotiation logic for security handshake.
4553
- *
4554
- * Implements the negotiation algorithm that determines the best
4555
- * security protocol to use between two communicating parties.
4556
- *
4557
- * @module security/negotiation/negotiate
4558
- */
4559
- /**
4560
- * Negotiates the best security protocol between initiator and responder.
4561
- *
4562
- * The algorithm iterates through the initiator's supported protocols
4563
- * (in preference order) and selects the first one that the responder
4564
- * also supports. If no overlap is found, falls back to 'none'.
4565
- *
4566
- * @param request - The initiator's security negotiation request
4567
- * @param responderSupported - Protocols supported by the responder
4568
- * @returns The negotiation result with the selected protocol
4569
- *
4570
- * @example Negotiating security protocol
4571
- * ```typescript
4572
- * const request = { supported: ['v2', 'v1', 'none'], preferred: 'v2' }
4573
- * const responderSupported = ['v1', 'none']
4574
- * const result = negotiateProtocol(request, responderSupported)
4575
- * // result.negotiated === 'v1' (first match from initiator's list)
4576
- * ```
4577
- */
4578
2355
  function negotiateProtocol(request, responderSupported) {
4579
2356
  for (const protocol of request.supported) {
4580
2357
  if (responderSupported.includes(protocol)) {
@@ -4589,55 +2366,12 @@
4589
2366
  isPreferred: request.preferred === 'none',
4590
2367
  };
4591
2368
  }
4592
- /**
4593
- * Creates a security negotiation response for the responder.
4594
- *
4595
- * Builds a response object containing the negotiated protocol
4596
- * and optional public parameters for protocol initialization.
4597
- *
4598
- * @param negotiated - The negotiated protocol version
4599
- * @param publicParams - Optional public parameters (e.g., key exchange hints)
4600
- * @returns A security negotiation response object
4601
- *
4602
- * @example Creating responder response
4603
- * ```typescript
4604
- * const response = createSecurityResponse('v2', { hint: 'value' })
4605
- * // { negotiated: 'v2', publicParams: { hint: 'value' } }
4606
- * ```
4607
- */
4608
2369
  function createSecurityResponse(negotiated, publicParams) {
4609
2370
  const response = { negotiated };
4610
2371
  return freeze(response);
4611
2372
  }
4612
2373
 
4613
- /**
4614
- * Default supported security protocols for the responder.
4615
- * Includes 'none' as fallback for backward compatibility.
4616
- */
4617
2374
  const DEFAULT_RESPONDER_SUPPORTED = ['none'];
4618
- /**
4619
- * Handles REQUEST_CONNECTION action.
4620
- * Creates or retrieves channel and initiates connection handshake.
4621
- *
4622
- * @param context - Routing context with state, registry, actions, and logger
4623
- * @param message - Message event containing the REQUEST_CONNECTION action
4624
- *
4625
- * @remarks
4626
- * Side Effects:
4627
- * - Creates new channel if not found in registry
4628
- * - Tracks process ID for handshake completion
4629
- * - Negotiates security protocol if security data present
4630
- * - Sends ACCEPT_CONNECTION if validation passes
4631
- * - Sends DENY_CONNECTION if contract or security policy fails
4632
- *
4633
- * @example Processing connection requests
4634
- * Incoming action triggers:
4635
- * 1. Channel lookup/creation
4636
- * 2. Contract validation
4637
- * 3. Security policy check
4638
- * 4. Security protocol negotiation (if applicable)
4639
- * 5. ACCEPT_CONNECTION response (or DENY if validation fails)
4640
- */
4641
2375
  function handleRequest(context, message) {
4642
2376
  const { state, registry, processManager, actions, logger } = context;
4643
2377
  const action = message.data;
@@ -4722,22 +2456,6 @@
4722
2456
  });
4723
2457
  }
4724
2458
 
4725
- /**
4726
- * Security error handling utilities.
4727
- *
4728
- * Provides functions for handling, categorizing, and emitting security-related
4729
- * errors during message encryption/decryption operations.
4730
- *
4731
- * @module security/errors
4732
- */
4733
- /**
4734
- * Security error class with additional metadata for programmatic handling.
4735
- *
4736
- * @example Creating security error
4737
- * ```typescript
4738
- * throw new SecurityError('Decryption failed', 'decryption_failed', originalError)
4739
- * ```
4740
- */
4741
2459
  class SecurityError extends Error {
4742
2460
  code;
4743
2461
  originalCause;
@@ -4749,25 +2467,6 @@
4749
2467
  setPrototypeOf(this, SecurityError.prototype);
4750
2468
  }
4751
2469
  }
4752
- /**
4753
- * Creates security error event data from an error.
4754
- *
4755
- * Converts various error types into a standardized SecurityErrorEventData
4756
- * structure for emitting via channel events.
4757
- *
4758
- * @param error - The error to convert
4759
- * @returns Standardized security error event data
4760
- *
4761
- * @example Converting errors to event data
4762
- * ```typescript
4763
- * try {
4764
- * decrypt(payload)
4765
- * } catch (error) {
4766
- * const eventData = createSecurityErrorEventData(error)
4767
- * channel.notifyEvent('security-error', eventData)
4768
- * }
4769
- * ```
4770
- */
4771
2470
  function createSecurityErrorEventData(error) {
4772
2471
  if (error instanceof SecurityError) {
4773
2472
  return {
@@ -4789,17 +2488,6 @@
4789
2488
  code: 'unknown',
4790
2489
  };
4791
2490
  }
4792
- /**
4793
- * Categorizes an error into a security error code.
4794
- *
4795
- * Analyzes the error message to determine the appropriate category.
4796
- * This is used when errors from network-protocol are caught.
4797
- *
4798
- * @param error - The error to categorize
4799
- * @returns The appropriate security error code
4800
- *
4801
- * @internal
4802
- */
4803
2491
  function categorizeError(error) {
4804
2492
  const message = error.message.toLowerCase();
4805
2493
  if (message.includes('decrypt') || message.includes('invalid key') || message.includes('corrupted') || message.includes('cipher')) {
@@ -4816,21 +2504,6 @@
4816
2504
  }
4817
2505
  return 'unknown';
4818
2506
  }
4819
- /**
4820
- * Logs a security error with appropriate formatting.
4821
- *
4822
- * Uses logger.error for actual errors and logger.warn for
4823
- * retryable/expected failures.
4824
- *
4825
- * @param logger - Logger instance to use for output
4826
- * @param channelName - Name of the channel where error occurred
4827
- * @param error - The security error event data containing message, code, and optional cause
4828
- *
4829
- * @example Logging security errors
4830
- * ```typescript
4831
- * logSecurityError(logger, 'my-channel', errorData)
4832
- * ```
4833
- */
4834
2507
  function logSecurityError(logger, channelName, error) {
4835
2508
  const prefix = `${channelName} security error:`;
4836
2509
  if (error.code === 'unknown') {
@@ -4841,37 +2514,6 @@
4841
2514
  }
4842
2515
  }
4843
2516
 
4844
- /**
4845
- * Route encrypted messages through security transport.
4846
- *
4847
- * Handles Uint8Array payloads received via postMessage, routing them
4848
- * through the appropriate channel's security transport for decryption.
4849
- *
4850
- * @module broker/routing/route-encrypted-message
4851
- */
4852
- /**
4853
- * Routes an encrypted message to the appropriate channel for decryption.
4854
- *
4855
- * This function handles Uint8Array payloads received via postMessage:
4856
- * 1. Identifies the target channel based on message origin
4857
- * 2. Routes the encrypted payload through the channel's security transport
4858
- * 3. The security transport decrypts and invokes the registered receive handler
4859
- *
4860
- * If no matching channel is found or the channel has no security transport,
4861
- * the message is silently dropped (with optional debug logging).
4862
- *
4863
- * @param context - Routing context with state, registry, actions, and logger
4864
- * @param router - Message router for handling decrypted actions
4865
- * @param event - Message event containing the encrypted Uint8Array payload
4866
- *
4867
- * @example Routing encrypted payloads
4868
- * ```typescript
4869
- * // In broker's onMessage handler:
4870
- * if (event.data instanceof Uint8Array) {
4871
- * routeEncryptedMessage(routingContext, router, event)
4872
- * }
4873
- * ```
4874
- */
4875
2517
  function routeEncryptedMessage(context, router, event) {
4876
2518
  const { state, registry, logger } = context;
4877
2519
  const origin = event?.origin;
@@ -4908,19 +2550,6 @@
4908
2550
  channel.notifyEvent('security-error', errorData);
4909
2551
  }
4910
2552
  }
4911
- /**
4912
- * Finds a channel by origin in the registry.
4913
- *
4914
- * Since encrypted messages don't contain senderId, we must match
4915
- * by origin. This works because each channel has a unique target window
4916
- * and thus a unique origin.
4917
- *
4918
- * @param registry - Channel registry to search
4919
- * @param origin - Origin of the message sender
4920
- * @returns The matching channel handle, or undefined if not found
4921
- *
4922
- * @internal
4923
- */
4924
2553
  function findChannelByOrigin(registry, origin) {
4925
2554
  const allChannels = getAll(registry);
4926
2555
  for (const channel of allChannels) {
@@ -4939,30 +2568,10 @@
4939
2568
  return undefined;
4940
2569
  }
4941
2570
 
4942
- /**
4943
- * Logs an action in a structured format.
4944
- *
4945
- * @param logger - Logger instance
4946
- * @param action - Action to log
4947
- * @param direction - Direction of action ('sent' or 'received')
4948
- */
4949
2571
  function logAction(logger, action, direction) {
4950
2572
  logger.debug(`Action ${direction}:`, action.type, action);
4951
2573
  }
4952
2574
 
4953
- /**
4954
- * Routes a message to the appropriate handler.
4955
- *
4956
- * @param router - Handler map containing action type to handler mappings
4957
- * @param context - Routing context with state, registry, actions, and logger
4958
- * @param message - Incoming message event containing the action to route
4959
- *
4960
- * @example Routing actions to handlers
4961
- * ```typescript
4962
- * const router = createRouter({ 'MESSAGE': handleMessage })
4963
- * routeMessage(router, routingContext, incomingEvent)
4964
- * ```
4965
- */
4966
2575
  function routeMessage(router, context, message) {
4967
2576
  const { logger } = context;
4968
2577
  try {
@@ -4985,26 +2594,6 @@
4985
2594
  }
4986
2595
  }
4987
2596
 
4988
- /**
4989
- * Filters message origin against whitelist/blacklist
4990
- *
4991
- * @param origin - The origin to check
4992
- * @param whitelist - List of allowed origins (takes precedence)
4993
- * @param blacklist - List of blocked origins
4994
- * @returns true if origin is allowed, false otherwise
4995
- *
4996
- * @example Filtering with a whitelist
4997
- * ```typescript
4998
- * filterOrigin('https://app.example.com', ['https://app.example.com'])
4999
- * // => true
5000
- * ```
5001
- *
5002
- * @example Filtering with a blacklist
5003
- * ```typescript
5004
- * filterOrigin('https://untrusted.com', [], ['https://untrusted.com'])
5005
- * // => false
5006
- * ```
5007
- */
5008
2597
  function filterOrigin(origin, whitelist = [], blacklist = []) {
5009
2598
  if (whitelist && whitelist.length > 0) {
5010
2599
  return whitelist.includes(origin);
@@ -5015,53 +2604,21 @@
5015
2604
  return true;
5016
2605
  }
5017
2606
 
5018
- /**
5019
- * Validates that a security policy is a function
5020
- *
5021
- * @param policy - The policy to validate
5022
- * @throws {Error} If policy is not a function
5023
- *
5024
- * @example Valid policy function
5025
- * ```typescript
5026
- * validatePolicy((event) => event.origin === 'https://trusted.com')
5027
- * // No error thrown
5028
- * ```
5029
- *
5030
- * @example Invalid policy throws
5031
- * ```typescript
5032
- * validatePolicy('not-a-function')
5033
- * // Throws: Security policy must be a function...
5034
- * ```
5035
- */
5036
2607
  function validatePolicy(policy) {
5037
2608
  if (typeof policy !== 'function') {
5038
2609
  throw createError('Security policy must be a function that returns true or false.');
5039
2610
  }
5040
2611
  }
5041
2612
 
5042
- /**
5043
- * Creates a message broker instance
5044
- *
5045
- * @param config - Broker configuration
5046
- * @param config.name - Unique name for the broker instance
5047
- * @param config.contract - Channel contract defining message protocols
5048
- * @param config.settings - Optional configuration overrides for broker behavior
5049
- * @returns Broker handle with public API
5050
- *
5051
- * @example Creating a message broker
5052
- * ```typescript
5053
- * const broker = createBroker({
5054
- * name: 'app-broker',
5055
- * contract: { messages: { ping: {}, pong: {} } },
5056
- * settings: { logLevel: 'warn' },
5057
- * })
5058
- * ```
5059
- */
5060
2613
  function createBroker(config) {
5061
2614
  assertNoCircularRef(config.contract, 'config.contract');
5062
2615
  assertNoCircularRef(config.settings, 'config.settings');
5063
2616
  validateName(config.name);
5064
2617
  validateContract$1(config.contract);
2618
+ const brokerWindow = config.window ?? (typeof window !== 'undefined' ? window : undefined);
2619
+ if (!brokerWindow) {
2620
+ throw createError('Cannot create broker: no window is available. Pass an explicit `window` in the broker config when running outside a browser environment.');
2621
+ }
5065
2622
  const mergedSettings = {
5066
2623
  ...defaultBrokerSettings,
5067
2624
  ...config.settings,
@@ -5075,7 +2632,7 @@
5075
2632
  const state = {
5076
2633
  id: uuidV4(),
5077
2634
  name: config.name,
5078
- window: window,
2635
+ window: brokerWindow,
5079
2636
  contract: config.contract,
5080
2637
  settings: mergedSettings,
5081
2638
  logger,
@@ -5128,10 +2685,7 @@
5128
2685
  }
5129
2686
  routeMessage(router, routingContext, event);
5130
2687
  };
5131
- /* istanbul ignore next -- environment detection for non-browser contexts */
5132
- if (typeof window !== 'undefined') {
5133
- window.addEventListener('message', onMessage);
5134
- }
2688
+ brokerWindow.addEventListener('message', onMessage);
5135
2689
  const broker = {
5136
2690
  id: state.id,
5137
2691
  name: state.name,
@@ -5200,10 +2754,6 @@
5200
2754
  return freeze(broker);
5201
2755
  }
5202
2756
 
5203
- /**
5204
- * Default contract allowing any message type.
5205
- * Useful for development and prototyping.
5206
- */
5207
2757
  const DEFAULT_CONTRACT = freeze({
5208
2758
  emitted: freeze([
5209
2759
  { type: 'MESSAGE', description: 'Generic message' },
@@ -5217,49 +2767,36 @@
5217
2767
  { type: 'ACK', description: 'Acknowledgment' },
5218
2768
  ]),
5219
2769
  });
5220
- /**
5221
- * Singleton broker instance with sensible defaults.
5222
- *
5223
- * Features:
5224
- * - Generic contract accepting MESSAGE, DATA, EVENT action types
5225
- * - No security restrictions (whitelist/blacklist)
5226
- * - Debug mode off by default
5227
- *
5228
- * @example
5229
- * ```typescript
5230
- * import { broker } from '@hyperfrontend/nexus'
5231
- *
5232
- * // Create a channel
5233
- * const channel = broker.addChannel('my-channel', targetWindow)
5234
- * channel.connect()
5235
- * channel.send('MESSAGE', { hello: 'world' })
5236
- * ```
5237
- */
5238
- createBroker({
5239
- name: 'default-broker',
5240
- contract: DEFAULT_CONTRACT,
2770
+
2771
+ const _Reflect$1 = globalThis.Reflect;
2772
+ const get = _Reflect$1.get;
2773
+ const has = _Reflect$1.has;
2774
+ const ownKeys = _Reflect$1.ownKeys;
2775
+ const getOwnPropertyDescriptor = _Reflect$1.getOwnPropertyDescriptor;
2776
+
2777
+ let instance = null;
2778
+ function resolveBroker() {
2779
+ if (!instance) {
2780
+ instance = createBroker({
2781
+ name: 'default-broker',
2782
+ contract: DEFAULT_CONTRACT,
2783
+ });
2784
+ }
2785
+ return instance;
2786
+ }
2787
+ new Proxy({}, {
2788
+ get: (_target, property) => get(resolveBroker(), property),
2789
+ has: (_target, property) => has(resolveBroker(), property),
2790
+ ownKeys: () => ownKeys(resolveBroker()),
2791
+ getOwnPropertyDescriptor: (_target, property) => {
2792
+ const descriptor = getOwnPropertyDescriptor(resolveBroker(), property);
2793
+ return descriptor ? { ...descriptor, configurable: true } : undefined;
2794
+ },
5241
2795
  });
5242
2796
 
5243
- // note: Runtime validation shared by the host/hostee factories and the config loader.
5244
- /**
5245
- * Narrows an unknown value to a non-null object.
5246
- *
5247
- * @param value - The value to test.
5248
- * @returns `true` when the value is a non-null, non-array object.
5249
- */
5250
2797
  function isRecord(value) {
5251
2798
  return typeof value === 'object' && value !== null && !isArray(value);
5252
2799
  }
5253
- /**
5254
- * Collects every problem with a single action list (`emitted` or `accepted`).
5255
- *
5256
- * Each malformed entry contributes its own message, distinguishing a non-object
5257
- * entry from one missing a usable `type`, so the caller can report them all at once.
5258
- *
5259
- * @param actions - The candidate action list.
5260
- * @param field - The field name, used to locate problems in messages.
5261
- * @param issues - The running list of human-readable problems, appended to in place.
5262
- */
5263
2800
  function collectActionListIssues(actions, field, issues) {
5264
2801
  if (!isArray(actions)) {
5265
2802
  issues.push(`"${field}" must be an array.`);
@@ -5273,14 +2810,21 @@
5273
2810
  if (typeof action['type'] !== 'string' || action['type'].length === 0) {
5274
2811
  issues.push(`"${field}[${index}]" must have a non-empty string "type".`);
5275
2812
  }
2813
+ if (action['respondsWith'] !== undefined && (typeof action['respondsWith'] !== 'string' || action['respondsWith'].length === 0)) {
2814
+ issues.push(`"${field}[${index}]" has a "respondsWith" that must be a non-empty string.`);
2815
+ }
2816
+ });
2817
+ }
2818
+ function collectRespondsWithIssues(actions, field, other, otherField, issues) {
2819
+ actions.forEach((action, index) => {
2820
+ if (action.respondsWith === undefined) {
2821
+ return;
2822
+ }
2823
+ if (!other.some((candidate) => candidate.type === action.respondsWith)) {
2824
+ issues.push(`"${field}[${index}]" responds with "${action.respondsWith}", but "${otherField}" has no action of that type.`);
2825
+ }
5276
2826
  });
5277
2827
  }
5278
- /**
5279
- * Names the kind of an unexpected value for an error message.
5280
- *
5281
- * @param value - The value to describe.
5282
- * @returns A short label such as `null`, `an array`, or `a number`.
5283
- */
5284
2828
  function describeType(value) {
5285
2829
  if (value === null) {
5286
2830
  return 'null';
@@ -5290,22 +2834,6 @@
5290
2834
  }
5291
2835
  return `a ${typeof value}`;
5292
2836
  }
5293
- /**
5294
- * Validates an unknown value as a {@link FeatureContract}.
5295
- *
5296
- * Reports every malformed action at once rather than stopping at the first, so a
5297
- * single error message lists all the problems to fix.
5298
- *
5299
- * @param contract - The candidate contract, typically parsed from disk.
5300
- * @returns The validated contract, typed.
5301
- * @throws {Error} When the value is not an object, or any action is malformed.
5302
- *
5303
- * @example Validating a parsed contract file
5304
- * ```typescript
5305
- * const contract = validateContract(parse(readFileSync('clock.contract.json', 'utf8')))
5306
- * contract.emitted.forEach((action) => console.log(action.type))
5307
- * ```
5308
- */
5309
2837
  function validateContract(contract) {
5310
2838
  if (!isRecord(contract)) {
5311
2839
  throw createError(`Invalid contract: expected an object with "emitted" and "accepted" arrays, but got ${describeType(contract)}.`);
@@ -5313,6 +2841,12 @@
5313
2841
  const issues = [];
5314
2842
  collectActionListIssues(contract['emitted'], 'emitted', issues);
5315
2843
  collectActionListIssues(contract['accepted'], 'accepted', issues);
2844
+ if (issues.length === 0) {
2845
+ const emitted = contract['emitted'];
2846
+ const accepted = contract['accepted'];
2847
+ collectRespondsWithIssues(emitted, 'emitted', accepted, 'accepted', issues);
2848
+ collectRespondsWithIssues(accepted, 'accepted', emitted, 'emitted', issues);
2849
+ }
5316
2850
  if (issues.length > 0) {
5317
2851
  throw createError(`Invalid contract:\n${issues.map((issue) => ` - ${issue}`).join('\n')}`);
5318
2852
  }
@@ -5322,47 +2856,29 @@
5322
2856
  };
5323
2857
  }
5324
2858
 
5325
- /**
5326
- * Internal control message types carried on the feature channel and hidden from consumers.
5327
- */
2859
+ const CONTROL_PREFIX = '__hf:';
5328
2860
  const ControlType = freeze({
5329
- /** Hostee-to-host liveness beat. */
5330
2861
  Beat: '__hf:beat',
5331
- /** Hostee-to-host content-size announcement. */
5332
2862
  Size: '__hf:size',
2863
+ Request: '__hf:request',
2864
+ Response: '__hf:response',
5333
2865
  });
5334
- /**
5335
- * Extends a feature contract so the channel may also send and receive control traffic.
5336
- *
5337
- * @param contract - The consumer-facing feature contract.
5338
- * @returns A contract that additionally permits the reserved control types.
5339
- *
5340
- * @example Building a broker contract that carries the control plane
5341
- * ```typescript
5342
- * const broker = createBroker({ name, contract: withControlContract(contract) })
5343
- * ```
5344
- */
2866
+ function isControlType(type) {
2867
+ return type.startsWith(CONTROL_PREFIX);
2868
+ }
5345
2869
  function withControlContract(contract) {
5346
- // note: Fresh action literals per direction — sharing references across emitted/accepted trips nexus's circular-reference guard, which flags any repeated object.
2870
+ const controlActions = () => [
2871
+ { type: ControlType.Beat },
2872
+ { type: ControlType.Size },
2873
+ { type: ControlType.Request },
2874
+ { type: ControlType.Response },
2875
+ ];
5347
2876
  return {
5348
- emitted: [...contract.emitted, { type: ControlType.Beat }, { type: ControlType.Size }],
5349
- accepted: [...contract.accepted, { type: ControlType.Beat }, { type: ControlType.Size }],
2877
+ emitted: [...contract.emitted, ...controlActions()],
2878
+ accepted: [...contract.accepted, ...controlActions()],
5350
2879
  };
5351
2880
  }
5352
2881
 
5353
- /**
5354
- * Creates an {@link EventEmitter} backed by a plain registry.
5355
- *
5356
- * @returns A frozen emitter instance.
5357
- *
5358
- * @example Subscribing and emitting
5359
- * ```typescript
5360
- * const emitter = createEventEmitter()
5361
- * const off = emitter.on('open', () => console.log('opened'))
5362
- * emitter.emit('open')
5363
- * off()
5364
- * ```
5365
- */
5366
2882
  function createEventEmitter() {
5367
2883
  const registry = {};
5368
2884
  const on = (event, handler) => {
@@ -5378,111 +2894,111 @@
5378
2894
  return freeze({ on, emit });
5379
2895
  }
5380
2896
 
5381
- /**
5382
- * Safe Promise factory and bound static methods.
5383
- *
5384
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/promise
5385
- */
5386
- /* eslint-disable workspace/lib-require-jsdoc-example */
5387
2897
  const _Promise = globalThis.Promise;
5388
2898
  const _Reflect = globalThis.Reflect;
5389
- /**
5390
- * (Safe copy) Creates a new Promise using the captured Promise constructor.
5391
- * Use this instead of `new Promise()`.
5392
- *
5393
- * @param executor - The executor function.
5394
- * @returns A new Promise instance.
5395
- */
5396
2899
  const createPromise = (executor) => _Reflect.construct(_Promise, [executor]);
5397
- /**
5398
- * (Safe copy) Returns a Promise that resolves with the given value.
5399
- */
5400
- _Promise.resolve.bind(_Promise);
5401
- /**
5402
- * (Safe copy) Returns a Promise that rejects with the given reason.
5403
- */
5404
- _Promise.reject.bind(_Promise);
5405
- /**
5406
- * (Safe copy) Returns a Promise that resolves when all promises resolve.
5407
- */
2900
+ const promiseResolve = _Promise.resolve.bind(_Promise);
2901
+ const promiseReject = _Promise.reject.bind(_Promise);
5408
2902
  _Promise.all.bind(_Promise);
5409
- /**
5410
- * (Safe copy) Returns a Promise that resolves/rejects with the first settled promise.
5411
- */
5412
2903
  _Promise.race.bind(_Promise);
5413
- /**
5414
- * (Safe copy) Returns a Promise that resolves when all promises settle.
5415
- */
5416
2904
  _Promise.allSettled.bind(_Promise);
5417
- /**
5418
- * (Safe copy) Returns a Promise that resolves with the first fulfilled promise.
5419
- */
5420
2905
  _Promise.any.bind(_Promise);
5421
- /**
5422
- * (Safe copy) Creates a Promise along with its resolve and reject functions.
5423
- * Note: Available only in ES2024+ environments.
5424
- */
5425
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5426
2906
  _Promise.withResolvers?.bind(_Promise);
5427
2907
 
5428
- /**
5429
- * Safe copies of Timer/Scheduling built-in functions.
5430
- *
5431
- * These references are captured at module initialization time to protect against
5432
- * prototype pollution attacks. Import only what you need for tree-shaking.
5433
- *
5434
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/timers
5435
- */
2908
+ const _setTimeout = globalThis.setTimeout;
5436
2909
  const _setInterval = globalThis.setInterval;
2910
+ const _clearTimeout = globalThis.clearTimeout;
5437
2911
  const _clearInterval = globalThis.clearInterval;
5438
- /**
5439
- * (Safe copy) Repeatedly calls a function with a fixed time delay between each call.
5440
- *
5441
- * @param callback - Function to call at each interval.
5442
- * @param delay - Time in milliseconds between calls.
5443
- * @param args - Additional arguments to pass to the callback.
5444
- * @returns A numeric ID for the interval.
5445
- *
5446
- * @example Setting an interval
5447
- * ```typescript
5448
- * let count = 0
5449
- * const intervalId = setInterval(() => {
5450
- * count++
5451
- * if (count >= 5) clearInterval(intervalId)
5452
- * }, 1000)
5453
- * ```
5454
- */
2912
+ const setTimeout = (callback, delay, ...args) => _setTimeout(callback, delay, ...args);
5455
2913
  const setInterval = (callback, delay, ...args) => _setInterval(callback, delay, ...args);
5456
- /**
5457
- * (Safe copy) Cancels a timed, repeating action previously established by setInterval.
5458
- *
5459
- * @param id - The identifier of the interval to cancel.
5460
- *
5461
- * @example Canceling an interval
5462
- * ```typescript
5463
- * const intervalId = setInterval(() => console.log('tick'), 1000)
5464
- * // Stop after some condition
5465
- * clearInterval(intervalId)
5466
- * ```
5467
- */
2914
+ const clearTimeout = (id) => {
2915
+ _clearTimeout(id);
2916
+ };
5468
2917
  const clearInterval = (id) => {
5469
2918
  _clearInterval(id);
5470
2919
  };
5471
2920
 
5472
- // note: Baked-in liveness — the feature emits a beat on a fixed cadence with no consumer involvement, and the host monitors these to detect a hung or crashed feature.
2921
+ const DEFAULT_TIMEOUT_MS = 30000;
2922
+ function createRequestPeer(origin, send) {
2923
+ let pending = {};
2924
+ const handlers = {};
2925
+ let nextCorrelation = 0;
2926
+ const take = (correlationId) => {
2927
+ const entry = pending[correlationId];
2928
+ if (entry) {
2929
+ clearTimeout(entry.timer);
2930
+ delete pending[correlationId];
2931
+ }
2932
+ return entry;
2933
+ };
2934
+ const respond = (request, outcome) => send(ControlType.Response, { correlationId: request.correlationId, from: origin, innerType: request.innerType, ...outcome });
2935
+ const answer = (request) => {
2936
+ const handler = handlers[request.innerType];
2937
+ if (!handler) {
2938
+ respond(request, { ok: false, error: `No handler is registered for '${request.innerType}'.` });
2939
+ return;
2940
+ }
2941
+ promiseResolve()
2942
+ .then(() => handler(request.payload))
2943
+ .then((payload) => respond(request, { ok: true, payload }), (error) => respond(request, { ok: false, error: error instanceof Error ? error.message : `${error}` }));
2944
+ };
2945
+ const settle = (response) => {
2946
+ const entry = take(response.correlationId);
2947
+ if (!entry) {
2948
+ return;
2949
+ }
2950
+ if (response.ok) {
2951
+ entry.resolve(response.payload);
2952
+ return;
2953
+ }
2954
+ entry.reject(createError(response.error ?? `Request '${response.innerType}' failed.`));
2955
+ };
2956
+ const dispatch = (type, data) => {
2957
+ const envelope = (typeof data === 'object' ? data : null);
2958
+ if (!envelope || envelope.from === origin) {
2959
+ return;
2960
+ }
2961
+ if (type === ControlType.Request) {
2962
+ answer(envelope);
2963
+ return;
2964
+ }
2965
+ if (type === ControlType.Response) {
2966
+ settle(envelope);
2967
+ }
2968
+ };
2969
+ const request = (type, data, options) => createPromise((resolve, reject) => {
2970
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
2971
+ const correlationId = `${origin}-${(nextCorrelation += 1)}`;
2972
+ const timer = setTimeout(() => {
2973
+ delete pending[correlationId];
2974
+ reject(createError(`Request '${type}' timed out after ${timeoutMs}ms.`));
2975
+ }, timeoutMs);
2976
+ pending[correlationId] = { resolve, reject, timer };
2977
+ send(ControlType.Request, { correlationId, from: origin, innerType: type, payload: data });
2978
+ });
2979
+ const handle = (type, handler) => {
2980
+ if (handlers[type]) {
2981
+ throw createError(`A handler for '${type}' is already registered.`);
2982
+ }
2983
+ handlers[type] = handler;
2984
+ return () => {
2985
+ if (handlers[type] === handler) {
2986
+ delete handlers[type];
2987
+ }
2988
+ };
2989
+ };
2990
+ const rejectAll = (reason) => {
2991
+ const failed = values(pending);
2992
+ pending = {};
2993
+ for (const entry of failed) {
2994
+ clearTimeout(entry.timer);
2995
+ entry.reject(createError(reason));
2996
+ }
2997
+ };
2998
+ return freeze({ request, handle, dispatch, rejectAll });
2999
+ }
3000
+
5473
3001
  const BEAT_INTERVAL_MS = 1000;
5474
- /**
5475
- * Creates the hostee-side heartbeat emitter.
5476
- *
5477
- * @param send - The control-channel send function (receives the reserved beat type).
5478
- * @returns A start/stop handle for the beat loop.
5479
- *
5480
- * @example Pulsing the host while connected
5481
- * ```typescript
5482
- * const heartbeat = createHeartbeatEmitter((type) => channel.send(type))
5483
- * heartbeat.start()
5484
- * ```
5485
- */
5486
3002
  function createHeartbeatEmitter(send) {
5487
3003
  let timer;
5488
3004
  return {
@@ -5499,24 +3015,6 @@
5499
3015
  };
5500
3016
  }
5501
3017
 
5502
- /**
5503
- * Observes an element for size changes and triggers a callback when resized.
5504
- *
5505
- * @param element - The element to observe for resize events
5506
- * @param callback - The function to call when the element is resized
5507
- * @returns A cleanup function to stop observing the element
5508
- *
5509
- * @example Observing element resize
5510
- * ```typescript
5511
- * const container = document.getElementById('resizable-panel')
5512
- * const stopObserving = onElementResize(container, (rect) => {
5513
- * console.log(`New size: ${rect.width}x${rect.height}`)
5514
- * })
5515
- *
5516
- * // Stop observing when done
5517
- * stopObserving()
5518
- * ```
5519
- */
5520
3018
  function onElementResize(element, callback) {
5521
3019
  const resizeObserver = new ResizeObserver((entries) => {
5522
3020
  for (const entry of entries) {
@@ -5533,25 +3031,6 @@
5533
3031
  };
5534
3032
  }
5535
3033
 
5536
- /**
5537
- * Converts a CSS object into a CSS string suitable for inline styles or style sheets.
5538
- * Automatically converts camelCase properties to kebab-case.
5539
- *
5540
- * @param cssObj - The CSS object with property-value pairs
5541
- * @returns A CSS string representation
5542
- *
5543
- * @example Converting style object to CSS
5544
- * ```typescript
5545
- * const styles = {
5546
- * backgroundColor: '#f0f0f0',
5547
- * fontSize: '14px',
5548
- * marginTop: '8px'
5549
- * }
5550
- *
5551
- * cssObjectToString(styles)
5552
- * // => 'background-color: #f0f0f0; font-size: 14px; margin-top: 8px; '
5553
- * ```
5554
- */
5555
3034
  function cssObjectToString(cssObj) {
5556
3035
  const errors = [];
5557
3036
  const cssString = entries(cssObj).reduce((prev, [property, value]) => {
@@ -5571,24 +3050,6 @@
5571
3050
  return cssString;
5572
3051
  }
5573
3052
 
5574
- /**
5575
- * Validates whether a string is a valid CSS selector by attempting to query with it.
5576
- *
5577
- * @param selector - The CSS selector string to validate
5578
- * @returns True if the selector is valid, false otherwise
5579
- *
5580
- * @example Validating CSS selectors
5581
- * ```typescript
5582
- * isValidCssSelector('.my-class')
5583
- * // => true
5584
- *
5585
- * isValidCssSelector('#header nav > ul')
5586
- * // => true
5587
- *
5588
- * isValidCssSelector('[invalid')
5589
- * // => false
5590
- * ```
5591
- */
5592
3053
  function isValidCssSelector(selector) {
5593
3054
  try {
5594
3055
  document.createDocumentFragment().querySelector(selector);
@@ -5599,31 +3060,6 @@
5599
3060
  }
5600
3061
  }
5601
3062
 
5602
- /**
5603
- * Generates a CSS rule string from a given selector and style declaration.
5604
- *
5605
- * This function takes a CSS selector and either a string or a CSSStyleDeclaration object
5606
- * representing the styles to be applied. It validates the selector and converts the CSS
5607
- * object to a string if needed. The function then constructs and returns a valid
5608
- * CSS rule as a string.
5609
- *
5610
- * @param selector - The CSS selector to which the styles will be applied
5611
- * @param css - The styles to apply, either as a string or CSSStyleDeclaration object
5612
- * @returns A string representing a complete CSS rule
5613
- * @throws {Error} When the selector is invalid or the css argument is not a valid string or object
5614
- *
5615
- * @example With style object
5616
- * ```typescript
5617
- * cssRule('.button', { padding: '8px 16px', borderRadius: '4px' })
5618
- * // => '.button{padding: 8px 16px; border-radius: 4px}'
5619
- * ```
5620
- *
5621
- * @example With CSS string
5622
- * ```typescript
5623
- * cssRule('.button:hover', 'background-color: #007bff; color: white')
5624
- * // => '.button:hover{background-color: #007bff; color: white}'
5625
- * ```
5626
- */
5627
3063
  function cssRule(selector, css) {
5628
3064
  if (!isValidCssSelector(selector)) {
5629
3065
  throw createError('A valid css select must be provided');
@@ -5637,23 +3073,6 @@
5637
3073
  return `${selector}{${css}}`;
5638
3074
  }
5639
3075
 
5640
- /**
5641
- * Creates CSS rules from a styles object, converting each selector-style pair into CSS rule strings.
5642
- *
5643
- * @param styles - An object mapping CSS selectors to style objects
5644
- * @returns A string containing all CSS rules separated by newlines
5645
- *
5646
- * @example Creating CSS rules from object
5647
- * ```typescript
5648
- * const styles = {
5649
- * '.card': { padding: '16px', boxShadow: '0 2px 4px rgba(0,0,0,0.1)' },
5650
- * '.card-title': { fontSize: '18px', fontWeight: 'bold' }
5651
- * }
5652
- *
5653
- * cssRules(styles)
5654
- * // => '.card{padding: 16px; box-shadow: 0 2px 4px rgba(0,0,0,0.1)}\n.card-title{font-size: 18px; font-weight: bold}'
5655
- * ```
5656
- */
5657
3076
  function cssRules(styles) {
5658
3077
  if (getType(styles) !== 'object')
5659
3078
  return '';
@@ -5666,33 +3085,6 @@
5666
3085
  const labels = createSet();
5667
3086
  const labeledStylesheets = createMap();
5668
3087
  const stylesheetLabels = createWeakMap();
5669
- /**
5670
- * Adds a new stylesheet to the document with optional label.
5671
- *
5672
- * @param css - The CSS rules to be added in the new stylesheet
5673
- * @param label - Optional label for the new stylesheet
5674
- * @returns A tuple where the first item is the created HTMLStyleElement, and the second item is a cleanup function
5675
- * @throws {Error} When css is not a string or StyleMap, is empty, or a stylesheet with the same label already exists
5676
- *
5677
- * @example CSS string
5678
- * ```typescript
5679
- * const [styleElement, removeStyles] = addStylesheet(`
5680
- * .modal { display: flex; align-items: center; }
5681
- * .modal-overlay { background: rgba(0, 0, 0, 0.5); }
5682
- * `, 'modal-styles')
5683
- *
5684
- * // Remove when done
5685
- * removeStyles()
5686
- * ```
5687
- *
5688
- * @example StyleMap object
5689
- * ```typescript
5690
- * const [styleElement, removeStyles] = addStylesheet({
5691
- * '.button': { backgroundColor: '#3498db', padding: '10px 20px' },
5692
- * '.button:hover': { backgroundColor: '#2980b9' },
5693
- * })
5694
- * ```
5695
- */
5696
3088
  function addStylesheet(css, label) {
5697
3089
  if (getType(css) === 'object') {
5698
3090
  css = cssRules(css);
@@ -5707,23 +3099,6 @@
5707
3099
  const removeCallback = () => removeStylesheet(style);
5708
3100
  return [style, removeCallback];
5709
3101
  }
5710
- /**
5711
- * Removes a stylesheet from the document.
5712
- *
5713
- * @param {string | HTMLStyleElement} ref - The label or the HTMLStyleElement of the stylesheet to be removed.
5714
- *
5715
- * @example By label
5716
- * ```typescript
5717
- * addStylesheet('.theme { color: red; }', 'theme-styles')
5718
- * removeStylesheet('theme-styles')
5719
- * ```
5720
- *
5721
- * @example By element reference
5722
- * ```typescript
5723
- * const [styleElement] = addStylesheet('.custom { margin: 0; }')
5724
- * removeStylesheet(styleElement)
5725
- * ```
5726
- */
5727
3102
  function removeStylesheet(ref) {
5728
3103
  const isLabel = getType(ref) === 'string';
5729
3104
  let style;
@@ -5741,7 +3116,6 @@
5741
3116
  stylesheets.delete(style);
5742
3117
  }
5743
3118
  catch {
5744
- /** Swallow any errors */
5745
3119
  }
5746
3120
  try {
5747
3121
  labels.delete(label);
@@ -5749,35 +3123,13 @@
5749
3123
  stylesheetLabels.delete(style);
5750
3124
  }
5751
3125
  catch {
5752
- /** Swallow any errors */
5753
3126
  }
5754
3127
  }
5755
3128
 
5756
- // note: The feature page resets its own body so the embedded UI fills the iframe edge-to-edge with a transparent backdrop; the host cannot touch the cross-origin body itself.
5757
3129
  const BODY_RESET_CSS = 'html,body{margin:0;padding:0;background:transparent}';
5758
- /**
5759
- * Neutralizes the feature page's body so the embedded UI is neither clipped nor padded.
5760
- *
5761
- * @example Resetting the body when a feature initializes
5762
- * ```typescript
5763
- * applyBodyReset()
5764
- * ```
5765
- */
5766
3130
  function applyBodyReset() {
5767
3131
  addStylesheet(BODY_RESET_CSS);
5768
3132
  }
5769
- /**
5770
- * Creates the hostee-side content-size announcer.
5771
- *
5772
- * @param send - The control-channel send function (receives the reserved size type).
5773
- * @returns A start/stop handle for the size-announcement loop.
5774
- *
5775
- * @example Announcing size to the host while connected
5776
- * ```typescript
5777
- * const announcer = createSizeAnnouncer((type, data) => channel.send(type, data))
5778
- * announcer.start()
5779
- * ```
5780
- */
5781
3133
  function createSizeAnnouncer(send) {
5782
3134
  let cleanup;
5783
3135
  const announce = () => send(ControlType.Size, { width: document.body.scrollWidth, height: document.body.scrollHeight });
@@ -5798,18 +3150,6 @@
5798
3150
  };
5799
3151
  }
5800
3152
 
5801
- // note: The host connects from a parent window (embedded iframe) or an opener window (popup/standalone); a top-level document has neither.
5802
- /**
5803
- * Resolves the window the host is expected to message the feature from.
5804
- *
5805
- * @param win - The feature's own window (`globalThis.window` in production).
5806
- * @returns The parent or opener window, or `null` when running top-level.
5807
- *
5808
- * @example Resolving the host window
5809
- * ```typescript
5810
- * const hostWindow = resolveHostWindow(window)
5811
- * ```
5812
- */
5813
3153
  function resolveHostWindow(win) {
5814
3154
  if (win.parent !== win) {
5815
3155
  return win.parent;
@@ -5819,23 +3159,10 @@
5819
3159
  }
5820
3160
  return null;
5821
3161
  }
5822
- /**
5823
- * Wires a hostee channel into the emitter and assembles the public handle.
5824
- *
5825
- * @param broker - The nexus broker for this feature.
5826
- * @param hostWindow - The resolved host window, or `null` when unembedded.
5827
- * @param emitter - The subscription registry backing `handle.on`.
5828
- * @returns The frozen {@link FeatureHandle}.
5829
- *
5830
- * @example Assembling a feature handle around a broker
5831
- * ```typescript
5832
- * const handle = createFeatureHandle(broker, resolveHostWindow(window), emitter)
5833
- * await handle.ready()
5834
- * ```
5835
- */
5836
3162
  function createFeatureHandle(broker, hostWindow, emitter) {
5837
3163
  let channel = null;
5838
3164
  let opened = false;
3165
+ const requests = createRequestPeer('feature', (type, data) => channel?.send(type, data));
5839
3166
  if (hostWindow) {
5840
3167
  const activeChannel = broker.addChannel('host', hostWindow);
5841
3168
  channel = activeChannel;
@@ -5852,14 +3179,25 @@
5852
3179
  emitter.emit('close');
5853
3180
  heartbeat.stop();
5854
3181
  announcer.stop();
3182
+ requests.rejectAll('The host channel closed before the host responded.');
5855
3183
  });
5856
3184
  activeChannel.on('deny', (data) => emitter.emit('error', data));
5857
3185
  activeChannel.on('invalid', (data) => emitter.emit('error', data));
5858
- activeChannel.onMessage((message) => emitter.emit(message.type, message.data));
3186
+ activeChannel.onMessage((message) => {
3187
+ if (isControlType(message.type)) {
3188
+ requests.dispatch(message.type, message.data);
3189
+ return;
3190
+ }
3191
+ emitter.emit(message.type, message.data);
3192
+ });
5859
3193
  activeChannel.connect();
5860
3194
  }
5861
3195
  return freeze({
5862
3196
  send: (type, data) => channel?.send(type, data),
3197
+ request: (type, data, options) => channel
3198
+ ? requests.request(type, data, options)
3199
+ : promiseReject(createError(`Cannot send request '${type}': the feature is not connected to a host.`)),
3200
+ handle: requests.handle,
5863
3201
  on: emitter.on,
5864
3202
  ready: () => createPromise((resolve) => {
5865
3203
  if (opened) {
@@ -5872,22 +3210,6 @@
5872
3210
  });
5873
3211
  }
5874
3212
 
5875
- /**
5876
- * Initializes a feature app on the hostee side and waits for the host connection.
5877
- *
5878
- * Creates a nexus broker for the feature, resolves the host window, and returns
5879
- * a handle for messaging and lifecycle.
5880
- *
5881
- * @param options - Feature name and contract.
5882
- * @returns A handle exposing `send`, `on`, `ready`, and `close`.
5883
- *
5884
- * @example Initializing a clock feature
5885
- * ```typescript
5886
- * const feature = createFeature({ name: 'clock', contract })
5887
- * feature.ready().then(() => feature.send('timeUpdated', { time: Date.now() }))
5888
- * feature.on('setTimezone', (data) => console.log(data))
5889
- * ```
5890
- */
5891
3213
  function createFeature(options) {
5892
3214
  const contract = withControlContract(validateContract(options.contract));
5893
3215
  const emitter = createEventEmitter();