@symbo.ls/brender 3.8.2 → 3.8.7

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.
@@ -49,6 +49,17 @@ var import_prefetch = require("./prefetch.js");
49
49
  var import_linkedom = require("linkedom");
50
50
  var import_create_instance = __toESM(require("@emotion/css/create-instance"), 1);
51
51
  const import_meta = {};
52
+ let _funcqlPlugin = null;
53
+ const getFuncqlPlugin = async () => {
54
+ if (_funcqlPlugin) return _funcqlPlugin;
55
+ try {
56
+ const mod = await import("@domql/funcql");
57
+ _funcqlPlugin = mod.funcqlPlugin;
58
+ return _funcqlPlugin;
59
+ } catch {
60
+ return null;
61
+ }
62
+ };
52
63
  const ssrResolve = (map, key) => {
53
64
  if (!map || !key) return void 0;
54
65
  if (map[key] !== void 0) return map[key];
@@ -122,8 +133,17 @@ const safeJsonReplacer = () => {
122
133
  return value;
123
134
  };
124
135
  };
125
- const _brenderRequire = (0, import_module.createRequire)(import_meta.url);
136
+ let _brenderRequire = null;
137
+ try {
138
+ if (import_meta.url) {
139
+ _brenderRequire = (0, import_module.createRequire)(import_meta.url);
140
+ }
141
+ } catch {
142
+ }
126
143
  const detectWorkspace = () => {
144
+ if (!import_meta.url || !_brenderRequire) {
145
+ return { isMonorepo: false, monorepoRoot: null, resolvePackage: (pkg) => pkg };
146
+ }
127
147
  const brenderDir = (0, import_fs.realpathSync)(new URL(".", import_meta.url).pathname);
128
148
  const monorepoRoot = (0, import_path.resolve)(brenderDir, "../..");
129
149
  const isMonorepo = (0, import_fs.existsSync)((0, import_path.resolve)(monorepoRoot, "packages", "smbls", "src", "createDomql.js"));
@@ -220,6 +240,15 @@ let _cachedCreateDomql = null;
220
240
  const bundleCreateDomql = async () => {
221
241
  if (_cachedCreateDomql) return _cachedCreateDomql;
222
242
  const ws = detectWorkspace();
243
+ if (!_brenderRequire) {
244
+ try {
245
+ const mod2 = await import("./dist/createDomql.bundled.mjs");
246
+ _cachedCreateDomql = mod2;
247
+ return mod2;
248
+ } catch (err) {
249
+ throw new Error(`brender: pre-bundled createDomql not available in bundled runtime: ${err.message}`);
250
+ }
251
+ }
223
252
  let entry;
224
253
  if (ws.isMonorepo) {
225
254
  entry = (0, import_path.resolve)(ws.monorepoRoot, "packages", "smbls", "src", "createDomql.js");
@@ -381,6 +410,14 @@ const bundleCreateDomql = async () => {
381
410
  );
382
411
  return { contents, loader: "js" };
383
412
  });
413
+ build.onLoad({ filter: /fetch\/adapters\/supabase\.js$/ }, () => {
414
+ return {
415
+ contents: `export const setup = async () => null;
416
+ export const supabaseAdapter = () => ({name:'supabase'});
417
+ `,
418
+ loader: "js"
419
+ };
420
+ });
384
421
  build.onLoad({ filter: /.*/, namespace: "brender-stub" }, () => {
385
422
  return { contents: "export const SyncComponent = {}; export const Inspect = {}; export const Notifications = {}; export default {}", loader: "js" };
386
423
  });
@@ -455,9 +492,13 @@ const bundleCreateDomql = async () => {
455
492
  ]
456
493
  });
457
494
  const mod = await import(`file://${outFile}`);
458
- try {
459
- (0, import_fs.unlinkSync)(outFile);
460
- } catch {
495
+ if (!process.env.BRENDER_DEBUG) {
496
+ try {
497
+ (0, import_fs.unlinkSync)(outFile);
498
+ } catch {
499
+ }
500
+ } else {
501
+ console.log("[brender] Bundle saved:", outFile);
461
502
  }
462
503
  _cachedCreateDomql = mod;
463
504
  return mod;
@@ -525,6 +566,22 @@ const UIKIT_STUBS = {
525
566
  },
526
567
  Text: { tag: "span" }
527
568
  };
