@lushly-dev/fast-element 3.0.1-preview.0 → 3.0.1-preview.2

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 (250) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/command/command.api.json +7857 -0
  3. package/dist/context/context.api.json +1084 -0
  4. package/dist/declarative/declarative.api.json +8130 -0
  5. package/dist/definition-schema/definition-schema.api.json +2689 -0
  6. package/dist/di/di.api.json +4945 -0
  7. package/dist/dts/arrays.d.ts +2 -0
  8. package/dist/dts/binding/binding.d.ts +59 -0
  9. package/dist/dts/binding/exports.d.ts +9 -0
  10. package/dist/dts/binding/normalize.d.ts +9 -0
  11. package/dist/dts/binding/one-time.d.ts +11 -0
  12. package/dist/dts/binding/one-way.d.ts +20 -0
  13. package/dist/dts/binding/signal.d.ts +36 -0
  14. package/dist/dts/binding/two-way.d.ts +44 -0
  15. package/dist/dts/command/behavior.d.ts +94 -0
  16. package/dist/dts/command/behaviors/confirm-behavior.d.ts +139 -0
  17. package/dist/dts/command/behaviors/undo-behavior.d.ts +178 -0
  18. package/dist/dts/command/command.d.ts +79 -0
  19. package/dist/dts/command/component-registry.d.ts +50 -0
  20. package/dist/dts/command/components/confirm-dialog.d.ts +117 -0
  21. package/dist/dts/command/index.d.ts +21 -0
  22. package/dist/dts/command/registry.d.ts +201 -0
  23. package/dist/dts/command/schema-validation.d.ts +45 -0
  24. package/dist/dts/command/types.d.ts +332 -0
  25. package/dist/dts/command/validation.d.ts +116 -0
  26. package/dist/dts/command/weak-ref.d.ts +11 -0
  27. package/dist/dts/components/attributes.d.ts +153 -0
  28. package/dist/dts/components/definition-schema-transforms.d.ts +9 -0
  29. package/dist/dts/components/element-controller.d.ts +349 -0
  30. package/dist/dts/components/element-hydration.d.ts +2 -0
  31. package/dist/dts/components/enable-hydration.d.ts +66 -0
  32. package/dist/dts/components/fast-definitions.d.ts +211 -0
  33. package/dist/dts/components/fast-element-registry.d.ts +23 -0
  34. package/dist/dts/components/fast-element.d.ts +91 -0
  35. package/dist/dts/components/hydration-tracker.d.ts +110 -0
  36. package/dist/dts/components/hydration.d.ts +52 -0
  37. package/dist/dts/components/schema.d.ts +205 -0
  38. package/dist/dts/context.d.ts +171 -0
  39. package/dist/dts/debug.d.ts +5 -0
  40. package/dist/dts/declarative/attribute-map.d.ts +58 -0
  41. package/dist/dts/declarative/debug.d.ts +4 -0
  42. package/dist/dts/declarative/index.d.ts +14 -0
  43. package/dist/dts/declarative/interfaces.d.ts +8 -0
  44. package/dist/dts/declarative/observer-map-utilities.d.ts +58 -0
  45. package/dist/dts/declarative/observer-map.d.ts +89 -0
  46. package/dist/dts/declarative/runtime.d.ts +5 -0
  47. package/dist/dts/declarative/syntax.d.ts +21 -0
  48. package/dist/dts/declarative/template-bridge.d.ts +33 -0
  49. package/dist/dts/declarative/template-parser.d.ts +98 -0
  50. package/dist/dts/declarative/template.d.ts +9 -0
  51. package/dist/dts/declarative/utilities.d.ts +358 -0
  52. package/dist/dts/definition-schema/api.d.ts +42 -0
  53. package/dist/dts/definition-schema/cem.d.ts +78 -0
  54. package/dist/dts/definition-schema/extension.d.ts +18 -0
  55. package/dist/dts/definition-schema/extract.d.ts +38 -0
  56. package/dist/dts/definition-schema/index.d.ts +9 -0
  57. package/dist/dts/definition-schema/storage.d.ts +35 -0
  58. package/dist/dts/definition-schema/types.d.ts +176 -0
  59. package/dist/dts/dev-mode.d.ts +13 -0
  60. package/dist/dts/di/di.d.ts +894 -0
  61. package/dist/dts/dom-policy.d.ts +101 -0
  62. package/dist/dts/dom.d.ts +88 -0
  63. package/dist/dts/hydration/diagnostics.d.ts +93 -0
  64. package/dist/dts/hydration/exports.d.ts +18 -0
  65. package/dist/dts/hydration/hydration-debugger.d.ts +35 -0
  66. package/dist/dts/hydration/messages.d.ts +62 -0
  67. package/dist/dts/hydration/runtime.d.ts +7 -0
  68. package/dist/dts/hydration/target-builder.d.ts +107 -0
  69. package/dist/dts/index.d.ts +51 -0
  70. package/dist/dts/index.debug.d.ts +1 -0
  71. package/dist/dts/index.rollup.d.ts +1 -0
  72. package/dist/dts/index.rollup.debug.d.ts +1 -0
  73. package/dist/dts/interfaces.d.ts +106 -0
  74. package/dist/dts/metadata.d.ts +26 -0
  75. package/dist/dts/observation/arrays.d.ts +248 -0
  76. package/dist/dts/observation/notifier.d.ts +127 -0
  77. package/dist/dts/observation/observable.d.ts +276 -0
  78. package/dist/dts/observation/update-queue.d.ts +40 -0
  79. package/dist/dts/observation/volatile.d.ts +9 -0
  80. package/dist/dts/platform.d.ts +87 -0
  81. package/dist/dts/registry.d.ts +2 -0
  82. package/dist/dts/render.d.ts +7 -0
  83. package/dist/dts/schema.d.ts +1 -0
  84. package/dist/dts/scoped/detection.d.ts +55 -0
  85. package/dist/dts/scoped/index.d.ts +68 -0
  86. package/dist/dts/scoped/mixin.d.ts +118 -0
  87. package/dist/dts/scoped/registry.d.ts +76 -0
  88. package/dist/dts/scoped/types.d.ts +48 -0
  89. package/dist/dts/state/exports.d.ts +3 -0
  90. package/dist/dts/state/reactive.d.ts +8 -0
  91. package/dist/dts/state/state.d.ts +141 -0
  92. package/dist/dts/state/visitor.d.ts +6 -0
  93. package/dist/dts/state/watch.d.ts +10 -0
  94. package/dist/dts/styles/css-directive.d.ts +50 -0
  95. package/dist/dts/styles/css.d.ts +34 -0
  96. package/dist/dts/styles/element-styles.d.ts +61 -0
  97. package/dist/dts/styles/host.d.ts +65 -0
  98. package/dist/dts/styles/style-strategy.d.ts +42 -0
  99. package/dist/dts/templating/children.d.ts +62 -0
  100. package/dist/dts/templating/compiler.d.ts +54 -0
  101. package/dist/dts/templating/html-binding-directive.d.ts +155 -0
  102. package/dist/dts/templating/html-directive.d.ts +218 -0
  103. package/dist/dts/templating/html.d.ts +1 -0
  104. package/dist/dts/templating/hydration-view.d.ts +130 -0
  105. package/dist/dts/templating/markup.d.ts +48 -0
  106. package/dist/dts/templating/node-observation.d.ts +99 -0
  107. package/dist/dts/templating/ref.d.ts +23 -0
  108. package/dist/dts/templating/render.d.ts +318 -0
  109. package/dist/dts/templating/repeat.d.ts +131 -0
  110. package/dist/dts/templating/slotted.d.ts +38 -0
  111. package/dist/dts/templating/template.d.ts +221 -0
  112. package/dist/dts/templating/view.d.ts +268 -0
  113. package/dist/dts/templating/when.d.ts +12 -0
  114. package/dist/dts/testing/exports.d.ts +3 -0
  115. package/dist/dts/testing/fakes.d.ts +16 -0
  116. package/dist/dts/testing/fixture.d.ts +84 -0
  117. package/dist/dts/testing/models.d.ts +20 -0
  118. package/dist/dts/testing/timeout.d.ts +7 -0
  119. package/dist/dts/trust/index.d.ts +8 -0
  120. package/dist/dts/trust/types.d.ts +48 -0
  121. package/dist/dts/trust/utils.d.ts +26 -0
  122. package/dist/dts/tsdoc-metadata.json +11 -0
  123. package/dist/dts/utilities.d.ts +76 -0
  124. package/dist/esm/arrays.js +1 -0
  125. package/dist/esm/binding/binding.js +18 -0
  126. package/dist/esm/binding/exports.js +6 -0
  127. package/dist/esm/binding/normalize.js +17 -0
  128. package/dist/esm/binding/one-time.js +21 -0
  129. package/dist/esm/binding/one-way.js +30 -0
  130. package/dist/esm/binding/signal.js +99 -0
  131. package/dist/esm/binding/two-way.js +109 -0
  132. package/dist/esm/command/behavior.js +155 -0
  133. package/dist/esm/command/behaviors/confirm-behavior.js +225 -0
  134. package/dist/esm/command/behaviors/undo-behavior.js +313 -0
  135. package/dist/esm/command/command.js +104 -0
  136. package/dist/esm/command/component-registry.js +107 -0
  137. package/dist/esm/command/components/confirm-dialog.js +370 -0
  138. package/dist/esm/command/index.js +22 -0
  139. package/dist/esm/command/registry.js +755 -0
  140. package/dist/esm/command/schema-validation.js +194 -0
  141. package/dist/esm/command/types.js +86 -0
  142. package/dist/esm/command/validation.js +508 -0
  143. package/dist/esm/command/weak-ref.js +5 -0
  144. package/dist/esm/components/attributes.js +219 -0
  145. package/dist/esm/components/definition-schema-transforms.js +23 -0
  146. package/dist/esm/components/element-controller.js +840 -0
  147. package/dist/esm/components/element-hydration.js +2 -0
  148. package/dist/esm/components/enable-hydration.js +123 -0
  149. package/dist/esm/components/fast-definitions.js +294 -0
  150. package/dist/esm/components/fast-element-registry.js +67 -0
  151. package/dist/esm/components/fast-element.js +82 -0
  152. package/dist/esm/components/hydration-tracker.js +211 -0
  153. package/dist/esm/components/hydration.js +187 -0
  154. package/dist/esm/components/schema.js +253 -0
  155. package/dist/esm/context.js +185 -0
  156. package/dist/esm/debug.js +71 -0
  157. package/dist/esm/declarative/attribute-map.js +122 -0
  158. package/dist/esm/declarative/debug.js +4 -0
  159. package/dist/esm/declarative/index.js +10 -0
  160. package/dist/esm/declarative/interfaces.js +9 -0
  161. package/dist/esm/declarative/observer-map-utilities.js +565 -0
  162. package/dist/esm/declarative/observer-map.js +216 -0
  163. package/dist/esm/declarative/runtime.js +14 -0
  164. package/dist/esm/declarative/syntax.js +36 -0
  165. package/dist/esm/declarative/template-bridge.js +160 -0
  166. package/dist/esm/declarative/template-parser.js +306 -0
  167. package/dist/esm/declarative/template.js +116 -0
  168. package/dist/esm/declarative/utilities.js +1069 -0
  169. package/dist/esm/definition-schema/api.js +129 -0
  170. package/dist/esm/definition-schema/cem.js +89 -0
  171. package/dist/esm/definition-schema/extension.js +53 -0
  172. package/dist/esm/definition-schema/extract.js +167 -0
  173. package/dist/esm/definition-schema/index.js +8 -0
  174. package/dist/esm/definition-schema/storage.js +58 -0
  175. package/dist/esm/definition-schema/types.js +8 -0
  176. package/dist/esm/dev-mode.js +22 -0
  177. package/dist/esm/di/di.js +1441 -0
  178. package/dist/esm/dom-policy.js +374 -0
  179. package/dist/esm/dom.js +101 -0
  180. package/dist/esm/hydration/diagnostics.js +50 -0
  181. package/dist/esm/hydration/exports.js +6 -0
  182. package/dist/esm/hydration/hydration-debugger.js +112 -0
  183. package/dist/esm/hydration/messages.js +84 -0
  184. package/dist/esm/hydration/runtime.js +33 -0
  185. package/dist/esm/hydration/target-builder.js +264 -0
  186. package/dist/esm/index.debug.js +3 -0
  187. package/dist/esm/index.js +49 -0
  188. package/dist/esm/index.rollup.debug.js +6 -0
  189. package/dist/esm/index.rollup.js +4 -0
  190. package/dist/esm/interfaces.js +55 -0
  191. package/dist/esm/metadata.js +57 -0
  192. package/dist/esm/observation/arrays.js +661 -0
  193. package/dist/esm/observation/notifier.js +162 -0
  194. package/dist/esm/observation/observable.js +324 -0
  195. package/dist/esm/observation/update-queue.js +56 -0
  196. package/dist/esm/observation/volatile.js +16 -0
  197. package/dist/esm/platform.js +98 -0
  198. package/dist/esm/registry.js +2 -0
  199. package/dist/esm/render.js +1 -0
  200. package/dist/esm/schema.js +1 -0
  201. package/dist/esm/scoped/detection.js +102 -0
  202. package/dist/esm/scoped/index.js +69 -0
  203. package/dist/esm/scoped/mixin.js +127 -0
  204. package/dist/esm/scoped/registry.js +125 -0
  205. package/dist/esm/scoped/types.js +4 -0
  206. package/dist/esm/state/exports.js +3 -0
  207. package/dist/esm/state/reactive.js +34 -0
  208. package/dist/esm/state/state.js +148 -0
  209. package/dist/esm/state/visitor.js +28 -0
  210. package/dist/esm/state/watch.js +36 -0
  211. package/dist/esm/styles/css-directive.js +36 -0
  212. package/dist/esm/styles/css.js +56 -0
  213. package/dist/esm/styles/element-styles.js +144 -0
  214. package/dist/esm/styles/host.js +1 -0
  215. package/dist/esm/styles/style-strategy.js +1 -0
  216. package/dist/esm/templating/children.js +67 -0
  217. package/dist/esm/templating/compiler.js +359 -0
  218. package/dist/esm/templating/html-binding-directive.js +288 -0
  219. package/dist/esm/templating/html-directive.js +113 -0
  220. package/dist/esm/templating/html.js +1 -0
  221. package/dist/esm/templating/hydration-view.js +228 -0
  222. package/dist/esm/templating/markup.js +83 -0
  223. package/dist/esm/templating/node-observation.js +84 -0
  224. package/dist/esm/templating/ref.js +21 -0
  225. package/dist/esm/templating/render.js +468 -0
  226. package/dist/esm/templating/repeat.js +434 -0
  227. package/dist/esm/templating/slotted.js +49 -0
  228. package/dist/esm/templating/template.js +206 -0
  229. package/dist/esm/templating/view.js +298 -0
  230. package/dist/esm/templating/when.js +22 -0
  231. package/dist/esm/testing/exports.js +3 -0
  232. package/dist/esm/testing/fakes.js +107 -0
  233. package/dist/esm/testing/fixture.js +86 -0
  234. package/dist/esm/testing/models.js +58 -0
  235. package/dist/esm/testing/timeout.js +24 -0
  236. package/dist/esm/trust/index.js +7 -0
  237. package/dist/esm/trust/types.js +6 -0
  238. package/dist/esm/trust/utils.js +126 -0
  239. package/dist/esm/utilities.js +140 -0
  240. package/dist/fast-element.api.json +26798 -0
  241. package/dist/fast-element.d.ts +3985 -0
  242. package/dist/fast-element.debug.js +7604 -0
  243. package/dist/fast-element.debug.min.js +3 -0
  244. package/dist/fast-element.js +7603 -0
  245. package/dist/fast-element.min.js +3 -0
  246. package/dist/fast-element.untrimmed.d.ts +4103 -0
  247. package/dist/observation/arrays/arrays.api.json +2621 -0
  248. package/dist/scoped/scoped.api.json +891 -0
  249. package/dist/trust/trust.api.json +629 -0
  250. package/package.json +21 -23
