@bsb/registry 1.0.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 (54) hide show
  1. package/README.md +133 -0
  2. package/bsb-plugin.json +47 -0
  3. package/lib/.bsb/clients/service-bsb-registry.d.ts +1118 -0
  4. package/lib/.bsb/clients/service-bsb-registry.d.ts.map +1 -0
  5. package/lib/.bsb/clients/service-bsb-registry.js +393 -0
  6. package/lib/.bsb/clients/service-bsb-registry.js.map +1 -0
  7. package/lib/plugins/service-bsb-registry/auth.d.ts +87 -0
  8. package/lib/plugins/service-bsb-registry/auth.d.ts.map +1 -0
  9. package/lib/plugins/service-bsb-registry/auth.js +197 -0
  10. package/lib/plugins/service-bsb-registry/auth.js.map +1 -0
  11. package/lib/plugins/service-bsb-registry/db/file.d.ts +73 -0
  12. package/lib/plugins/service-bsb-registry/db/file.d.ts.map +1 -0
  13. package/lib/plugins/service-bsb-registry/db/file.js +588 -0
  14. package/lib/plugins/service-bsb-registry/db/file.js.map +1 -0
  15. package/lib/plugins/service-bsb-registry/db/index.d.ts +75 -0
  16. package/lib/plugins/service-bsb-registry/db/index.d.ts.map +1 -0
  17. package/lib/plugins/service-bsb-registry/db/index.js +24 -0
  18. package/lib/plugins/service-bsb-registry/db/index.js.map +1 -0
  19. package/lib/plugins/service-bsb-registry/index.d.ts +1228 -0
  20. package/lib/plugins/service-bsb-registry/index.d.ts.map +1 -0
  21. package/lib/plugins/service-bsb-registry/index.js +661 -0
  22. package/lib/plugins/service-bsb-registry/index.js.map +1 -0
  23. package/lib/plugins/service-bsb-registry/types.d.ts +559 -0
  24. package/lib/plugins/service-bsb-registry/types.d.ts.map +1 -0
  25. package/lib/plugins/service-bsb-registry/types.js +235 -0
  26. package/lib/plugins/service-bsb-registry/types.js.map +1 -0
  27. package/lib/plugins/service-bsb-registry-ui/http-server.d.ts +138 -0
  28. package/lib/plugins/service-bsb-registry-ui/http-server.d.ts.map +1 -0
  29. package/lib/plugins/service-bsb-registry-ui/http-server.js +1660 -0
  30. package/lib/plugins/service-bsb-registry-ui/http-server.js.map +1 -0
  31. package/lib/plugins/service-bsb-registry-ui/index.d.ts +62 -0
  32. package/lib/plugins/service-bsb-registry-ui/index.d.ts.map +1 -0
  33. package/lib/plugins/service-bsb-registry-ui/index.js +101 -0
  34. package/lib/plugins/service-bsb-registry-ui/index.js.map +1 -0
  35. package/lib/plugins/service-bsb-registry-ui/static/assets/images/apple-touch-icon.png +0 -0
  36. package/lib/plugins/service-bsb-registry-ui/static/assets/images/favicon-16x16.png +0 -0
  37. package/lib/plugins/service-bsb-registry-ui/static/assets/images/favicon-32x32.png +0 -0
  38. package/lib/plugins/service-bsb-registry-ui/static/assets/images/favicon.ico +0 -0
  39. package/lib/plugins/service-bsb-registry-ui/static/css/style.css +1849 -0
  40. package/lib/plugins/service-bsb-registry-ui/static/js/app.js +336 -0
  41. package/lib/plugins/service-bsb-registry-ui/templates/layouts/main.hbs +39 -0
  42. package/lib/plugins/service-bsb-registry-ui/templates/pages/error.hbs +13 -0
  43. package/lib/plugins/service-bsb-registry-ui/templates/pages/home.hbs +62 -0
  44. package/lib/plugins/service-bsb-registry-ui/templates/pages/not-found.hbs +13 -0
  45. package/lib/plugins/service-bsb-registry-ui/templates/pages/plugin-detail.hbs +537 -0
  46. package/lib/plugins/service-bsb-registry-ui/templates/pages/plugins.hbs +40 -0
  47. package/lib/plugins/service-bsb-registry-ui/templates/partials/pagination.hbs +41 -0
  48. package/lib/plugins/service-bsb-registry-ui/templates/partials/plugin-card.hbs +40 -0
  49. package/lib/plugins/service-bsb-registry-ui/templates/partials/search-form.hbs +31 -0
  50. package/lib/schemas/service-bsb-registry-ui.json +57 -0
  51. package/lib/schemas/service-bsb-registry-ui.plugin.json +73 -0
  52. package/lib/schemas/service-bsb-registry.json +1883 -0
  53. package/lib/schemas/service-bsb-registry.plugin.json +68 -0
  54. package/package.json +60 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/service-bsb-registry/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,661 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.Plugin = exports.Config = exports.EventSchemas = exports.RegistryConfigSchema = void 0;
