@intranefr/superbackend 1.4.3 → 1.5.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 (65) hide show
  1. package/.env.example +6 -1
  2. package/README.md +5 -5
  3. package/index.js +23 -5
  4. package/package.json +5 -2
  5. package/public/sdk/ui-components.iife.js +191 -0
  6. package/sdk/error-tracking/browser/package.json +4 -3
  7. package/sdk/error-tracking/browser/src/embed.js +29 -0
  8. package/sdk/ui-components/browser/src/index.js +228 -0
  9. package/src/controllers/admin.controller.js +139 -1
  10. package/src/controllers/adminHeadless.controller.js +82 -0
  11. package/src/controllers/adminMigration.controller.js +5 -1
  12. package/src/controllers/adminScripts.controller.js +229 -0
  13. package/src/controllers/adminTerminals.controller.js +39 -0
  14. package/src/controllers/adminUiComponents.controller.js +315 -0
  15. package/src/controllers/adminUiComponentsAi.controller.js +34 -0
  16. package/src/controllers/orgAdmin.controller.js +286 -0
  17. package/src/controllers/uiComponentsPublic.controller.js +118 -0
  18. package/src/middleware/auth.js +7 -0
  19. package/src/middleware.js +119 -0
  20. package/src/models/HeadlessModelDefinition.js +10 -0
  21. package/src/models/ScriptDefinition.js +42 -0
  22. package/src/models/ScriptRun.js +22 -0
  23. package/src/models/UiComponent.js +29 -0
  24. package/src/models/UiComponentProject.js +26 -0
  25. package/src/models/UiComponentProjectComponent.js +18 -0
  26. package/src/routes/admin.routes.js +2 -0
  27. package/src/routes/adminHeadless.routes.js +6 -0
  28. package/src/routes/adminScripts.routes.js +21 -0
  29. package/src/routes/adminTerminals.routes.js +13 -0
  30. package/src/routes/adminUiComponents.routes.js +29 -0
  31. package/src/routes/llmUi.routes.js +26 -0
  32. package/src/routes/orgAdmin.routes.js +5 -0
  33. package/src/routes/uiComponentsPublic.routes.js +9 -0
  34. package/src/services/consoleOverride.service.js +291 -0
  35. package/src/services/email.service.js +17 -1
  36. package/src/services/headlessExternalModels.service.js +292 -0
  37. package/src/services/headlessModels.service.js +26 -6
  38. package/src/services/scriptsRunner.service.js +259 -0
  39. package/src/services/terminals.service.js +152 -0
  40. package/src/services/terminalsWs.service.js +100 -0
  41. package/src/services/uiComponentsAi.service.js +312 -0
  42. package/src/services/uiComponentsCrypto.service.js +39 -0
  43. package/src/services/webhook.service.js +2 -2
  44. package/src/services/workflow.service.js +1 -1
  45. package/src/utils/encryption.js +5 -3
  46. package/views/admin-coolify-deploy.ejs +1 -1
  47. package/views/admin-dashboard-home.ejs +1 -1
  48. package/views/admin-dashboard.ejs +1 -1
  49. package/views/admin-errors.ejs +2 -2
  50. package/views/admin-global-settings.ejs +3 -3
  51. package/views/admin-headless.ejs +294 -24
  52. package/views/admin-json-configs.ejs +8 -1
  53. package/views/admin-llm.ejs +2 -2
  54. package/views/admin-organizations.ejs +365 -9
  55. package/views/admin-scripts.ejs +497 -0
  56. package/views/admin-seo-config.ejs +1 -1
  57. package/views/admin-terminals.ejs +328 -0
  58. package/views/admin-test.ejs +3 -3
  59. package/views/admin-ui-components.ejs +709 -0
  60. package/views/admin-users.ejs +440 -4
  61. package/views/admin-webhooks.ejs +1 -1
  62. package/views/admin-workflows.ejs +1 -1
  63. package/views/partials/admin-assets-script.ejs +3 -3
  64. package/views/partials/dashboard/nav-items.ejs +3 -0
  65. package/views/partials/dashboard/palette.ejs +1 -1