569
+ const buildPathRegistry = (element, path = "") => {
570
+ if (!element || !element.__ref) return {};
571
+ const registry = {};
572
+ const brKey = element.__ref.__brKey;
573
+ if (brKey) registry[path === "" ? "__root" : path] = brKey;
574
+ if (element.__ref.__children) {
575
+ for (const childKey of element.__ref.__children) {
576
+ const child = element[childKey];
577
+ if (child && child.__ref) {
578
+ const childPath = path === "" ? childKey : `${path}.${childKey}`;
579
+ Object.assign(registry, buildPathRegistry(child, childPath));
580
+ }
581
+ }
582
+ }
583
+ return registry;
584
+ };
528
585
  const render = async (data, options = {}) => {
529
586
  const { route = "/", pathname, state: stateOverrides, context: contextOverrides, prefetch = false } = options;
530
587
  const locationPath = pathname || route;
@@ -651,12 +708,20 @@ const render = async (data, options = {}) => {
651
708
  // Caller overrides
652
709
  ...contextOverrides || {}
653
710
  };
711
+ const funcql = await getFuncqlPlugin();
712
+ if (funcql) {
713
+ ctx.plugins = ctx.plugins || [];
714
+ if (!ctx.plugins.some((p) => p.name === "funcql")) {
715
+ ctx.plugins.push(funcql);
716
+ }
717
+ }
654
718
  (0, import_keys.resetKeys)();
655
719
  const element = await createDomqlElement(app, ctx);
656
720
  const flushDelay = prefetch ? 2e3 : 50;
657
721
  await new Promise((r) => setTimeout(r, flushDelay));
658
722
  (0, import_keys.assignKeys)(body);
659
723
  const registry = (0, import_keys.mapKeysToElements)(element);
724
+ const brRegistry = buildPathRegistry(element);
660
725
  const metadata = (0, import_metadata.extractMetadata)(data, route, element, element?.state);
661
726
  const emotionCSS = [];
662
727
  const emotionInstance = ctx.emotion || element && element.context && element.context.emotion;
@@ -707,7 +772,7 @@ const render = async (data, options = {}) => {
707
772
  else delete globalThis.document;
708
773
  if (_prevLoc !== void 0) globalThis.location = _prevLoc;
709
774
  else delete globalThis.location;
710
- return { html, metadata, registry, element, emotionCSS, document, window, ssrTranslations, prefetchedPages };
775
+ return { html, metadata, registry, brRegistry, element, emotionCSS, document, window, ssrTranslations, prefetchedPages };
711
776
  };
712
777
  const renderElement = async (elementDef, options = {}) => {
713
778
  const { context = {} } = options;
@@ -753,6 +818,11 @@ const generateGlobalCSS = async (ds, config) => {
753
818
  const { existsSync: existsSync2, writeFileSync: writeFileSync2, unlinkSync: unlinkSync2 } = await import("fs");
754
819
  const { tmpdir: tmpdir2 } = await import("os");
755
820
  const { randomBytes: randomBytes2 } = await import("crypto");
821
+ try {
822
+ tmpdir2();
823
+ } catch {
824
+ return {};
825
+ }
756
826
  const esbuild = await import("esbuild");
757
827
  const dsJson = JSON.stringify(ds || {}, safeJsonReplacer());
758
828
  const cfgJson = JSON.stringify(config || {}, safeJsonReplacer());
@@ -970,6 +1040,7 @@ const renderRoute = async (data, options = {}) => {
970
1040
  fontLinks: generateFontLinks(ds),
971
1041
  metadata: result.metadata || (0, import_metadata.extractMetadata)(data, route),
972
1042
  brKeyCount: result.registry ? Object.keys(result.registry).length : 0,
1043
+ brRegistry: result.brRegistry || {},
973
1044
  ssrTranslations: result.ssrTranslations,
974
1045
  prefetchedState,
975
1046
  activeLang
@@ -1019,7 +1090,10 @@ const renderPage = async (data, route = "/", options = {}) => {
1019
1090
  `;
1020
1091
  }
1021
1092
  }
1022
- isrBody = `${translationSeed}<script>window.__BRENDER__ = true<\/script>
1093
+ const brRegistryJson = result.brRegistry && Object.keys(result.brRegistry).length ? JSON.stringify(result.brRegistry) : null;
1094
+ const brRegistryScript = brRegistryJson ? `<script>window.__BR_REGISTRY__=${brRegistryJson}<\/script>
1095
+ ` : "";
1096
+ isrBody = `${translationSeed}${brRegistryScript}<script>window.__BRENDER__=true<\/script>
1023
1097
  <script type="module" src="${prefix}${isr.clientScript}"><\/script>`;
1024
1098
  } else {
1025
1099
  isrBody = `<script type="module">
@@ -8,6 +8,17 @@ import { resetKeys, assignKeys, mapKeysToElements } from "./keys.js";
8
8
  import { extractMetadata, generateHeadHtml } from "./metadata.js";
9
9
  import { hydrate } from "./hydrate.js";
10
10
  import { prefetchPageData, injectPrefetchedState, fetchSSRTranslations } from "./prefetch.js";
11
+ let _funcqlPlugin = null;
12
+ const getFuncqlPlugin = async () => {
13
+ if (_funcqlPlugin) return _funcqlPlugin;
14
+ try {
15
+ const mod = await import("@domql/funcql");
16
+ _funcqlPlugin = mod.funcqlPlugin;
17
+ return _funcqlPlugin;
18
+ } catch {
19
+ return null;
20
+ }
21
+ };
11
22
  import { parseHTML } from "linkedom";
12
23
  import createEmotionInstance from "@emotion/css/create-instance";
13
24
  const ssrResolve = (map, key) => {
@@ -83,8 +94,17 @@ const safeJsonReplacer = () => {
83
94
  return value;
84
95
  };
85
96
  };
86
- const _brenderRequire = createRequire(import.meta.url);
97
+ let _brenderRequire = null;
98
+ try {
99
+ if (import.meta.url) {
100
+ _brenderRequire = createRequire(import.meta.url);
101
+ }
102
+ } catch {
103
+ }
87
104
  const detectWorkspace = () => {
105
+ if (!import.meta.url || !_brenderRequire) {
106
+ return { isMonorepo: false, monorepoRoot: null, resolvePackage: (pkg) => pkg };
107
+ }
88
108
  const brenderDir = realpathSync(new URL(".", import.meta.url).pathname);
89
109
  const monorepoRoot = resolve(brenderDir, "../..");
90
110
  const isMonorepo = existsSync(resolve(monorepoRoot, "packages", "smbls", "src", "createDomql.js"));
@@ -181,6 +201,15 @@ let _cachedCreateDomql = null;
181
201
  const bundleCreateDomql = async () => {
182
202
  if (_cachedCreateDomql) return _cachedCreateDomql;
183
203
  const ws = detectWorkspace();
204
+ if (!_brenderRequire) {
205
+ try {
206
+ const mod2 = await import("./dist/createDomql.bundled.mjs");
207
+ _cachedCreateDomql = mod2;
208
+ return mod2;
209
+ } catch (err) {
210
+ throw new Error(`brender: pre-bundled createDomql not available in bundled runtime: ${err.message}`);
211
+ }
212
+ }
184
213
  let entry;
185
214
  if (ws.isMonorepo) {
186
215
  entry = resolve(ws.monorepoRoot, "packages", "smbls", "src", "createDomql.js");
@@ -342,6 +371,14 @@ const bundleCreateDomql = async () => {
342
371
  );
343
372
  return { contents, loader: "js" };
344
373
  });
374
+ build.onLoad({ filter: /fetch\/adapters\/supabase\.js$/ }, () => {
375
+ return {
376
+ contents: `export const setup = async () => null;
377
+ export const supabaseAdapter = () => ({name:'supabase'});
378
+ `,
379
+ loader: "js"
380
+ };
381
+ });
345
382
  build.onLoad({ filter: /.*/, namespace: "brender-stub" }, () => {
346
383
  return { contents: "export const SyncComponent = {}; export const Inspect = {}; export const Notifications = {}; export default {}", loader: "js" };
347
384
  });
@@ -416,9 +453,13 @@ const bundleCreateDomql = async () => {
416
453
  ]
417
454
  });
418
455
  const mod = await import(`file://${outFile}`);
419
- try {
420
- unlinkSync(outFile);
421
- } catch {
456
+ if (!process.env.BRENDER_DEBUG) {
457
+ try {
458
+ unlinkSync(outFile);
459
+ } catch {
460
+ }
461
+ } else {
462
+ console.log("[brender] Bundle saved:", outFile);
422
463
  }
423
464
  _cachedCreateDomql = mod;
424
465
  return mod;
@@ -486,6 +527,22 @@ const UIKIT_STUBS = {
486
527
  },
487
528
  Text: { tag: "span" }
488
529
  };
530
+ const buildPathRegistry = (element, path = "") => {
531
+ if (!element || !element.__ref) return {};
532
+ const registry = {};
533
+ const brKey = element.__ref.__brKey;
534
+ if (brKey) registry[path === "" ? "__root" : path] = brKey;
535
+ if (element.__ref.__children) {
536
+ for (const childKey of element.__ref.__children) {
537
+ const child = element[childKey];
538
+ if (child && child.__ref) {
539
+ const childPath = path === "" ? childKey : `${path}.${childKey}`;
540
+ Object.assign(registry, buildPathRegistry(child, childPath));
541
+ }
542
+ }
543
+ }
544
+ return registry;
545
+ };
489
546
  const render = async (data, options = {}) => {
490
547
  const { route = "/", pathname, state: stateOverrides, context: contextOverrides, prefetch = false } = options;
491
548
  const locationPath = pathname || route;
@@ -612,12 +669,20 @@ const render = async (data, options = {}) => {
612
669
  // Caller overrides
613
670
  ...contextOverrides || {}
614
671
  };
672
+ const funcql = await getFuncqlPlugin();
673
+ if (funcql) {
674
+ ctx.plugins = ctx.plugins || [];
675
+ if (!ctx.plugins.some((p) => p.name === "funcql")) {
676
+ ctx.plugins.push(funcql);
677
+ }
678
+ }
615
679
  resetKeys();
616
680
  const element = await createDomqlElement(app, ctx);
617
681
  const flushDelay = prefetch ? 2e3 : 50;
618
682
  await new Promise((r) => setTimeout(r, flushDelay));
619
683
  assignKeys(body);
620
684
  const registry = mapKeysToElements(element);
685
+ const brRegistry = buildPathRegistry(element);
621
686
  const metadata = extractMetadata(data, route, element, element?.state);
622
687
  const emotionCSS = [];
623
688
  const emotionInstance = ctx.emotion || element && element.context && element.context.emotion;
@@ -668,7 +733,7 @@ const render = async (data, options = {}) => {
668
733
  else delete globalThis.document;
669
734
  if (_prevLoc !== void 0) globalThis.location = _prevLoc;
670
735
  else delete globalThis.location;
671
- return { html, metadata, registry, element, emotionCSS, document, window, ssrTranslations, prefetchedPages };
736
+ return { html, metadata, registry, brRegistry, element, emotionCSS, document, window, ssrTranslations, prefetchedPages };
672
737
  };
673
738
  const renderElement = async (elementDef, options = {}) => {
674
739
  const { context = {} } = options;
@@ -714,6 +779,11 @@ const generateGlobalCSS = async (ds, config) => {
714
779
  const { existsSync: existsSync2, writeFileSync: writeFileSync2, unlinkSync: unlinkSync2 } = await import("fs");
715
780
  const { tmpdir: tmpdir2 } = await import("os");
716
781
  const { randomBytes: randomBytes2 } = await import("crypto");
782
+ try {
783
+ tmpdir2();
784
+ } catch {
785
+ return {};
786
+ }
717
787
  const esbuild = await import("esbuild");
718
788
  const dsJson = JSON.stringify(ds || {}, safeJsonReplacer());
719
789
  const cfgJson = JSON.stringify(config || {}, safeJsonReplacer());
@@ -931,6 +1001,7 @@ const renderRoute = async (data, options = {}) => {
931
1001
  fontLinks: generateFontLinks(ds),
932
1002
  metadata: result.metadata || extractMetadata(data, route),
933
1003
  brKeyCount: result.registry ? Object.keys(result.registry).length : 0,
1004
+ brRegistry: result.brRegistry || {},
934
1005
  ssrTranslations: result.ssrTranslations,
935
1006
  prefetchedState,
936
1007
  activeLang
@@ -980,7 +1051,10 @@ const renderPage = async (data, route = "/", options = {}) => {
980
1051
  `;
981
1052
  }
982
1053
  }
983
- isrBody = `${translationSeed}<script>window.__BRENDER__ = true<\/script>
1054
+ const brRegistryJson = result.brRegistry && Object.keys(result.brRegistry).length ? JSON.stringify(result.brRegistry) : null;
1055
+ const brRegistryScript = brRegistryJson ? `<script>window.__BR_REGISTRY__=${brRegistryJson}<\/script>
1056
+ ` : "";
1057
+ isrBody = `${translationSeed}${brRegistryScript}<script>window.__BRENDER__=true<\/script>
984
1058
  <script type="module" src="${prefix}${isr.clientScript}"><\/script>`;
985
1059
  } else {
986
1060
  isrBody = `<script type="module">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbo.ls/brender",
3
- "version": "3.8.2",
3
+ "version": "3.8.7",
4
4
  "license": "CC-BY-NC-4.0",
5
5
  "type": "module",
6
6
  "module": "./dist/esm/index.js",
@@ -36,7 +36,7 @@
36
36
  "dev:rita": "node examples/serve-rita.js"
37
37
  },
38
38
  "dependencies": {
39
- "@symbo.ls/helmet": "^3.8.1",
39
+ "@symbo.ls/helmet": "^3.8.7",
40
40
  "linkedom": "^0.16.8"
41
41
  },
42
42
  "devDependencies": {
package/render.js CHANGED
@@ -8,6 +8,20 @@ import { resetKeys, assignKeys, mapKeysToElements } from './keys.js'
8
8
  import { extractMetadata, generateHeadHtml } from './metadata.js'
9
9
  import { hydrate } from './hydrate.js'
10
10
  import { prefetchPageData, injectPrefetchedState, fetchSSRTranslations } from './prefetch.js'
11
+
12
+ // funcql plugin — enables evaluation of funcql schemas as property values
13
+ // during SSR. Async-imported to avoid polluting non-funcql codepaths.
14
+ let _funcqlPlugin = null
15
+ const getFuncqlPlugin = async () => {
16
+ if (_funcqlPlugin) return _funcqlPlugin
17
+ try {
18
+ const mod = await import('@domql/funcql')
19
+ _funcqlPlugin = mod.funcqlPlugin
20
+ return _funcqlPlugin
21
+ } catch {
22
+ return null
23
+ }
24
+ }
11
25
  import { parseHTML } from 'linkedom'
12
26
  import createEmotionInstance from '@emotion/css/create-instance'
13
27
 
@@ -104,9 +118,23 @@ const safeJsonReplacer = () => {
104
118
  // ── Workspace detection ──────────────────────────────────────────────────────
105
119
  // Detect whether brender is running inside the monorepo or as an installed
106
120
  // npm package, and resolve paths accordingly.
107
- const _brenderRequire = createRequire(import.meta.url)
121
+ // Guard createRequire for bundled environments (e.g. Cloudflare Workers)
122
+ // where import.meta.url may be undefined after bundling.
123
+ let _brenderRequire = null
124
+ try {
125
+ if (import.meta.url) {
126
+ _brenderRequire = createRequire(import.meta.url)
127
+ }
128
+ } catch {
129
+ // Bundled runtime — filesystem-based resolution not available
130
+ }
108
131
 
109
132
  const detectWorkspace = () => {
133
+ // In bundled environments (CF Workers), import.meta.url may be undefined.
134
+ // Return a minimal workspace config that skips filesystem-based resolution.
135
+ if (!import.meta.url || !_brenderRequire) {
136
+ return { isMonorepo: false, monorepoRoot: null, resolvePackage: (pkg) => pkg }
137
+ }
110
138
  const brenderDir = realpathSync(new URL('.', import.meta.url).pathname)
111
139
  const monorepoRoot = resolve(brenderDir, '../..')
112
140
 
@@ -232,6 +260,18 @@ const bundleCreateDomql = async () => {
232
260
 
233
261
  const ws = detectWorkspace()
234
262
 
263
+ // In bundled environments (CF Workers), use the pre-bundled createDomql.
264
+ // Generated by: node scripts/prebundle-createDomql.js (or during npm prepublish)
265
+ if (!_brenderRequire) {
266
+ try {
267
+ const mod = await import('./dist/createDomql.bundled.mjs')
268
+ _cachedCreateDomql = mod
269
+ return mod
270
+ } catch (err) {
271
+ throw new Error(`brender: pre-bundled createDomql not available in bundled runtime: ${err.message}`)
272
+ }
273
+ }
274
+
235
275
  // Resolve entry point
236
276
  let entry
237
277
  if (ws.isMonorepo) {
@@ -412,6 +452,15 @@ const bundleCreateDomql = async () => {
412
452
  )
413
453
  return { contents, loader: 'js' }
414
454
  })
455
+ // Stub the Supabase adapter during brender — the prefetch system handles
456
+ // data fetching separately via its own Supabase client. The DOMQL fetch
457
+ // plugin's on.create handler shouldn't fire during SSR.
458
+ build.onLoad({ filter: /fetch\/adapters\/supabase\.js$/ }, () => {
459
+ return {
460
+ contents: `export const setup = async () => null;\nexport const supabaseAdapter = () => ({name:'supabase'});\n`,
461
+ loader: 'js'
462
+ }
463
+ })
415
464
  // No-op: globals.js keeps window = globalThis
416
465
  // We set globalThis.document/location before import to make it SSR-safe
417
466
  // Stub loader for brender-stub namespace (used for @symbo.ls/sync etc.)
@@ -470,7 +519,12 @@ const bundleCreateDomql = async () => {
470
519
  })
471
520
 
472
521
  const mod = await import(`file://${outFile}`)
473
- try { unlinkSync(outFile) } catch {} // cleanup: ignore if temp file already removed
522
+ // Keep temp file in debug mode for inspection
523
+ if (!process.env.BRENDER_DEBUG) {
524
+ try { unlinkSync(outFile) } catch {}
525
+ } else {
526
+ console.log('[brender] Bundle saved:', outFile)
527
+ }
474
528
 
475
529
  _cachedCreateDomql = mod
476
530
  return mod
@@ -758,6 +812,15 @@ export const render = async (data, options = {}) => {
758
812
  ...(contextOverrides || {})
759
813
  }
760
814
 
815
+ // Add funcql plugin if available (enables funcql schema evaluation in exec())
816
+ const funcql = await getFuncqlPlugin()
817
+ if (funcql) {
818
+ ctx.plugins = ctx.plugins || []
819
+ if (!ctx.plugins.some(p => p.name === 'funcql')) {
820
+ ctx.plugins.push(funcql)
821
+ }
822
+ }
823
+
761
824
  resetKeys()
762
825
 
763
826
  const element = await createDomqlElement(app, ctx)
@@ -937,6 +1000,10 @@ const generateGlobalCSS = async (ds, config) => {
937
1000
  const { existsSync, writeFileSync, unlinkSync } = await import('fs')
938
1001
  const { tmpdir } = await import('os')
939
1002
  const { randomBytes } = await import('crypto')
1003
+
1004
+ // Guard: skip if filesystem APIs aren't available (e.g. CF Workers)
1005
+ try { tmpdir() } catch { return {} }
1006
+
940
1007
  const esbuild = await import('esbuild')
941
1008
 
942
1009
  // Write a temporary script that imports scratch, runs set(), and