@symbo.ls/mermaid 3.14.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.
package/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # @symbo.ls/mermaid
2
+
3
+ Self-sustaining server runner for Symbols projects. Renders DOMQL projects from JSON to HTML, with optional [brender](../brender) SSR.
4
+
5
+ Works in three modes:
6
+
7
+ 1. **Standalone** — `mermaid` CLI / `createMermaidApp({ jsonPath })` Express app
8
+ 2. **Pure render** — `renderProjectHtml(data, opts)` returns an HTML string in any runtime
9
+ 3. **Pluggable data source** — `createMermaidApp({ getProjectData })` lets you wire KV / API / DB
10
+
11
+ For the production hosting service (Cloudflare Workers + KV/API + custom domains), see [@symbo.ls/mermaid-server](../../../server/packages/mermaid-server) which builds on top of this package.
12
+
13
+ ## Quick start
14
+
15
+ ```bash
16
+ # Run a project from a local JSON file
17
+ npx mermaid --json ./app.json
18
+ # open http://localhost:3333
19
+
20
+ # Or as a library
21
+ node -e "import('@symbo.ls/mermaid').then(m => m.createMermaidApp({ jsonPath: './app.json' }).listen(3333))"
22
+ ```
23
+
24
+ ## API
25
+
26
+ ### `createMermaidApp(options)`
27
+
28
+ Returns an Express application.
29
+
30
+ | Option | Type | Description |
31
+ | ---------------- | -------- | --------------------------------------------------------------------- |
32
+ | `jsonPath` | string | Path to local JSON file (or directory containing `app.json`) |
33
+ | `appkeyOverride` | string | Force a specific appkey for all requests |
34
+ | `channel` | string | `production` (default) \| `staging` \| `development` |
35
+ | `getProjectData` | function | `async (req, ctx) => { data, appkey?, channel?, isProd? }` |
36
+ | `cors` | boolean | Enable CORS middleware (default `true`) |
37
+ | `editor` | object | `{ bundle: string }` to inject `@symbo.ls/preview-editor` in non-prod |
38
+
39
+ ### `renderProjectHtml(data, options)`
40
+
41
+ Pure runtime-agnostic renderer. Returns `{ html, status, contentType }`.
42
+
43
+ | Option | Type | Description |
44
+ | ----------------------- | ------- | ------------------------------------------------------ |
45
+ | `appkey` | string | identifier for metadata + comments |
46
+ | `pathname` | string | request pathname |
47
+ | `channel` | string | `production` \| `staging` \| `development` |
48
+ | `isProd` | boolean | controls SEO indexing (custom domains only) |
49
+ | `req` | object | Express-like request (used by bundle context) |
50
+ | `userId` | string | injects `window.__SYMBOLS_AUTH__` |
51
+ | `smblsIife` | string | preloaded smbls IIFE source (CF Workers / build-time) |
52
+ | `editorBundle` | string | preview-editor bundle to inject (non-prod only) |
53
+ | `analyzedScript` | string | analytics tracking `<script>` to inject |
54
+ | `indexHtml` | string | override the index.html template |
55
+ | `ssrCache` | object | pre-computed SSR cache (skips runtime brender) |
56
+ | `ProjectDataService` | object | passed to `getBundleScript` for shared-library merging |
57
+
58
+ ### `loadJsonData(jsonPath)`
59
+
60
+ Synchronously load and parse a project JSON file.
61
+
62
+ ### Other exports
63
+
64
+ `renderNotPublishedHtml`, `transpileFunctionStrings`, `getBundleScript`, `getSmblsIife`, `normalizeRoute`, `resolvePageRoute`, `extractRouteParams`, `STATIC_EXT`, `deepDestringifyFunctions`, `stripFunctionStrings`, `generateSitemap` (if brender is available), `replaceVariablesInFileSync`, `transpileFuncql`.
65
+
66
+ ## Environment variables
67
+
68
+ | Variable | Default | Description |
69
+ | ----------- | ---------------------------------- | ---------------------------- |
70
+ | `PORT` | `3333` | HTTP port (CLI only) |
71
+ | `JSON_PATH` | `./app.json` | JSON data path (CLI only) |
72
+ | `APPKEY` | — | Override appkey (CLI only) |
73
+ | `NODE_ENV` | `development` | Channel selection |
74
+ | `LOG_LEVEL` | `debug` (dev) / `info` (prod) | Log verbosity |
75
+ | `BRENDER` | enabled | Set to `false` to skip SSR |
package/bin/start.js ADDED
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * mermaid CLI — start a standalone Symbols project server from a JSON file.
4
+ *
5
+ * mermaid # serve ./app.json on :3333
6
+ * mermaid --json ./project-data.json # custom JSON path
7
+ * mermaid --port 4000 # custom port
8
+ * mermaid --appkey demo/site # force a specific appkey
9
+ *
10
+ * Env:
11
+ * PORT, JSON_PATH, APPKEY, NODE_ENV, LOG_LEVEL
12
+ */
13
+
14
+ import { createMermaidApp, loadJsonData } from '../src/server.js';
15
+ import log from '../src/utils/logger.js';
16
+
17
+ const args = process.argv.slice(2);
18
+ function arg(name, fallback) {
19
+ const i = args.indexOf(`--${name}`);
20
+ return i !== -1 ? args[i + 1] : fallback;
21
+ }
22
+
23
+ const PORT = parseInt(arg('port', process.env.PORT || '3333'), 10);
24
+ const JSON_PATH = arg('json', process.env.JSON_PATH || './app.json');
25
+ const APPKEY = arg('appkey', process.env.APPKEY || null);
26
+ const NODE_ENV = process.env.NODE_ENV || 'development';
27
+ const LOG_LEVEL = process.env.LOG_LEVEL || (NODE_ENV === 'production' ? 'info' : 'debug');
28
+
29
+ log.level = LOG_LEVEL;
30
+
31
+ log.info(`[MERMAID] Starting standalone runner...`);
32
+ log.info(`[MERMAID] Environment: ${NODE_ENV}`);
33
+ log.info(`[MERMAID] JSON: ${JSON_PATH}`);
34
+ if (APPKEY) log.info(`[MERMAID] Locked to appkey: ${APPKEY}`);
35
+
36
+ try {
37
+ // Validate JSON exists up front
38
+ loadJsonData(JSON_PATH);
39
+ } catch (err) {
40
+ log.error(`[MERMAID] ${err.message}`);
41
+ process.exit(1);
42
+ }
43
+
44
+ const app = createMermaidApp({
45
+ jsonPath: JSON_PATH,
46
+ appkeyOverride: APPKEY,
47
+ channel: NODE_ENV === 'production' ? 'production' : 'development',
48
+ });
49
+
50
+ const server = app.listen(PORT, () => {
51
+ log.info(`[MERMAID] Running on http://localhost:${PORT}`);
52
+ });
53
+
54
+ const shutdown = (signal) => {
55
+ log.info(`[MERMAID] ${signal} received, shutting down...`);
56
+ server.close();
57
+ process.exit(0);
58
+ };
59
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
60
+ process.on('SIGINT', () => shutdown('SIGINT'));
package/index.js ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @symbo.ls/mermaid — self-sustaining server runner for Symbols projects.
3
+ *
4
+ * Renders DOMQL projects from JSON to HTML, with optional brender SSR.
5
+ * Works as an Express server, a pure render function, or in CF Workers.
6
+ */
7
+
8
+ export { createMermaidApp, loadJsonData } from './src/server.js';
9
+ export {
10
+ renderProjectHtml,
11
+ renderNotPublishedHtml,
12
+ transpileFunctionStrings,
13
+ generateSitemap,
14
+ prefetchPageData,
15
+ } from './src/render.js';
16
+ export { getBundleScript, getSmblsIife } from './src/bundle.js';
17
+ export { normalizeRoute, resolvePageRoute, extractRouteParams, STATIC_EXT } from './src/route.js';
18
+ export { deepDestringifyFunctions, stripFunctionStrings } from './src/utils/deepDestringify.js';
19
+ export { transpile as transpileFuncql } from './src/utils/funcql-transpile.js';
20
+ export { default as replaceVariablesInFileSync } from './src/utils/vartranformer.js';
21
+ export { default as log } from './src/utils/logger.js';
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@symbo.ls/mermaid",
3
+ "version": "3.14.0",
4
+ "type": "module",
5
+ "license": "CC-BY-NC-4.0",
6
+ "description": "Self-sustaining server runner for Symbols projects from JSON. Renders DOMQL projects to HTML with optional brender SSR.",
7
+ "main": "index.js",
8
+ "exports": {
9
+ ".": "./index.js",
10
+ "./server": "./src/server.js",
11
+ "./render": "./src/render.js",
12
+ "./bundle": "./src/bundle.js",
13
+ "./route": "./src/route.js",
14
+ "./utils/deepDestringify": "./src/utils/deepDestringify.js",
15
+ "./utils/funcql-transpile": "./src/utils/funcql-transpile.js",
16
+ "./utils/vartranformer": "./src/utils/vartranformer.js",
17
+ "./utils/logger": "./src/utils/logger.js",
18
+ "./template.html": "./src/template.html"
19
+ },
20
+ "files": ["index.js", "src", "README.md"],
21
+ "scripts": {
22
+ "start": "node bin/start.js"
23
+ },
24
+ "bin": {
25
+ "mermaid": "./bin/start.js"
26
+ },
27
+ "dependencies": {
28
+ "@symbo.ls/utils": "^3.14.0",
29
+ "cors": "^2.8.5",
30
+ "express": "^4.21.2"
31
+ },
32
+ "optionalDependencies": {
33
+ "@symbo.ls/brender": "^3.14.0"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ }
38
+ }
package/src/bundle.js ADDED
@@ -0,0 +1,118 @@
1
+ import log from './utils/logger.js';
2
+ // The smbls IIFE source as a string. Single source of truth — generated by
3
+ // `smbls build:iife` and exported from the smbls package itself, so this works
4
+ // identically in Node, CF Workers, and any other runtime as a static import.
5
+ import smblsIife from 'smbls/iife-string';
6
+
7
+ log.debug(`[BUNDLE] smbls IIFE loaded from smbls/iife-string (${smblsIife.length} bytes)`);
8
+
9
+ export function getSmblsIife() {
10
+ return smblsIife;
11
+ }
12
+
13
+ /**
14
+ * Generate the client-side bundle script that creates the Smbls app.
15
+ * Uses the same approach as templates/start.js runFromJson:
16
+ * Smbls.deepDestringifyFunctions(data) -> Smbls.create(app, context)
17
+ */
18
+ export function getBundleScript(appKey, data, req, { ProjectDataService } = {}) {
19
+ log.debug(`[BUNDLE] getBundleScript started for ${appKey}`);
20
+
21
+ if (!data) {
22
+ log.warn('[BUNDLE] No data provided, returning empty');
23
+ return '';
24
+ }
25
+
26
+ // Ensure data is fully merged before bundling
27
+ try {
28
+ if (data.merged) {
29
+ Object.assign(data, data.merged);
30
+ log.debug(`[BUNDLE] Applied precomputed merged data for project ${data.id}`);
31
+ } else if (
32
+ ProjectDataService &&
33
+ Array.isArray(data.sharedLibraries) &&
34
+ data.sharedLibraries.length > 0
35
+ ) {
36
+ const mergedData = ProjectDataService.mergeSharedLibraries(
37
+ data,
38
+ data.sharedLibraries,
39
+ data.projectType,
40
+ );
41
+ Object.assign(data, mergedData);
42
+ log.debug(`[BUNDLE] Merged ${data.sharedLibraries.length} shared libraries`);
43
+ }
44
+ } catch (mergeError) {
45
+ log.error(`[BUNDLE] Failed to ensure merged data: ${mergeError.message}`);
46
+ }
47
+
48
+ // Serialize the project data as JSON (same as templates __mockedData approach)
49
+ const jsonData = JSON.stringify(data);
50
+
51
+ // Determine liveSync setting
52
+ const release = req?.query?.release || false;
53
+ const version = req?.query?.version;
54
+ const isSymboLs = typeof appKey === 'string' && appKey.endsWith('symbo.ls');
55
+ const shouldDisableLiveSync = Boolean(release) || Boolean(version) || !isSymboLs;
56
+
57
+ const settingsStr = isSymboLs
58
+ ? JSON.stringify({ ...data.settings, liveSync: !shouldDisableLiveSync, inspect: true })
59
+ : 'null';
60
+
61
+ // Generate the client script - same pattern as templates/start.js runFromJson
62
+ const script = `
63
+ var context = Smbls.deepDestringifyFunctions(${jsonData});
64
+ context.packageManager = 'esm.sh';
65
+ if (context.db && context.db.createClient) delete context.db.createClient;
66
+ if (context.fetch && context.fetch.createClient) delete context.fetch.createClient;
67
+
68
+ // Runtime defaults (these are baked into the starter-kit/frank but missing from server data)
69
+ if (context.useReset === undefined) context.useReset = true;
70
+ if (context.useVariable === undefined) context.useVariable = true;
71
+ if (context.useFontImport === undefined) context.useFontImport = true;
72
+ if (context.useIconSprite === undefined) context.useIconSprite = true;
73
+ if (context.useSvgSprite === undefined) context.useSvgSprite = true;
74
+ if (context.useDefaultConfig === undefined) context.useDefaultConfig = true;
75
+ if (context.useDocumentTheme === undefined) context.useDocumentTheme = true;
76
+ if (!context.routerOptions) context.routerOptions = { scrollToTop: true };
77
+
78
+ var app = context.app || {};
79
+ delete context.app;
80
+ var settings = ${settingsStr};
81
+ if (settings) context.editor = settings;
82
+
83
+ // When brender pre-rendered this page, adopt the existing DOM nodes
84
+ // instead of tearing them down. The [data-ssr] wrapper is moved out of the
85
+ // way so pre-rendered children land at document.body level — matching the
86
+ // level at which DOMQL creates its own elements. The __BRENDER__ flag and
87
+ // __BR_REGISTRY__ map were injected into <head> by the server.
88
+ // Theme initialization: set data-theme before app renders to avoid FOUC
89
+ if (context.useDocumentTheme !== false) {
90
+ (function() {
91
+ try {
92
+ var t = null;
93
+ var key = context.themeStorageKey;
94
+ if (key) { try { t = localStorage.getItem(key) } catch(e) {} }
95
+ if (!t) t = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
96
+ if (t) document.documentElement.setAttribute('data-theme', t);
97
+ } catch(e) {}
98
+ })();
99
+ }
100
+
101
+ var ssrShell = document.querySelector('[data-ssr]');
102
+ if (window.__BRENDER__ && ssrShell) {
103
+ // Move pre-rendered children to body before DOMQL skeleton creation
104
+ var frag = document.createDocumentFragment();
105
+ while (ssrShell.firstChild) frag.appendChild(ssrShell.firstChild);
106
+ document.body.insertBefore(frag, ssrShell);
107
+ ssrShell.remove();
108
+ } else if (ssrShell) {
109
+ // No brender hydration — remove empty/stale SSR shell
110
+ ssrShell.remove();
111
+ }
112
+
113
+ Smbls.create(app, context);
114
+ `;
115
+
116
+ log.debug(`[BUNDLE] Script generated (${script.length} bytes)`);
117
+ return script;
118
+ }