@@ -0,0 +1,755 @@
1
+ /**
2
+ * CommandRegistry singleton for managing command lifecycle.
3
+ * Enables AI agents and command palettes to discover and execute commands.
4
+ *
5
+ * @remarks
6
+ * - SSR-safe: Guards against document access in Node.js
7
+ * - The `cmd-exec` document event bridge is opt-in (`enableEventBridge()`)
8
+ * - Uses AFD's failure() helper for error results
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { CommandRegistry } from '@lushly-dev/fast-element/command.js';
13
+ *
14
+ * // Execute a command
15
+ * const result = await CommandRegistry.execute('editor.save');
16
+ * if (result.success) {
17
+ * console.log('Saved:', result.data);
18
+ * } else {
19
+ * console.error('Failed:', result.error.message);
20
+ * }
21
+ *
22
+ * // Get actions for command palette
23
+ * const actions = CommandRegistry.getActions();
24
+ *
25
+ * // Subscribe to changes
26
+ * const unsubscribe = CommandRegistry.onChange((added, removed) => {
27
+ * console.log('Commands changed:', { added, removed });
28
+ * });
29
+ * ```
30
+ */
31
+ import { isDevMode } from "../dev-mode.js";
32
+ import { validateCommandArguments } from "./schema-validation.js";
33
+ import { DEFAULT_EXPOSE_CONFIG, ErrorCodes, failure } from "./types.js";
34
+ /**
35
+ * Reads a `canExecute`/`when` getter, treating a throwing getter as `false` so a
36
+ * single faulty command cannot break command listing.
37
+ * @internal
38
+ */
39
+ function readFlag(read) {
40
+ try {
41
+ return read();
42
+ }
43
+ catch (_a) {
44
+ return false;
45
+ }
46
+ }
47
+ /**
48
+ * Internal implementation of the CommandRegistry.
49
+ * @internal
50
+ */
51
+ class CommandRegistryImpl {
52
+ constructor() {
53
+ this.commands = new Map();
54
+ this.shadowed = new Map();
55
+ this.listeners = new Set();
56
+ this.interceptors = [];
57
+ this.postHooks = [];
58
+ this.eventBridgeUsers = 0;
59
+ }
60
+ /**
61
+ * Enables the document-level `cmd-exec` event bridge.
62
+ *
63
+ * @returns A function that disables the bridge again
64
+ *
65
+ * @remarks
66
+ * The bridge is **opt-in**. A `cmd-exec` event
67
+ * (`new CustomEvent("cmd-exec", { detail: { id, args } })`) is an external
68
+ * invocation path: any script, extension or agent that can dispatch events on
69
+ * the document can use it. Events are therefore treated as `agent` origin
70
+ * invocations (see {@link CommandRegistryAPI.invoke}): the command must be
71
+ * exposed to `agent`, its `parameters` schema is enforced, and destructive
72
+ * commands fail closed unless a confirmation interceptor is installed.
73
+ *
74
+ * Calling this more than once is safe; the single shared listener is removed
75
+ * only when every caller has disposed. SSR-safe: a no-op outside a browser.
76
+ *
77
+ * @public
78
+ */
79
+ enableEventBridge() {
80
+ if (typeof document === "undefined") {
81
+ return () => { };
82
+ }
83
+ if (this.eventBridge === void 0) {
84
+ const bridge = (e) => {
85
+ const detail = e.detail;
86
+ if (!detail ||
87
+ typeof detail !== "object" ||
88
+ typeof detail.id !== "string") {
89
+ return;
90
+ }
91
+ const args = Array.isArray(detail.args) ? detail.args : [];
92
+ void this.invoke(detail.id, args, { origin: "agent" });
93
+ };
94
+ this.eventBridge = bridge;
95
+ document.addEventListener("cmd-exec", bridge);
96
+ }
97
+ this.eventBridgeUsers++;
98
+ let disposed = false;
99
+ return () => {
100
+ if (disposed)
101
+ return;
102
+ disposed = true;
103
+ if (--this.eventBridgeUsers === 0 && this.eventBridge !== void 0) {
104
+ document.removeEventListener("cmd-exec", this.eventBridge);
105
+ this.eventBridge = void 0;
106
+ }
107
+ };
108
+ }
109
+ /**
110
+ * Registers a command entry in the registry.
111
+ *
112
+ * @param id - The command identifier
113
+ * @param command - The command entry to register
114
+ *
115
+ * @remarks
116
+ * - Emits a warning in DEV_MODE if overwriting an existing command
117
+ * - Notifies listeners of the registration
118
+ * - Emits telemetry event
119
+ *
120
+ * @public
121
+ */
122
+ register(id, command) {
123
+ const current = this.commands.get(id);
124
+ const stack = this.shadowed.get(id);
125
+ // Re-registering an entry that is currently shadowed promotes it.
126
+ if (stack !== void 0) {
127
+ const index = stack.indexOf(command);
128
+ if (index !== -1)
129
+ stack.splice(index, 1);
130
+ }
131
+ if (current !== void 0 && current !== command) {
132
+ if (isDevMode()) {
133
+ console.warn(`[fast-af] Command "${id}" already registered. Overwriting.`);
134
+ }
135
+ // Keep the displaced entry so removing the newest source falls back to it.
136
+ const shadows = stack !== null && stack !== void 0 ? stack : [];
137
+ shadows.push(current);
138
+ this.shadowed.set(id, shadows);
139
+ }
140
+ this.commands.set(id, command);
141
+ this.notify([id], []);
142
+ this.emitTelemetry("command.registered", { id, label: command.label });
143
+ }
144
+ /**
145
+ * Unregisters a command from the registry.
146
+ *
147
+ * @param id - The command identifier to unregister
148
+ * @param expected - When provided, only this registration is removed
149
+ *
150
+ * @remarks
151
+ * - No-op if command doesn't exist
152
+ * - The most recent registration wins, but earlier registrations of the same ID
153
+ * (for example from several connected elements) are remembered: removing the
154
+ * active `expected` entry makes the previous one active again, and the
155
+ * command is only unregistered when no registration remains
156
+ * - Without `expected`, the command and all its registrations are removed
157
+ * - Notifies listeners of the removal (or of the replacement when falling back)
158
+ * - Emits telemetry event on removal
159
+ *
160
+ * @public
161
+ */
162
+ unregister(id, expected) {
163
+ var _a, _b;
164
+ var _c;
165
+ if (expected !== void 0 && this.commands.get(id) !== expected) {
166
+ // A stale removal of a shadowed registration.
167
+ const stack = this.shadowed.get(id);
168
+ const index = (_c = stack === null || stack === void 0 ? void 0 : stack.indexOf(expected)) !== null && _c !== void 0 ? _c : -1;
169
+ if (stack !== void 0 && index !== -1) {
170
+ stack.splice(index, 1);
171
+ if (stack.length === 0)
172
+ this.shadowed.delete(id);
173
+ }
174
+ return;
175
+ }
176
+ const fallback = expected !== void 0 ? (_a = this.shadowed.get(id)) === null || _a === void 0 ? void 0 : _a.pop() : void 0;
177
+ if (fallback !== void 0) {
178
+ if (((_b = this.shadowed.get(id)) === null || _b === void 0 ? void 0 : _b.length) === 0)
179
+ this.shadowed.delete(id);
180
+ this.commands.set(id, fallback);
181
+ this.notify([id], []);
182
+ return;
183
+ }
184
+ this.shadowed.delete(id);
185
+ if (this.commands.delete(id)) {
186
+ this.notify([], [id]);
187
+ this.emitTelemetry("command.unregistered", { id });
188
+ }
189
+ }
190
+ /**
191
+ * Retrieves a command entry by ID.
192
+ *
193
+ * @param id - The command identifier
194
+ * @returns The command entry, or undefined if not found
195
+ *
196
+ * @public
197
+ */
198
+ get(id) {
199
+ return this.commands.get(id);
200
+ }
201
+ /**
202
+ * Executes a command by ID on behalf of the application's own code.
203
+ *
204
+ * @param id - The command identifier
205
+ * @param args - Arguments to pass to the command
206
+ * @returns A Promise resolving to the CommandResult
207
+ *
208
+ * @remarks
209
+ * This is the trusted, programmatic path: `expose`, `parameters` and the
210
+ * confirmation requirement are not enforced here (use `invoke()` for
211
+ * invocations that originate from an agent, MCP server, CLI or event).
212
+ *
213
+ * Never throws. Returns a failure result if:
214
+ * - Command not found (COMMAND_NOT_FOUND)
215
+ * - Command is disabled (COMMAND_DISABLED)
216
+ * - A `canExecute` getter throws (EXECUTION_FAILED)
217
+ *
218
+ * Interceptors are run in priority order before the command handler.
219
+ *
220
+ * @public
221
+ */
222
+ execute(id, ...args) {
223
+ return this.run(id, args);
224
+ }
225
+ /**
226
+ * Invokes a command on behalf of an external caller (agent, MCP, CLI, palette
227
+ * or the `cmd-exec` event bridge).
228
+ *
229
+ * @param id - The command identifier
230
+ * @param args - Arguments to pass to the command
231
+ * @param options - Describes the invocation origin
232
+ * @returns A Promise resolving to the CommandResult; never rejects
233
+ *
234
+ * @remarks
235
+ * Applies the safety properties that `execute()` leaves to the caller:
236
+ * - `expose`: the command must be exposed to `options.origin`
237
+ * (COMMAND_NOT_EXPOSED); same secure defaults as `getActions()`
238
+ * - `parameters`: when the command declares a JSON Schema it is applied to
239
+ * `args[0]` (INVALID_PARAMETERS); see the `parameters` option docs
240
+ * - `destructive`: fails closed (CONFIRMATION_REQUIRED) unless a confirmation
241
+ * interceptor (named `confirm`, or flagged `confirms: true`) is installed
242
+ *
243
+ * @public
244
+ */
245
+ invoke(id, args, options) {
246
+ return this.run(id, Array.isArray(args) ? args : [], options);
247
+ }
248
+ /**
249
+ * Shared execution path for `execute()` and `invoke()`.
250
+ * @internal
251
+ */
252
+ run(id, args, options) {
253
+ var _a;
254
+ var _b;
255
+ const cmd = this.commands.get(id);
256
+ if (!cmd) {
257
+ return Promise.resolve(failure({
258
+ code: ErrorCodes.COMMAND_NOT_FOUND,
259
+ message: `Command not found: ${id}`,
260
+ suggestion: "Check command ID or ensure component is connected",
261
+ retryable: false,
262
+ }));
263
+ }
264
+ const origin = options === null || options === void 0 ? void 0 : options.origin;
265
+ if (origin !== void 0) {
266
+ const expose = Object.assign(Object.assign({}, DEFAULT_EXPOSE_CONFIG), cmd.expose);
267
+ if (expose[origin] !== true) {
268
+ return Promise.resolve(this.reject(id, origin, failure({
269
+ code: ErrorCodes.COMMAND_NOT_EXPOSED,
270
+ message: `Command not exposed to ${String(origin)}: ${id}`,
271
+ suggestion: "Enable the interface in the command's expose configuration",
272
+ retryable: false,
273
+ })));
274
+ }
275
+ }
276
+ let canExecute;
277
+ try {
278
+ canExecute = cmd.canExecute;
279
+ }
280
+ catch (error) {
281
+ return Promise.resolve(failure({
282
+ code: ErrorCodes.EXECUTION_FAILED,
283
+ message: `Command availability check failed: ${id}`,
284
+ suggestion: "Inspect the command's canExecute implementation",
285
+ retryable: false,
286
+ details: {
287
+ cause: error instanceof Error
288
+ ? { message: error.message, name: error.name }
289
+ : { value: String(error) },
290
+ },
291
+ }));
292
+ }
293
+ if (!canExecute) {
294
+ return Promise.resolve(failure({
295
+ code: ErrorCodes.COMMAND_DISABLED,
296
+ message: `Command disabled: ${id}`,
297
+ suggestion: "Wait for the command to become available",
298
+ retryable: true,
299
+ }));
300
+ }
301
+ if (origin !== void 0) {
302
+ if (cmd.parameters) {
303
+ const issues = validateCommandArguments(args, cmd.parameters);
304
+ if (issues.length > 0) {
305
+ return Promise.resolve(this.reject(id, origin, failure({
306
+ code: ErrorCodes.INVALID_PARAMETERS,
307
+ message: `Invalid parameters for ${id}: ${issues
308
+ .map(i => `${i.path} ${i.message}`)
309
+ .join("; ")}`,
310
+ suggestion: "Provide arguments matching the command's parameters schema",
311
+ retryable: false,
312
+ details: { issues },
313
+ })));
314
+ }
315
+ }
316
+ if (cmd.destructive &&
317
+ !this.interceptors.some(i => i.confirms === true || i.name === "confirm")) {
318
+ return Promise.resolve(this.reject(id, origin, failure({
319
+ code: ErrorCodes.CONFIRMATION_REQUIRED,
320
+ message: `Command requires confirmation: ${id}`,
321
+ suggestion: "Install a confirmation interceptor (for example ConfirmBehavior) before invoking destructive commands externally",
322
+ retryable: false,
323
+ })));
324
+ }
325
+ }
326
+ const start = (_b = (_a = globalThis.performance) === null || _a === void 0 ? void 0 : _a.now()) !== null && _b !== void 0 ? _b : Date.now();
327
+ // Build the handler that executes the command
328
+ const executeHandler = () => {
329
+ return Promise.resolve(cmd.execute(...args));
330
+ };
331
+ // Build the interceptor chain and execute
332
+ const chain = this.buildInterceptorChain(cmd, args, executeHandler);
333
+ return Promise.resolve()
334
+ .then(chain)
335
+ .then(result => this.normalizeResult(id, result))
336
+ .catch(error => failure({
337
+ code: ErrorCodes.EXECUTION_FAILED,
338
+ message: `Command execution failed: ${id}`,
339
+ suggestion: "Inspect the command implementation and retry after resolving the error",
340
+ retryable: false,
341
+ details: {
342
+ cause: error instanceof Error
343
+ ? { message: error.message, name: error.name }
344
+ : { value: String(error) },
345
+ },
346
+ }))
347
+ .then(result => {
348
+ var _a;
349
+ var _b;
350
+ this.emitTelemetry("command.executed", {
351
+ id,
352
+ duration: ((_b = (_a = globalThis.performance) === null || _a === void 0 ? void 0 : _a.now()) !== null && _b !== void 0 ? _b : Date.now()) - start,
353
+ success: result.success,
354
+ });
355
+ // Run post-hooks after execution
356
+ this.runPostHooks(cmd, args, result);
357
+ return result;
358
+ });
359
+ }
360
+ /**
361
+ * Gets all available commands as Action objects for command palette integration.
362
+ *
363
+ * @param filter - Optional filter to restrict which commands are returned
364
+ * @returns Array of Action objects for visible, available commands
365
+ *
366
+ * @remarks
367
+ * - Filters out commands declared `hidden: true`
368
+ * - Filters out commands where `when` is false
369
+ * - Sets `disabled` based on `canExecute`
370
+ * - Compatible with ninja-keys and similar libraries
371
+ * - When filter.interface is specified, only returns commands exposed to that interface
372
+ * - Secure defaults: MCP and CLI are opt-in (false by default)
373
+ *
374
+ * @example
375
+ * ```typescript
376
+ * // Get all visible commands (for command palette)
377
+ * const allActions = CommandRegistry.getActions();
378
+ *
379
+ * // Get only commands exposed to MCP
380
+ * const mcpActions = CommandRegistry.getActions({ interface: 'mcp' });
381
+ *
382
+ * // Get only commands exposed to CLI
383
+ * const cliActions = CommandRegistry.getActions({ interface: 'cli' });
384
+ * ```
385
+ *
386
+ * @public
387
+ */
388
+ getActions(filter) {
389
+ return Array.from(this.commands.values())
390
+ .filter(cmd => {
391
+ // Hidden commands stay registered/executable but never surface as actions
392
+ if (cmd.hidden)
393
+ return false;
394
+ // Always filter by visibility
395
+ if (!readFlag(() => cmd.when))
396
+ return false;
397
+ // If interface filter is specified, check expose config
398
+ if (filter === null || filter === void 0 ? void 0 : filter.interface) {
399
+ const expose = Object.assign(Object.assign({}, DEFAULT_EXPOSE_CONFIG), cmd.expose);
400
+ return expose[filter.interface] === true;
401
+ }
402
+ return true;
403
+ })
404
+ .map(cmd => ({
405
+ id: cmd.id,
406
+ title: cmd.label,
407
+ icon: cmd.icon,
408
+ section: cmd.category,
409
+ hotkey: cmd.keybinding,
410
+ disabled: !readFlag(() => cmd.canExecute),
411
+ handler: () => {
412
+ this.execute(cmd.id);
413
+ },
414
+ }));
415
+ }
416
+ /**
417
+ * Subscribes to command registry changes.
418
+ *
419
+ * @param listener - Callback invoked when commands are added or removed
420
+ * @returns Unsubscribe function
421
+ *
422
+ * @example
423
+ * ```typescript
424
+ * const unsubscribe = CommandRegistry.onChange((added, removed) => {
425
+ * console.log('Added:', added, 'Removed:', removed);
426
+ * });
427
+ *
428
+ * // Later, to stop listening:
429
+ * unsubscribe();
430
+ * ```
431
+ *
432
+ * @public
433
+ */
434
+ onChange(listener) {
435
+ this.listeners.add(listener);
436
+ return () => this.listeners.delete(listener);
437
+ }
438
+ /**
439
+ * Clears all registered commands.
440
+ *
441
+ * @remarks
442
+ * For testing purposes only.
443
+ * Notifies listeners of all removed commands.
444
+ *
445
+ * @public
446
+ */
447
+ clear() {
448
+ const ids = Array.from(this.commands.keys());
449
+ this.commands.clear();
450
+ this.shadowed.clear();
451
+ if (ids.length > 0) {
452
+ this.notify([], ids);
453
+ }
454
+ }
455
+ /**
456
+ * Gets a scoped registry for micro-frontend isolation.
457
+ *
458
+ * @param _shadowRoot - The shadow root to scope to (unused in Phase 1)
459
+ * @returns The global registry (scoped registries are Phase 2)
460
+ *
461
+ * @remarks
462
+ * Phase 2 stub: Returns global registry with warning in DEV_MODE.
463
+ * Future implementation will support isolated command registries
464
+ * per shadow root for micro-frontend architectures.
465
+ *
466
+ * @public
467
+ */
468
+ forScope(_shadowRoot) {
469
+ if (isDevMode()) {
470
+ console.warn("[fast-af] Scoped registries are Phase 2. Returning global registry.");
471
+ }
472
+ return this;
473
+ }
474
+ /**
475
+ * Sets a telemetry handler for command events.
476
+ *
477
+ * @param handler - Function to handle telemetry events
478
+ *
479
+ * @remarks
480
+ * Events emitted:
481
+ * - "command.registered" - \{ id, label \}
482
+ * - "command.unregistered" - \{ id \}
483
+ * - "command.executed" - \{ id, duration, success \}
484
+ * - "command.rejected" - \{ id, origin, code \} (external invocation refused)
485
+ *
486
+ * @example
487
+ * ```typescript
488
+ * CommandRegistry.setTelemetryHandler((event, data) => {
489
+ * analytics.track(event, data);
490
+ * });
491
+ * ```
492
+ *
493
+ * @public
494
+ */
495
+ setTelemetryHandler(handler) {
496
+ this.telemetryHandler = handler;
497
+ }
498
+ /**
499
+ * Adds an interceptor to the command execution chain.
500
+ *
501
+ * @param interceptor - The interceptor to add
502
+ *
503
+ * @remarks
504
+ * - Interceptors run in priority order (lower priority runs first)
505
+ * - Default priority is 100
506
+ * - Multiple interceptors can have the same priority
507
+ * - Interceptors with the same name will not be duplicated
508
+ *
509
+ * @example
510
+ * ```typescript
511
+ * CommandRegistry.addInterceptor({
512
+ * name: 'logging',
513
+ * priority: 1,
514
+ * intercept: async (cmd, args, next) => {
515
+ * console.log('Executing:', cmd.id);
516
+ * const result = await next();
517
+ * console.log('Result:', result);
518
+ * return result;
519
+ * }
520
+ * });
521
+ * ```
522
+ *
523
+ * @public
524
+ */
525
+ addInterceptor(interceptor) {
526
+ var _a;
527
+ // Avoid duplicates by name
528
+ if (this.interceptors.some(i => i.name === interceptor.name)) {
529
+ if (isDevMode()) {
530
+ console.warn(`[fast-af] Interceptor "${interceptor.name}" already registered. Skipping.`);
531
+ }
532
+ return;
533
+ }
534
+ this.interceptors.push(interceptor);
535
+ this.interceptors.sort((a, b) => { var _a, _b; return ((_a = a.priority) !== null && _a !== void 0 ? _a : 100) - ((_b = b.priority) !== null && _b !== void 0 ? _b : 100); });
536
+ this.emitTelemetry("interceptor.added", {
537
+ name: interceptor.name,
538
+ priority: (_a = interceptor.priority) !== null && _a !== void 0 ? _a : 100,
539
+ });
540
+ }
541
+ /**
542
+ * Removes an interceptor by name.
543
+ *
544
+ * @param name - The name of the interceptor to remove
545
+ *
546
+ * @remarks
547
+ * - No-op if interceptor doesn't exist
548
+ * - Emits telemetry event on removal
549
+ *
550
+ * @example
551
+ * ```typescript
552
+ * CommandRegistry.removeInterceptor('confirm');
553
+ * ```
554
+ *
555
+ * @public
556
+ */
557
+ removeInterceptor(name) {
558
+ const before = this.interceptors.length;
559
+ this.interceptors = this.interceptors.filter(i => i.name !== name);
560
+ if (this.interceptors.length < before) {
561
+ this.emitTelemetry("interceptor.removed", { name });
562
+ }
563
+ }
564
+ /**
565
+ * Adds a post-hook to observe command execution results.
566
+ *
567
+ * @param hook - The post-hook to add
568
+ *
569
+ * @remarks
570
+ * - Post-hooks run after command execution completes
571
+ * - They cannot modify the result
572
+ * - Post-hooks with the same name will not be duplicated
573
+ *
574
+ * @example
575
+ * ```typescript
576
+ * CommandRegistry.addPostHook({
577
+ * name: 'undo',
578
+ * afterExecute: (cmd, args, result) => {
579
+ * if (result.success) {
580
+ * undoStack.push({ cmd, args, result });
581
+ * }
582
+ * }
583
+ * });
584
+ * ```
585
+ *
586
+ * @public
587
+ */
588
+ addPostHook(hook) {
589
+ // Avoid duplicates by name
590
+ if (this.postHooks.some(h => h.name === hook.name)) {
591
+ if (isDevMode()) {
592
+ console.warn(`[fast-af] PostHook "${hook.name}" already registered. Skipping.`);
593
+ }
594
+ return;
595
+ }
596
+ this.postHooks.push(hook);
597
+ this.emitTelemetry("posthook.added", { name: hook.name });
598
+ }
599
+ /**
600
+ * Removes a post-hook by name.
601
+ *
602
+ * @param name - The name of the post-hook to remove
603
+ *
604
+ * @remarks
605
+ * - No-op if post-hook doesn't exist
606
+ * - Emits telemetry event on removal
607
+ *
608
+ * @example
609
+ * ```typescript
610
+ * CommandRegistry.removePostHook('undo');
611
+ * ```
612
+ *
613
+ * @public
614
+ */
615
+ removePostHook(name) {
616
+ const before = this.postHooks.length;
617
+ this.postHooks = this.postHooks.filter(h => h.name !== name);
618
+ if (this.postHooks.length < before) {
619
+ this.emitTelemetry("posthook.removed", { name });
620
+ }
621
+ }
622
+ /**
623
+ * Notifies all listeners of command changes.
624
+ * @internal
625
+ */
626
+ notify(added, removed) {
627
+ this.listeners.forEach(listener => {
628
+ try {
629
+ listener(added, removed);
630
+ }
631
+ catch (error) {
632
+ if (isDevMode()) {
633
+ console.error("[fast-af] Command listener threw an error:", error);
634
+ }
635
+ }
636
+ });
637
+ }
638
+ /**
639
+ * Emits a telemetry event if handler is set.
640
+ * @internal
641
+ */
642
+ emitTelemetry(event, data) {
643
+ var _a;
644
+ try {
645
+ (_a = this.telemetryHandler) === null || _a === void 0 ? void 0 : _a.call(this, event, data);
646
+ }
647
+ catch (error) {
648
+ if (isDevMode()) {
649
+ console.error("[fast-af] Telemetry handler threw an error:", error);
650
+ }
651
+ }
652
+ }
653
+ /**
654
+ * Emits telemetry for an externally-originated invocation that was refused
655
+ * before reaching the command, and passes the failure through.
656
+ * @internal
657
+ */
658
+ reject(id, origin, result) {
659
+ this.emitTelemetry("command.rejected", {
660
+ id,
661
+ origin,
662
+ code: result.success ? void 0 : result.error.code,
663
+ });
664
+ return result;
665
+ }
666
+ normalizeResult(id, result) {
667
+ if (result &&
668
+ typeof result === "object" &&
669
+ result.success === true &&
670
+ "data" in result) {
671
+ return result;
672
+ }
673
+ if (result &&
674
+ typeof result === "object" &&
675
+ result.success === false &&
676
+ "error" in result) {
677
+ return result;
678
+ }
679
+ return failure({
680
+ code: ErrorCodes.INTERNAL_ERROR,
681
+ message: `Command returned an invalid result: ${id}`,
682
+ suggestion: "Return success(data) or failure(error) from the command",
683
+ retryable: false,
684
+ });
685
+ }
686
+ /**
687
+ * Builds the interceptor chain for command execution.
688
+ * @internal
689
+ */
690
+ buildInterceptorChain(command, args, handler) {
691
+ // Build chain from back to front
692
+ let chain = handler;
693
+ for (let i = this.interceptors.length - 1; i >= 0; i--) {
694
+ const interceptor = this.interceptors[i];
695
+ const next = chain;
696
+ chain = () => interceptor.intercept(command, args, next);
697
+ }
698
+ return chain;
699
+ }
700
+ /**
701
+ * Runs all post-hooks after command execution.
702
+ * @internal
703
+ */
704
+ runPostHooks(command, args, result) {
705
+ for (const hook of this.postHooks) {
706
+ try {
707
+ hook.afterExecute(command, args, result);
708
+ }
709
+ catch (error) {
710
+ if (isDevMode()) {
711
+ console.error(`[fast-af] PostHook "${hook.name}" threw an error:`, error);
712
+ }
713
+ }
714
+ }
715
+ }
716
+ }
717
+ /**
718
+ * Global CommandRegistry singleton.
719
+ *
720
+ * @remarks
721
+ * The CommandRegistry is the central hub for command management.
722
+ * Commands are registered automatically when FASTElement components
723
+ * with \@command decorators connect to the DOM.
724
+ *
725
+ * Key features:
726
+ * - SSR-safe with lazy initialization
727
+ * - Reactive: subscribe to changes via onChange()
728
+ * - Command palette integration via getActions()
729
+ * - Telemetry hooks for analytics
730
+ *
731
+ * @example
732
+ * ```typescript
733
+ * import { CommandRegistry, success, failure } from '\@lushly-dev/fast-element/command.js';
734
+ *
735
+ * // Manual command registration (usually done automatically)
736
+ * CommandRegistry.register('custom.action', \{
737
+ * id: 'custom.action',
738
+ * label: 'Custom Action',
739
+ * execute: () => success(\{ done: true \}),
740
+ * canExecute: true,
741
+ * when: true,
742
+ * source: new WeakRef(document.body),
743
+ * \});
744
+ *
745
+ * // Execute command
746
+ * const result = await CommandRegistry.execute('custom.action');
747
+ *
748
+ * // Command palette integration
749
+ * const actions = CommandRegistry.getActions();
750
+ * ninjaKeys.data = actions;
751
+ * ```
752
+ *
753
+ * @public
754
+ */
755
+ export const CommandRegistry = new CommandRegistryImpl();