@intranefr/superbackend 1.4.4 → 1.5.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 (195) hide show
  1. package/.env.example +5 -0
  2. package/README.md +11 -0
  3. package/index.js +39 -1
  4. package/package.json +11 -3
  5. package/public/sdk/ui-components.iife.js +191 -0
  6. package/sdk/ui-components/browser/src/index.js +228 -0
  7. package/src/admin/endpointRegistry.js +120 -0
  8. package/src/controllers/admin.controller.js +111 -5
  9. package/src/controllers/adminBlockDefinitions.controller.js +127 -0
  10. package/src/controllers/adminBlockDefinitionsAi.controller.js +54 -0
  11. package/src/controllers/adminCache.controller.js +342 -0
  12. package/src/controllers/adminContextBlockDefinitions.controller.js +141 -0
  13. package/src/controllers/adminCrons.controller.js +388 -0
  14. package/src/controllers/adminDbBrowser.controller.js +124 -0
  15. package/src/controllers/adminEjsVirtual.controller.js +13 -3
  16. package/src/controllers/adminHeadless.controller.js +91 -2
  17. package/src/controllers/adminHealthChecks.controller.js +570 -0
  18. package/src/controllers/adminI18n.controller.js +51 -29
  19. package/src/controllers/adminLlm.controller.js +126 -2
  20. package/src/controllers/adminPages.controller.js +720 -0
  21. package/src/controllers/adminPagesContextBlocksAi.controller.js +54 -0
  22. package/src/controllers/adminProxy.controller.js +113 -0
  23. package/src/controllers/adminRateLimits.controller.js +138 -0
  24. package/src/controllers/adminRbac.controller.js +803 -0
  25. package/src/controllers/adminScripts.controller.js +320 -0
  26. package/src/controllers/adminSeoConfig.controller.js +71 -48
  27. package/src/controllers/adminTerminals.controller.js +39 -0
  28. package/src/controllers/adminUiComponents.controller.js +315 -0
  29. package/src/controllers/adminUiComponentsAi.controller.js +34 -0
  30. package/src/controllers/blogAdmin.controller.js +279 -0
  31. package/src/controllers/blogAiAdmin.controller.js +224 -0
  32. package/src/controllers/blogAutomationAdmin.controller.js +141 -0
  33. package/src/controllers/blogInternal.controller.js +26 -0
  34. package/src/controllers/blogPublic.controller.js +89 -0
  35. package/src/controllers/fileManager.controller.js +190 -0
  36. package/src/controllers/fileManagerStoragePolicy.controller.js +23 -0
  37. package/src/controllers/healthChecksPublic.controller.js +196 -0
  38. package/src/controllers/metrics.controller.js +64 -4
  39. package/src/controllers/orgAdmin.controller.js +366 -0
  40. package/src/controllers/uiComponentsPublic.controller.js +118 -0
  41. package/src/middleware/auth.js +7 -0
  42. package/src/middleware/internalCronAuth.js +29 -0
  43. package/src/middleware/rbac.js +62 -0
  44. package/src/middleware.js +879 -56
  45. package/src/models/BlockDefinition.js +27 -0
  46. package/src/models/BlogAutomationLock.js +14 -0
  47. package/src/models/BlogAutomationRun.js +39 -0
  48. package/src/models/BlogPost.js +42 -0
  49. package/src/models/CacheEntry.js +26 -0
  50. package/src/models/ConsoleEntry.js +32 -0
  51. package/src/models/ConsoleLog.js +23 -0
  52. package/src/models/ContextBlockDefinition.js +33 -0
  53. package/src/models/CronExecution.js +47 -0
  54. package/src/models/CronJob.js +70 -0
  55. package/src/models/ExternalDbConnection.js +49 -0
  56. package/src/models/FileEntry.js +22 -0
  57. package/src/models/HeadlessModelDefinition.js +10 -0
  58. package/src/models/HealthAutoHealAttempt.js +57 -0
  59. package/src/models/HealthCheck.js +132 -0
  60. package/src/models/HealthCheckRun.js +51 -0
  61. package/src/models/HealthIncident.js +49 -0
  62. package/src/models/Page.js +95 -0
  63. package/src/models/PageCollection.js +42 -0
  64. package/src/models/ProxyEntry.js +66 -0
  65. package/src/models/RateLimitCounter.js +19 -0
  66. package/src/models/RateLimitMetricBucket.js +20 -0
  67. package/src/models/RbacGrant.js +25 -0
  68. package/src/models/RbacGroup.js +16 -0
  69. package/src/models/RbacGroupMember.js +13 -0
  70. package/src/models/RbacGroupRole.js +13 -0
  71. package/src/models/RbacRole.js +25 -0
  72. package/src/models/RbacUserRole.js +13 -0
  73. package/src/models/ScriptDefinition.js +42 -0
  74. package/src/models/ScriptRun.js +22 -0
  75. package/src/models/UiComponent.js +29 -0
  76. package/src/models/UiComponentProject.js +26 -0
  77. package/src/models/UiComponentProjectComponent.js +18 -0
  78. package/src/routes/admin.routes.js +1 -0
  79. package/src/routes/adminBlog.routes.js +21 -0
  80. package/src/routes/adminBlogAi.routes.js +16 -0
  81. package/src/routes/adminBlogAutomation.routes.js +27 -0
  82. package/src/routes/adminCache.routes.js +20 -0
  83. package/src/routes/adminConsoleManager.routes.js +302 -0
  84. package/src/routes/adminCrons.routes.js +25 -0
  85. package/src/routes/adminDbBrowser.routes.js +65 -0
  86. package/src/routes/adminEjsVirtual.routes.js +2 -1
  87. package/src/routes/adminHeadless.routes.js +8 -1
  88. package/src/routes/adminHealthChecks.routes.js +28 -0
  89. package/src/routes/adminI18n.routes.js +4 -3
  90. package/src/routes/adminLlm.routes.js +4 -2
  91. package/src/routes/adminPages.routes.js +55 -0
  92. package/src/routes/adminProxy.routes.js +15 -0
  93. package/src/routes/adminRateLimits.routes.js +17 -0
  94. package/src/routes/adminRbac.routes.js +38 -0
  95. package/src/routes/adminScripts.routes.js +21 -0
  96. package/src/routes/adminSeoConfig.routes.js +5 -4
  97. package/src/routes/adminTerminals.routes.js +13 -0
  98. package/src/routes/adminUiComponents.routes.js +30 -0
  99. package/src/routes/blogInternal.routes.js +14 -0
  100. package/src/routes/blogPublic.routes.js +9 -0
  101. package/src/routes/fileManager.routes.js +62 -0
  102. package/src/routes/fileManagerStoragePolicy.routes.js +9 -0
  103. package/src/routes/healthChecksPublic.routes.js +9 -0
  104. package/src/routes/log.routes.js +43 -60
  105. package/src/routes/metrics.routes.js +4 -2
  106. package/src/routes/orgAdmin.routes.js +6 -0
  107. package/src/routes/pages.routes.js +123 -0
  108. package/src/routes/proxy.routes.js +46 -0
  109. package/src/routes/rbac.routes.js +47 -0
  110. package/src/routes/uiComponentsPublic.routes.js +9 -0
  111. package/src/routes/webhook.routes.js +2 -1
  112. package/src/routes/workflows.routes.js +4 -0
  113. package/src/services/blockDefinitionsAi.service.js +247 -0
  114. package/src/services/blog.service.js +99 -0
  115. package/src/services/blogAutomation.service.js +978 -0
  116. package/src/services/blogCronsBootstrap.service.js +184 -0
  117. package/src/services/blogPublishing.service.js +58 -0
  118. package/src/services/cacheLayer.service.js +696 -0
  119. package/src/services/consoleManager.service.js +700 -0
  120. package/src/services/consoleOverride.service.js +6 -1
  121. package/src/services/cronScheduler.service.js +350 -0
  122. package/src/services/dbBrowser.service.js +536 -0
  123. package/src/services/ejsVirtual.service.js +102 -32
  124. package/src/services/fileManager.service.js +475 -0
  125. package/src/services/fileManagerStoragePolicy.service.js +285 -0
  126. package/src/services/headlessExternalModels.service.js +292 -0
  127. package/src/services/headlessModels.service.js +26 -6
  128. package/src/services/healthChecks.service.js +650 -0
  129. package/src/services/healthChecksBootstrap.service.js +109 -0
  130. package/src/services/healthChecksScheduler.service.js +106 -0
  131. package/src/services/llmDefaults.service.js +190 -0
  132. package/src/services/migrationAssets/s3.js +2 -2
  133. package/src/services/pages.service.js +602 -0
  134. package/src/services/pagesContext.service.js +331 -0
  135. package/src/services/pagesContextBlocksAi.service.js +349 -0
  136. package/src/services/proxy.service.js +535 -0
  137. package/src/services/rateLimiter.service.js +623 -0
  138. package/src/services/rbac.service.js +212 -0
  139. package/src/services/scriptsRunner.service.js +259 -0
  140. package/src/services/terminals.service.js +152 -0
  141. package/src/services/terminalsWs.service.js +100 -0
  142. package/src/services/uiComponentsAi.service.js +299 -0
  143. package/src/services/uiComponentsCrypto.service.js +39 -0
  144. package/src/services/workflow.service.js +23 -8
  145. package/src/utils/orgRoles.js +14 -0
  146. package/src/utils/rbac/engine.js +60 -0
  147. package/src/utils/rbac/rightsRegistry.js +29 -0
  148. package/views/admin-blog-automation.ejs +877 -0
  149. package/views/admin-blog-edit.ejs +542 -0
  150. package/views/admin-blog.ejs +399 -0
  151. package/views/admin-cache.ejs +681 -0
  152. package/views/admin-console-manager.ejs +680 -0
  153. package/views/admin-crons.ejs +645 -0
  154. package/views/admin-db-browser.ejs +445 -0
  155. package/views/admin-ejs-virtual.ejs +16 -10
  156. package/views/admin-file-manager.ejs +942 -0
  157. package/views/admin-headless.ejs +294 -24
  158. package/views/admin-health-checks.ejs +725 -0
  159. package/views/admin-i18n.ejs +59 -5
  160. package/views/admin-llm.ejs +99 -1
  161. package/views/admin-organizations.ejs +528 -10
  162. package/views/admin-pages.ejs +2424 -0
  163. package/views/admin-proxy.ejs +491 -0
  164. package/views/admin-rate-limiter.ejs +625 -0
  165. package/views/admin-rbac.ejs +1331 -0
  166. package/views/admin-scripts.ejs +497 -0
  167. package/views/admin-seo-config.ejs +61 -7
  168. package/views/admin-terminals.ejs +328 -0
  169. package/views/admin-ui-components.ejs +741 -0
  170. package/views/admin-users.ejs +261 -4
  171. package/views/admin-workflows.ejs +7 -7
  172. package/views/file-manager.ejs +866 -0
  173. package/views/pages/blocks/contact.ejs +27 -0
  174. package/views/pages/blocks/cta.ejs +18 -0
  175. package/views/pages/blocks/faq.ejs +20 -0
  176. package/views/pages/blocks/features.ejs +19 -0
  177. package/views/pages/blocks/hero.ejs +13 -0
  178. package/views/pages/blocks/html.ejs +5 -0
  179. package/views/pages/blocks/image.ejs +14 -0
  180. package/views/pages/blocks/testimonials.ejs +26 -0
  181. package/views/pages/blocks/text.ejs +10 -0
  182. package/views/pages/layouts/default.ejs +51 -0
  183. package/views/pages/layouts/minimal.ejs +42 -0
  184. package/views/pages/layouts/sidebar.ejs +54 -0
  185. package/views/pages/partials/footer.ejs +13 -0
  186. package/views/pages/partials/header.ejs +12 -0
  187. package/views/pages/partials/sidebar.ejs +8 -0
  188. package/views/pages/runtime/page.ejs +10 -0
  189. package/views/pages/templates/article.ejs +20 -0
  190. package/views/pages/templates/default.ejs +12 -0
  191. package/views/pages/templates/landing.ejs +14 -0
  192. package/views/pages/templates/listing.ejs +15 -0
  193. package/views/partials/admin-image-upload-modal.ejs +221 -0
  194. package/views/partials/dashboard/nav-items.ejs +14 -0
  195. package/views/partials/llm-provider-model-picker.ejs +183 -0