37
+ const zod_1 = require("zod");
38
+ const base_1 = require("@bsb/base");
39
+ const db_1 = require("./db");
40
+ const auth_1 = require("./auth");
41
+ const Types = __importStar(require("./types"));
42
+ // ============================================================================
43
+ // Configuration
44
+ // ============================================================================
45
+ /**
46
+ * Configuration schema for BSB Registry (Core - Event-Driven).
47
+ */
48
+ exports.RegistryConfigSchema = zod_1.z.object({
49
+ database: zod_1.z.object({
50
+ type: zod_1.z.enum(['file', 'postgres']).default('file'),
51
+ path: zod_1.z.string().default('./.temp/data'),
52
+ }),
53
+ auth: zod_1.z.object({
54
+ requireAuth: zod_1.z.boolean().default(true),
55
+ }),
56
+ });
57
+ /**
58
+ * Event schemas for BSB Registry Core.
59
+ * All operations are event-driven for maximum flexibility.
60
+ */
61
+ exports.EventSchemas = (0, base_1.createEventSchemas)({
62
+ onReturnableEvents: {
63
+ // Plugin Operations
64
+ 'registry.plugin.publish': (0, base_1.createReturnableEvent)(Types.PublishRequestSchema, Types.PublishResponseSchema, 'Publish a new plugin or version'),
65
+ 'registry.plugin.get': (0, base_1.createReturnableEvent)(base_1.bsb.object({
66
+ org: base_1.bsb.string({ description: 'Organization name' }),
67
+ name: base_1.bsb.string({ description: 'Plugin name' }),
68
+ version: (0, base_1.optional)(base_1.bsb.string({ description: 'Version (defaults to latest)' })),
69
+ }), Types.RegistryEntrySchema, 'Get plugin details by org/name'),
70
+ 'registry.plugin.list': (0, base_1.createReturnableEvent)(Types.ListQuerySchema, Types.ListResultsSchema, 'List plugins with filtering'),
71
+ 'registry.plugin.search': (0, base_1.createReturnableEvent)(Types.SearchQuerySchema, Types.SearchResultsSchema, 'Search plugins by query'),
72
+ 'registry.plugin.delete': (0, base_1.createReturnableEvent)(base_1.bsb.object({
73
+ org: base_1.bsb.string({ description: 'Organization name' }),
74
+ name: base_1.bsb.string({ description: 'Plugin name' }),
75
+ version: (0, base_1.optional)(base_1.bsb.string({ description: 'Version (or all if not provided)' })),
76
+ }), base_1.bsb.object({
77
+ success: base_1.bsb.boolean('Success status'),
78
+ deleted: base_1.bsb.int32({ min: 0, description: 'Number of versions deleted' }),
79
+ }), 'Delete a plugin or specific version'),
80
+ 'registry.plugin.versions': (0, base_1.createReturnableEvent)(base_1.bsb.object({
81
+ org: base_1.bsb.string({ description: 'Organization name' }),
82
+ name: base_1.bsb.string({ description: 'Plugin name' }),
83
+ majorMinor: (0, base_1.optional)(base_1.bsb.string({ description: 'Filter by major.minor' })),
84
+ }), Types.VersionListSchema, 'Get all versions of a plugin'),
85
+ // Stats
86
+ 'registry.stats.get': (0, base_1.createReturnableEvent)(base_1.bsb.object({}), Types.RegistryStatsSchema, 'Get registry statistics'),
87
+ // Auth Operations
88
+ 'registry.auth.login': (0, base_1.createReturnableEvent)(base_1.bsb.object({
89
+ username: base_1.bsb.string({ description: 'Username' }),
90
+ password: base_1.bsb.string({ description: 'Encrypted password' }),
91
+ }), base_1.bsb.object({
92
+ success: base_1.bsb.boolean('Login success'),
93
+ token: (0, base_1.optional)(base_1.bsb.string({ description: 'Auth token' })),
94
+ expiresAt: (0, base_1.optional)(base_1.bsb.datetime('Expiration')),
95
+ message: (0, base_1.optional)(base_1.bsb.string({ description: 'Error message' })),
96
+ }), 'Authenticate user and get token'),
97
+ 'registry.auth.verify': (0, base_1.createReturnableEvent)(base_1.bsb.object({
98
+ token: base_1.bsb.string({ description: 'Token to verify' }),
99
+ }), base_1.bsb.object({
100
+ valid: base_1.bsb.boolean('Token validity'),
101
+ userId: (0, base_1.optional)(base_1.bsb.string({ description: 'User ID' })),
102
+ permissions: (0, base_1.optional)(base_1.bsb.array(base_1.bsb.string())),
103
+ }), 'Verify authentication token'),
104
+ },
105
+ });
106
+ /**
107
+ * Config for BSB Registry Core.
108
+ */
109
+ exports.Config = (0, base_1.createConfigSchema)({
110
+ name: 'BSB Registry Core',
111
+ description: 'Event-driven plugin registry core for multi-language BSB plugin storage and discovery',
112
+ image: '../../../docs/public/assets/images/bsb-logo.png',
113
+ tags: ['registry', 'plugin', 'marketplace', 'discovery', 'publishing', 'events', 'storage'],
114
+ documentation: ['./docs/service-bsb-registry.md', './docs/bsb-registry-db-file.md'],
115
+ }, exports.RegistryConfigSchema);
116
+ // ============================================================================
117
+ // Plugin
118
+ // ============================================================================
119
+ /**
120
+ * BSB Registry Core Plugin (Event-Driven)
121
+ *
122
+ * A language-agnostic plugin registry for the BSB framework, similar to npm registry
123
+ * but supporting plugins from multiple languages (Node.js, C#, Go, Java, Python).
124
+ *
125
+ * This is the CORE registry - it only handles events. For HTTP access, use:
126
+ * - service-bsb-registry-api (HTTP REST API gateway)
127
+ * - service-bsb-registry-ui (Web interface)
128
+ *
129
+ * Features:
130
+ * - Event-driven architecture (works locally or distributed)
131
+ * - SQLite/PostgreSQL storage
132
+ * - Organization-based naming (org/plugin-name)
133
+ * - Version matching (major.minor with patch interchangeability)
134
+ * - Authentication with encrypted passwords
135
+ * - Full-text search
136
+ * - Documentation storage
137
+ *
138
+ * Events:
139
+ * - registry.plugin.publish - Publish a plugin
140
+ * - registry.plugin.get - Get plugin details
141
+ * - registry.plugin.list - List plugins
142
+ * - registry.plugin.search - Search plugins
143
+ * - registry.plugin.delete - Delete plugin
144
+ * - registry.plugin.versions - Get versions
145
+ * - registry.stats.get - Get statistics
146
+ * - registry.auth.login - Login
147
+ * - registry.auth.verify - Verify token
148
+ */
149
+ class Plugin extends base_1.BSBService {
150
+ // v9: Required static properties
151
+ static Config = exports.Config;
152
+ static EventSchemas = exports.EventSchemas;
153
+ initBeforePlugins;
154
+ initAfterPlugins;
155
+ runBeforePlugins;
156
+ runAfterPlugins;
157
+ storage;
158
+ authManager;
159
+ constructor(config) {
160
+ super({
161
+ ...config,
162
+ eventSchemas: exports.EventSchemas,
163
+ });
164
+ // Create storage backend via factory
165
+ this.storage = (0, db_1.createStorage)({
166
+ type: this.config.database.type,
167
+ path: this.config.database.path,
168
+ });
169
+ // Create auth manager -- delegates all storage to the same RegistryDB
170
+ this.authManager = new auth_1.AuthManager({ requireAuth: this.config.auth.requireAuth }, this.storage);
171
+ }
172
+ /**
173
+ * Initialize resources and register event handlers.
174
+ */
175
+ async init(obs) {
176
+ obs.log.info('Initializing BSB Registry Core');
177
+ // Initialize storage (database migrations, etc.)
178
+ await this.storage.init(obs);
179
+ // Initialize authentication
180
+ await this.authManager.init(obs);
181
+ // Register event handlers
182
+ await this.registerEventHandlers(obs);
183
+ obs.log.info('BSB Registry Core initialized - event handlers registered');
184
+ }
185
+ /**
186
+ * Run phase - nothing to start (event-driven).
187
+ */
188
+ async run(obs) {
189
+ obs.log.info('BSB Registry Core ready - listening for events');
190
+ }
191
+ /**
192
+ * Cleanup resources.
193
+ */
194
+ dispose() {
195
+ if (this.storage) {
196
+ this.storage.dispose();
197
+ }
198
+ }
199
+ /**
200
+ * Register all event handlers.
201
+ */
202
+ async registerEventHandlers(obs) {
203
+ // Plugin Operations
204
+ await this.events.onReturnableEvent('registry.plugin.publish', obs, async (trace, data) => {
205
+ return await this.handlePluginPublish(trace, data);
206
+ });
207
+ await this.events.onReturnableEvent('registry.plugin.get', obs, async (trace, data) => {
208
+ return await this.handlePluginGet(trace, data);
209
+ });
210
+ await this.events.onReturnableEvent('registry.plugin.list', obs, async (trace, data) => {
211
+ return await this.handlePluginList(trace, data);
212
+ });
213
+ await this.events.onReturnableEvent('registry.plugin.search', obs, async (trace, data) => {
214
+ return await this.handlePluginSearch(trace, data);
215
+ });
216
+ await this.events.onReturnableEvent('registry.plugin.delete', obs, async (trace, data) => {
217
+ return await this.handlePluginDelete(trace, data);
218
+ });
219
+ await this.events.onReturnableEvent('registry.plugin.versions', obs, async (trace, data) => {
220
+ return await this.handlePluginVersions(trace, data);
221
+ });
222
+ // Stats
223
+ await this.events.onReturnableEvent('registry.stats.get', obs, async (trace, data) => {
224
+ return await this.handleStatsGet(trace);
225
+ });
226
+ // Auth
227
+ await this.events.onReturnableEvent('registry.auth.login', obs, async (trace, data) => {
228
+ return await this.handleAuthLogin(trace, data);
229
+ });
230
+ await this.events.onReturnableEvent('registry.auth.verify', obs, async (trace, data) => {
231
+ return await this.handleAuthVerify(trace, data);
232
+ });
233
+ obs.log.debug('Registered {count} event handlers', { count: 9 });
234
+ }
235
+ // ============================================================================
236
+ // Event Handlers (Business Logic with Full Tracing)
237
+ // ============================================================================
238
+ /**
239
+ * Handle plugin publish request
240
+ */
241
+ async handlePluginPublish(trace, data) {
242
+ const span = trace.startSpan('registry.plugin.publish', {
243
+ org: data.org,
244
+ name: data.name,
245
+ version: data.version,
246
+ language: data.language,
247
+ });
248
+ try {
249
+ trace.log.debug('Publishing plugin {org}/{name} v{version}', {
250
+ org: data.org,
251
+ name: data.name,
252
+ version: data.version,
253
+ });
254
+ // Auth is enforced at the HTTP layer (UI server authenticates
255
+ // the request and resolves the token to a userId before calling
256
+ // this event). The core plugin trusts the caller.
257
+ // Check if version already exists (immutable versions)
258
+ const pluginId = `${data.org}/${data.name}`;
259
+ const existsSpan = trace.startSpan('storage.versionExists');
260
+ const exists = await this.storage.versionExists(trace, data.org, data.name, data.version);
261
+ existsSpan.end();
262
+ if (exists) {
263
+ trace.log.warn('Version already exists: {id}@{version}', {
264
+ id: pluginId,
265
+ version: data.version,
266
+ });
267
+ return {
268
+ success: false,
269
+ pluginId,
270
+ version: data.version,
271
+ message: `Version ${data.version} already exists. Published versions are immutable - publish a new version instead.`,
272
+ };
273
+ }
274
+ // Build registry entry
275
+ const buildSpan = trace.startSpan('build.entry');
276
+ const majorMinor = data.version.split('.').slice(0, 2).join('.');
277
+ // eventSchema arrives as a parsed object (validated at the HTTP boundary).
278
+ // It's the full EventSchemaExport: { pluginName, version, events, dependencies? }
279
+ const parsedExport = (data.eventSchema && typeof data.eventSchema === 'object')
280
+ ? data.eventSchema
281
+ : {};
282
+ // Store only the events map -- pluginName and version are already
283
+ // at the root level of the registry entry; no need to duplicate them.
284
+ const eventsMap = parsedExport.events ?? {};
285
+ // Compute event counts from the events map
286
+ const counts = this.countEvents(eventsMap);
287
+ // configSchema arrives as a parsed JSON Schema object (validated at HTTP boundary).
288
+ // If present, it must have type: "object" and properties -- the HTTP layer enforces this.
289
+ const configSchema = (data.configSchema && typeof data.configSchema === 'object')
290
+ ? data.configSchema
291
+ : undefined;
292
+ const capabilities = (data.capabilities && typeof data.capabilities === 'object')
293
+ ? data.capabilities
294
+ : (parsedExport.capabilities && typeof parsedExport.capabilities === 'object')
295
+ ? parsedExport.capabilities
296
+ : undefined;
297
+ // Extract dependencies from the full export if not provided at top level
298
+ const dependencies = (data.dependencies && data.dependencies.length > 0)
299
+ ? data.dependencies
300
+ : parsedExport.dependencies ?? [];
301
+ const entry = {
302
+ id: pluginId,
303
+ org: data.org,
304
+ name: data.name,
305
+ displayName: data.metadata.displayName,
306
+ description: data.metadata.description,
307
+ version: data.version,
308
+ majorMinor: majorMinor,
309
+ language: data.language,
310
+ category: data.metadata.category,
311
+ tags: data.metadata.tags,
312
+ author: data.metadata.author,
313
+ license: data.metadata.license,
314
+ homepage: data.metadata.homepage,
315
+ repository: data.metadata.repository,
316
+ visibility: data.visibility || 'public',
317
+ eventSchema: eventsMap,
318
+ capabilities,
319
+ configSchema,
320
+ typeDefinitions: data.typeDefinitions,
321
+ documentation: data.documentation,
322
+ dependencies,
323
+ package: data.package,
324
+ runtime: data.runtime,
325
+ eventCount: counts.total,
326
+ emitEventCount: counts.emit,
327
+ onEventCount: counts.on,
328
+ returnableEventCount: counts.returnable,
329
+ broadcastEventCount: counts.broadcast,
330
+ publishedBy: data.publishedBy || 'system',
331
+ publishedAt: new Date().toISOString(),
332
+ updatedAt: new Date().toISOString(),
333
+ downloads: 0,
334
+ };
335
+ buildSpan.end();
336
+ // Store in database (will reject if version exists - double check)
337
+ const insertSpan = trace.startSpan('storage.insert');
338
+ await this.storage.insert(trace, entry);
339
+ insertSpan.end();
340
+ trace.log.info('Plugin published successfully: {id}@{version}', {
341
+ id: pluginId,
342
+ version: data.version,
343
+ });
344
+ return {
345
+ success: true,
346
+ pluginId,
347
+ version: data.version,
348
+ message: 'Plugin published successfully',
349
+ };
350
+ }
351
+ catch (error) {
352
+ trace.log.error('Failed to publish plugin: {error}', { error: error.message });
353
+ throw error;
354
+ }
355
+ finally {
356
+ span.end();
357
+ }
358
+ }
359
+ /**
360
+ * Handle get plugin request
361
+ */
362
+ async handlePluginGet(trace, data) {
363
+ const span = trace.startSpan('registry.plugin.get', { org: data.org, name: data.name });
364
+ try {
365
+ trace.log.debug('Getting plugin {org}/{name}', { org: data.org, name: data.name });
366
+ const getSpan = trace.startSpan('storage.get');
367
+ const plugin = await this.storage.get(trace, data.org, data.name, data.version);
368
+ getSpan.end();
369
+ if (!plugin) {
370
+ trace.log.warn('Plugin not found: {org}/{name}', { org: data.org, name: data.name });
371
+ throw new Error(`Plugin not found: ${data.org}/${data.name}`);
372
+ }
373
+ // Add plugin metadata to span for dashboarding
374
+ span.setAttributes({
375
+ version: plugin.version,
376
+ category: plugin.category,
377
+ language: plugin.language,
378
+ });
379
+ trace.log.debug('Plugin retrieved: {id}@{version}', { id: plugin.id, version: plugin.version });
380
+ return plugin;
381
+ }
382
+ catch (error) {
383
+ trace.log.error('Failed to get plugin: {error}', { error: error.message });
384
+ throw error;
385
+ }
386
+ finally {
387
+ span.end();
388
+ }
389
+ }
390
+ /**
391
+ * Handle list plugins request
392
+ */
393
+ async handlePluginList(trace, data) {
394
+ const span = trace.startSpan('registry.plugin.list', {
395
+ ...(data.org && { org: data.org }),
396
+ ...(data.language && { language: data.language }),
397
+ ...(data.category && { category: data.category }),
398
+ ...(data.limit && { limit: data.limit }),
399
+ ...(data.offset && { offset: data.offset }),
400
+ });
401
+ try {
402
+ trace.log.debug('Listing plugins');
403
+ const listSpan = trace.startSpan('storage.list');
404
+ const result = await this.storage.list(trace, data);
405
+ listSpan.end();
406
+ const page = Math.floor((data.offset || 0) / (data.limit || 50)) + 1;
407
+ // Add result metrics to span for dashboarding
408
+ span.setAttributes({
409
+ resultCount: result.total,
410
+ returnedCount: result.results.length,
411
+ });
412
+ trace.log.debug('Listed {count} plugins (total: {total})', {
413
+ count: result.results.length,
414
+ total: result.total,
415
+ });
416
+ return {
417
+ results: result.results,
418
+ total: result.total,
419
+ page,
420
+ };
421
+ }
422
+ catch (error) {
423
+ trace.log.error('Failed to list plugins: {error}', { error: error.message });
424
+ throw error;
425
+ }
426
+ finally {
427
+ span.end();
428
+ }
429
+ }
430
+ /**
431
+ * Handle search plugins request
432
+ */
433
+ async handlePluginSearch(trace, data) {
434
+ const span = trace.startSpan('registry.plugin.search', {
435
+ query: data.query,
436
+ ...(data.category && { category: data.category }),
437
+ ...(data.language && { language: data.language }),
438
+ limit: data.limit || 20,
439
+ ...(data.offset && { offset: data.offset }),
440
+ });
441
+ try {
442
+ trace.log.debug('Searching plugins: {query}', { query: data.query });
443
+ const searchSpan = trace.startSpan('storage.search');
444
+ const result = await this.storage.search(trace, data);
445
+ searchSpan.end();
446
+ // Add result count to span for dashboarding
447
+ span.setAttributes({
448
+ resultCount: result.total,
449
+ });
450
+ trace.log.info('Found {count} plugins matching "{query}"', {
451
+ count: result.total,
452
+ query: data.query,
453
+ });
454
+ return {
455
+ results: result.results,
456
+ total: result.total,
457
+ query: data.query,
458
+ };
459
+ }
460
+ catch (error) {
461
+ trace.log.error('Failed to search plugins: {error}', { error: error.message });
462
+ throw error;
463
+ }
464
+ finally {
465
+ span.end();
466
+ }
467
+ }
468
+ /**
469
+ * Handle delete plugin request
470
+ */
471
+ async handlePluginDelete(trace, data) {
472
+ const span = trace.startSpan('registry.plugin.delete', { org: data.org, name: data.name });
473
+ try {
474
+ trace.log.debug('Deleting plugin {org}/{name}', { org: data.org, name: data.name });
475
+ // Auth is enforced at the HTTP layer.
476
+ // Get current versions count
477
+ const countSpan = trace.startSpan('storage.getVersions');
478
+ const versions = await this.storage.getVersions(trace, data.org, data.name);
479
+ const versionCount = data.version
480
+ ? versions.filter(v => v.version === data.version).length
481
+ : versions.length;
482
+ countSpan.end();
483
+ // Perform deletion
484
+ const deleteSpan = trace.startSpan('storage.delete');
485
+ await this.storage.delete(trace, data.org, data.name, data.version);
486
+ deleteSpan.end();
487
+ trace.log.info('Deleted {count} version(s) of {org}/{name}', {
488
+ count: versionCount,
489
+ org: data.org,
490
+ name: data.name,
491
+ });
492
+ return {
493
+ success: true,
494
+ deleted: versionCount,
495
+ };
496
+ }
497
+ catch (error) {
498
+ trace.log.error('Failed to delete plugin: {error}', { error: error.message });
499
+ throw error;
500
+ }
501
+ finally {
502
+ span.end();
503
+ }
504
+ }
505
+ /**
506
+ * Handle get plugin versions request
507
+ */
508
+ async handlePluginVersions(trace, data) {
509
+ const span = trace.startSpan('registry.plugin.versions', { org: data.org, name: data.name });
510
+ try {
511
+ trace.log.debug('Getting versions for {org}/{name}', { org: data.org, name: data.name });
512
+ const versionsSpan = trace.startSpan('storage.getVersions');
513
+ const versions = await this.storage.getVersions(trace, data.org, data.name, data.majorMinor);
514
+ versionsSpan.end();
515
+ const latest = versions.length > 0 ? versions[0].version : '0.0.0';
516
+ // Build major.minor -> latest patch map
517
+ const latestMap = {};
518
+ for (const v of versions) {
519
+ if (!latestMap[v.majorMinor] || v.version > latestMap[v.majorMinor]) {
520
+ latestMap[v.majorMinor] = v.version;
521
+ }
522
+ }
523
+ trace.log.debug('Found {count} versions for {org}/{name}', {
524
+ count: versions.length,
525
+ org: data.org,
526
+ name: data.name,
527
+ });
528
+ return {
529
+ versions,
530
+ latest,
531
+ latestForMajorMinor: JSON.stringify(latestMap),
532
+ };
533
+ }
534
+ catch (error) {
535
+ trace.log.error('Failed to get versions: {error}', { error: error.message });
536
+ throw error;
537
+ }
538
+ finally {
539
+ span.end();
540
+ }
541
+ }
542
+ /**
543
+ * Handle get stats request
544
+ */
545
+ async handleStatsGet(trace) {
546
+ const span = trace.startSpan('registry.stats.get');
547
+ try {
548
+ trace.log.debug('Getting registry statistics');
549
+ const statsSpan = trace.startSpan('storage.getStats');
550
+ const stats = await this.storage.getStats(trace);
551
+ statsSpan.end();
552
+ trace.log.debug('Registry stats: {totalPlugins} plugins', { totalPlugins: stats.totalPlugins });
553
+ return stats;
554
+ }
555
+ catch (error) {
556
+ trace.log.error('Failed to get stats: {error}', { error: error.message });
557
+ throw error;
558
+ }
559
+ finally {
560
+ span.end();
561
+ }
562
+ }
563
+ /**
564
+ * Handle login request
565
+ */
566
+ async handleAuthLogin(trace, data) {
567
+ const span = trace.startSpan('registry.auth.login', { username: data.username });
568
+ try {
569
+ trace.log.info('Auth login attempt for user {username}', { username: data.username });
570
+ // TODO: Implement proper user/password authentication with encrypted password storage
571
+ // For now, login is not implemented - use API tokens instead
572
+ trace.log.warn('Login not implemented - use API tokens for authentication');
573
+ return {
574
+ success: false,
575
+ message: 'User/password authentication not implemented. Use API tokens instead.',
576
+ };
577
+ }
578
+ catch (error) {
579
+ trace.log.error('Login error: {error}', { error: error.message });
580
+ return {
581
+ success: false,
582
+ message: 'Authentication error',
583
+ };
584
+ }
585
+ finally {
586
+ span.end();
587
+ }
588
+ }
589
+ /**
590
+ * Handle verify token request
591
+ */
592
+ async handleAuthVerify(trace, data) {
593
+ const span = trace.startSpan('registry.auth.verify');
594
+ try {
595
+ trace.log.debug('Verifying auth token');
596
+ const verifySpan = trace.startSpan('auth.manager.verify');
597
+ const resolved = await this.authManager.resolveToken(trace, data.token);
598
+ verifySpan.end();
599
+ if (resolved) {
600
+ trace.log.debug('Token is valid for user {userId}', { userId: resolved.userId });
601
+ return {
602
+ valid: true,
603
+ userId: resolved.userId,
604
+ permissions: resolved.effectivePermissions,
605
+ };
606
+ }
607
+ else {
608
+ trace.log.warn('Token verification failed');
609
+ return {
610
+ valid: false,
611
+ };
612
+ }
613
+ }
614
+ catch (error) {
615
+ trace.log.error('Token verification error: {error}', { error: error.message });
616
+ return {
617
+ valid: false,
618
+ };
619
+ }
620
+ finally {
621
+ span.end();
622
+ }
623
+ }
624
+ // ============================================================================
625
+ // Helpers
626
+ // ============================================================================
627
+ /**
628
+ * Count events by type from an events map (Record<string, EventExportEntry>).
629
+ * The events map is the flat map of event name to definition with `category`.
630
+ * Gracefully returns zeros if the map is invalid.
631
+ */
632
+ countEvents(eventsMap) {
633
+ const counts = { total: 0, emit: 0, on: 0, returnable: 0, broadcast: 0 };
634
+ if (!eventsMap || typeof eventsMap !== 'object')
635
+ return counts;
636
+ for (const def of Object.values(eventsMap)) {
637
+ switch (def.category) {
638
+ case 'emitEvents':
639
+ counts.emit++;
640
+ break;
641
+ case 'onEvents':
642
+ counts.on++;
643
+ break;
644
+ case 'emitReturnableEvents':
645
+ case 'onReturnableEvents':
646
+ counts.returnable++;
647
+ break;
648
+ case 'emitBroadcast':
649
+ case 'onBroadcast':
650
+ counts.broadcast++;
651
+ break;
652
+ }
653
+ }
654
+ counts.total = counts.emit + counts.on + counts.returnable + counts.broadcast;
655
+ return counts;
656
+ }
657
+ }
658
+ exports.Plugin = Plugin;
659
+ // Export plugin factory
660
+ exports.default = Plugin;
661
+ //# sourceMappingURL=index.js.map