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

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 +6 -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 +15 -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 +110 -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 +312 -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 +752 -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 +511 -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 +839 -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 +210 -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 +121 -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 +1440 -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 +63 -0
  192. package/dist/esm/observation/arrays.js +661 -0
  193. package/dist/esm/observation/notifier.js +164 -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 +7618 -0
  243. package/dist/fast-element.debug.min.js +3 -0
  244. package/dist/fast-element.js +7617 -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 +12 -12
@@ -0,0 +1,752 @@
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, _c;
164
+ if (expected !== void 0 && this.commands.get(id) !== expected) {
165
+ // A stale removal of a shadowed registration.
166
+ const stack = this.shadowed.get(id);
167
+ const index = (_a = stack === null || stack === void 0 ? void 0 : stack.indexOf(expected)) !== null && _a !== void 0 ? _a : -1;
168
+ if (stack !== void 0 && index !== -1) {
169
+ stack.splice(index, 1);
170
+ if (stack.length === 0)
171
+ this.shadowed.delete(id);
172
+ }
173
+ return;
174
+ }
175
+ const fallback = expected !== void 0 ? (_b = this.shadowed.get(id)) === null || _b === void 0 ? void 0 : _b.pop() : void 0;
176
+ if (fallback !== void 0) {
177
+ if (((_c = this.shadowed.get(id)) === null || _c === void 0 ? void 0 : _c.length) === 0)
178
+ this.shadowed.delete(id);
179
+ this.commands.set(id, fallback);
180
+ this.notify([id], []);
181
+ return;
182
+ }
183
+ this.shadowed.delete(id);
184
+ if (this.commands.delete(id)) {
185
+ this.notify([], [id]);
186
+ this.emitTelemetry("command.unregistered", { id });
187
+ }
188
+ }
189
+ /**
190
+ * Retrieves a command entry by ID.
191
+ *
192
+ * @param id - The command identifier
193
+ * @returns The command entry, or undefined if not found
194
+ *
195
+ * @public
196
+ */
197
+ get(id) {
198
+ return this.commands.get(id);
199
+ }
200
+ /**
201
+ * Executes a command by ID on behalf of the application's own code.
202
+ *
203
+ * @param id - The command identifier
204
+ * @param args - Arguments to pass to the command
205
+ * @returns A Promise resolving to the CommandResult
206
+ *
207
+ * @remarks
208
+ * This is the trusted, programmatic path: `expose`, `parameters` and the
209
+ * confirmation requirement are not enforced here (use `invoke()` for
210
+ * invocations that originate from an agent, MCP server, CLI or event).
211
+ *
212
+ * Never throws. Returns a failure result if:
213
+ * - Command not found (COMMAND_NOT_FOUND)
214
+ * - Command is disabled (COMMAND_DISABLED)
215
+ * - A `canExecute` getter throws (EXECUTION_FAILED)
216
+ *
217
+ * Interceptors are run in priority order before the command handler.
218
+ *
219
+ * @public
220
+ */
221
+ execute(id, ...args) {
222
+ return this.run(id, args);
223
+ }
224
+ /**
225
+ * Invokes a command on behalf of an external caller (agent, MCP, CLI, palette
226
+ * or the `cmd-exec` event bridge).
227
+ *
228
+ * @param id - The command identifier
229
+ * @param args - Arguments to pass to the command
230
+ * @param options - Describes the invocation origin
231
+ * @returns A Promise resolving to the CommandResult; never rejects
232
+ *
233
+ * @remarks
234
+ * Applies the safety properties that `execute()` leaves to the caller:
235
+ * - `expose`: the command must be exposed to `options.origin`
236
+ * (COMMAND_NOT_EXPOSED); same secure defaults as `getActions()`
237
+ * - `parameters`: when the command declares a JSON Schema it is applied to
238
+ * `args[0]` (INVALID_PARAMETERS); see the `parameters` option docs
239
+ * - `destructive`: fails closed (CONFIRMATION_REQUIRED) unless a confirmation
240
+ * interceptor (named `confirm`, or flagged `confirms: true`) is installed
241
+ *
242
+ * @public
243
+ */
244
+ invoke(id, args, options) {
245
+ return this.run(id, Array.isArray(args) ? args : [], options);
246
+ }
247
+ /**
248
+ * Shared execution path for `execute()` and `invoke()`.
249
+ * @internal
250
+ */
251
+ run(id, args, options) {
252
+ var _a, _b;
253
+ const cmd = this.commands.get(id);
254
+ if (!cmd) {
255
+ return Promise.resolve(failure({
256
+ code: ErrorCodes.COMMAND_NOT_FOUND,
257
+ message: `Command not found: ${id}`,
258
+ suggestion: "Check command ID or ensure component is connected",
259
+ retryable: false,
260
+ }));
261
+ }
262
+ const origin = options === null || options === void 0 ? void 0 : options.origin;
263
+ if (origin !== void 0) {
264
+ const expose = Object.assign(Object.assign({}, DEFAULT_EXPOSE_CONFIG), cmd.expose);
265
+ if (expose[origin] !== true) {
266
+ return Promise.resolve(this.reject(id, origin, failure({
267
+ code: ErrorCodes.COMMAND_NOT_EXPOSED,
268
+ message: `Command not exposed to ${String(origin)}: ${id}`,
269
+ suggestion: "Enable the interface in the command's expose configuration",
270
+ retryable: false,
271
+ })));
272
+ }
273
+ }
274
+ let canExecute;
275
+ try {
276
+ canExecute = cmd.canExecute;
277
+ }
278
+ catch (error) {
279
+ return Promise.resolve(failure({
280
+ code: ErrorCodes.EXECUTION_FAILED,
281
+ message: `Command availability check failed: ${id}`,
282
+ suggestion: "Inspect the command's canExecute implementation",
283
+ retryable: false,
284
+ details: {
285
+ cause: error instanceof Error
286
+ ? { message: error.message, name: error.name }
287
+ : { value: String(error) },
288
+ },
289
+ }));
290
+ }
291
+ if (!canExecute) {
292
+ return Promise.resolve(failure({
293
+ code: ErrorCodes.COMMAND_DISABLED,
294
+ message: `Command disabled: ${id}`,
295
+ suggestion: "Wait for the command to become available",
296
+ retryable: true,
297
+ }));
298
+ }
299
+ if (origin !== void 0) {
300
+ if (cmd.parameters) {
301
+ const issues = validateCommandArguments(args, cmd.parameters);
302
+ if (issues.length > 0) {
303
+ return Promise.resolve(this.reject(id, origin, failure({
304
+ code: ErrorCodes.INVALID_PARAMETERS,
305
+ message: `Invalid parameters for ${id}: ${issues
306
+ .map(i => `${i.path} ${i.message}`)
307
+ .join("; ")}`,
308
+ suggestion: "Provide arguments matching the command's parameters schema",
309
+ retryable: false,
310
+ details: { issues },
311
+ })));
312
+ }
313
+ }
314
+ if (cmd.destructive &&
315
+ !this.interceptors.some(i => i.confirms === true || i.name === "confirm")) {
316
+ return Promise.resolve(this.reject(id, origin, failure({
317
+ code: ErrorCodes.CONFIRMATION_REQUIRED,
318
+ message: `Command requires confirmation: ${id}`,
319
+ suggestion: "Install a confirmation interceptor (for example ConfirmBehavior) before invoking destructive commands externally",
320
+ retryable: false,
321
+ })));
322
+ }
323
+ }
324
+ const start = (_b = (_a = globalThis.performance) === null || _a === void 0 ? void 0 : _a.now()) !== null && _b !== void 0 ? _b : Date.now();
325
+ // Build the handler that executes the command
326
+ const executeHandler = () => {
327
+ return Promise.resolve(cmd.execute(...args));
328
+ };
329
+ // Build the interceptor chain and execute
330
+ const chain = this.buildInterceptorChain(cmd, args, executeHandler);
331
+ return Promise.resolve()
332
+ .then(chain)
333
+ .then(result => this.normalizeResult(id, result))
334
+ .catch(error => failure({
335
+ code: ErrorCodes.EXECUTION_FAILED,
336
+ message: `Command execution failed: ${id}`,
337
+ suggestion: "Inspect the command implementation and retry after resolving the error",
338
+ retryable: false,
339
+ details: {
340
+ cause: error instanceof Error
341
+ ? { message: error.message, name: error.name }
342
+ : { value: String(error) },
343
+ },
344
+ }))
345
+ .then(result => {
346
+ var _a, _b;
347
+ this.emitTelemetry("command.executed", {
348
+ id,
349
+ duration: ((_b = (_a = globalThis.performance) === null || _a === void 0 ? void 0 : _a.now()) !== null && _b !== void 0 ? _b : Date.now()) - start,
350
+ success: result.success,
351
+ });
352
+ // Run post-hooks after execution
353
+ this.runPostHooks(cmd, args, result);
354
+ return result;
355
+ });
356
+ }
357
+ /**
358
+ * Gets all available commands as Action objects for command palette integration.
359
+ *
360
+ * @param filter - Optional filter to restrict which commands are returned
361
+ * @returns Array of Action objects for visible, available commands
362
+ *
363
+ * @remarks
364
+ * - Filters out commands declared `hidden: true`
365
+ * - Filters out commands where `when` is false
366
+ * - Sets `disabled` based on `canExecute`
367
+ * - Compatible with ninja-keys and similar libraries
368
+ * - When filter.interface is specified, only returns commands exposed to that interface
369
+ * - Secure defaults: MCP and CLI are opt-in (false by default)
370
+ *
371
+ * @example
372
+ * ```typescript
373
+ * // Get all visible commands (for command palette)
374
+ * const allActions = CommandRegistry.getActions();
375
+ *
376
+ * // Get only commands exposed to MCP
377
+ * const mcpActions = CommandRegistry.getActions({ interface: 'mcp' });
378
+ *
379
+ * // Get only commands exposed to CLI
380
+ * const cliActions = CommandRegistry.getActions({ interface: 'cli' });
381
+ * ```
382
+ *
383
+ * @public
384
+ */
385
+ getActions(filter) {
386
+ return Array.from(this.commands.values())
387
+ .filter(cmd => {
388
+ // Hidden commands stay registered/executable but never surface as actions
389
+ if (cmd.hidden)
390
+ return false;
391
+ // Always filter by visibility
392
+ if (!readFlag(() => cmd.when))
393
+ return false;
394
+ // If interface filter is specified, check expose config
395
+ if (filter === null || filter === void 0 ? void 0 : filter.interface) {
396
+ const expose = Object.assign(Object.assign({}, DEFAULT_EXPOSE_CONFIG), cmd.expose);
397
+ return expose[filter.interface] === true;
398
+ }
399
+ return true;
400
+ })
401
+ .map(cmd => ({
402
+ id: cmd.id,
403
+ title: cmd.label,
404
+ icon: cmd.icon,
405
+ section: cmd.category,
406
+ hotkey: cmd.keybinding,
407
+ disabled: !readFlag(() => cmd.canExecute),
408
+ handler: () => {
409
+ this.execute(cmd.id);
410
+ },
411
+ }));
412
+ }
413
+ /**
414
+ * Subscribes to command registry changes.
415
+ *
416
+ * @param listener - Callback invoked when commands are added or removed
417
+ * @returns Unsubscribe function
418
+ *
419
+ * @example
420
+ * ```typescript
421
+ * const unsubscribe = CommandRegistry.onChange((added, removed) => {
422
+ * console.log('Added:', added, 'Removed:', removed);
423
+ * });
424
+ *
425
+ * // Later, to stop listening:
426
+ * unsubscribe();
427
+ * ```
428
+ *
429
+ * @public
430
+ */
431
+ onChange(listener) {
432
+ this.listeners.add(listener);
433
+ return () => this.listeners.delete(listener);
434
+ }
435
+ /**
436
+ * Clears all registered commands.
437
+ *
438
+ * @remarks
439
+ * For testing purposes only.
440
+ * Notifies listeners of all removed commands.
441
+ *
442
+ * @public
443
+ */
444
+ clear() {
445
+ const ids = Array.from(this.commands.keys());
446
+ this.commands.clear();
447
+ this.shadowed.clear();
448
+ if (ids.length > 0) {
449
+ this.notify([], ids);
450
+ }
451
+ }
452
+ /**
453
+ * Gets a scoped registry for micro-frontend isolation.
454
+ *
455
+ * @param _shadowRoot - The shadow root to scope to (unused in Phase 1)
456
+ * @returns The global registry (scoped registries are Phase 2)
457
+ *
458
+ * @remarks
459
+ * Phase 2 stub: Returns global registry with warning in DEV_MODE.
460
+ * Future implementation will support isolated command registries
461
+ * per shadow root for micro-frontend architectures.
462
+ *
463
+ * @public
464
+ */
465
+ forScope(_shadowRoot) {
466
+ if (isDevMode()) {
467
+ console.warn("[fast-af] Scoped registries are Phase 2. Returning global registry.");
468
+ }
469
+ return this;
470
+ }
471
+ /**
472
+ * Sets a telemetry handler for command events.
473
+ *
474
+ * @param handler - Function to handle telemetry events
475
+ *
476
+ * @remarks
477
+ * Events emitted:
478
+ * - "command.registered" - \{ id, label \}
479
+ * - "command.unregistered" - \{ id \}
480
+ * - "command.executed" - \{ id, duration, success \}
481
+ * - "command.rejected" - \{ id, origin, code \} (external invocation refused)
482
+ *
483
+ * @example
484
+ * ```typescript
485
+ * CommandRegistry.setTelemetryHandler((event, data) => {
486
+ * analytics.track(event, data);
487
+ * });
488
+ * ```
489
+ *
490
+ * @public
491
+ */
492
+ setTelemetryHandler(handler) {
493
+ this.telemetryHandler = handler;
494
+ }
495
+ /**
496
+ * Adds an interceptor to the command execution chain.
497
+ *
498
+ * @param interceptor - The interceptor to add
499
+ *
500
+ * @remarks
501
+ * - Interceptors run in priority order (lower priority runs first)
502
+ * - Default priority is 100
503
+ * - Multiple interceptors can have the same priority
504
+ * - Interceptors with the same name will not be duplicated
505
+ *
506
+ * @example
507
+ * ```typescript
508
+ * CommandRegistry.addInterceptor({
509
+ * name: 'logging',
510
+ * priority: 1,
511
+ * intercept: async (cmd, args, next) => {
512
+ * console.log('Executing:', cmd.id);
513
+ * const result = await next();
514
+ * console.log('Result:', result);
515
+ * return result;
516
+ * }
517
+ * });
518
+ * ```
519
+ *
520
+ * @public
521
+ */
522
+ addInterceptor(interceptor) {
523
+ var _a;
524
+ // Avoid duplicates by name
525
+ if (this.interceptors.some(i => i.name === interceptor.name)) {
526
+ if (isDevMode()) {
527
+ console.warn(`[fast-af] Interceptor "${interceptor.name}" already registered. Skipping.`);
528
+ }
529
+ return;
530
+ }
531
+ this.interceptors.push(interceptor);
532
+ 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); });
533
+ this.emitTelemetry("interceptor.added", {
534
+ name: interceptor.name,
535
+ priority: (_a = interceptor.priority) !== null && _a !== void 0 ? _a : 100,
536
+ });
537
+ }
538
+ /**
539
+ * Removes an interceptor by name.
540
+ *
541
+ * @param name - The name of the interceptor to remove
542
+ *
543
+ * @remarks
544
+ * - No-op if interceptor doesn't exist
545
+ * - Emits telemetry event on removal
546
+ *
547
+ * @example
548
+ * ```typescript
549
+ * CommandRegistry.removeInterceptor('confirm');
550
+ * ```
551
+ *
552
+ * @public
553
+ */
554
+ removeInterceptor(name) {
555
+ const before = this.interceptors.length;
556
+ this.interceptors = this.interceptors.filter(i => i.name !== name);
557
+ if (this.interceptors.length < before) {
558
+ this.emitTelemetry("interceptor.removed", { name });
559
+ }
560
+ }
561
+ /**
562
+ * Adds a post-hook to observe command execution results.
563
+ *
564
+ * @param hook - The post-hook to add
565
+ *
566
+ * @remarks
567
+ * - Post-hooks run after command execution completes
568
+ * - They cannot modify the result
569
+ * - Post-hooks with the same name will not be duplicated
570
+ *
571
+ * @example
572
+ * ```typescript
573
+ * CommandRegistry.addPostHook({
574
+ * name: 'undo',
575
+ * afterExecute: (cmd, args, result) => {
576
+ * if (result.success) {
577
+ * undoStack.push({ cmd, args, result });
578
+ * }
579
+ * }
580
+ * });
581
+ * ```
582
+ *
583
+ * @public
584
+ */
585
+ addPostHook(hook) {
586
+ // Avoid duplicates by name
587
+ if (this.postHooks.some(h => h.name === hook.name)) {
588
+ if (isDevMode()) {
589
+ console.warn(`[fast-af] PostHook "${hook.name}" already registered. Skipping.`);
590
+ }
591
+ return;
592
+ }
593
+ this.postHooks.push(hook);
594
+ this.emitTelemetry("posthook.added", { name: hook.name });
595
+ }
596
+ /**
597
+ * Removes a post-hook by name.
598
+ *
599
+ * @param name - The name of the post-hook to remove
600
+ *
601
+ * @remarks
602
+ * - No-op if post-hook doesn't exist
603
+ * - Emits telemetry event on removal
604
+ *
605
+ * @example
606
+ * ```typescript
607
+ * CommandRegistry.removePostHook('undo');
608
+ * ```
609
+ *
610
+ * @public
611
+ */
612
+ removePostHook(name) {
613
+ const before = this.postHooks.length;
614
+ this.postHooks = this.postHooks.filter(h => h.name !== name);
615
+ if (this.postHooks.length < before) {
616
+ this.emitTelemetry("posthook.removed", { name });
617
+ }
618
+ }
619
+ /**
620
+ * Notifies all listeners of command changes.
621
+ * @internal
622
+ */
623
+ notify(added, removed) {
624
+ this.listeners.forEach(listener => {
625
+ try {
626
+ listener(added, removed);
627
+ }
628
+ catch (error) {
629
+ if (isDevMode()) {
630
+ console.error("[fast-af] Command listener threw an error:", error);
631
+ }
632
+ }
633
+ });
634
+ }
635
+ /**
636
+ * Emits a telemetry event if handler is set.
637
+ * @internal
638
+ */
639
+ emitTelemetry(event, data) {
640
+ var _a;
641
+ try {
642
+ (_a = this.telemetryHandler) === null || _a === void 0 ? void 0 : _a.call(this, event, data);
643
+ }
644
+ catch (error) {
645
+ if (isDevMode()) {
646
+ console.error("[fast-af] Telemetry handler threw an error:", error);
647
+ }
648
+ }
649
+ }
650
+ /**
651
+ * Emits telemetry for an externally-originated invocation that was refused
652
+ * before reaching the command, and passes the failure through.
653
+ * @internal
654
+ */
655
+ reject(id, origin, result) {
656
+ this.emitTelemetry("command.rejected", {
657
+ id,
658
+ origin,
659
+ code: result.success ? void 0 : result.error.code,
660
+ });
661
+ return result;
662
+ }
663
+ normalizeResult(id, result) {
664
+ if (result &&
665
+ typeof result === "object" &&
666
+ result.success === true &&
667
+ "data" in result) {
668
+ return result;
669
+ }
670
+ if (result &&
671
+ typeof result === "object" &&
672
+ result.success === false &&
673
+ "error" in result) {
674
+ return result;
675
+ }
676
+ return failure({
677
+ code: ErrorCodes.INTERNAL_ERROR,
678
+ message: `Command returned an invalid result: ${id}`,
679
+ suggestion: "Return success(data) or failure(error) from the command",
680
+ retryable: false,
681
+ });
682
+ }
683
+ /**
684
+ * Builds the interceptor chain for command execution.
685
+ * @internal
686
+ */
687
+ buildInterceptorChain(command, args, handler) {
688
+ // Build chain from back to front
689
+ let chain = handler;
690
+ for (let i = this.interceptors.length - 1; i >= 0; i--) {
691
+ const interceptor = this.interceptors[i];
692
+ const next = chain;
693
+ chain = () => interceptor.intercept(command, args, next);
694
+ }
695
+ return chain;
696
+ }
697
+ /**
698
+ * Runs all post-hooks after command execution.
699
+ * @internal
700
+ */
701
+ runPostHooks(command, args, result) {
702
+ for (const hook of this.postHooks) {
703
+ try {
704
+ hook.afterExecute(command, args, result);
705
+ }
706
+ catch (error) {
707
+ if (isDevMode()) {
708
+ console.error(`[fast-af] PostHook "${hook.name}" threw an error:`, error);
709
+ }
710
+ }
711
+ }
712
+ }
713
+ }
714
+ /**
715
+ * Global CommandRegistry singleton.
716
+ *
717
+ * @remarks
718
+ * The CommandRegistry is the central hub for command management.
719
+ * Commands are registered automatically when FASTElement components
720
+ * with \@command decorators connect to the DOM.
721
+ *
722
+ * Key features:
723
+ * - SSR-safe with lazy initialization
724
+ * - Reactive: subscribe to changes via onChange()
725
+ * - Command palette integration via getActions()
726
+ * - Telemetry hooks for analytics
727
+ *
728
+ * @example
729
+ * ```typescript
730
+ * import { CommandRegistry, success, failure } from '\@lushly-dev/fast-element/command.js';
731
+ *
732
+ * // Manual command registration (usually done automatically)
733
+ * CommandRegistry.register('custom.action', \{
734
+ * id: 'custom.action',
735
+ * label: 'Custom Action',
736
+ * execute: () => success(\{ done: true \}),
737
+ * canExecute: true,
738
+ * when: true,
739
+ * source: new WeakRef(document.body),
740
+ * \});
741
+ *
742
+ * // Execute command
743
+ * const result = await CommandRegistry.execute('custom.action');
744
+ *
745
+ * // Command palette integration
746
+ * const actions = CommandRegistry.getActions();
747
+ * ninjaKeys.data = actions;
748
+ * ```
749
+ *
750
+ * @public
751
+ */
752
+ export const CommandRegistry = new CommandRegistryImpl();