package/.env.example CHANGED
@@ -50,3 +50,8 @@ MAX_FILE_SIZE_HARD_CAP=10485760
50
50
  # Encryption key for encrypted settings (new preferred name)
51
51
  # SUPERBACKEND_ENCRYPTION_KEY=your-32-byte-encryption-key
52
52
  # Legacy fallback: SAASBACKEND_ENCRYPTION_KEY=your-32-byte-encryption-key
53
+
54
+ # Console Manager
55
+ # Set to 'false' to disable console manager initialization
56
+ # When disabled, console methods are not overridden and no console entries are tracked
57
+ # CONSOLE_MANAGER_ENABLED=true
package/README.md CHANGED
@@ -23,6 +23,13 @@ Node.js middleware that gives your project backend superpowers. Handles authenti
23
23
  - **Webhooks**: Outgoing webhook system for event-driven integrations
24
24
  - **Metrics & Activity**: Usage tracking and analytics for business insights
25
25
  - **Middleware Mode**: Drop-in Express middleware that preserves your app structure
26
+ - **Workflows System**: Node-based automation with LLM integration, conditionals, and HTTP calls
27
+ - **LLM UI Integration**: AI-powered UI components and conversational interfaces
28
+ - **Admin Scripts & Terminals**: Operational tooling for script execution and terminal management
29
+ - **Migration System**: Database migration and data transfer between environments
30
+ - **Upload Namespaces**: Advanced file organization with customizable storage rules
31
+ - **UI Components**: Project-scoped reusable UI widgets with browser SDK integration
32
+ - **UI Components AI Assistance**: AI-powered generation and iteration of UI component code
26
33
 