package/.env.example CHANGED
@@ -43,5 +43,10 @@ MAX_FILE_SIZE_HARD_CAP=10485760
43
43
  # S3_REGION=us-east-1
44
44
  # S3_ACCESS_KEY_ID=minioadmin
45
45
  # S3_SECRET_ACCESS_KEY=minioadmin
46
- # S3_BUCKET=saasbackend
46
+ # S3_BUCKET=superbackend
47
+ # Legacy fallback: S3_BUCKET=saasbackend
47
48
  # S3_FORCE_PATH_STYLE=true
49
+
50
+ # Encryption key for encrypted settings (new preferred name)
51
+ # SUPERBACKEND_ENCRYPTION_KEY=your-32-byte-encryption-key
52
+ # Legacy fallback: SAASBACKEND_ENCRYPTION_KEY=your-32-byte-encryption-key
package/README.md CHANGED
@@ -29,13 +29,13 @@ Node.js middleware that gives your project backend superpowers. Handles authenti
29
29
  ## Installation
30
30
 
31
31
  ```bash
32
- npm install superbackend
32
+ npm install @intranefr/superbackend
33
33
  ```
34
34
 
35
35
  or
36
36
 
37
37
  ```bash
38
- yarn add superbackend
38
+ yarn add @intranefr/superbackend
39
39
  ```
40
40
 
41
41
  ---
@@ -45,7 +45,7 @@ yarn add superbackend
45
45
  ```javascript
46
46
  require('dotenv').config();
47
47
  const express = require('express');
48
- const { middleware } = require('saasbackend');
48
+ const { middleware } = require('@intranefr/superbackend');
49
49
 
50
50
  const app = express();
51
51
 
@@ -101,8 +101,8 @@ Please read the [CONTRIBUTING.md](#) for guidelines.
101
101
  <img src="https://img.shields.io/badge/Intrane-intranefr-blue?style=flat-square" alt="Intrane"/>
102
102
  </a>
103
103
  &nbsp;
104
- <a href="https://www.npmjs.com/package/superbackend" target="_blank">
105
- <img src="https://img.shields.io/npm/v/superbackend?style=flat-square" alt="npm"/>
104
+ <a href="https://www.npmjs.com/package/@intranefr/superbackend" target="_blank">
105
+ <img src="https://img.shields.io/npm/v/@intranefr%2Fsuperbackend?style=flat-square" alt="npm"/>
106
106
  </a>
107
107
 
108
108
  ## License
package/index.js CHANGED
@@ -2,7 +2,7 @@ require("dotenv").config({ path: process.env.ENV_FILE || ".env" });
2
2
  const express = require("express");
3
3
 
4
4
  /**
5
- * Creates the SaaS backend as Express middleware
5
+ * Creates the SuperBackend as Express middleware
6
6
  * @param {Object} options - Configuration options
7
7
  * @param {string} options.mongodbUri - MongoDB connection string
8
8
  * @param {string} options.corsOrigin - CORS origin(s)
@@ -11,9 +11,10 @@ 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
- * Creates and starts a standalone SaaS backend server
17
+ * Creates and starts a standalone SuperBackend server
17
18
  * @param {Object} options - Configuration options
18
19
  * @param {number} options.port - Port to listen on
19
20
  * @param {string} options.mongodbUri - MongoDB connection string
@@ -24,20 +25,36 @@ 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
- console.log(`🚀 SaaSBackend standalone server running on http://localhost:${PORT}`);
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
 
