ultravisor-beacon-capability 1.0.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 (38) hide show
  1. package/README.md +106 -0
  2. package/docs/.nojekyll +0 -0
  3. package/docs/README.md +103 -0
  4. package/docs/_brand.json +18 -0
  5. package/docs/_cover.md +13 -0
  6. package/docs/_sidebar.md +31 -0
  7. package/docs/_topbar.md +5 -0
  8. package/docs/_version.json +7 -0
  9. package/docs/api/README.md +44 -0
  10. package/docs/api/action-convention.md +148 -0
  11. package/docs/api/add-action.md +68 -0
  12. package/docs/api/beacon-capability.md +89 -0
  13. package/docs/api/build-action-map.md +88 -0
  14. package/docs/api/connect.md +81 -0
  15. package/docs/api/disconnect.md +50 -0
  16. package/docs/api/is-connected.md +33 -0
  17. package/docs/api/lifecycle-hooks.md +115 -0
  18. package/docs/architecture.md +237 -0
  19. package/docs/css/docuserve.css +327 -0
  20. package/docs/examples/README.md +58 -0
  21. package/docs/examples/certificate-expiry-monitor.md +212 -0
  22. package/docs/examples/docker-container-management.md +265 -0
  23. package/docs/examples/log-archive-and-upload.md +214 -0
  24. package/docs/examples/log-file-cleanup.md +199 -0
  25. package/docs/examples/mysql-maintenance.md +253 -0
  26. package/docs/examples/postgresql-aggregation.md +247 -0
  27. package/docs/examples/rest-api-health-check.md +213 -0
  28. package/docs/examples/rest-endpoint-sync.md +240 -0
  29. package/docs/examples/server-metrics-collection.md +199 -0
  30. package/docs/examples/shell-commands.md +176 -0
  31. package/docs/index.html +39 -0
  32. package/docs/quickstart.md +199 -0
  33. package/docs/retold-catalog.json +85 -0
  34. package/docs/retold-keyword-index.json +10642 -0
  35. package/package.json +33 -0
  36. package/source/Ultravisor-Beacon-Capability-ActionMap.cjs +132 -0
  37. package/source/Ultravisor-Beacon-Capability.cjs +276 -0
  38. package/test/Ultravisor-Beacon-Capability_tests.js +744 -0
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "ultravisor-beacon-capability",
3
+ "version": "1.0.0",
4
+ "description": "Convention-based base class for building Ultravisor beacon capabilities with minimal boilerplate",
5
+ "main": "source/Ultravisor-Beacon-Capability.cjs",
6
+ "scripts": {
7
+ "test": "npx mocha -u tdd --exit --timeout 10000 ./test/*_tests*",
8
+ "coverage": "npx nyc --reporter=lcov --reporter=text-summary npx mocha -- -u tdd --exit --timeout 10000 ./test/*_tests*"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/stevenvelozo/ultravisor-beacon-capability.git"
13
+ },
14
+ "keywords": [
15
+ "ultravisor",
16
+ "beacon",
17
+ "capability",
18
+ "task",
19
+ "automation",
20
+ "fable"
21
+ ],
22
+ "author": "Steven Velozo <steven@velozo.com>",
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "fable-serviceproviderbase": "^3.0.19",
26
+ "ultravisor-beacon": "^0.0.15"
27
+ },
28
+ "devDependencies": {
29
+ "fable": "^3.1.72",
30
+ "pict-docuserve": "^1.2.0",
31
+ "quackage": "^1.2.3"
32
+ }
33
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Ultravisor Beacon Capability Action Map
3
+ *
4
+ * Discovers action methods on a UltravisorBeaconCapability instance
5
+ * by walking its prototype chain and matching the `action` prefix
6
+ * convention.
7
+ *
8
+ * An action named "DoSomething" is defined by:
9
+ * - actionDoSomething(pSettings, pWorkItem, fCallback, fReportProgress)
10
+ * - actionDoSomething_Schema (getter/property: SettingsSchema array)
11
+ * - actionDoSomething_Description (getter/property: description string)
12
+ *
13
+ * Returns a map of ActionName -> { Description, SettingsSchema, Handler }
14
+ * where Handler matches the ultravisor-beacon registerCapability() shape:
15
+ * Handler(pWorkItem, pContext, fCallback, fReportProgress)
16
+ */
17
+
18
+ const ACTION_PREFIX = 'action';
19
+ const ACTION_PREFIX_LENGTH = ACTION_PREFIX.length;
20
+
21
+ /**
22
+ * Build an action map from an instance's prototype chain.
23
+ *
24
+ * @param {object} pInstance - The capability instance to inspect
25
+ * @returns {object} Map of ActionName -> { Description, SettingsSchema, Handler }
26
+ */
27
+ function buildActionMap(pInstance)
28
+ {
29
+ let tmpActionMap = {};
30
+ let tmpVisited = new Set();
31
+
32
+ // Walk the prototype chain, stopping before Object.prototype
33
+ let tmpProto = Object.getPrototypeOf(pInstance);
34
+ while (tmpProto && tmpProto !== Object.prototype)
35
+ {
36
+ let tmpPropertyNames = Object.getOwnPropertyNames(tmpProto);
37
+
38
+ for (let i = 0; i < tmpPropertyNames.length; i++)
39
+ {
40
+ let tmpName = tmpPropertyNames[i];
41
+
42
+ // Skip already visited (subclass overrides win)
43
+ if (tmpVisited.has(tmpName))
44
+ {
45
+ continue;
46
+ }
47
+ tmpVisited.add(tmpName);
48
+
49
+ // Must start with 'action' and be longer than just the prefix
50
+ if (tmpName.length <= ACTION_PREFIX_LENGTH)
51
+ {
52
+ continue;
53
+ }
54
+ if (tmpName.substring(0, ACTION_PREFIX_LENGTH) !== ACTION_PREFIX)
55
+ {
56
+ continue;
57
+ }
58
+
59
+ // Skip companion suffixes
60
+ if (tmpName.endsWith('_Schema') || tmpName.endsWith('_Description'))
61
+ {
62
+ continue;
63
+ }
64
+
65
+ // Must be a function (not a getter or plain property)
66
+ let tmpDescriptor = Object.getOwnPropertyDescriptor(tmpProto, tmpName);
67
+ if (!tmpDescriptor || typeof tmpDescriptor.value !== 'function')
68
+ {
69
+ continue;
70
+ }
71
+
72
+ // Extract the action name by stripping the prefix
73
+ let tmpActionName = tmpName.substring(ACTION_PREFIX_LENGTH);
74
+
75
+ // Look up companion schema
76
+ let tmpSchema = resolveCompanion(pInstance, `${tmpName}_Schema`, []);
77
+
78
+ // Look up companion description
79
+ let tmpDescription = resolveCompanion(pInstance, `${tmpName}_Description`, '');
80
+
81
+ // Create bound handler that wraps the call with pre-extracted Settings
82
+ let tmpBoundMethod = pInstance[tmpName].bind(pInstance);
83
+ let tmpHandler = function (pWorkItem, pContext, fCallback, fReportProgress)
84
+ {
85
+ let tmpSettings = (pWorkItem && pWorkItem.Settings) ? pWorkItem.Settings : {};
86
+ return tmpBoundMethod(tmpSettings, pWorkItem, fCallback, fReportProgress);
87
+ };
88
+
89
+ tmpActionMap[tmpActionName] = {
90
+ Description: tmpDescription,
91
+ SettingsSchema: tmpSchema,
92
+ Handler: tmpHandler
93
+ };
94
+ }
95
+
96
+ tmpProto = Object.getPrototypeOf(tmpProto);
97
+ }
98
+
99
+ return tmpActionMap;
100
+ }
101
+
102
+ /**
103
+ * Resolve a companion property (schema or description) from an instance.
104
+ * Supports getters, plain values, and methods.
105
+ *
106
+ * @param {object} pInstance - The instance to read from
107
+ * @param {string} pKey - The property name to look up
108
+ * @param {*} pDefault - Default value if not found
109
+ * @returns {*} The resolved value
110
+ */
111
+ function resolveCompanion(pInstance, pKey, pDefault)
112
+ {
113
+ try
114
+ {
115
+ let tmpValue = pInstance[pKey];
116
+ if (typeof tmpValue === 'undefined')
117
+ {
118
+ return pDefault;
119
+ }
120
+ if (typeof tmpValue === 'function')
121
+ {
122
+ return tmpValue.call(pInstance);
123
+ }
124
+ return tmpValue;
125
+ }
126
+ catch (pError)
127
+ {
128
+ return pDefault;
129
+ }
130
+ }
131
+
132
+ module.exports = { buildActionMap };
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Ultravisor Beacon Capability
3
+ *
4
+ * Convention-based base class for building Ultravisor beacon
5
+ * capabilities with minimal boilerplate. Extend this class,
6
+ * define action methods with the `action` prefix, and call
7
+ * connect() to register with an Ultravisor server.
8
+ *
9
+ * Action Convention:
10
+ * actionDoSomething(pSettings, pWorkItem, fCallback, fReportProgress)
11
+ * get actionDoSomething_Schema() // optional: SettingsSchema array
12
+ * get actionDoSomething_Description() // optional: description string
13
+ *
14
+ * Lifecycle Hooks (override as needed):
15
+ * onInitialize(fCallback) // called after beacon connects
16
+ * onShutdown(fCallback) // called when beacon disconnects
17
+ *
18
+ * Usage:
19
+ * class MyCapability extends UltravisorBeaconCapability
20
+ * {
21
+ * constructor(pFable, pOptions, pServiceHash)
22
+ * {
23
+ * super(pFable, pOptions, pServiceHash);
24
+ * this.serviceType = 'MyCapability';
25
+ * this.capabilityName = 'MyCapability';
26
+ * }
27
+ *
28
+ * actionDoWork(pSettings, pWorkItem, fCallback)
29
+ * {
30
+ * return fCallback(null, { Outputs: { Result: 'done' } });
31
+ * }
32
+ * }
33
+ *
34
+ * let tmpCap = tmpFable.instantiateServiceProvider('MyCapability');
35
+ * tmpCap.connect({ ServerURL: 'http://ultravisor:54321' }, (pErr) => {});
36
+ */
37
+
38
+ const libFableServiceProviderBase = require('fable-serviceproviderbase');
39
+ const libBeaconService = require('ultravisor-beacon');
40
+ const libActionMap = require('./Ultravisor-Beacon-Capability-ActionMap.cjs');
41
+
42
+ class UltravisorBeaconCapability extends libFableServiceProviderBase
43
+ {
44
+ constructor(pFable, pOptions, pServiceHash)
45
+ {
46
+ super(pFable, pOptions, pServiceHash);
47
+
48
+ this.serviceType = 'UltravisorBeaconCapability';
49
+
50
+ /** @type {string} Capability name registered with Ultravisor */
51
+ this.capabilityName = '';
52
+
53
+ /** @type {string} Optional provider display name */
54
+ this.providerName = '';
55
+
56
+ /** @type {object|null} The underlying beacon service instance */
57
+ this._BeaconService = null;
58
+
59
+ /** @type {object} Explicitly registered actions (via addAction) */
60
+ this._ExplicitActions = {};
61
+ }
62
+
63
+ // ================================================================
64
+ // Lifecycle Hooks (override in subclass)
65
+ // ================================================================
66
+
67
+ /**
68
+ * Called after the beacon connects, before polling begins.
69
+ * Override to perform async setup (e.g. database connections).
70
+ *
71
+ * @param {function} fCallback - function(pError)
72
+ */
73
+ onInitialize(fCallback)
74
+ {
75
+ return fCallback(null);
76
+ }
77
+
78
+ /**
79
+ * Called when the beacon disconnects.
80
+ * Override to perform cleanup.
81
+ *
82
+ * @param {function} fCallback - function(pError)
83
+ */
84
+ onShutdown(fCallback)
85
+ {
86
+ return fCallback(null);
87
+ }
88
+
89
+ // ================================================================
90
+ // Public API
91
+ // ================================================================
92
+
93
+ /**
94
+ * Explicitly register an action (escape hatch for dynamic actions).
95
+ * Uses the raw registerCapability handler signature.
96
+ *
97
+ * @param {string} pName - Action name
98
+ * @param {object} pDefinition - { Description, SettingsSchema, Handler }
99
+ */
100
+ addAction(pName, pDefinition)
101
+ {
102
+ if (!pName || typeof pName !== 'string')
103
+ {
104
+ this.log.error('[BeaconCapability] addAction requires a string name.');
105
+ return;
106
+ }
107
+ if (!pDefinition || typeof pDefinition.Handler !== 'function')
108
+ {
109
+ this.log.error(`[BeaconCapability] addAction "${pName}" requires a Handler function.`);
110
+ return;
111
+ }
112
+
113
+ this._ExplicitActions[pName] = {
114
+ Description: pDefinition.Description || '',
115
+ SettingsSchema: pDefinition.SettingsSchema || [],
116
+ Handler: pDefinition.Handler
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Connect to an Ultravisor server, discover actions, and begin
122
+ * accepting work items.
123
+ *
124
+ * @param {object} pBeaconConfig - Connection configuration
125
+ * @param {string} pBeaconConfig.ServerURL - Ultravisor server URL
126
+ * @param {string} [pBeaconConfig.Name] - Beacon name
127
+ * @param {string} [pBeaconConfig.Password] - Auth password
128
+ * @param {number} [pBeaconConfig.MaxConcurrent] - Max parallel work items
129
+ * @param {string} [pBeaconConfig.StagingPath] - File staging directory
130
+ * @param {object} [pBeaconConfig.Tags] - Metadata tags
131
+ * @param {Array} [pBeaconConfig.BindAddresses] - Network addresses to advertise
132
+ * @param {function} fCallback - function(pError, pBeaconInfo)
133
+ */
134
+ connect(pBeaconConfig, fCallback)
135
+ {
136
+ if (typeof fCallback !== 'function')
137
+ {
138
+ fCallback = (pError) =>
139
+ {
140
+ if (pError)
141
+ {
142
+ this.log.error(`[BeaconCapability] connect error: ${pError.message}`);
143
+ }
144
+ };
145
+ }
146
+
147
+ if (!pBeaconConfig || !pBeaconConfig.ServerURL)
148
+ {
149
+ return fCallback(new Error('[BeaconCapability] ServerURL is required in beacon config.'));
150
+ }
151
+
152
+ if (!this.capabilityName)
153
+ {
154
+ return fCallback(new Error('[BeaconCapability] capabilityName must be set before calling connect().'));
155
+ }
156
+
157
+ // Build the action map from convention + explicit registrations
158
+ let tmpActions = this._buildActions();
159
+
160
+ if (Object.keys(tmpActions).length === 0)
161
+ {
162
+ this.log.warn(`[BeaconCapability] Capability "${this.capabilityName}" has no actions.`);
163
+ }
164
+
165
+ // Build the capability descriptor
166
+ let tmpSelf = this;
167
+ let tmpDescriptor = {
168
+ Capability: this.capabilityName,
169
+ Name: this.providerName || `${this.capabilityName}Provider`,
170
+ actions: tmpActions,
171
+ initialize: function (fInitCallback)
172
+ {
173
+ tmpSelf.onInitialize(fInitCallback);
174
+ },
175
+ shutdown: function (fShutdownCallback)
176
+ {
177
+ tmpSelf.onShutdown(fShutdownCallback);
178
+ }
179
+ };
180
+
181
+ // Register the beacon service type with Fable
182
+ this.fable.addServiceTypeIfNotExists('UltravisorBeacon', libBeaconService);
183
+
184
+ // Instantiate the beacon service
185
+ this._BeaconService = this.fable.instantiateServiceProviderWithoutRegistration('UltravisorBeacon',
186
+ {
187
+ ServerURL: pBeaconConfig.ServerURL,
188
+ Name: pBeaconConfig.Name || this.capabilityName,
189
+ Password: pBeaconConfig.Password || '',
190
+ MaxConcurrent: pBeaconConfig.MaxConcurrent || 1,
191
+ StagingPath: pBeaconConfig.StagingPath || process.cwd(),
192
+ Tags: pBeaconConfig.Tags || {},
193
+ BindAddresses: pBeaconConfig.BindAddresses || []
194
+ });
195
+
196
+ // Register the capability
197
+ this._BeaconService.registerCapability(tmpDescriptor);
198
+
199
+ // Enable the beacon (connects to server)
200
+ this._BeaconService.enable(
201
+ (pEnableError, pBeaconInfo) =>
202
+ {
203
+ if (pEnableError)
204
+ {
205
+ this._BeaconService = null;
206
+ return fCallback(pEnableError);
207
+ }
208
+ return fCallback(null, pBeaconInfo);
209
+ });
210
+ }
211
+
212
+ /**
213
+ * Disconnect from the Ultravisor server.
214
+ *
215
+ * @param {function} fCallback - function(pError)
216
+ */
217
+ disconnect(fCallback)
218
+ {
219
+ if (typeof fCallback !== 'function')
220
+ {
221
+ fCallback = () => {};
222
+ }
223
+
224
+ if (!this._BeaconService)
225
+ {
226
+ return fCallback(null);
227
+ }
228
+
229
+ this._BeaconService.disable(
230
+ (pDisableError) =>
231
+ {
232
+ this._BeaconService = null;
233
+ return fCallback(pDisableError || null);
234
+ });
235
+ }
236
+
237
+ /**
238
+ * Check whether the beacon is currently connected.
239
+ *
240
+ * @returns {boolean}
241
+ */
242
+ isConnected()
243
+ {
244
+ return (this._BeaconService !== null && this._BeaconService._Enabled === true);
245
+ }
246
+
247
+ // ================================================================
248
+ // Internal
249
+ // ================================================================
250
+
251
+ /**
252
+ * Build the merged action map from convention-discovered actions
253
+ * and explicitly registered actions.
254
+ *
255
+ * Explicit registrations take precedence on name collision.
256
+ *
257
+ * @returns {object} Action map for the capability descriptor
258
+ */
259
+ _buildActions()
260
+ {
261
+ // Discover actions from prototype chain
262
+ let tmpDiscoveredActions = libActionMap.buildActionMap(this);
263
+
264
+ // Merge explicit actions (explicit wins on collision)
265
+ let tmpExplicitNames = Object.keys(this._ExplicitActions);
266
+ for (let i = 0; i < tmpExplicitNames.length; i++)
267
+ {
268
+ let tmpName = tmpExplicitNames[i];
269
+ tmpDiscoveredActions[tmpName] = this._ExplicitActions[tmpName];
270
+ }
271
+
272
+ return tmpDiscoveredActions;
273
+ }
274
+ }
275
+
276
+ module.exports = UltravisorBeaconCapability;