@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.
- package/README.md +133 -0
- package/bsb-plugin.json +47 -0
- package/lib/.bsb/clients/service-bsb-registry.d.ts +1118 -0
- package/lib/.bsb/clients/service-bsb-registry.d.ts.map +1 -0
- package/lib/.bsb/clients/service-bsb-registry.js +393 -0
- package/lib/.bsb/clients/service-bsb-registry.js.map +1 -0
- package/lib/plugins/service-bsb-registry/auth.d.ts +87 -0
- package/lib/plugins/service-bsb-registry/auth.d.ts.map +1 -0
- package/lib/plugins/service-bsb-registry/auth.js +197 -0
- package/lib/plugins/service-bsb-registry/auth.js.map +1 -0
- package/lib/plugins/service-bsb-registry/db/file.d.ts +73 -0
- package/lib/plugins/service-bsb-registry/db/file.d.ts.map +1 -0
- package/lib/plugins/service-bsb-registry/db/file.js +588 -0
- package/lib/plugins/service-bsb-registry/db/file.js.map +1 -0
- package/lib/plugins/service-bsb-registry/db/index.d.ts +75 -0
- package/lib/plugins/service-bsb-registry/db/index.d.ts.map +1 -0
- package/lib/plugins/service-bsb-registry/db/index.js +24 -0
- package/lib/plugins/service-bsb-registry/db/index.js.map +1 -0
- package/lib/plugins/service-bsb-registry/index.d.ts +1228 -0
- package/lib/plugins/service-bsb-registry/index.d.ts.map +1 -0
- package/lib/plugins/service-bsb-registry/index.js +661 -0
- package/lib/plugins/service-bsb-registry/index.js.map +1 -0
- package/lib/plugins/service-bsb-registry/types.d.ts +559 -0
- package/lib/plugins/service-bsb-registry/types.d.ts.map +1 -0
- package/lib/plugins/service-bsb-registry/types.js +235 -0
- package/lib/plugins/service-bsb-registry/types.js.map +1 -0
- package/lib/plugins/service-bsb-registry-ui/http-server.d.ts +138 -0
- package/lib/plugins/service-bsb-registry-ui/http-server.d.ts.map +1 -0
- package/lib/plugins/service-bsb-registry-ui/http-server.js +1660 -0
- package/lib/plugins/service-bsb-registry-ui/http-server.js.map +1 -0
- package/lib/plugins/service-bsb-registry-ui/index.d.ts +62 -0
- package/lib/plugins/service-bsb-registry-ui/index.d.ts.map +1 -0
- package/lib/plugins/service-bsb-registry-ui/index.js +101 -0
- package/lib/plugins/service-bsb-registry-ui/index.js.map +1 -0
- package/lib/plugins/service-bsb-registry-ui/static/assets/images/apple-touch-icon.png +0 -0
- package/lib/plugins/service-bsb-registry-ui/static/assets/images/favicon-16x16.png +0 -0
- package/lib/plugins/service-bsb-registry-ui/static/assets/images/favicon-32x32.png +0 -0
- package/lib/plugins/service-bsb-registry-ui/static/assets/images/favicon.ico +0 -0
- package/lib/plugins/service-bsb-registry-ui/static/css/style.css +1849 -0
- package/lib/plugins/service-bsb-registry-ui/static/js/app.js +336 -0
- package/lib/plugins/service-bsb-registry-ui/templates/layouts/main.hbs +39 -0
- package/lib/plugins/service-bsb-registry-ui/templates/pages/error.hbs +13 -0
- package/lib/plugins/service-bsb-registry-ui/templates/pages/home.hbs +62 -0
- package/lib/plugins/service-bsb-registry-ui/templates/pages/not-found.hbs +13 -0
- package/lib/plugins/service-bsb-registry-ui/templates/pages/plugin-detail.hbs +537 -0
- package/lib/plugins/service-bsb-registry-ui/templates/pages/plugins.hbs +40 -0
- package/lib/plugins/service-bsb-registry-ui/templates/partials/pagination.hbs +41 -0
- package/lib/plugins/service-bsb-registry-ui/templates/partials/plugin-card.hbs +40 -0
- package/lib/plugins/service-bsb-registry-ui/templates/partials/search-form.hbs +31 -0
- package/lib/schemas/service-bsb-registry-ui.json +57 -0
- package/lib/schemas/service-bsb-registry-ui.plugin.json +73 -0
- package/lib/schemas/service-bsb-registry.json +1883 -0
- package/lib/schemas/service-bsb-registry.plugin.json +68 -0
- 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
|