@nsxbet/admin-sdk 0.5.1 → 0.6.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 (173) hide show
  1. package/CHECKLIST.md +40 -10
  2. package/README.md +337 -36
  3. package/dist/auth/client/gateway-token.d.ts +19 -0
  4. package/dist/auth/client/gateway-token.js +89 -0
  5. package/dist/auth/client/in-memory.d.ts +5 -1
  6. package/dist/auth/client/in-memory.js +75 -38
  7. package/dist/auth/client/index.d.ts +0 -1
  8. package/dist/auth/client/interface.d.ts +6 -3
  9. package/dist/auth/client/keycloak.d.ts +0 -1
  10. package/dist/auth/client/keycloak.js +6 -3
  11. package/dist/auth/components/UserSelector.d.ts +0 -1
  12. package/dist/auth/components/UserSelector.js +89 -7
  13. package/dist/auth/components/index.d.ts +0 -1
  14. package/dist/auth/index.d.ts +0 -1
  15. package/dist/components/AuthProvider.d.ts +0 -1
  16. package/dist/components/Timestamp.d.ts +7 -0
  17. package/dist/components/Timestamp.js +50 -0
  18. package/dist/hooks/useAuth.d.ts +0 -1
  19. package/dist/hooks/useAuth.js +1 -1
  20. package/dist/hooks/useFetch.d.ts +0 -1
  21. package/dist/hooks/useI18n.d.ts +0 -1
  22. package/dist/hooks/usePlatformAPI.d.ts +0 -1
  23. package/dist/hooks/useTelemetry.d.ts +0 -1
  24. package/dist/hooks/useTimestamp.d.ts +8 -0
  25. package/dist/hooks/useTimestamp.js +122 -0
  26. package/dist/i18n/config.d.ts +20 -2
  27. package/dist/i18n/config.js +48 -0
  28. package/dist/i18n/index.d.ts +2 -3
  29. package/dist/i18n/index.js +1 -1
  30. package/dist/i18n/locales/en-US.json +95 -18
  31. package/dist/i18n/locales/es.json +95 -18
  32. package/dist/i18n/locales/pt-BR.json +95 -18
  33. package/dist/i18n/locales/ro.json +95 -18
  34. package/dist/index.d.ts +11 -7
  35. package/dist/index.js +5 -1
  36. package/dist/registry/AdminShellRegistry.d.ts +1 -2
  37. package/dist/registry/cache/cached-catalog.d.ts +11 -0
  38. package/dist/registry/cache/cached-catalog.js +42 -0
  39. package/dist/registry/cache/catalog-cache.d.ts +10 -0
  40. package/dist/registry/cache/catalog-cache.js +58 -0
  41. package/dist/registry/cache/index.d.ts +5 -0
  42. package/dist/registry/cache/index.js +3 -0
  43. package/dist/registry/cache/types.d.ts +20 -0
  44. package/dist/registry/cache/types.js +3 -0
  45. package/dist/registry/client/http.d.ts +0 -1
  46. package/dist/registry/client/http.js +13 -0
  47. package/dist/registry/client/in-memory.d.ts +0 -1
  48. package/dist/registry/client/in-memory.js +117 -12
  49. package/dist/registry/client/index.d.ts +0 -1
  50. package/dist/registry/client/interface.d.ts +21 -6
  51. package/dist/registry/index.d.ts +5 -2
  52. package/dist/registry/index.js +4 -0
  53. package/dist/registry/types/index.d.ts +2 -3
  54. package/dist/registry/types/manifest.d.ts +20 -24
  55. package/dist/registry/types/manifest.js +17 -18
  56. package/dist/registry/types/module.d.ts +43 -14
  57. package/dist/registry/useRegistryPolling.d.ts +15 -0
  58. package/dist/registry/useRegistryPolling.js +66 -0
  59. package/dist/router/DynamicModule.d.ts +6 -22
  60. package/dist/router/DynamicModule.js +25 -48
  61. package/dist/router/ModuleErrorBoundary.d.ts +39 -0
  62. package/dist/router/ModuleErrorBoundary.js +101 -0
  63. package/dist/router/index.d.ts +1 -1
  64. package/dist/router/url-allowlist.d.ts +22 -0
  65. package/dist/router/url-allowlist.js +65 -0
  66. package/dist/shell/AdminShell.d.ts +0 -1
  67. package/dist/shell/AdminShell.js +178 -43
  68. package/dist/shell/BackofficeShell.d.ts +0 -1
  69. package/dist/shell/BackofficeShell.js +59 -25
  70. package/dist/shell/components/CommandPalette.d.ts +0 -1
  71. package/dist/shell/components/CommandPalette.js +26 -50
  72. package/dist/shell/components/DevtoolsPanel.d.ts +11 -0
  73. package/dist/shell/components/DevtoolsPanel.js +145 -0
  74. package/dist/shell/components/HomePage.d.ts +0 -1
  75. package/dist/shell/components/HomePage.js +9 -4
  76. package/dist/shell/components/LeftNav.d.ts +0 -1
  77. package/dist/shell/components/LeftNav.js +91 -93
  78. package/dist/shell/components/MainContent.d.ts +3 -2
  79. package/dist/shell/components/MainContent.js +8 -23
  80. package/dist/shell/components/ModuleOverview.d.ts +0 -1
  81. package/dist/shell/components/ModuleOverview.js +4 -20
  82. package/dist/shell/components/ProfilePage.d.ts +0 -1
  83. package/dist/shell/components/ProfilePage.js +1 -1
  84. package/dist/shell/components/RegistryPage.d.ts +0 -1
  85. package/dist/shell/components/RegistryPage.js +154 -64
  86. package/dist/shell/components/RegistryStatusBanner.d.ts +6 -0
  87. package/dist/shell/components/RegistryStatusBanner.js +31 -0
  88. package/dist/shell/components/RegistryUnavailable.d.ts +4 -0
  89. package/dist/shell/components/RegistryUnavailable.js +7 -0
  90. package/dist/shell/components/SettingsPage.d.ts +0 -1
  91. package/dist/shell/components/StackedPanel.d.ts +15 -0
  92. package/dist/shell/components/StackedPanel.js +45 -0
  93. package/dist/shell/components/TopBar.d.ts +4 -2
  94. package/dist/shell/components/TopBar.js +9 -3
  95. package/dist/shell/components/UpdateBanner.d.ts +5 -0
  96. package/dist/shell/components/UpdateBanner.js +8 -0
  97. package/dist/shell/components/index.d.ts +4 -1
  98. package/dist/shell/components/index.js +2 -0
  99. package/dist/shell/components/theme-provider.d.ts +0 -1
  100. package/dist/shell/components/theme-provider.js +8 -5
  101. package/dist/shell/hooks/useCspViolations.d.ts +12 -0
  102. package/dist/shell/hooks/useCspViolations.js +34 -0
  103. package/dist/shell/index.d.ts +1 -2
  104. package/dist/shell/polling-config.d.ts +10 -0
  105. package/dist/shell/polling-config.js +26 -0
  106. package/dist/shell/search/fuzzy.d.ts +0 -1
  107. package/dist/shell/search/index.d.ts +0 -1
  108. package/dist/shell/telemetry.d.ts +0 -1
  109. package/dist/shell/types.d.ts +34 -18
  110. package/dist/tailwind/index.d.ts +0 -1
  111. package/dist/types/keycloak.d.ts +0 -1
  112. package/dist/types/platform.d.ts +12 -1
  113. package/dist/vite/AdminShellSharedDeps.d.ts +64 -0
  114. package/dist/vite/AdminShellSharedDeps.js +215 -0
  115. package/dist/vite/config.d.ts +2 -2
  116. package/dist/vite/config.js +5 -7
  117. package/dist/vite/i18n-plugin.d.ts +13 -0
  118. package/dist/vite/i18n-plugin.js +81 -0
  119. package/dist/vite/index.d.ts +2 -1
  120. package/dist/vite/index.js +2 -0
  121. package/dist/vite/plugins.d.ts +0 -1
  122. package/package.json +6 -2
  123. package/dist/auth/client/in-memory.d.ts.map +0 -1
  124. package/dist/auth/client/index.d.ts.map +0 -1
  125. package/dist/auth/client/interface.d.ts.map +0 -1
  126. package/dist/auth/client/keycloak.d.ts.map +0 -1
  127. package/dist/auth/components/UserSelector.d.ts.map +0 -1
  128. package/dist/auth/components/index.d.ts.map +0 -1
  129. package/dist/auth/index.d.ts.map +0 -1
  130. package/dist/components/AuthProvider.d.ts.map +0 -1
  131. package/dist/hooks/useAuth.d.ts.map +0 -1
  132. package/dist/hooks/useFetch.d.ts.map +0 -1
  133. package/dist/hooks/useI18n.d.ts.map +0 -1
  134. package/dist/hooks/usePlatformAPI.d.ts.map +0 -1
  135. package/dist/hooks/useTelemetry.d.ts.map +0 -1
  136. package/dist/i18n/config.d.ts.map +0 -1
  137. package/dist/i18n/index.d.ts.map +0 -1
  138. package/dist/index.d.ts.map +0 -1
  139. package/dist/registry/AdminShellRegistry.d.ts.map +0 -1
  140. package/dist/registry/client/http.d.ts.map +0 -1
  141. package/dist/registry/client/in-memory.d.ts.map +0 -1
  142. package/dist/registry/client/index.d.ts.map +0 -1
  143. package/dist/registry/client/interface.d.ts.map +0 -1
  144. package/dist/registry/index.d.ts.map +0 -1
  145. package/dist/registry/types/index.d.ts.map +0 -1
  146. package/dist/registry/types/manifest.d.ts.map +0 -1
  147. package/dist/registry/types/module.d.ts.map +0 -1
  148. package/dist/router/DynamicModule.d.ts.map +0 -1
  149. package/dist/router/index.d.ts.map +0 -1
  150. package/dist/shell/AdminShell.d.ts.map +0 -1
  151. package/dist/shell/BackofficeShell.d.ts.map +0 -1
  152. package/dist/shell/components/CommandPalette.d.ts.map +0 -1
  153. package/dist/shell/components/HomePage.d.ts.map +0 -1
  154. package/dist/shell/components/LeftNav.d.ts.map +0 -1
  155. package/dist/shell/components/MainContent.d.ts.map +0 -1
  156. package/dist/shell/components/ModuleOverview.d.ts.map +0 -1
  157. package/dist/shell/components/ProfilePage.d.ts.map +0 -1
  158. package/dist/shell/components/RegistryPage.d.ts.map +0 -1
  159. package/dist/shell/components/SettingsPage.d.ts.map +0 -1
  160. package/dist/shell/components/TopBar.d.ts.map +0 -1
  161. package/dist/shell/components/index.d.ts.map +0 -1
  162. package/dist/shell/components/theme-provider.d.ts.map +0 -1
  163. package/dist/shell/index.d.ts.map +0 -1
  164. package/dist/shell/search/fuzzy.d.ts.map +0 -1
  165. package/dist/shell/search/index.d.ts.map +0 -1
  166. package/dist/shell/telemetry.d.ts.map +0 -1
  167. package/dist/shell/types.d.ts.map +0 -1
  168. package/dist/tailwind/index.d.ts.map +0 -1
  169. package/dist/types/keycloak.d.ts.map +0 -1
  170. package/dist/types/platform.d.ts.map +0 -1
  171. package/dist/vite/config.d.ts.map +0 -1
  172. package/dist/vite/index.d.ts.map +0 -1
  173. package/dist/vite/plugins.d.ts.map +0 -1