27
34
  ---
28
35
 
@@ -85,6 +92,10 @@ See the `docs/features/` directory for detailed guides:
85
92
  - [Core Configuration](docs/features/core-configuration.md)
86
93
  - [Admin API Usage](docs/features/admin-api-usage.md)
87
94
  - [Billing & Subscriptions](docs/features/billing-and-subscriptions.md)
95
+ - [Workflows System](docs/features/workflows-system.md)
96
+ - [LLM UI Integration](docs/features/llm-ui-integration.md)
97
+ - [Migration System](docs/features/migration-system.md)
98
+ - [Upload Namespaces](docs/features/upload-namespaces.md)
88
99
 
89
100
  ---
90
101
 
package/index.js CHANGED
@@ -11,6 +11,7 @@ const express = require("express");
11
11
  * @returns {express.Router} Configured Express router
12
12
  */
13
13
  const middleware = require("./src/middleware");
14
+ const { attachTerminalWebsocketServer } = require('./src/services/terminalsWs.service');
14
15
 
15
16
  /**
16
17
  * Creates and starts a standalone SuperBackend server
@@ -24,13 +25,26 @@ function startServer(options = {}) {
24
25
  const app = express();
25
26
  const PORT = options.port || process.env.PORT || 3000;
26
27
 
27
- app.use(module.exports.middleware(options));
28
+ const router = module.exports.middleware(options);
29
+ app.use(router);
28
30
 
29
31
  // Start server
30
32
  const server = app.listen(PORT, () => {
31
33
  console.log(`🚀 SuperBackend standalone server running on http://localhost:${PORT}`);
32
34
  });
33
35
 
36
+ // Attach WebSocket server via middleware helper or directly
37
+ console.log('[Index] Attaching WebSocket server...');
38
+ if (typeof router.attachWs === 'function') {
39
+ console.log('[Index] Using router.attachWs');
40
+ router.attachWs(server);
41
+ } else {
42
+ // Fallback: attach directly with admin path
43
+ const adminPath = router.adminPath || '/admin';
44
+ console.log('[Index] Using fallback attach with adminPath:', adminPath);
45
+ attachTerminalWebsocketServer(server, { basePathPrefix: adminPath });
46
+ }
47
+
34
48
  return { app, server };
35
49
  }
36
50
 
@@ -48,6 +62,8 @@ const saasbackend = {
48
62
  storage: require("./src/services/storage"),
49
63
  i18n: require("./src/services/i18n.service"),
50
64
  audit: require("./src/services/audit.service"),
65
+ cacheLayer: require("./src/services/cacheLayer.service"),
66
+ rbac: require("./src/services/rbac.service"),
51
67
  globalSettings: require("./src/services/globalSettings.service"),
52
68
  jsonConfigs: require("./src/services/jsonConfigs.service"),
53
69
  assets: require("./src/services/assets.service"),
@@ -58,12 +74,24 @@ const saasbackend = {
58
74
  forms: require("./src/services/forms.service"),
59
75
  webhooks: require("./src/services/webhook.service"),
60
76
  workflow: require("./src/services/workflow.service"),
77
+ healthChecks: require("./src/services/healthChecks.service"),
78
+ dbBrowser: require("./src/services/dbBrowser.service"),
79
+ rateLimiter: require("./src/services/rateLimiter.service"),
61
80
  },
62
81
  models: {
63
82
  ActionEvent: require("./src/models/ActionEvent"),
64
83
  ActivityLog: require("./src/models/ActivityLog"),
65
84
  Asset: require("./src/models/Asset"),
66
85
  AuditEvent: require("./src/models/AuditEvent"),
86
+ CacheEntry: require("./src/models/CacheEntry"),
87
+ RateLimitCounter: require("./src/models/RateLimitCounter"),
88
+ RateLimitMetricBucket: require("./src/models/RateLimitMetricBucket"),
89
+ RbacRole: require("./src/models/RbacRole"),
90
+ RbacUserRole: require("./src/models/RbacUserRole"),
91
+ RbacGroup: require("./src/models/RbacGroup"),
92
+ RbacGroupMember: require("./src/models/RbacGroupMember"),
93
+ RbacGroupRole: require("./src/models/RbacGroupRole"),
94
+ RbacGrant: require("./src/models/RbacGrant"),
67
95
  EmailLog: require("./src/models/EmailLog"),
68
96
  ErrorAggregate: require("./src/models/ErrorAggregate"),
69
97
  FormSubmission: require("./src/models/FormSubmission"),
@@ -85,12 +113,22 @@ const saasbackend = {
85
113
  Webhook: require("./src/models/Webhook"),
86
114
  Workflow: require("./src/models/Workflow"),
87
115
  WorkflowExecution: require("./src/models/WorkflowExecution"),
116
+
117
+ HealthCheck: require("./src/models/HealthCheck"),
118
+ HealthCheckRun: require("./src/models/HealthCheckRun"),
119
+ HealthIncident: require("./src/models/HealthIncident"),
120
+ HealthAutoHealAttempt: require("./src/models/HealthAutoHealAttempt"),
121
+
122
+ ExternalDbConnection: require("./src/models/ExternalDbConnection"),
88
123
  },
89
124
  helpers: {
90
125
  auth: require("./src/middleware/auth"),
91
126
  org: require("./src/middleware/org"),
127
+ rbac: require("./src/middleware/rbac"),
92
128
  i18n: require("./src/services/i18n.service"),
93
129
  jsonConfigs: require("./src/services/jsonConfigs.service"),
130
+ terminals: require("./src/services/terminalsWs.service"),
131
+ rateLimiter: require("./src/services/rateLimiter.service"),
94
132
  },
95
133
  };
96
134
 
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@intranefr/superbackend",
3
- "version": "1.4.4",
3
+ "version": "1.5.1",
4
4
  "description": "Node.js middleware that gives your project backend superpowers",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "start": "node server.js",
8
- "dev": "nodemon server.js",
8
+ "dev": "nodemon --verbose --ignore uploads --ignore '*.log' server.js",
9
9
  "start:minio": "docker compose -f compose.standalone.yml --profile minio-only up -d minio",
10
10
  "minio:envs": "node -e \"console.log(['S3_ENDPOINT=http://localhost:9000','S3_REGION=us-east-1','S3_ACCESS_KEY_ID=minioadmin','S3_SECRET_ACCESS_KEY=minioadmin','S3_BUCKET=saasbackend','S3_FORCE_PATH_STYLE=true'].join('\\n'))\"",
11
11
  "build:sdk:error-tracking:browser": "esbuild sdk/error-tracking/browser/src/embed.js --bundle --format=iife --global-name=saasbackendErrorTrackingEmbed --outfile=sdk/error-tracking/browser/dist/embed.iife.js",
12
+ "build:sdk:ui-components:browser": "esbuild sdk/ui-components/browser/src/index.js --bundle --format=iife --outfile=public/sdk/ui-components.iife.js",
12
13
  "test": "jest",
13
14
  "test:watch": "jest --watch",
14
15
  "test:coverage": "jest --coverage"
@@ -35,17 +36,24 @@
35
36
  "bcryptjs": "^2.4.3",
36
37
  "cheerio": "^1.0.0-rc.12",
37
38
  "cors": "^2.8.5",
39
+ "cron-parser": "3.5.0",
38
40
  "dotenv": "^16.3.1",
39
41
  "ejs": "^3.1.9",
40
42
  "express": "^4.18.2",
41
43
  "jsonwebtoken": "^9.0.2",
44
+ "marked": "^4.3.0",
42
45
  "mongoose": "^8.0.0",
43
46
  "multer": "^1.4.5-lts.1",
47
+ "mysql2": "^3.16.1",
48
+ "node-cron": "^4.2.1",
49
+ "node-pty": "^1.1.0",
44
50
  "openai": "^4.0.0",
51
+ "redis": "^4.7.1",
45
52
  "resend": "^6.4.0",
46
53
  "ssh2-sftp-client": "^12.0.1",
47
54
  "stripe": "^14.0.0",
48
- "vm2": "^3.10.0"
55
+ "vm2": "^3.10.0",
56
+ "ws": "^8.18.0"
49
57
  },
50
58
  "devDependencies": {
51
59
  "esbuild": "^0.25.0",
@@ -0,0 +1,191 @@
1
+ (() => {
2
+ // sdk/ui-components/browser/src/index.js
3
+ (function() {
4
+ function toStr(v) {
5
+ return v === void 0 || v === null ? "" : String(v);
6
+ }
7
+ function normalizeApiUrl(apiUrl) {
8
+ const u = toStr(apiUrl).trim();
9
+ if (!u) return "";
10
+ return u.replace(/\/$/, "");
11
+ }
12
+ function buildHeaders(apiKey) {
13
+ const headers = {};
14
+ const key = toStr(apiKey).trim();
15
+ if (key) headers["x-project-key"] = key;
16
+ return headers;
17
+ }
18
+ async function fetchJson(url, headers) {
19
+ const res = await fetch(url, { headers: headers || {} });
20
+ const text = await res.text();
21
+ let data = null;
22
+ try {
23
+ data = text ? JSON.parse(text) : null;
24
+ } catch {
25
+ data = null;
26
+ }
27
+ if (!res.ok) {
28
+ const msg = data && data.error ? data.error : "Request failed";
29
+ const err = new Error(msg);
30
+ err.status = res.status;
31
+ throw err;
32
+ }
33
+ return data;
34
+ }
35
+ function ensureTemplate(code, html) {
36
+ const id = "ui-cmp-" + code;
37
+ let tpl = document.getElementById(id);
38
+ if (!tpl) {
39
+ tpl = document.createElement("template");
40
+ tpl.id = id;
41
+ document.body.appendChild(tpl);
42
+ }
43
+ tpl.innerHTML = toStr(html);
44
+ return tpl;
45
+ }
46
+ function injectCssScoped(code, cssText) {
47
+ const id = "ui-cmp-style-" + code;
48
+ let el = document.getElementById(id);
49
+ if (!el) {
50
+ el = document.createElement("style");
51
+ el.id = id;
52
+ document.head.appendChild(el);
53
+ }
54
+ el.textContent = toStr(cssText);
55
+ }
56
+ function createShadowRootContainer() {
57
+ const host = document.createElement("div");
58
+ host.style.all = "initial";
59
+ const shadow = host.attachShadow({ mode: "open" });
60
+ return { host, shadow };
61
+ }
62
+ function defaultMountTarget() {
63
+ return document.body;
64
+ }
65
+ function compileComponentJs(jsCode) {
66
+ const code = toStr(jsCode);
67
+ if (!code.trim()) {
68
+ return function() {
69
+ return {};
70
+ };
71
+ }
72
+ return new Function("api", "templateRootEl", "props", code);
73
+ }
74
+ const state = {
75
+ initialized: false,
76
+ projectId: null,
77
+ apiKey: null,
78
+ apiUrl: "",
79
+ cssIsolation: "scoped",
80
+ components: {}
81
+ };
82
+ function registerComponent(def) {
83
+ const code = toStr(def.code).trim().toLowerCase();
84
+ if (!code) return;
85
+ const version = def.version;
86
+ const html = def.html;
87
+ const js = def.js;
88
+ const css = def.css;
89
+ ensureTemplate(code, html);
90
+ const component = {
91
+ code,
92
+ version,
93
+ css,
94
+ js,
95
+ create: function(props, options) {
96
+ const opts = options || {};
97
+ const mountEl = opts.mountEl || defaultMountTarget();
98
+ const isolation = opts.cssIsolation || state.cssIsolation;
99
+ const tpl = ensureTemplate(code, html);
100
+ const fragment = tpl.content.cloneNode(true);
101
+ let templateRootEl;
102
+ let instanceRoot;
103
+ let shadow = null;
104
+ if (isolation === "shadow") {
105
+ const c = createShadowRootContainer();
106
+ instanceRoot = c.host;
107
+ shadow = c.shadow;
108
+ templateRootEl = shadow;
109
+ if (css) {
110
+ const style = document.createElement("style");
111
+ style.textContent = toStr(css);
112
+ shadow.appendChild(style);
113
+ }
114
+ shadow.appendChild(fragment);
115
+ } else {
116
+ instanceRoot = document.createElement("div");
117
+ templateRootEl = instanceRoot;
118
+ if (css) injectCssScoped(code, css);
119
+ instanceRoot.appendChild(fragment);
120
+ }
121
+ mountEl.appendChild(instanceRoot);
122
+ const api = {
123
+ unmount: function() {
124
+ try {
125
+ instanceRoot.remove();
126
+ } catch {
127
+ }
128
+ },
129
+ mountEl,
130
+ hostEl: instanceRoot,
131
+ shadowRoot: shadow
132
+ };
133
+ const fn = compileComponentJs(js);
134
+ let exported = {};
135
+ try {
136
+ exported = fn(api, templateRootEl, props || {}) || {};
137
+ } catch (e) {
138
+ exported = {
139
+ error: e
140
+ };
141
+ }
142
+ return Object.assign({ api }, exported);
143
+ }
144
+ };
145
+ state.components[code] = component;
146
+ uiCmp[code] = component;
147
+ uiComponents[code] = component;
148
+ }
149
+ async function init(options) {
150
+ const opts = options || {};
151
+ const projectId = toStr(opts.projectId).trim();
152
+ if (!projectId) throw new Error("projectId is required");
153
+ const apiUrl = normalizeApiUrl(opts.apiUrl);
154
+ const apiKey = opts.apiKey;
155
+ state.projectId = projectId;
156
+ state.apiKey = apiKey;
157
+ state.apiUrl = apiUrl;
158
+ const cssIsolation = toStr(opts.cssIsolation || "scoped").trim().toLowerCase();
159
+ state.cssIsolation = cssIsolation === "shadow" ? "shadow" : "scoped";
160
+ const base = state.apiUrl;
161
+ const url = base + "/api/ui-components/projects/" + encodeURIComponent(projectId) + "/manifest";
162
+ const data = await fetchJson(url, buildHeaders(apiKey));
163
+ const items = data && Array.isArray(data.components) ? data.components : [];
164
+ for (const def of items) {
165
+ registerComponent(def);
166
+ }
167
+ state.initialized = true;
168
+ return { project: data ? data.project : null, count: items.length };
169
+ }
170
+ async function load(code) {
171
+ const c = toStr(code).trim().toLowerCase();
172
+ if (!c) throw new Error("code is required");
173
+ if (!state.projectId) throw new Error("uiCmp not initialized");
174
+ if (state.components[c]) return state.components[c];
175
+ const base = state.apiUrl;
176
+ const url = base + "/api/ui-components/projects/" + encodeURIComponent(state.projectId) + "/components/" + encodeURIComponent(c);
177
+ const data = await fetchJson(url, buildHeaders(state.apiKey));
178
+ if (!data || !data.component) throw new Error("Component not found");
179
+ registerComponent(data.component);
180
+ return state.components[c];
181
+ }
182
+ const uiCmp = {
183
+ init,
184
+ load,
185
+ _state: state
186
+ };
187
+ const uiComponents = uiCmp;
188
+ window.uiCmp = uiCmp;
189
+ window.uiComponents = uiComponents;
190
+ })();
191
+ })();
@@ -0,0 +1,228 @@
1
+ (function () {
2
+ function toStr(v) {
3
+ return v === undefined || v === null ? '' : String(v);
4
+ }
5
+
6
+ function normalizeApiUrl(apiUrl) {
7
+ const u = toStr(apiUrl).trim();
8
+ if (!u) return '';
9
+ return u.replace(/\/$/, '');
10
+ }
11
+
12
+ function buildHeaders(apiKey) {
13
+ const headers = {};
14
+ const key = toStr(apiKey).trim();
15
+ if (key) headers['x-project-key'] = key;
16
+ return headers;
17
+ }
18
+
19
+ async function fetchJson(url, headers) {
20
+ const res = await fetch(url, { headers: headers || {} });
21
+ const text = await res.text();
22
+ let data = null;
23
+ try {
24
+ data = text ? JSON.parse(text) : null;
25
+ } catch {
26
+ data = null;
27
+ }
28
+ if (!res.ok) {
29
+ const msg = data && data.error ? data.error : 'Request failed';
30
+ const err = new Error(msg);
31
+ err.status = res.status;
32
+ throw err;
33
+ }
34
+ return data;
35
+ }
36
+
37
+ function ensureTemplate(code, html) {
38
+ const id = 'ui-cmp-' + code;
39
+ let tpl = document.getElementById(id);
40
+ if (!tpl) {
41
+ tpl = document.createElement('template');
42
+ tpl.id = id;
43
+ document.body.appendChild(tpl);
44
+ }
45
+ tpl.innerHTML = toStr(html);
46
+ return tpl;
47
+ }
48
+
49
+ function injectCssScoped(code, cssText) {
50
+ const id = 'ui-cmp-style-' + code;
51
+ let el = document.getElementById(id);
52
+ if (!el) {
53
+ el = document.createElement('style');
54
+ el.id = id;
55
+ document.head.appendChild(el);
56
+ }
57
+ el.textContent = toStr(cssText);
58
+ }
59
+
60
+ function createShadowRootContainer() {
61
+ const host = document.createElement('div');
62
+ host.style.all = 'initial';
63
+ const shadow = host.attachShadow({ mode: 'open' });
64
+ return { host, shadow };
65
+ }
66
+
67
+ function defaultMountTarget() {
68
+ return document.body;
69
+ }
70
+
71
+ function compileComponentJs(jsCode) {
72
+ const code = toStr(jsCode);
73
+ if (!code.trim()) {
74
+ return function () {
75
+ return {};
76
+ };
77
+ }
78
+ return new Function('api', 'templateRootEl', 'props', code);
79
+ }
80
+
81
+ const state = {
82
+ initialized: false,
83
+ projectId: null,
84
+ apiKey: null,
85
+ apiUrl: '',
86
+ cssIsolation: 'scoped',
87
+ components: {},
88
+ };
89
+
90
+ function registerComponent(def) {
91
+ const code = toStr(def.code).trim().toLowerCase();
92
+ if (!code) return;
93
+
94
+ const version = def.version;
95
+ const html = def.html;
96
+ const js = def.js;
97
+ const css = def.css;
98
+
99
+ ensureTemplate(code, html);
100
+
101
+ const component = {
102
+ code,
103
+ version,
104
+ css,
105
+ js,
106
+ create: function (props, options) {
107
+ const opts = options || {};
108
+ const mountEl = opts.mountEl || defaultMountTarget();
109
+ const isolation = opts.cssIsolation || state.cssIsolation;
110
+
111
+ const tpl = ensureTemplate(code, html);
112
+ const fragment = tpl.content.cloneNode(true);
113
+
114
+ let templateRootEl;
115
+ let instanceRoot;
116
+ let shadow = null;
117
+
118
+ if (isolation === 'shadow') {
119
+ const c = createShadowRootContainer();
120
+ instanceRoot = c.host;
121
+ shadow = c.shadow;
122
+ templateRootEl = shadow;
123
+ if (css) {
124
+ const style = document.createElement('style');
125
+ style.textContent = toStr(css);
126
+ shadow.appendChild(style);
127
+ }
128
+ shadow.appendChild(fragment);
129
+ } else {
130
+ instanceRoot = document.createElement('div');
131
+ templateRootEl = instanceRoot;
132
+ if (css) injectCssScoped(code, css);
133
+ instanceRoot.appendChild(fragment);
134
+ }
135
+
136
+ mountEl.appendChild(instanceRoot);
137
+
138
+ const api = {
139
+ unmount: function () {
140
+ try {
141
+ instanceRoot.remove();
142
+ } catch {}
143
+ },
144
+ mountEl,
145
+ hostEl: instanceRoot,
146
+ shadowRoot: shadow,
147
+ };
148
+
149
+ const fn = compileComponentJs(js);
150
+ let exported = {};
151
+ try {
152
+ exported = fn(api, templateRootEl, props || {}) || {};
153
+ } catch (e) {
154
+ exported = {
155
+ error: e,
156
+ };
157
+ }
158
+
159
+ return Object.assign({ api }, exported);
160
+ },
161
+ };
162
+
163
+ state.components[code] = component;
164
+ uiCmp[code] = component;
165
+ uiComponents[code] = component;
166
+ }
167
+
168
+ async function init(options) {
169
+ const opts = options || {};
170
+ const projectId = toStr(opts.projectId).trim();
171
+ if (!projectId) throw new Error('projectId is required');
172
+
173
+ const apiUrl = normalizeApiUrl(opts.apiUrl);
174
+ const apiKey = opts.apiKey;
175
+
176
+ state.projectId = projectId;
177
+ state.apiKey = apiKey;
178
+ state.apiUrl = apiUrl;
179
+
180
+ const cssIsolation = toStr(opts.cssIsolation || 'scoped').trim().toLowerCase();
181
+ state.cssIsolation = cssIsolation === 'shadow' ? 'shadow' : 'scoped';
182
+
183
+ const base = state.apiUrl;
184
+ const url = base + '/api/ui-components/projects/' + encodeURIComponent(projectId) + '/manifest';
185
+
186
+ const data = await fetchJson(url, buildHeaders(apiKey));
187
+ const items = data && Array.isArray(data.components) ? data.components : [];
188
+
189
+ for (const def of items) {
190
+ registerComponent(def);
191
+ }
192
+
193
+ state.initialized = true;
194
+ return { project: data ? data.project : null, count: items.length };
195
+ }
196
+
197
+ async function load(code) {
198
+ const c = toStr(code).trim().toLowerCase();
199
+ if (!c) throw new Error('code is required');
200
+ if (!state.projectId) throw new Error('uiCmp not initialized');
201
+ if (state.components[c]) return state.components[c];
202
+
203
+ const base = state.apiUrl;
204
+ const url =
205
+ base +
206
+ '/api/ui-components/projects/' +
207
+ encodeURIComponent(state.projectId) +
208
+ '/components/' +
209
+ encodeURIComponent(c);
210
+
211
+ const data = await fetchJson(url, buildHeaders(state.apiKey));
212
+ if (!data || !data.component) throw new Error('Component not found');
213
+
214
+ registerComponent(data.component);
215
+ return state.components[c];
216
+ }
217
+
218
+ const uiCmp = {
219
+ init,
220
+ load,
221
+ _state: state,
222
+ };
223
+
224
+ const uiComponents = uiCmp;
225
+
226
+ window.uiCmp = uiCmp;
227
+ window.uiComponents = uiComponents;
228
+ })();