37
51
  const saasbackend = {
38
52
  server: startServer,
53
+ consoleOverride: require("./src/services/consoleOverride.service"),
39
54
  middleware: (options = {}) => {
40
- globalThis.saasbackend = saasbackend;
55
+ // Set both registries for backward compatibility
56
+ globalThis.superbackend = saasbackend;
57
+ globalThis.saasbackend = saasbackend; // Legacy support
41
58
  return middleware(options);
42
59
  },
43
60
  services: {
@@ -88,6 +105,7 @@ const saasbackend = {
88
105
  org: require("./src/middleware/org"),
89
106
  i18n: require("./src/services/i18n.service"),
90
107
  jsonConfigs: require("./src/services/jsonConfigs.service"),
108
+ terminals: require("./src/services/terminalsWs.service"),
91
109
  },
92
110
  };
93
111
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intranefr/superbackend",
3
- "version": "1.4.3",
3
+ "version": "1.5.0",
4
4
  "description": "Node.js middleware that gives your project backend superpowers",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -9,6 +9,7 @@
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"
@@ -41,11 +42,13 @@
41
42
  "jsonwebtoken": "^9.0.2",
42
43
  "mongoose": "^8.0.0",
43
44
  "multer": "^1.4.5-lts.1",
45
+ "node-pty": "^1.1.0",
44
46
  "openai": "^4.0.0",
45
47
  "resend": "^6.4.0",
46
48
  "ssh2-sftp-client": "^12.0.1",
47
49
  "stripe": "^14.0.0",
48
- "vm2": "^3.10.0"
50
+ "vm2": "^3.10.0",
51
+ "ws": "^8.18.0"
49
52
  },
50
53
  "devDependencies": {
51
54
  "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
+ })();
@@ -1,6 +1,7 @@
1
1
  {
2
- "name": "@saasbackend/error-tracking-browser-sdk",
3
- "version": "1.0.0",
2
+ "name": "@intranefr/superbackend-error-tracking-browser-sdk",
3
+ "version": "2.0.0",
4
+ "description": "Error tracking SDK for SuperBackend browser applications.",
4
5
  "type": "module",
5
6
  "exports": {
6
7
  ".": {
@@ -8,7 +9,7 @@
8
9
  }
9
10
  },
10
11
  "scripts": {
11
- "build": "esbuild src/embed.js --bundle --format=iife --global-name=saasbackendErrorTrackingEmbed --outfile=dist/embed.iife.js --minify"
12
+ "build": "esbuild src/embed.js --bundle --format=iife --global-name=superbackendErrorTrackingEmbed --outfile=dist/embed.iife.js --minify"
12
13
  },
13
14
  "devDependencies": {
14
15
  "esbuild": "^0.27.2"
@@ -1,11 +1,39 @@
1
1
  import { createErrorTrackingClient } from './core.js';
2
2
 
3
+ function attachToSuperbackendGlobal() {
4
+ const root = (typeof window !== 'undefined' ? window : undefined);
5
+ if (!root) return;
6
+
7
+ if (root.saasbackendErrorTrackingEmbed && !root.superbackendErrorTrackingEmbed) {
8
+ root.superbackendErrorTrackingEmbed = root.saasbackendErrorTrackingEmbed;
9
+ }
10
+
11
+ root.superbackend = root.superbackend || {};
12
+
13
+ if (!root.superbackend.errorTracking) {
14
+ root.superbackend.errorTracking = createErrorTrackingClient();
15
+ }
16
+
17
+ if (root.superbackend.errorTracking && typeof root.superbackend.errorTracking.init === 'function') {
18
+ root.superbackend.errorTracking.init();
19
+ }
20
+ }
21
+
3
22
  function attachToSaasbackendGlobal() {
4
23
  const root = (typeof window !== 'undefined' ? window : undefined);
5
24
  if (!root) return;
6
25
 
26
+ // Show deprecation warning in console
27
+ if (console.warn) {
28
+ console.warn('DEPRECATION: Global "window.saasbackend" is deprecated. Use "window.superbackend" instead.');
29
+ }
30
+
7
31
  root.saasbackend = root.saasbackend || {};
8
32
 
33
+ if (root.superbackendErrorTrackingEmbed && !root.saasbackendErrorTrackingEmbed) {
34
+ root.saasbackendErrorTrackingEmbed = root.superbackendErrorTrackingEmbed;
35
+ }
36
+
9
37
  if (!root.saasbackend.errorTracking) {
10
38
  root.saasbackend.errorTracking = createErrorTrackingClient();
11
39
  }
@@ -15,4 +43,5 @@ function attachToSaasbackendGlobal() {
15
43
  }
16
44
  }
17
45
 
46
+ attachToSuperbackendGlobal();
18
47
  attachToSaasbackendGlobal();
@@ -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
+ })();