@@ -11,8 +11,37 @@ const DEFAULT_STORAGE_KEY = '@nsxbet/registry';
11
11
  */
12
12
  export function createInMemoryRegistryClient(options = {}) {
13
13
  const storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;
14
+ const versionsKey = `${storageKey}-versions`;
14
15
  // Initialize or load storage
15
16
  let data = loadStorage(storageKey);
17
+ function loadVersions() {
18
+ try {
19
+ const stored = localStorage.getItem(versionsKey);
20
+ if (stored)
21
+ return JSON.parse(stored);
22
+ }
23
+ catch { /* ignore */ }
24
+ return { versions: [], autoIncrement: 0 };
25
+ }
26
+ function saveVersions(vData) {
27
+ try {
28
+ localStorage.setItem(versionsKey, JSON.stringify(vData));
29
+ }
30
+ catch { /* ignore */ }
31
+ }
32
+ function recordVersion(registeredModuleId, version, baseUrl, metadata) {
33
+ const vData = loadVersions();
34
+ vData.versions.push({
35
+ id: ++vData.autoIncrement,
36
+ registeredModuleId,
37
+ version,
38
+ baseUrl,
39
+ publishedAt: new Date().toISOString(),
40
+ active: false,
41
+ metadata,
42
+ });
43
+ saveVersions(vData);
44
+ }
16
45
  // Apply seed if provided and storage is empty
17
46
  // Note: seed is for standalone mode where modules are imported directly (no baseUrl needed)
18
47
  if (options.seed && data.modules.length === 0) {
@@ -24,7 +53,7 @@ export function createInMemoryRegistryClient(options = {}) {
24
53
  moduleId: validated.id,
25
54
  baseUrl: '',
26
55
  manifest: validated,
27
- navOrder: validated.navOrder ?? data.autoIncrement * 10,
56
+ navigationOrder: validated.navigationOrder ?? data.autoIncrement * 10,
28
57
  enabled: true,
29
58
  registeredAt: now,
30
59
  updatedAt: now,
@@ -43,7 +72,7 @@ export function createInMemoryRegistryClient(options = {}) {
43
72
  moduleId: validated.id,
44
73
  baseUrl,
45
74
  manifest: validated,
46
- navOrder: validated.navOrder ?? data.autoIncrement * 10,
75
+ navigationOrder: validated.navigationOrder ?? data.autoIncrement * 10,
47
76
  enabled: true,
48
77
  registeredAt: now,
49
78
  updatedAt: now,
@@ -63,10 +92,10 @@ export function createInMemoryRegistryClient(options = {}) {
63
92
  result = result.filter((m) => m.manifest.category === filters.category);
64
93
  }
65
94
  if (filters?.status) {
66
- result = result.filter((m) => m.manifest.status === filters.status);
95
+ result = result.filter(() => filters.status === 'active');
67
96
  }
68
- // Sort by navOrder
69
- result.sort((a, b) => a.navOrder - b.navOrder);
97
+ // Sort by navigationOrder
98
+ result.sort((a, b) => a.navigationOrder - b.navigationOrder);
70
99
  return result;
71
100
  },
72
101
  async find(id) {
@@ -107,7 +136,7 @@ export function createInMemoryRegistryClient(options = {}) {
107
136
  moduleId: validated.id,
108
137
  baseUrl,
109
138
  manifest: validated,
110
- navOrder: validated.navOrder ?? data.autoIncrement * 10,
139
+ navigationOrder: validated.navigationOrder ?? data.autoIncrement * 10,
111
140
  enabled: true,
112
141
  registeredAt: now,
113
142
  updatedAt: now,
@@ -125,12 +154,17 @@ export function createInMemoryRegistryClient(options = {}) {
125
154
  const module = data.modules[index];
126
155
  const updated = {
127
156
  ...module,
128
- ...(updateData.navOrder !== undefined && { navOrder: updateData.navOrder }),
157
+ ...(updateData.navigationOrder !== undefined && { navigationOrder: updateData.navigationOrder }),
129
158
  ...(updateData.enabled !== undefined && { enabled: updateData.enabled }),
159
+ ...(updateData.baseUrl !== undefined && { baseUrl: updateData.baseUrl }),
160
+ ...(updateData.version !== undefined && { version: updateData.version }),
130
161
  updatedAt: new Date().toISOString(),
131
162
  };
132
163
  data.modules[index] = updated;
133
164
  saveStorage(storageKey, data);
165
+ if (updateData.version !== undefined) {
166
+ recordVersion(id, updated.version, updated.baseUrl);
167
+ }
134
168
  return updated;
135
169
  },
136
170
  async delete(id) {
@@ -145,16 +179,71 @@ export function createInMemoryRegistryClient(options = {}) {
145
179
  async reorder(order) {
146
180
  data = loadStorage(storageKey);
147
181
  const now = new Date().toISOString();
148
- for (const { id, navOrder } of order) {
182
+ for (const { id, navigationOrder } of order) {
149
183
  const module = data.modules.find((m) => m.id === id);
150
184
  if (module) {
151
- module.navOrder = navOrder;
185
+ module.navigationOrder = navigationOrder;
152
186
  module.updatedAt = now;
153
187
  }
154
188
  }
155
189
  saveStorage(storageKey, data);
156
190
  },
191
+ async listVersions(id) {
192
+ data = loadStorage(storageKey);
193
+ const module = data.modules.find((m) => m.id === id);
194
+ if (!module) {
195
+ throw new Error(`Module with id ${id} not found`);
196
+ }
197
+ const vData = loadVersions();
198
+ return vData.versions
199
+ .filter((v) => v.registeredModuleId === id)
200
+ .sort((a, b) => new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime())
201
+ .map(({ registeredModuleId: _, ...v }) => ({
202
+ ...v,
203
+ active: v.version === module.version,
204
+ }));
205
+ },
206
+ async rollback(id, targetVersion) {
207
+ data = loadStorage(storageKey);
208
+ const index = data.modules.findIndex((m) => m.id === id);
209
+ if (index === -1) {
210
+ throw new Error(`Module with id ${id} not found`);
211
+ }
212
+ const module = data.modules[index];
213
+ if (module.version === targetVersion) {
214
+ return module;
215
+ }
216
+ const vData = loadVersions();
217
+ const versionEntry = vData.versions.find((v) => v.registeredModuleId === id && v.version === targetVersion);
218
+ if (!versionEntry) {
219
+ throw new Error(`Version ${targetVersion} not found for module ${module.moduleId}`);
220
+ }
221
+ const previousVersion = module.version;
222
+ const updated = {
223
+ ...module,
224
+ version: targetVersion,
225
+ baseUrl: versionEntry.baseUrl,
226
+ updatedAt: new Date().toISOString(),
227
+ };
228
+ data.modules[index] = updated;
229
+ saveStorage(storageKey, data);
230
+ recordVersion(id, targetVersion, versionEntry.baseUrl, {
231
+ rolledBackFrom: previousVersion,
232
+ });
233
+ return updated;
234
+ },
157
235
  };
236
+ function computeLocalVersion(storageData) {
237
+ const enabledModules = storageData.modules.filter((m) => m.enabled);
238
+ const maxUpdatedAt = enabledModules.reduce((max, m) => (m.updatedAt > max ? m.updatedAt : max), '');
239
+ const raw = `${maxUpdatedAt}:${enabledModules.length}`;
240
+ let hash = 0;
241
+ for (let i = 0; i < raw.length; i++) {
242
+ const chr = raw.charCodeAt(i);
243
+ hash = ((hash << 5) - hash + chr) | 0;
244
+ }
245
+ return Math.abs(hash).toString(16).padStart(8, '0');
246
+ }
158
247
  // Catalog operations
159
248
  const catalog = {
160
249
  async get() {
@@ -175,23 +264,39 @@ export function createInMemoryRegistryClient(options = {}) {
175
264
  team: m.manifest.owners?.team ?? 'unknown',
176
265
  supportChannel: m.manifest.owners?.supportChannel ?? '#general',
177
266
  },
178
- status: m.manifest.status ?? 'active',
179
- navOrder: m.navOrder,
267
+ status: 'active',
268
+ navigationOrder: m.navigationOrder,
180
269
  icon: m.manifest.icon,
270
+ navigation: m.manifest.navigation ? {
271
+ style: m.manifest.navigation.style,
272
+ sections: m.manifest.navigation.sections?.map(s => ({
273
+ id: s.id,
274
+ title: s.label,
275
+ })),
276
+ } : undefined,
181
277
  commands: m.manifest.commands?.map((cmd) => ({
182
278
  id: cmd.id,
183
279
  title: cmd.title,
184
280
  keywords: cmd.keywords,
185
281
  route: cmd.route,
186
282
  icon: cmd.icon,
283
+ section: cmd.section,
187
284
  })),
188
285
  }));
286
+ data = loadStorage(storageKey);
189
287
  return {
190
- version: '1.0.0',
288
+ version: computeLocalVersion(data),
191
289
  generatedAt: new Date().toISOString(),
192
290
  modules: catalogModules,
193
291
  };
194
292
  },
293
+ async version() {
294
+ data = loadStorage(storageKey);
295
+ return {
296
+ version: computeLocalVersion(data),
297
+ generatedAt: new Date().toISOString(),
298
+ };
299
+ },
195
300
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
196
301
  async getImportMap(_environment) {
197
302
  const allModules = await modules.findAll({ enabled: true });
@@ -4,4 +4,3 @@
4
4
  export type { RegistryClient, ModuleOperations, CatalogOperations } from './interface';
5
5
  export { createInMemoryRegistryClient, clearInMemoryRegistry, type InMemoryRegistryOptions, type PreConfiguredModule, } from './in-memory';
6
6
  export { createHttpRegistryClient, type HttpRegistryOptions, } from './http';
7
- //# sourceMappingURL=index.d.ts.map
@@ -4,7 +4,7 @@
4
4
  * Defines the interface for registry operations.
5
5
  * Can be implemented by in-memory (localStorage) or HTTP clients.
6
6
  */
7
- import type { RegisteredModule, UpdateModuleDto, ReorderModuleDto, ModuleFilters, Catalog, ImportMap, AdminModuleManifest } from '../types';
7
+ import type { RegisteredModule, UpdateModuleDto, ReorderModuleDto, ModuleFilters, Catalog, CatalogVersion, ImportMap, AdminModuleManifest, ModuleVersion } from '../types';
8
8
  /**
9
9
  * Module operations interface
10
10
  */
@@ -42,10 +42,21 @@ export interface ModuleOperations {
42
42
  */
43
43
  delete(id: number): Promise<void>;
44
44
  /**
45
- * Reorder modules (batch update navOrder)
46
- * @param order Array of id and navOrder pairs
45
+ * Reorder modules (batch update navigationOrder)
46
+ * @param order Array of id and navigationOrder pairs
47
47
  */
48
48
  reorder(order: ReorderModuleDto[]): Promise<void>;
49
+ /**
50
+ * List published versions for a module
51
+ * @param id Module ID
52
+ */
53
+ listVersions(id: number): Promise<ModuleVersion[]>;
54
+ /**
55
+ * Rollback a module to a previously published version
56
+ * @param id Module ID
57
+ * @param targetVersion Version string to roll back to
58
+ */
59
+ rollback(id: number, targetVersion: string): Promise<RegisteredModule>;
49
60
  }
50
61
  /**
51
62
  * Catalog operations interface (read-only)
@@ -55,6 +66,11 @@ export interface CatalogOperations {
55
66
  * Get the full catalog (denormalized for shell consumption)
56
67
  */
57
68
  get(): Promise<Catalog>;
69
+ /**
70
+ * Get the current catalog version without fetching the full catalog.
71
+ * Used for lightweight polling to detect changes.
72
+ */
73
+ version(): Promise<CatalogVersion>;
58
74
  /**
59
75
  * Get the import map for a specific environment
60
76
  * @param environment Environment name (e.g., 'production', 'local')
@@ -79,8 +95,8 @@ export interface CatalogOperations {
79
95
  *
80
96
  * // Reorder
81
97
  * await registry.modules.reorder([
82
- * { id: 1, navOrder: 2 },
83
- * { id: 2, navOrder: 1 },
98
+ * { id: 1, navigationOrder: 2 },
99
+ * { id: 2, navigationOrder: 1 },
84
100
  * ]);
85
101
  *
86
102
  * // Get catalog
@@ -93,4 +109,3 @@ export interface RegistryClient {
93
109
  /** Catalog operations (read-only) */
94
110
  catalog: CatalogOperations;
95
111
  }
96
- //# sourceMappingURL=interface.d.ts.map
@@ -3,10 +3,13 @@
3
3
  *
4
4
  * Provides types and clients for module registry management.
5
5
  */
6
- export type { AdminModuleManifest, ModuleCommand, ModulePermissions, ModuleOwners, ModuleStatus, RegisteredModule, UpdateModuleDto, ReorderModuleDto, ModuleFilters, Catalog, CatalogModule, ImportMap, } from './types';
6
+ export type { AdminModuleManifest, ModuleCommand, ModulePermissions, ModuleOwners, RegisteredModule, UpdateModuleDto, ReorderModuleDto, ModuleFilters, Catalog, CatalogModule, CatalogVersion, ImportMap, ModuleVersion, } from './types';
7
7
  export { validateManifest, parseManifest } from './types';
8
8
  export type { RegistryClient, ModuleOperations, CatalogOperations } from './client';
9
9
  export { createInMemoryRegistryClient, clearInMemoryRegistry, createHttpRegistryClient, type InMemoryRegistryOptions, type PreConfiguredModule, type HttpRegistryOptions, } from './client';
10
+ export { CatalogCache, createCachedCatalog, CACHE_SCHEMA_VERSION, CACHE_STORAGE_KEY, DEFAULT_STALE_THRESHOLD_MS, } from './cache';
11
+ export type { CachedCatalogOperations, CachedCatalogOptions, CachedCatalog, RegistryCacheStatus, } from './cache';
12
+ export { useRegistryPolling } from './useRegistryPolling';
13
+ export type { RegistryPollingState, UseRegistryPollingOptions } from './useRegistryPolling';
10
14
  export type { RegistryContextValue, AdminShellRegistryProps, } from './AdminShellRegistry';
11
15
  export { AdminShellRegistry, useRegistry, useRegistryClient, } from './AdminShellRegistry';
12
- //# sourceMappingURL=index.d.ts.map
@@ -5,4 +5,8 @@
5
5
  */
6
6
  export { validateManifest, parseManifest } from './types';
7
7
  export { createInMemoryRegistryClient, clearInMemoryRegistry, createHttpRegistryClient, } from './client';
8
+ // Cache
9
+ export { CatalogCache, createCachedCatalog, CACHE_SCHEMA_VERSION, CACHE_STORAGE_KEY, DEFAULT_STALE_THRESHOLD_MS, } from './cache';
10
+ // Polling
11
+ export { useRegistryPolling } from './useRegistryPolling';
8
12
  export { AdminShellRegistry, useRegistry, useRegistryClient, } from './AdminShellRegistry';
@@ -3,7 +3,6 @@
3
3
  *
4
4
  * Re-exports all registry-related types.
5
5
  */
6
- export type { AdminModuleManifest, ModuleCommand, ModulePermissions, ModuleOwners, ModuleStatus, } from './manifest';
6
+ export type { AdminModuleManifest, ModuleCommand, ModulePermissions, ModuleOwners, } from './manifest';
7
7
  export { validateManifest, parseManifest } from './manifest';
8
- export type { RegisteredModule, UpdateModuleDto, ReorderModuleDto, ModuleFilters, Catalog, CatalogModule, ImportMap, } from './module';
9
- //# sourceMappingURL=index.d.ts.map
8
+ export type { RegisteredModule, UpdateModuleDto, ReorderModuleDto, ModuleFilters, Catalog, CatalogModule, CatalogVersion, ImportMap, ModuleVersion, } from './module';
@@ -4,22 +4,23 @@
4
4
  * This interface defines the structure of admin.module.json files
5
5
  * that module developers create to register their modules.
6
6
  */
7
+ import type { LocalizedField } from '../../i18n/config.js';
7
8
  /**
8
9
  * Command within a module that can be searched and invoked
9
10
  */
10
11
  export interface ModuleCommand {
11
12
  /** Unique identifier for the command within the module */
12
13
  id: string;
13
- /** Human-readable title */
14
- title: string;
15
- /** i18n key for the title (for translation) */
16
- titleKey?: string;
14
+ /** Human-readable title (localized per locale) */
15
+ title: LocalizedField;
17
16
  /** Route path for this command */
18
17
  route: string;
19
18
  /** Optional Lucide icon name in kebab-case (e.g., "clipboard-list", "file-text") */
20
19
  icon?: string;
21
20
  /** Keywords for search */
22
21
  keywords?: string[];
22
+ /** Section ID for grouping in stacked navigation */
23
+ section?: string;
23
24
  }
24
25
  /**
25
26
  * Permission configuration for a module
@@ -43,10 +44,6 @@ export interface ModuleOwners {
43
44
  /** Support channel (e.g., Slack channel) */
44
45
  supportChannel?: string;
45
46
  }
46
- /**
47
- * Module status
48
- */
49
- export type ModuleStatus = 'active' | 'deprecated' | 'disabled';
50
47
  /**
51
48
  * Admin Module Manifest
52
49
  *
@@ -55,30 +52,30 @@ export type ModuleStatus = 'active' | 'deprecated' | 'disabled';
55
52
  export interface AdminModuleManifest {
56
53
  /** Unique identifier for the module (e.g., "@admin/tasks") */
57
54
  id: string;
58
- /** Human-readable title */
59
- title: string;
55
+ /** Human-readable title (localized per locale) */
56
+ title: LocalizedField;
60
57
  /** Base route where module is mounted (e.g., "/admin/tasks") */
61
58
  routeBase: string;
62
- /** i18n key for the title (for translation) */
63
- titleKey?: string;
64
- /** URL slug (derived from id if not provided) */
65
- slug?: string;
66
- /** Description of what the module does */
67
- description?: string;
68
- /** i18n key for the description (for translation) */
69
- descriptionKey?: string;
59
+ /** Description of what the module does (localized per locale, required) */
60
+ description: LocalizedField;
70
61
  /** Category for navigation grouping (e.g., "Tools", "Customer") */
71
62
  category?: string;
72
- /** Semantic version */
73
- version?: string;
74
63
  /** Lucide icon name in kebab-case (e.g., "users", "clipboard-list") */
75
64
  icon?: string;
76
65
  /** Keywords for search */
77
66
  keywords?: string[];
78
67
  /** Navigation order (lower = higher priority) */
79
- navOrder?: number;
80
- /** Module status */
81
- status?: ModuleStatus;
68
+ navigationOrder?: number;
69
+ /** Navigation configuration */
70
+ navigation?: {
71
+ /** Navigation style: "collapsible" (default) or "stacked" (dedicated panel) */
72
+ style: "collapsible" | "stacked";
73
+ /** Sections for grouping commands in stacked mode */
74
+ sections?: Array<{
75
+ id: string;
76
+ label: LocalizedField;
77
+ }>;
78
+ };
82
79
  /** Commands/actions available in this module */
83
80
  commands?: ModuleCommand[];
84
81
  /** Permission configuration */
@@ -95,4 +92,3 @@ export declare function validateManifest(value: unknown): value is AdminModuleMa
95
92
  * @throws Error if validation fails
96
93
  */
97
94
  export declare function parseManifest(value: unknown): AdminModuleManifest;
98
- //# sourceMappingURL=manifest.d.ts.map
@@ -4,6 +4,17 @@
4
4
  * This interface defines the structure of admin.module.json files
5
5
  * that module developers create to register their modules.
6
6
  */
7
+ import { SUPPORTED_LOCALES } from '../../i18n/config.js';
8
+ function isLocalizedField(value) {
9
+ if (typeof value !== 'object' || value === null)
10
+ return false;
11
+ const obj = value;
12
+ for (const locale of SUPPORTED_LOCALES) {
13
+ if (typeof obj[locale] !== 'string')
14
+ return false;
15
+ }
16
+ return true;
17
+ }
7
18
  /**
8
19
  * Validates that an object is a valid AdminModuleManifest
9
20
  */
@@ -16,35 +27,23 @@ export function validateManifest(value) {
16
27
  if (typeof obj.id !== 'string' || obj.id.trim() === '') {
17
28
  return false;
18
29
  }
19
- if (typeof obj.title !== 'string' || obj.title.trim() === '') {
30
+ if (!isLocalizedField(obj.title)) {
20
31
  return false;
21
32
  }
22
33
  if (typeof obj.routeBase !== 'string' || !obj.routeBase.startsWith('/')) {
23
34
  return false;
24
35
  }
25
- // Optional field validations
26
- if (obj.slug !== undefined && typeof obj.slug !== 'string') {
27
- return false;
28
- }
29
- if (obj.description !== undefined && typeof obj.description !== 'string') {
36
+ if (!isLocalizedField(obj.description)) {
30
37
  return false;
31
38
  }
39
+ // Optional field validations
32
40
  if (obj.category !== undefined && typeof obj.category !== 'string') {
33
41
  return false;
34
42
  }
35
- if (obj.version !== undefined && typeof obj.version !== 'string') {
36
- return false;
37
- }
38
43
  if (obj.icon !== undefined && typeof obj.icon !== 'string') {
39
44
  return false;
40
45
  }
41
- if (obj.navOrder !== undefined && typeof obj.navOrder !== 'number') {
42
- return false;
43
- }
44
- if (obj.status !== undefined &&
45
- obj.status !== 'active' &&
46
- obj.status !== 'deprecated' &&
47
- obj.status !== 'disabled') {
46
+ if (obj.navigationOrder !== undefined && typeof obj.navigationOrder !== 'number') {
48
47
  return false;
49
48
  }
50
49
  if (obj.keywords !== undefined) {
@@ -61,7 +60,7 @@ export function validateManifest(value) {
61
60
  return false;
62
61
  if (typeof cmd.id !== 'string')
63
62
  return false;
64
- if (typeof cmd.title !== 'string')
63
+ if (!isLocalizedField(cmd.title))
65
64
  return false;
66
65
  if (typeof cmd.route !== 'string')
67
66
  return false;
@@ -75,7 +74,7 @@ export function validateManifest(value) {
75
74
  */
76
75
  export function parseManifest(value) {
77
76
  if (!validateManifest(value)) {
78
- throw new Error('Invalid manifest: must have id, title, and routeBase (starting with /)');
77
+ throw new Error('Invalid manifest: must have id, title (localized), description (localized), and routeBase (starting with /). Title and description must be objects with all 4 locales: en-US, pt-BR, es, ro.');
79
78
  }
80
79
  return value;
81
80
  }
@@ -14,10 +14,12 @@ export interface RegisteredModule {
14
14
  moduleId: string;
15
15
  /** Base URL where the module is hosted */
16
16
  baseUrl: string;
17
+ /** Currently active version (semver) */
18
+ version?: string;
17
19
  /** Full parsed module.manifest.json (without entry - fetched at runtime) */
18
20
  manifest: AdminModuleManifest;
19
21
  /** Admin-controlled navigation order */
20
- navOrder: number;
22
+ navigationOrder: number;
21
23
  /** Whether the module is enabled */
22
24
  enabled: boolean;
23
25
  /** ISO timestamp when registered */
@@ -31,9 +33,13 @@ export interface RegisteredModule {
31
33
  */
32
34
  export interface UpdateModuleDto {
33
35
  /** Update navigation order */
34
- navOrder?: number;
36
+ navigationOrder?: number;
35
37
  /** Enable or disable the module */
36
38
  enabled?: boolean;
39
+ /** Update base URL */
40
+ baseUrl?: string;
41
+ /** Update active version */
42
+ version?: string;
37
43
  }
38
44
  /**
39
45
  * DTO for reordering modules
@@ -42,7 +48,7 @@ export interface ReorderModuleDto {
42
48
  /** Module ID */
43
49
  id: number;
44
50
  /** New navigation order */
45
- navOrder: number;
51
+ navigationOrder: number;
46
52
  }
47
53
  /**
48
54
  * Filters for listing modules
@@ -66,18 +72,17 @@ export interface Catalog {
66
72
  /** Array of modules in catalog format */
67
73
  modules: CatalogModule[];
68
74
  }
75
+ import type { LocalizedField } from '../../i18n/config.js';
69
76
  /**
70
77
  * Module in catalog format (denormalized for shell)
71
78
  * Note: entry is not included - DynamicModule fetches module.manifest.json at runtime
72
79
  */
73
80
  export interface CatalogModule {
74
81
  id: string;
75
- title: string;
76
- /** i18n key for the title (for translation) */
77
- titleKey?: string;
78
- description: string;
79
- /** i18n key for the description (for translation) */
80
- descriptionKey?: string;
82
+ /** Human-readable title (localized per locale) */
83
+ title: LocalizedField;
84
+ /** Description of what the module does (localized per locale) */
85
+ description: LocalizedField;
81
86
  category: string;
82
87
  routeBase: string;
83
88
  /** Base URL where the module is hosted */
@@ -92,18 +97,32 @@ export interface CatalogModule {
92
97
  supportChannel: string;
93
98
  };
94
99
  status: 'active' | 'deprecated' | 'disabled';
95
- navOrder?: number;
100
+ navigationOrder?: number;
96
101
  icon?: string;
102
+ navigation?: {
103
+ style: "collapsible" | "stacked";
104
+ sections?: Array<{
105
+ id: string;
106
+ title: LocalizedField;
107
+ }>;
108
+ };
97
109
  commands?: Array<{
98
110
  id: string;
99
- title: string;
100
- /** i18n key for the title (for translation) */
101
- titleKey?: string;
111
+ /** Human-readable title (localized per locale) */
112
+ title: LocalizedField;
102
113
  keywords?: string[];
103
114
  route: string;
104
115
  icon?: string;
116
+ section?: string;
105
117
  }>;
106
118
  }
119
+ /**
120
+ * Lightweight catalog version (no full payload)
121
+ */
122
+ export interface CatalogVersion {
123
+ version: string;
124
+ generatedAt: string;
125
+ }
107
126
  /**
108
127
  * Import map structure for dynamic module loading
109
128
  */
@@ -112,4 +131,14 @@ export interface ImportMap {
112
131
  [moduleName: string]: string;
113
132
  };
114
133
  }
115
- //# sourceMappingURL=module.d.ts.map
134
+ /**
135
+ * A published version entry from the version history
136
+ */
137
+ export interface ModuleVersion {
138
+ id: number;
139
+ version: string;
140
+ baseUrl: string;
141
+ publishedAt: string;
142
+ active: boolean;
143
+ metadata?: Record<string, unknown>;
144
+ }
@@ -0,0 +1,15 @@
1
+ import type { RegistryClient } from './client/interface';
2
+ export interface RegistryPollingState {
3
+ /** Whether newer catalog version is available */
4
+ hasUpdates: boolean;
5
+ /** Dismiss the current banner; re-shows on a subsequent newer version */
6
+ dismiss: () => void;
7
+ }
8
+ export interface UseRegistryPollingOptions {
9
+ registryClient: RegistryClient;
10
+ /** Version string from the initial catalog.get() response */
11
+ initialVersion: string;
12
+ /** Polling interval in milliseconds. 0 or undefined disables polling. */
13
+ interval: number | undefined;
14
+ }
15
+ export declare function useRegistryPolling({ registryClient, initialVersion, interval, }: UseRegistryPollingOptions): RegistryPollingState;
@@ -0,0 +1,66 @@
1
+ import { useState, useEffect, useRef, useCallback } from 'react';
2
+ export function useRegistryPolling({ registryClient, initialVersion, interval, }) {
3
+ const [hasUpdates, setHasUpdates] = useState(false);
4
+ const dismissedVersionRef = useRef(null);
5
+ const latestServerVersionRef = useRef(initialVersion);
6
+ const loadedVersionRef = useRef(initialVersion);
7
+ useEffect(() => {
8
+ loadedVersionRef.current = initialVersion;
9
+ latestServerVersionRef.current = initialVersion;
10
+ dismissedVersionRef.current = null;
11
+ setHasUpdates(false);
12
+ }, [initialVersion]);
13
+ const dismiss = useCallback(() => {
14
+ dismissedVersionRef.current = latestServerVersionRef.current;
15
+ setHasUpdates(false);
16
+ }, []);
17
+ useEffect(() => {
18
+ if (!interval || interval <= 0)
19
+ return;
20
+ let timerId = null;
21
+ let destroyed = false;
22
+ async function check() {
23
+ try {
24
+ const { version } = await registryClient.catalog.version();
25
+ if (destroyed)
26
+ return;
27
+ latestServerVersionRef.current = version;
28
+ const changed = version !== loadedVersionRef.current;
29
+ const dismissed = version === dismissedVersionRef.current;
30
+ setHasUpdates(changed && !dismissed);
31
+ }
32
+ catch {
33
+ // Network errors are silently ignored per spec
34
+ }
35
+ }
36
+ function scheduleNext() {
37
+ if (destroyed)
38
+ return;
39
+ timerId = setTimeout(async () => {
40
+ await check();
41
+ scheduleNext();
42
+ }, interval);
43
+ }
44
+ function handleVisibilityChange() {
45
+ if (document.hidden) {
46
+ if (timerId !== null) {
47
+ clearTimeout(timerId);
48
+ timerId = null;
49
+ }
50
+ }
51
+ else {
52
+ check();
53
+ scheduleNext();
54
+ }
55
+ }
56
+ scheduleNext();
57
+ document.addEventListener('visibilitychange', handleVisibilityChange);
58
+ return () => {
59
+ destroyed = true;
60
+ if (timerId !== null)
61
+ clearTimeout(timerId);
62
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
63
+ };
64
+ }, [registryClient, interval]);
65
+ return { hasUpdates, dismiss };
66
+ }