@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,336 @@
1
+ /**
2
+ * BSB Registry UI Client-Side Application
3
+ */
4
+
5
+ // State
6
+ const state = {
7
+ plugins: [],
8
+ filteredPlugins: [],
9
+ currentPage: 1,
10
+ pageSize: 12,
11
+ searchQuery: '',
12
+ category: null,
13
+ sort: 'recent',
14
+ };
15
+
16
+ // API Base URL (proxied through UI server to avoid CORS)
17
+ const API_BASE = '/api';
18
+
19
+ // Initialize app
20
+ document.addEventListener('DOMContentLoaded', () => {
21
+ initEventListeners();
22
+ loadStats();
23
+ loadPlugins();
24
+ });
25
+
26
+ // Event Listeners
27
+ function initEventListeners() {
28
+ // Search
29
+ document.getElementById('searchBtn').addEventListener('click', handleSearch);
30
+ document.getElementById('searchInput').addEventListener('keypress', (e) => {
31
+ if (e.key === 'Enter') handleSearch();
32
+ });
33
+
34
+ // Category filters
35
+ document.querySelectorAll('.filter-tag').forEach(tag => {
36
+ tag.addEventListener('click', (e) => {
37
+ const category = e.target.dataset.category;
38
+ handleCategoryFilter(category);
39
+ });
40
+ });
41
+
42
+ // Sort
43
+ document.getElementById('sortSelect').addEventListener('change', (e) => {
44
+ state.sort = e.target.value;
45
+ sortPlugins();
46
+ renderPlugins();
47
+ });
48
+
49
+ // Modal close
50
+ document.querySelector('.modal-close').addEventListener('click', closeModal);
51
+ document.getElementById('pluginModal').addEventListener('click', (e) => {
52
+ if (e.target.id === 'pluginModal') closeModal();
53
+ });
54
+ }
55
+
56
+ // Load stats from API
57
+ async function loadStats() {
58
+ try {
59
+ const response = await fetch(`${API_BASE}/stats`);
60
+ if (!response.ok) throw new Error('Failed to fetch stats');
61
+
62
+ const stats = await response.json();
63
+
64
+ document.getElementById('totalPlugins').textContent = stats.totalPlugins || 0;
65
+ document.getElementById('totalLanguages').textContent = Object.keys(stats.byLanguage || {}).length || 0;
66
+ document.getElementById('totalDownloads').textContent = formatNumber(stats.totalDownloads || 0);
67
+ } catch (error) {
68
+ console.error('Error loading stats:', error);
69
+ }
70
+ }
71
+
72
+ // Load plugins from API
73
+ async function loadPlugins() {
74
+ showLoading();
75
+
76
+ try {
77
+ const response = await fetch(`${API_BASE}/plugins?limit=1000`);
78
+ if (!response.ok) throw new Error('Failed to fetch plugins');
79
+
80
+ const data = await response.json();
81
+ state.plugins = data.results || [];
82
+ state.filteredPlugins = [...state.plugins];
83
+
84
+ sortPlugins();
85
+ renderPlugins();
86
+ } catch (error) {
87
+ console.error('Error loading plugins:', error);
88
+ showError('Failed to load plugins. Make sure the registry API is running.');
89
+ }
90
+ }
91
+
92
+ // Search plugins
93
+ async function handleSearch() {
94
+ const query = document.getElementById('searchInput').value.trim();
95
+ state.searchQuery = query;
96
+ state.currentPage = 1;
97
+
98
+ if (!query) {
99
+ state.filteredPlugins = [...state.plugins];
100
+ renderPlugins();
101
+ return;
102
+ }
103
+
104
+ showLoading();
105
+
106
+ try {
107
+ const response = await fetch(`${API_BASE}/plugins/search?q=${encodeURIComponent(query)}`);
108
+ if (!response.ok) throw new Error('Search failed');
109
+
110
+ const data = await response.json();
111
+ state.filteredPlugins = data.results || [];
112
+
113
+ document.getElementById('sectionTitle').textContent = `Search Results for "${query}"`;
114
+ renderPlugins();
115
+ } catch (error) {
116
+ console.error('Error searching:', error);
117
+ showError('Search failed');
118
+ }
119
+ }
120
+
121
+ // Filter by category
122
+ function handleCategoryFilter(category) {
123
+ state.category = category;
124
+ state.currentPage = 1;
125
+
126
+ if (!category) {
127
+ state.filteredPlugins = [...state.plugins];
128
+ } else {
129
+ state.filteredPlugins = state.plugins.filter(p => p.category === category);
130
+ }
131
+
132
+ document.getElementById('sectionTitle').textContent = `${capitalize(category)} Plugins`;
133
+
134
+ // Update active state
135
+ document.querySelectorAll('.filter-tag').forEach(tag => {
136
+ tag.classList.toggle('active', tag.dataset.category === category);
137
+ });
138
+
139
+ sortPlugins();
140
+ renderPlugins();
141
+ }
142
+
143
+ // Sort plugins
144
+ function sortPlugins() {
145
+ state.filteredPlugins.sort((a, b) => {
146
+ switch (state.sort) {
147
+ case 'name':
148
+ return a.id.localeCompare(b.id);
149
+ case 'popular':
150
+ return (b.downloads || 0) - (a.downloads || 0);
151
+ case 'recent':
152
+ default:
153
+ return new Date(b.updatedAt) - new Date(a.updatedAt);
154
+ }
155
+ });
156
+ }
157
+
158
+ // Render plugins
159
+ function renderPlugins() {
160
+ const container = document.getElementById('pluginList');
161
+ const plugins = getPaginatedPlugins();
162
+
163
+ if (plugins.length === 0) {
164
+ container.innerHTML = '<div class="loading">No plugins found</div>';
165
+ return;
166
+ }
167
+
168
+ container.innerHTML = plugins.map(plugin => `
169
+ <div class="plugin-card" onclick="showPluginDetail('${plugin.id}')">
170
+ <div class="plugin-header">
171
+ <div>
172
+ <div class="plugin-name">${escapeHtml(plugin.name)}</div>
173
+ <div class="plugin-id">${escapeHtml(plugin.id)}</div>
174
+ </div>
175
+ <div class="plugin-version">v${escapeHtml(plugin.version)}</div>
176
+ </div>
177
+
178
+ <p class="plugin-description">${escapeHtml(plugin.description || 'No description')}</p>
179
+
180
+ <div class="plugin-meta">
181
+ <span>${plugin.language}</span>
182
+ <span>&middot;</span>
183
+ <span>${formatNumber(plugin.downloads || 0)} downloads</span>
184
+ <span>&middot;</span>
185
+ <span>${plugin.eventCount || 0} events</span>
186
+ </div>
187
+
188
+ <div class="plugin-tags">
189
+ <span class="tag category">${escapeHtml(plugin.category)}</span>
190
+ <span class="tag language">${escapeHtml(plugin.language)}</span>
191
+ ${(plugin.tags || []).slice(0, 3).map(tag =>
192
+ `<span class="tag">${escapeHtml(tag)}</span>`
193
+ ).join('')}
194
+ </div>
195
+ </div>
196
+ `).join('');
197
+
198
+ renderPagination();
199
+ }
200
+
201
+ // Get paginated plugins
202
+ function getPaginatedPlugins() {
203
+ const start = (state.currentPage - 1) * state.pageSize;
204
+ const end = start + state.pageSize;
205
+ return state.filteredPlugins.slice(start, end);
206
+ }
207
+
208
+ // Render pagination
209
+ function renderPagination() {
210
+ const container = document.getElementById('pagination');
211
+ const totalPages = Math.ceil(state.filteredPlugins.length / state.pageSize);
212
+
213
+ if (totalPages <= 1) {
214
+ container.innerHTML = '';
215
+ return;
216
+ }
217
+
218
+ const pages = [];
219
+ for (let i = 1; i <= totalPages; i++) {
220
+ if (i === 1 || i === totalPages || (i >= state.currentPage - 2 && i <= state.currentPage + 2)) {
221
+ pages.push(i);
222
+ } else if (pages[pages.length - 1] !== '...') {
223
+ pages.push('...');
224
+ }
225
+ }
226
+
227
+ container.innerHTML = pages.map(page => {
228
+ if (page === '...') {
229
+ return '<span class="page-btn disabled">...</span>';
230
+ }
231
+ return `
232
+ <button
233
+ class="page-btn ${page === state.currentPage ? 'active' : ''}"
234
+ onclick="goToPage(${page})"
235
+ >
236
+ ${page}
237
+ </button>
238
+ `;
239
+ }).join('');
240
+ }
241
+
242
+ // Navigate to page
243
+ function goToPage(page) {
244
+ state.currentPage = page;
245
+ renderPlugins();
246
+ window.scrollTo({ top: 0, behavior: 'smooth' });
247
+ }
248
+
249
+ // Show plugin detail modal
250
+ async function showPluginDetail(pluginId) {
251
+ const modal = document.getElementById('pluginModal');
252
+ const modalBody = document.getElementById('modalBody');
253
+
254
+ modalBody.innerHTML = '<div class="loading">Loading plugin details...</div>';
255
+ modal.classList.add('active');
256
+
257
+ try {
258
+ const response = await fetch(`${API_BASE}/plugins/${pluginId}`);
259
+ if (!response.ok) throw new Error('Failed to fetch plugin details');
260
+
261
+ const plugin = await response.json();
262
+
263
+ modalBody.innerHTML = `
264
+ <h2>${escapeHtml(plugin.name)}</h2>
265
+ <p class="plugin-id">${escapeHtml(plugin.id)} @ v${escapeHtml(plugin.version)}</p>
266
+
267
+ <p>${escapeHtml(plugin.description)}</p>
268
+
269
+ <div class="plugin-meta" style="margin: 1rem 0;">
270
+ <span><strong>Language:</strong> ${escapeHtml(plugin.language)}</span>
271
+ <span>&middot;</span>
272
+ <span><strong>Category:</strong> ${escapeHtml(plugin.category)}</span>
273
+ <span>&middot;</span>
274
+ <span><strong>Events:</strong> ${plugin.eventCount || 0}</span>
275
+ </div>
276
+
277
+ ${plugin.author ? `<p><strong>Author:</strong> ${escapeHtml(plugin.author)}</p>` : ''}
278
+ ${plugin.license ? `<p><strong>License:</strong> ${escapeHtml(plugin.license)}</p>` : ''}
279
+ ${plugin.homepage ? `<p><strong>Homepage:</strong> <a href="${escapeHtml(plugin.homepage)}" target="_blank">${escapeHtml(plugin.homepage)}</a></p>` : ''}
280
+ ${plugin.repository ? `<p><strong>Repository:</strong> <a href="${escapeHtml(plugin.repository)}" target="_blank">${escapeHtml(plugin.repository)}</a></p>` : ''}
281
+
282
+ <h3 style="margin-top: 2rem;">Installation</h3>
283
+ <pre style="background: var(--bg); padding: 1rem; border-radius: 0.5rem; overflow-x: auto;">npx bsb client install ${escapeHtml(plugin.id)}</pre>
284
+
285
+ ${plugin.package?.nodejs ? `
286
+ <h3 style="margin-top: 2rem;">NPM Package</h3>
287
+ <pre style="background: var(--bg); padding: 1rem; border-radius: 0.5rem; overflow-x: auto;">npm install ${escapeHtml(plugin.package.nodejs)}</pre>
288
+ ` : ''}
289
+
290
+ <div style="margin-top: 2rem;">
291
+ <h3>Tags</h3>
292
+ <div class="plugin-tags">
293
+ ${(plugin.tags || []).map(tag =>
294
+ `<span class="tag">${escapeHtml(tag)}</span>`
295
+ ).join('')}
296
+ </div>
297
+ </div>
298
+ `;
299
+ } catch (error) {
300
+ console.error('Error loading plugin details:', error);
301
+ modalBody.innerHTML = '<div class="error">Failed to load plugin details</div>';
302
+ }
303
+ }
304
+
305
+ // Close modal
306
+ function closeModal() {
307
+ document.getElementById('pluginModal').classList.remove('active');
308
+ }
309
+
310
+ // Show loading state
311
+ function showLoading() {
312
+ document.getElementById('pluginList').innerHTML = '<div class="loading">Loading plugins...</div>';
313
+ }
314
+
315
+ // Show error
316
+ function showError(message) {
317
+ document.getElementById('pluginList').innerHTML =
318
+ `<div class="error">${escapeHtml(message)}</div>`;
319
+ }
320
+
321
+ // Utility functions
322
+ function formatNumber(num) {
323
+ if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M';
324
+ if (num >= 1000) return (num / 1000).toFixed(1) + 'K';
325
+ return num.toString();
326
+ }
327
+
328
+ function capitalize(str) {
329
+ return str.charAt(0).toUpperCase() + str.slice(1);
330
+ }
331
+
332
+ function escapeHtml(text) {
333
+ const div = document.createElement('div');
334
+ div.textContent = text;
335
+ return div.innerHTML;
336
+ }
@@ -0,0 +1,39 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{title}}</title>
7
+ <link rel="icon" type="image/x-icon" href="/static/assets/images/favicon.ico">
8
+ <link rel="icon" type="image/png" sizes="16x16" href="/static/assets/images/favicon-16x16.png">
9
+ <link rel="icon" type="image/png" sizes="32x32" href="/static/assets/images/favicon-32x32.png">
10
+ <link rel="apple-touch-icon" sizes="180x180" href="/static/assets/images/apple-touch-icon.png">
11
+ <link rel="stylesheet" href="/static/css/style.css">
12
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/github-dark.min.css">
13
+ </head>
14
+ <body>
15
+ <header class="header">
16
+ <div class="header-content container">
17
+ <div class="logo">
18
+ <a href="/">BSB Registry</a>
19
+ </div>
20
+ <nav class="nav">
21
+ <a href="/" class="nav-link{{#if (eq activePage 'home')}} active{{/if}}">Home</a>
22
+ <a href="/plugins" class="nav-link{{#if (eq activePage 'browse')}} active{{/if}}">Browse</a>
23
+ </nav>
24
+ </div>
25
+ </header>
26
+
27
+ <main>
28
+ {{{body}}}
29
+ </main>
30
+
31
+ <footer class="footer">
32
+ <div class="container">
33
+ <p>BSB Framework - Multi-Language Plugin Registry</p>
34
+ </div>
35
+ </footer>
36
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script>
37
+ <script>hljs.highlightAll();</script>
38
+ </body>
39
+ </html>
@@ -0,0 +1,13 @@
1
+ <section class="plugins">
2
+ <div class="container">
3
+ <div class="empty-state">
4
+ <h1 class="empty-state-code">{{statusCode}}</h1>
5
+ <h2 class="empty-state-title">{{title}}</h2>
6
+ <p class="empty-state-text">{{message}}</p>
7
+ <div class="empty-state-actions">
8
+ <a href="/" class="btn">Go Home</a>
9
+ <a href="/plugins" class="btn btn-secondary">Browse Plugins</a>
10
+ </div>
11
+ </div>
12
+ </div>
13
+ </section>
@@ -0,0 +1,62 @@
1
+ <section class="hero">
2
+ <div class="container">
3
+ <h1 class="hero-title">BSB Plugin Registry</h1>
4
+ <p class="hero-subtitle">Discover and publish multi-language plugins for the BSB Framework</p>
5
+
6
+ <form method="GET" action="/plugins" class="search-box">
7
+ <input
8
+ type="search"
9
+ name="query"
10
+ class="search-input"
11
+ placeholder="Search plugins by name, description, or tags..."
12
+ >
13
+ <button type="submit" class="search-btn">Search</button>
14
+ </form>
15
+
16
+ <div class="quick-filters">
17
+ <span class="filter-label">Popular:</span>
18
+ <a href="/plugins?language=nodejs" class="filter-tag">Node.js</a>
19
+ <a href="/plugins?language=csharp" class="filter-tag">C#</a>
20
+ <a href="/plugins?language=go" class="filter-tag">Go</a>
21
+ <a href="/plugins?language=java" class="filter-tag">Java</a>
22
+ <a href="/plugins?language=python" class="filter-tag">Python</a>
23
+ </div>
24
+ </div>
25
+ </section>
26
+
27
+ {{#if stats}}
28
+ <section class="stats">
29
+ <div class="stats-grid container">
30
+ <div class="stat-card">
31
+ <div class="stat-value">{{stats.totalPlugins}}</div>
32
+ <div class="stat-label">Total Plugins</div>
33
+ </div>
34
+ <div class="stat-card">
35
+ <div class="stat-value">{{stats.totalDownloads}}</div>
36
+ <div class="stat-label">Total Downloads</div>
37
+ </div>
38
+ </div>
39
+ </section>
40
+ {{/if}}
41
+
42
+ <section class="plugins">
43
+ <div class="container">
44
+ {{#if plugins}}
45
+ <div class="section-header">
46
+ <h2 class="section-title">Recent Plugins</h2>
47
+ <a href="/plugins" class="nav-link">Browse All &rarr;</a>
48
+ </div>
49
+
50
+ <div class="plugin-grid">
51
+ {{#each plugins}}
52
+ {{> plugin-card}}
53
+ {{/each}}
54
+ </div>
55
+ {{else}}
56
+ <div class="empty-state">
57
+ <h2 class="empty-state-title">No plugins published yet</h2>
58
+ <p class="empty-state-text">Be the first to publish a plugin to the registry.</p>
59
+ </div>
60
+ {{/if}}
61
+ </div>
62
+ </section>
@@ -0,0 +1,13 @@
1
+ <section class="plugins">
2
+ <div class="container">
3
+ <div class="empty-state">
4
+ <h1 class="empty-state-code">404</h1>
5
+ <h2 class="empty-state-title">{{message}}</h2>
6
+ <p class="empty-state-text">The plugin you're looking for doesn't exist in the registry.</p>
7
+ <div class="empty-state-actions">
8
+ <a href="/" class="btn">Go Home</a>
9
+ <a href="/plugins" class="btn btn-secondary">Browse Plugins</a>
10
+ </div>
11
+ </div>
12
+ </div>
13
+ </section>