@cmssy/react 0.1.7 → 0.1.9

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/dist/client.cjs CHANGED
@@ -747,9 +747,105 @@ function CmssyLazyLayout({ load, ...props }) {
747
747
  if (!blocks) return null;
748
748
  return /* @__PURE__ */ jsxRuntime.jsx(CmssyEditableLayout, { ...props, blocks });
749
749
  }
750
+ var CmssyAuthContext = react.createContext(null);
751
+ function CmssyAuthProvider({
752
+ children,
753
+ basePath = "/api/cmssy/auth",
754
+ initialUser
755
+ }) {
756
+ const seeded = initialUser !== void 0;
757
+ const [user, setUser] = react.useState(initialUser ?? null);
758
+ const [loading, setLoading] = react.useState(!seeded);
759
+ const generation = react.useRef(0);
760
+ const base = react.useMemo(() => basePath.replace(/\/+$/, ""), [basePath]);
761
+ const commitUser = react.useCallback((next) => {
762
+ generation.current += 1;
763
+ setUser(next);
764
+ }, []);
765
+ const fetchUser = react.useCallback(async () => {
766
+ try {
767
+ const res = await fetch(`${base}/me`, {
768
+ credentials: "same-origin",
769
+ cache: "no-store"
770
+ });
771
+ const data = await res.json();
772
+ return data.user ?? null;
773
+ } catch {
774
+ return null;
775
+ }
776
+ }, [base]);
777
+ react.useEffect(() => {
778
+ if (seeded) return;
779
+ const gen = generation.current;
780
+ void (async () => {
781
+ const next = await fetchUser();
782
+ if (gen === generation.current) {
783
+ setUser(next);
784
+ setLoading(false);
785
+ }
786
+ })();
787
+ }, [fetchUser, seeded]);
788
+ const postAction = react.useCallback(
789
+ async (action, body) => {
790
+ const res = await fetch(`${base}/${action}`, {
791
+ method: "POST",
792
+ credentials: "same-origin",
793
+ headers: { "content-type": "application/json" },
794
+ body: JSON.stringify(body)
795
+ });
796
+ return await res.json();
797
+ },
798
+ [base]
799
+ );
800
+ const signIn = react.useCallback(
801
+ async (identity, password) => {
802
+ const data = await postAction("sign-in", { identity, password });
803
+ if (data.ok && data.user) commitUser(data.user);
804
+ return { ok: Boolean(data.ok), message: data.message };
805
+ },
806
+ [postAction, commitUser]
807
+ );
808
+ const register = react.useCallback(
809
+ async (identity, password, fields) => {
810
+ const data = await postAction("register", {
811
+ identity,
812
+ password,
813
+ fields: fields ?? {}
814
+ });
815
+ return { ok: Boolean(data.ok), message: data.message };
816
+ },
817
+ [postAction]
818
+ );
819
+ const signOut = react.useCallback(async () => {
820
+ try {
821
+ await postAction("sign-out", {});
822
+ } finally {
823
+ commitUser(null);
824
+ }
825
+ }, [postAction, commitUser]);
826
+ const refresh = react.useCallback(async () => {
827
+ const gen = generation.current;
828
+ const next = await fetchUser();
829
+ if (gen === generation.current) setUser(next);
830
+ }, [fetchUser]);
831
+ const value = react.useMemo(
832
+ () => ({ user, loading, signIn, register, signOut, refresh }),
833
+ [user, loading, signIn, register, signOut, refresh]
834
+ );
835
+ return /* @__PURE__ */ jsxRuntime.jsx(CmssyAuthContext.Provider, { value, children });
836
+ }
837
+ function useCmssyUser() {
838
+ const ctx = react.useContext(CmssyAuthContext);
839
+ if (!ctx) {
840
+ throw new Error("useCmssyUser must be used within <CmssyAuthProvider>");
841
+ }
842
+ return ctx;
843
+ }
750
844
 
845
+ exports.CmssyAuthProvider = CmssyAuthProvider;
751
846
  exports.CmssyEditableLayout = CmssyEditableLayout;
752
847
  exports.CmssyEditablePage = CmssyEditablePage;
753
848
  exports.CmssyLazyEditor = CmssyLazyEditor;
754
849
  exports.CmssyLazyLayout = CmssyLazyLayout;
850
+ exports.useCmssyUser = useCmssyUser;
755
851
  exports.useEditBridge = useEditBridge;
package/dist/client.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { B as BlockSchema, a as BlockMeta, C as CmssyPageData, b as BlockDefinition, c as CmssyFormDefinition, d as CmssyLayoutGroup } from './registry-BaFQ2k7r.cjs';
3
- import 'react';
2
+ import { B as BlockSchema, a as BlockMeta, C as CmssyPageData, b as BlockDefinition, c as CmssyFormDefinition, d as CmssyLayoutGroup } from './registry-e3O4s_bN.cjs';
3
+ import { ReactNode } from 'react';
4
4
 
5
5
  interface EditBridgeConfig {
6
6
  editorOrigin: string;
@@ -80,4 +80,28 @@ interface CmssyLazyLayoutProps {
80
80
  }
81
81
  declare function CmssyLazyLayout({ load, ...props }: CmssyLazyLayoutProps): react_jsx_runtime.JSX.Element | null;
82
82
 
83
- export { CmssyEditableLayout, type CmssyEditableLayoutProps, CmssyEditablePage, type CmssyEditablePageProps, CmssyLazyEditor, type CmssyLazyEditorProps, CmssyLazyLayout, type CmssyLazyLayoutProps, type EditBridgeConfig, type EditBridgeState, type PatchMap, useEditBridge };
83
+ interface CmssyAuthUser {
84
+ recordId: string;
85
+ email: string;
86
+ }
87
+ interface CmssyAuthActionResult {
88
+ ok: boolean;
89
+ message?: string;
90
+ }
91
+ interface CmssyAuthState {
92
+ user: CmssyAuthUser | null;
93
+ loading: boolean;
94
+ signIn(identity: string, password: string): Promise<CmssyAuthActionResult>;
95
+ register(identity: string, password: string, fields?: Record<string, unknown>): Promise<CmssyAuthActionResult>;
96
+ signOut(): Promise<void>;
97
+ refresh(): Promise<void>;
98
+ }
99
+ interface CmssyAuthProviderProps {
100
+ children: ReactNode;
101
+ basePath?: string;
102
+ initialUser?: CmssyAuthUser | null;
103
+ }
104
+ declare function CmssyAuthProvider({ children, basePath, initialUser, }: CmssyAuthProviderProps): react_jsx_runtime.JSX.Element;
105
+ declare function useCmssyUser(): CmssyAuthState;
106
+
107
+ export { type CmssyAuthActionResult, CmssyAuthProvider, type CmssyAuthProviderProps, type CmssyAuthState, type CmssyAuthUser, CmssyEditableLayout, type CmssyEditableLayoutProps, CmssyEditablePage, type CmssyEditablePageProps, CmssyLazyEditor, type CmssyLazyEditorProps, CmssyLazyLayout, type CmssyLazyLayoutProps, type EditBridgeConfig, type EditBridgeState, type PatchMap, useCmssyUser, useEditBridge };
package/dist/client.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { B as BlockSchema, a as BlockMeta, C as CmssyPageData, b as BlockDefinition, c as CmssyFormDefinition, d as CmssyLayoutGroup } from './registry-BaFQ2k7r.js';
3
- import 'react';
2
+ import { B as BlockSchema, a as BlockMeta, C as CmssyPageData, b as BlockDefinition, c as CmssyFormDefinition, d as CmssyLayoutGroup } from './registry-e3O4s_bN.js';
3
+ import { ReactNode } from 'react';
4
4
 
5
5
  interface EditBridgeConfig {
6
6
  editorOrigin: string;
@@ -80,4 +80,28 @@ interface CmssyLazyLayoutProps {
80
80
  }
81
81
  declare function CmssyLazyLayout({ load, ...props }: CmssyLazyLayoutProps): react_jsx_runtime.JSX.Element | null;
82
82
 
83
- export { CmssyEditableLayout, type CmssyEditableLayoutProps, CmssyEditablePage, type CmssyEditablePageProps, CmssyLazyEditor, type CmssyLazyEditorProps, CmssyLazyLayout, type CmssyLazyLayoutProps, type EditBridgeConfig, type EditBridgeState, type PatchMap, useEditBridge };
83
+ interface CmssyAuthUser {
84
+ recordId: string;
85
+ email: string;
86
+ }
87
+ interface CmssyAuthActionResult {
88
+ ok: boolean;
89
+ message?: string;
90
+ }
91
+ interface CmssyAuthState {
92
+ user: CmssyAuthUser | null;
93
+ loading: boolean;
94
+ signIn(identity: string, password: string): Promise<CmssyAuthActionResult>;
95
+ register(identity: string, password: string, fields?: Record<string, unknown>): Promise<CmssyAuthActionResult>;
96
+ signOut(): Promise<void>;
97
+ refresh(): Promise<void>;
98
+ }
99
+ interface CmssyAuthProviderProps {
100
+ children: ReactNode;
101
+ basePath?: string;
102
+ initialUser?: CmssyAuthUser | null;
103
+ }
104
+ declare function CmssyAuthProvider({ children, basePath, initialUser, }: CmssyAuthProviderProps): react_jsx_runtime.JSX.Element;
105
+ declare function useCmssyUser(): CmssyAuthState;
106
+
107
+ export { type CmssyAuthActionResult, CmssyAuthProvider, type CmssyAuthProviderProps, type CmssyAuthState, type CmssyAuthUser, CmssyEditableLayout, type CmssyEditableLayoutProps, CmssyEditablePage, type CmssyEditablePageProps, CmssyLazyEditor, type CmssyLazyEditorProps, CmssyLazyLayout, type CmssyLazyLayoutProps, type EditBridgeConfig, type EditBridgeState, type PatchMap, useCmssyUser, useEditBridge };
package/dist/client.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { useState, useRef, useEffect, useMemo, createElement } from 'react';
2
+ import { createContext, useState, useRef, useEffect, useMemo, useCallback, useContext, createElement } from 'react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
 
5
5
  // src/registry.ts
@@ -745,5 +745,99 @@ function CmssyLazyLayout({ load, ...props }) {
745
745
  if (!blocks) return null;
746
746
  return /* @__PURE__ */ jsx(CmssyEditableLayout, { ...props, blocks });
747
747
  }
748
+ var CmssyAuthContext = createContext(null);
749
+ function CmssyAuthProvider({
750
+ children,
751
+ basePath = "/api/cmssy/auth",
752
+ initialUser
753
+ }) {
754
+ const seeded = initialUser !== void 0;
755
+ const [user, setUser] = useState(initialUser ?? null);
756
+ const [loading, setLoading] = useState(!seeded);
757
+ const generation = useRef(0);
758
+ const base = useMemo(() => basePath.replace(/\/+$/, ""), [basePath]);
759
+ const commitUser = useCallback((next) => {
760
+ generation.current += 1;
761
+ setUser(next);
762
+ }, []);
763
+ const fetchUser = useCallback(async () => {
764
+ try {
765
+ const res = await fetch(`${base}/me`, {
766
+ credentials: "same-origin",
767
+ cache: "no-store"
768
+ });
769
+ const data = await res.json();
770
+ return data.user ?? null;
771
+ } catch {
772
+ return null;
773
+ }
774
+ }, [base]);
775
+ useEffect(() => {
776
+ if (seeded) return;
777
+ const gen = generation.current;
778
+ void (async () => {
779
+ const next = await fetchUser();
780
+ if (gen === generation.current) {
781
+ setUser(next);
782
+ setLoading(false);
783
+ }
784
+ })();
785
+ }, [fetchUser, seeded]);
786
+ const postAction = useCallback(
787
+ async (action, body) => {
788
+ const res = await fetch(`${base}/${action}`, {
789
+ method: "POST",
790
+ credentials: "same-origin",
791
+ headers: { "content-type": "application/json" },
792
+ body: JSON.stringify(body)
793
+ });
794
+ return await res.json();
795
+ },
796
+ [base]
797
+ );
798
+ const signIn = useCallback(
799
+ async (identity, password) => {
800
+ const data = await postAction("sign-in", { identity, password });
801
+ if (data.ok && data.user) commitUser(data.user);
802
+ return { ok: Boolean(data.ok), message: data.message };
803
+ },
804
+ [postAction, commitUser]
805
+ );
806
+ const register = useCallback(
807
+ async (identity, password, fields) => {
808
+ const data = await postAction("register", {
809
+ identity,
810
+ password,
811
+ fields: fields ?? {}
812
+ });
813
+ return { ok: Boolean(data.ok), message: data.message };
814
+ },
815
+ [postAction]
816
+ );
817
+ const signOut = useCallback(async () => {
818
+ try {
819
+ await postAction("sign-out", {});
820
+ } finally {
821
+ commitUser(null);
822
+ }
823
+ }, [postAction, commitUser]);
824
+ const refresh = useCallback(async () => {
825
+ const gen = generation.current;
826
+ const next = await fetchUser();
827
+ if (gen === generation.current) setUser(next);
828
+ }, [fetchUser]);
829
+ const value = useMemo(
830
+ () => ({ user, loading, signIn, register, signOut, refresh }),
831
+ [user, loading, signIn, register, signOut, refresh]
832
+ );
833
+ return /* @__PURE__ */ jsx(CmssyAuthContext.Provider, { value, children });
834
+ }
835
+ function useCmssyUser() {
836
+ const ctx = useContext(CmssyAuthContext);
837
+ if (!ctx) {
838
+ throw new Error("useCmssyUser must be used within <CmssyAuthProvider>");
839
+ }
840
+ return ctx;
841
+ }
748
842
 
749
- export { CmssyEditableLayout, CmssyEditablePage, CmssyLazyEditor, CmssyLazyLayout, useEditBridge };
843
+ export { CmssyAuthProvider, CmssyEditableLayout, CmssyEditablePage, CmssyLazyEditor, CmssyLazyLayout, useCmssyUser, useEditBridge };
package/dist/index.cjs CHANGED
@@ -35,6 +35,13 @@ function buildBlockMap(blocks) {
35
35
  for (const block of blocks) map[block.type] = block.component;
36
36
  return map;
37
37
  }
38
+ function buildLoaderMap(blocks) {
39
+ const map = /* @__PURE__ */ Object.create(null);
40
+ for (const block of blocks) {
41
+ if (block.loader) map[block.type] = block.loader;
42
+ }
43
+ return map;
44
+ }
38
45
  function blocksToSchemas(blocks) {
39
46
  const out = /* @__PURE__ */ Object.create(null);
40
47
  for (const block of blocks) {
@@ -60,19 +67,6 @@ function blocksToMeta(blocks, defaults = {}) {
60
67
  return out;
61
68
  }
62
69
 
63
- // src/components/block-context.ts
64
- function buildBlockContext(locale, defaultLocale, enabledLocales, isPreview, forms) {
65
- return {
66
- locale: {
67
- current: locale,
68
- default: defaultLocale,
69
- enabled: enabledLocales && enabledLocales.length > 0 ? enabledLocales : Array.from(/* @__PURE__ */ new Set([defaultLocale, locale]))
70
- },
71
- isPreview: isPreview ?? false,
72
- forms
73
- };
74
- }
75
-
76
70
  // src/content/get-block-content.ts
77
71
  function isPlainObject(value) {
78
72
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -96,6 +90,19 @@ function getBlockContentForLanguage(content, locale, defaultLocale = "en", avail
96
90
  const chosen = localeMap[locale] ?? localeMap[defaultLocale] ?? localeMap[fallbackKey];
97
91
  return { ...nonTranslatable, ...chosen };
98
92
  }
93
+
94
+ // src/components/block-context.ts
95
+ function buildBlockContext(locale, defaultLocale, enabledLocales, isPreview, forms) {
96
+ return {
97
+ locale: {
98
+ current: locale,
99
+ default: defaultLocale,
100
+ enabled: enabledLocales && enabledLocales.length > 0 ? enabledLocales : Array.from(/* @__PURE__ */ new Set([defaultLocale, locale]))
101
+ },
102
+ isPreview: isPreview ?? false,
103
+ forms
104
+ };
105
+ }
99
106
  var WARN_CAP = 256;
100
107
  var warned = /* @__PURE__ */ new Set();
101
108
  function UnknownBlock({ type }) {
@@ -106,12 +113,14 @@ function UnknownBlock({ type }) {
106
113
  }
107
114
  return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-cmssy-unknown-block": type });
108
115
  }
109
- function renderResolvedBlock(block, map, locale, defaultLocale, context) {
116
+ function renderResolvedBlock(block, map, locale, defaultLocale, options = {}) {
117
+ const { context, data, resolvedContent, enabledLocales } = options;
110
118
  const Component = Object.hasOwn(map, block.type) ? map[block.type] : void 0;
111
- const content = getBlockContentForLanguage(
119
+ const content = resolvedContent ?? getBlockContentForLanguage(
112
120
  block.content,
113
121
  locale,
114
- defaultLocale
122
+ defaultLocale,
123
+ enabledLocales?.length ? enabledLocales : void 0
115
124
  );
116
125
  return /* @__PURE__ */ jsxRuntime.jsx(
117
126
  "div",
@@ -119,12 +128,12 @@ function renderResolvedBlock(block, map, locale, defaultLocale, context) {
119
128
  "data-block-id": block.id,
120
129
  "data-block-type": block.type,
121
130
  style: Component ? void 0 : { display: "none" },
122
- children: Component ? react.createElement(Component, { content, context }) : /* @__PURE__ */ jsxRuntime.jsx(UnknownBlock, { type: block.type })
131
+ children: Component ? react.createElement(Component, { content, context, data }) : /* @__PURE__ */ jsxRuntime.jsx(UnknownBlock, { type: block.type })
123
132
  },
124
133
  block.id
125
134
  );
126
135
  }
127
- function CmssyServerPage({
136
+ async function CmssyServerPage({
128
137
  page,
129
138
  blocks,
130
139
  locale = "en",
@@ -134,6 +143,7 @@ function CmssyServerPage({
134
143
  }) {
135
144
  if (!page) return null;
136
145
  const map = buildBlockMap(blocks);
146
+ const loaderMap = buildLoaderMap(blocks);
137
147
  const context = buildBlockContext(
138
148
  locale,
139
149
  defaultLocale,
@@ -141,8 +151,38 @@ function CmssyServerPage({
141
151
  false,
142
152
  forms
143
153
  );
154
+ const resolved = await Promise.all(
155
+ page.blocks.map(async (block) => {
156
+ const content = getBlockContentForLanguage(
157
+ block.content,
158
+ locale,
159
+ defaultLocale,
160
+ enabledLocales?.length ? enabledLocales : void 0
161
+ );
162
+ const loader = loaderMap[block.type];
163
+ let data;
164
+ if (loader) {
165
+ try {
166
+ data = await loader({ content, context });
167
+ } catch (err) {
168
+ if (typeof console !== "undefined") {
169
+ console.warn(
170
+ `[cmssy] loader for block "${block.type}" (${block.id}) failed`,
171
+ err
172
+ );
173
+ }
174
+ }
175
+ }
176
+ return { content, data };
177
+ })
178
+ );
144
179
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: page.blocks.map(
145
- (block) => renderResolvedBlock(block, map, locale, defaultLocale, context)
180
+ (block, i) => renderResolvedBlock(block, map, locale, defaultLocale, {
181
+ context,
182
+ data: resolved[i]?.data,
183
+ resolvedContent: resolved[i]?.content,
184
+ enabledLocales
185
+ })
146
186
  ) });
147
187
  }
148
188
  function CmssyServerLayout({
@@ -159,7 +199,10 @@ function CmssyServerLayout({
159
199
  const map = buildBlockMap(blocks);
160
200
  const context = buildBlockContext(locale, defaultLocale, enabledLocales);
161
201
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: layoutBlocks.map(
162
- (block) => renderResolvedBlock(block, map, locale, defaultLocale, context)
202
+ (block) => renderResolvedBlock(block, map, locale, defaultLocale, {
203
+ context,
204
+ enabledLocales
205
+ })
163
206
  ) });
164
207
  }
165
208
 
@@ -642,4 +685,5 @@ exports.parseEditorMessage = parseEditorMessage;
642
685
  exports.postToEditor = postToEditor;
643
686
  exports.resolveForms = resolveForms;
644
687
  exports.resolveSiteLocales = resolveSiteLocales;
688
+ exports.resolveWorkspaceId = resolveWorkspaceId;
645
689
  exports.splitLocaleFromPath = splitLocaleFromPath;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { F as FieldDefinition, C as CmssyPageData, b as BlockDefinition, c as CmssyFormDefinition, d as CmssyLayoutGroup, E as EditorToAppMessage, A as AppToEditorMessage, e as FetchLike, f as CmssyClientConfig, R as RawBlock, g as BlockMap, h as CmssyBlockContext } from './registry-BaFQ2k7r.cjs';
2
- export { a as BlockMeta, i as BlockRect, B as BlockSchema, j as BoundsMessage, k as ClickMessage, l as CmssyBranding, m as CmssyFormField, n as CmssyFormSettings, o as CmssyFormSubmitResponse, p as CmssyLocaleContext, q as CmssyModelDefinition, r as CmssyModelRecord, s as CmssyRecordList, t as CmssySiteConfig, u as FORM_QUERY, v as FetchLikeResponse, w as FetchPageOptions, x as FieldType, M as MODEL_DEFINITIONS_QUERY, y as MODEL_RECORDS_QUERY, P as PROTOCOL_VERSION, z as ParentReadyMessage, D as PatchMessage, G as RawLayoutBlock, H as ReadyMessage, S as SITE_CONFIG_QUERY, I as SUBMIT_FORM_MUTATION, J as SelectMessage, K as SubmitFormInput, L as blocksToMeta, N as blocksToSchemas, O as buildBlockContext, Q as buildBlockMap, T as defineBlock, U as fetchLayouts, V as fetchPage, W as isProtocolCompatible, X as normalizeSlug } from './registry-BaFQ2k7r.cjs';
1
+ import { F as FieldDefinition, C as CmssyPageData, b as BlockDefinition, c as CmssyFormDefinition, d as CmssyLayoutGroup, E as EditorToAppMessage, A as AppToEditorMessage, e as FetchLike, f as CmssyClientConfig, R as RawBlock, g as BlockMap, h as CmssyBlockContext } from './registry-e3O4s_bN.cjs';
2
+ export { a as BlockMeta, i as BlockRect, B as BlockSchema, j as BoundsMessage, k as ClickMessage, l as CmssyBranding, m as CmssyFormField, n as CmssyFormSettings, o as CmssyFormSubmitResponse, p as CmssyLocaleContext, q as CmssyModelDefinition, r as CmssyModelRecord, s as CmssyRecordList, t as CmssySiteConfig, u as FORM_QUERY, v as FetchLikeResponse, w as FetchPageOptions, x as FieldType, M as MODEL_DEFINITIONS_QUERY, y as MODEL_RECORDS_QUERY, P as PROTOCOL_VERSION, z as ParentReadyMessage, D as PatchMessage, G as RawLayoutBlock, H as ReadyMessage, S as SITE_CONFIG_QUERY, I as SUBMIT_FORM_MUTATION, J as SelectMessage, K as SubmitFormInput, L as blocksToMeta, N as blocksToSchemas, O as buildBlockContext, Q as buildBlockMap, T as defineBlock, U as fetchLayouts, V as fetchPage, W as isProtocolCompatible, X as normalizeSlug } from './registry-e3O4s_bN.cjs';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
  import 'react';
5
5
 
@@ -31,7 +31,12 @@ interface CmssyServerPageProps {
31
31
  /** Form definitions referenced by page blocks, exposed via context.forms. */
32
32
  forms?: Record<string, CmssyFormDefinition>;
33
33
  }
34
- declare function CmssyServerPage({ page, blocks, locale, defaultLocale, enabledLocales, forms, }: CmssyServerPageProps): react_jsx_runtime.JSX.Element | null;
34
+ /**
35
+ * Async React Server Component (Next.js App Router / RSC). It runs each block's
36
+ * loader server-side before rendering, so it must be rendered in a server
37
+ * component tree (as `createCmssyPage` does) - not in a client component.
38
+ */
39
+ declare function CmssyServerPage({ page, blocks, locale, defaultLocale, enabledLocales, forms, }: CmssyServerPageProps): Promise<react_jsx_runtime.JSX.Element | null>;
35
40
 
36
41
  interface CmssyServerLayoutProps {
37
42
  groups: CmssyLayoutGroup[];
@@ -60,6 +65,8 @@ interface GraphqlRequestOptions {
60
65
  }
61
66
  declare function graphqlRequest<T>(config: CmssyClientConfig, query: string, variables: Record<string, unknown>, options?: GraphqlRequestOptions, label?: string): Promise<T>;
62
67
 
68
+ declare function resolveWorkspaceId(config: CmssyClientConfig, options?: GraphqlRequestOptions): Promise<string>;
69
+
63
70
  interface QueryScopedOptions extends GraphqlRequestOptions {
64
71
  workspaceId?: string;
65
72
  }
@@ -101,4 +108,4 @@ interface UnknownBlockProps {
101
108
  }
102
109
  declare function UnknownBlock({ type }: UnknownBlockProps): react_jsx_runtime.JSX.Element;
103
110
 
104
- export { AppToEditorMessage, BlockDefinition, BlockMap, CmssyBlock, CmssyBlockContext, type CmssyBlockProps, type CmssyClient, CmssyClientConfig, CmssyFormDefinition, CmssyLayoutGroup, CmssyPageData, CmssyServerLayout, type CmssyServerLayoutProps, CmssyServerPage, type CmssyServerPageProps, type CmssySiteLocales, EditorToAppMessage, FetchLike, type FieldControl, FieldDefinition, type GraphqlRequestOptions, type PostTarget, type QueryScopedOptions, RawBlock, UnknownBlock, type UnknownBlockProps, collectFormIds, createCmssyClient, fields, getBlockContentForLanguage, graphqlRequest, normalizeOrigin, parseEditorMessage, postToEditor, resolveForms, resolveSiteLocales, splitLocaleFromPath };
111
+ export { AppToEditorMessage, BlockDefinition, BlockMap, CmssyBlock, CmssyBlockContext, type CmssyBlockProps, type CmssyClient, CmssyClientConfig, CmssyFormDefinition, CmssyLayoutGroup, CmssyPageData, CmssyServerLayout, type CmssyServerLayoutProps, CmssyServerPage, type CmssyServerPageProps, type CmssySiteLocales, EditorToAppMessage, FetchLike, type FieldControl, FieldDefinition, type GraphqlRequestOptions, type PostTarget, type QueryScopedOptions, RawBlock, UnknownBlock, type UnknownBlockProps, collectFormIds, createCmssyClient, fields, getBlockContentForLanguage, graphqlRequest, normalizeOrigin, parseEditorMessage, postToEditor, resolveForms, resolveSiteLocales, resolveWorkspaceId, splitLocaleFromPath };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { F as FieldDefinition, C as CmssyPageData, b as BlockDefinition, c as CmssyFormDefinition, d as CmssyLayoutGroup, E as EditorToAppMessage, A as AppToEditorMessage, e as FetchLike, f as CmssyClientConfig, R as RawBlock, g as BlockMap, h as CmssyBlockContext } from './registry-BaFQ2k7r.js';
2
- export { a as BlockMeta, i as BlockRect, B as BlockSchema, j as BoundsMessage, k as ClickMessage, l as CmssyBranding, m as CmssyFormField, n as CmssyFormSettings, o as CmssyFormSubmitResponse, p as CmssyLocaleContext, q as CmssyModelDefinition, r as CmssyModelRecord, s as CmssyRecordList, t as CmssySiteConfig, u as FORM_QUERY, v as FetchLikeResponse, w as FetchPageOptions, x as FieldType, M as MODEL_DEFINITIONS_QUERY, y as MODEL_RECORDS_QUERY, P as PROTOCOL_VERSION, z as ParentReadyMessage, D as PatchMessage, G as RawLayoutBlock, H as ReadyMessage, S as SITE_CONFIG_QUERY, I as SUBMIT_FORM_MUTATION, J as SelectMessage, K as SubmitFormInput, L as blocksToMeta, N as blocksToSchemas, O as buildBlockContext, Q as buildBlockMap, T as defineBlock, U as fetchLayouts, V as fetchPage, W as isProtocolCompatible, X as normalizeSlug } from './registry-BaFQ2k7r.js';
1
+ import { F as FieldDefinition, C as CmssyPageData, b as BlockDefinition, c as CmssyFormDefinition, d as CmssyLayoutGroup, E as EditorToAppMessage, A as AppToEditorMessage, e as FetchLike, f as CmssyClientConfig, R as RawBlock, g as BlockMap, h as CmssyBlockContext } from './registry-e3O4s_bN.js';
2
+ export { a as BlockMeta, i as BlockRect, B as BlockSchema, j as BoundsMessage, k as ClickMessage, l as CmssyBranding, m as CmssyFormField, n as CmssyFormSettings, o as CmssyFormSubmitResponse, p as CmssyLocaleContext, q as CmssyModelDefinition, r as CmssyModelRecord, s as CmssyRecordList, t as CmssySiteConfig, u as FORM_QUERY, v as FetchLikeResponse, w as FetchPageOptions, x as FieldType, M as MODEL_DEFINITIONS_QUERY, y as MODEL_RECORDS_QUERY, P as PROTOCOL_VERSION, z as ParentReadyMessage, D as PatchMessage, G as RawLayoutBlock, H as ReadyMessage, S as SITE_CONFIG_QUERY, I as SUBMIT_FORM_MUTATION, J as SelectMessage, K as SubmitFormInput, L as blocksToMeta, N as blocksToSchemas, O as buildBlockContext, Q as buildBlockMap, T as defineBlock, U as fetchLayouts, V as fetchPage, W as isProtocolCompatible, X as normalizeSlug } from './registry-e3O4s_bN.js';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
  import 'react';
5
5
 
@@ -31,7 +31,12 @@ interface CmssyServerPageProps {
31
31
  /** Form definitions referenced by page blocks, exposed via context.forms. */
32
32
  forms?: Record<string, CmssyFormDefinition>;
33
33
  }
34
- declare function CmssyServerPage({ page, blocks, locale, defaultLocale, enabledLocales, forms, }: CmssyServerPageProps): react_jsx_runtime.JSX.Element | null;
34
+ /**
35
+ * Async React Server Component (Next.js App Router / RSC). It runs each block's
36
+ * loader server-side before rendering, so it must be rendered in a server
37
+ * component tree (as `createCmssyPage` does) - not in a client component.
38
+ */
39
+ declare function CmssyServerPage({ page, blocks, locale, defaultLocale, enabledLocales, forms, }: CmssyServerPageProps): Promise<react_jsx_runtime.JSX.Element | null>;
35
40
 
36
41
  interface CmssyServerLayoutProps {
37
42
  groups: CmssyLayoutGroup[];
@@ -60,6 +65,8 @@ interface GraphqlRequestOptions {
60
65
  }
61
66
  declare function graphqlRequest<T>(config: CmssyClientConfig, query: string, variables: Record<string, unknown>, options?: GraphqlRequestOptions, label?: string): Promise<T>;
62
67
 
68
+ declare function resolveWorkspaceId(config: CmssyClientConfig, options?: GraphqlRequestOptions): Promise<string>;
69
+
63
70
  interface QueryScopedOptions extends GraphqlRequestOptions {
64
71
  workspaceId?: string;
65
72
  }
@@ -101,4 +108,4 @@ interface UnknownBlockProps {
101
108
  }
102
109
  declare function UnknownBlock({ type }: UnknownBlockProps): react_jsx_runtime.JSX.Element;
103
110
 
104
- export { AppToEditorMessage, BlockDefinition, BlockMap, CmssyBlock, CmssyBlockContext, type CmssyBlockProps, type CmssyClient, CmssyClientConfig, CmssyFormDefinition, CmssyLayoutGroup, CmssyPageData, CmssyServerLayout, type CmssyServerLayoutProps, CmssyServerPage, type CmssyServerPageProps, type CmssySiteLocales, EditorToAppMessage, FetchLike, type FieldControl, FieldDefinition, type GraphqlRequestOptions, type PostTarget, type QueryScopedOptions, RawBlock, UnknownBlock, type UnknownBlockProps, collectFormIds, createCmssyClient, fields, getBlockContentForLanguage, graphqlRequest, normalizeOrigin, parseEditorMessage, postToEditor, resolveForms, resolveSiteLocales, splitLocaleFromPath };
111
+ export { AppToEditorMessage, BlockDefinition, BlockMap, CmssyBlock, CmssyBlockContext, type CmssyBlockProps, type CmssyClient, CmssyClientConfig, CmssyFormDefinition, CmssyLayoutGroup, CmssyPageData, CmssyServerLayout, type CmssyServerLayoutProps, CmssyServerPage, type CmssyServerPageProps, type CmssySiteLocales, EditorToAppMessage, FetchLike, type FieldControl, FieldDefinition, type GraphqlRequestOptions, type PostTarget, type QueryScopedOptions, RawBlock, UnknownBlock, type UnknownBlockProps, collectFormIds, createCmssyClient, fields, getBlockContentForLanguage, graphqlRequest, normalizeOrigin, parseEditorMessage, postToEditor, resolveForms, resolveSiteLocales, resolveWorkspaceId, splitLocaleFromPath };
package/dist/index.js CHANGED
@@ -33,6 +33,13 @@ function buildBlockMap(blocks) {
33
33
  for (const block of blocks) map[block.type] = block.component;
34
34
  return map;
35
35
  }
36
+ function buildLoaderMap(blocks) {
37
+ const map = /* @__PURE__ */ Object.create(null);
38
+ for (const block of blocks) {
39
+ if (block.loader) map[block.type] = block.loader;
40
+ }
41
+ return map;
42
+ }
36
43
  function blocksToSchemas(blocks) {
37
44
  const out = /* @__PURE__ */ Object.create(null);
38
45
  for (const block of blocks) {
@@ -58,19 +65,6 @@ function blocksToMeta(blocks, defaults = {}) {
58
65
  return out;
59
66
  }
60
67
 
61
- // src/components/block-context.ts
62
- function buildBlockContext(locale, defaultLocale, enabledLocales, isPreview, forms) {
63
- return {
64
- locale: {
65
- current: locale,
66
- default: defaultLocale,
67
- enabled: enabledLocales && enabledLocales.length > 0 ? enabledLocales : Array.from(/* @__PURE__ */ new Set([defaultLocale, locale]))
68
- },
69
- isPreview: isPreview ?? false,
70
- forms
71
- };
72
- }
73
-
74
68
  // src/content/get-block-content.ts
75
69
  function isPlainObject(value) {
76
70
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -94,6 +88,19 @@ function getBlockContentForLanguage(content, locale, defaultLocale = "en", avail
94
88
  const chosen = localeMap[locale] ?? localeMap[defaultLocale] ?? localeMap[fallbackKey];
95
89
  return { ...nonTranslatable, ...chosen };
96
90
  }
91
+
92
+ // src/components/block-context.ts
93
+ function buildBlockContext(locale, defaultLocale, enabledLocales, isPreview, forms) {
94
+ return {
95
+ locale: {
96
+ current: locale,
97
+ default: defaultLocale,
98
+ enabled: enabledLocales && enabledLocales.length > 0 ? enabledLocales : Array.from(/* @__PURE__ */ new Set([defaultLocale, locale]))
99
+ },
100
+ isPreview: isPreview ?? false,
101
+ forms
102
+ };
103
+ }
97
104
  var WARN_CAP = 256;
98
105
  var warned = /* @__PURE__ */ new Set();
99
106
  function UnknownBlock({ type }) {
@@ -104,12 +111,14 @@ function UnknownBlock({ type }) {
104
111
  }
105
112
  return /* @__PURE__ */ jsx("div", { "data-cmssy-unknown-block": type });
106
113
  }
107
- function renderResolvedBlock(block, map, locale, defaultLocale, context) {
114
+ function renderResolvedBlock(block, map, locale, defaultLocale, options = {}) {
115
+ const { context, data, resolvedContent, enabledLocales } = options;
108
116
  const Component = Object.hasOwn(map, block.type) ? map[block.type] : void 0;
109
- const content = getBlockContentForLanguage(
117
+ const content = resolvedContent ?? getBlockContentForLanguage(
110
118
  block.content,
111
119
  locale,
112
- defaultLocale
120
+ defaultLocale,
121
+ enabledLocales?.length ? enabledLocales : void 0
113
122
  );
114
123
  return /* @__PURE__ */ jsx(
115
124
  "div",
@@ -117,12 +126,12 @@ function renderResolvedBlock(block, map, locale, defaultLocale, context) {
117
126
  "data-block-id": block.id,
118
127
  "data-block-type": block.type,
119
128
  style: Component ? void 0 : { display: "none" },
120
- children: Component ? createElement(Component, { content, context }) : /* @__PURE__ */ jsx(UnknownBlock, { type: block.type })
129
+ children: Component ? createElement(Component, { content, context, data }) : /* @__PURE__ */ jsx(UnknownBlock, { type: block.type })
121
130
  },
122
131
  block.id
123
132
  );
124
133
  }
125
- function CmssyServerPage({
134
+ async function CmssyServerPage({
126
135
  page,
127
136
  blocks,
128
137
  locale = "en",
@@ -132,6 +141,7 @@ function CmssyServerPage({
132
141
  }) {
133
142
  if (!page) return null;
134
143
  const map = buildBlockMap(blocks);
144
+ const loaderMap = buildLoaderMap(blocks);
135
145
  const context = buildBlockContext(
136
146
  locale,
137
147
  defaultLocale,
@@ -139,8 +149,38 @@ function CmssyServerPage({
139
149
  false,
140
150
  forms
141
151
  );
152
+ const resolved = await Promise.all(
153
+ page.blocks.map(async (block) => {
154
+ const content = getBlockContentForLanguage(
155
+ block.content,
156
+ locale,
157
+ defaultLocale,
158
+ enabledLocales?.length ? enabledLocales : void 0
159
+ );
160
+ const loader = loaderMap[block.type];
161
+ let data;
162
+ if (loader) {
163
+ try {
164
+ data = await loader({ content, context });
165
+ } catch (err) {
166
+ if (typeof console !== "undefined") {
167
+ console.warn(
168
+ `[cmssy] loader for block "${block.type}" (${block.id}) failed`,
169
+ err
170
+ );
171
+ }
172
+ }
173
+ }
174
+ return { content, data };
175
+ })
176
+ );
142
177
  return /* @__PURE__ */ jsx(Fragment, { children: page.blocks.map(
143
- (block) => renderResolvedBlock(block, map, locale, defaultLocale, context)
178
+ (block, i) => renderResolvedBlock(block, map, locale, defaultLocale, {
179
+ context,
180
+ data: resolved[i]?.data,
181
+ resolvedContent: resolved[i]?.content,
182
+ enabledLocales
183
+ })
144
184
  ) });
145
185
  }
146
186
  function CmssyServerLayout({
@@ -157,7 +197,10 @@ function CmssyServerLayout({
157
197
  const map = buildBlockMap(blocks);
158
198
  const context = buildBlockContext(locale, defaultLocale, enabledLocales);
159
199
  return /* @__PURE__ */ jsx(Fragment, { children: layoutBlocks.map(
160
- (block) => renderResolvedBlock(block, map, locale, defaultLocale, context)
200
+ (block) => renderResolvedBlock(block, map, locale, defaultLocale, {
201
+ context,
202
+ enabledLocales
203
+ })
161
204
  ) });
162
205
  }
163
206
 
@@ -611,4 +654,4 @@ function CmssyBlock({
611
654
  );
612
655
  }
613
656
 
614
- export { CmssyBlock, CmssyServerLayout, CmssyServerPage, FORM_QUERY, MODEL_DEFINITIONS_QUERY, MODEL_RECORDS_QUERY, PROTOCOL_VERSION, SITE_CONFIG_QUERY, SUBMIT_FORM_MUTATION, UnknownBlock, blocksToMeta, blocksToSchemas, buildBlockContext, buildBlockMap, collectFormIds, createCmssyClient, defineBlock, fetchLayouts, fetchPage, fields, getBlockContentForLanguage, graphqlRequest, isProtocolCompatible, normalizeOrigin, normalizeSlug, parseEditorMessage, postToEditor, resolveForms, resolveSiteLocales, splitLocaleFromPath };
657
+ export { CmssyBlock, CmssyServerLayout, CmssyServerPage, FORM_QUERY, MODEL_DEFINITIONS_QUERY, MODEL_RECORDS_QUERY, PROTOCOL_VERSION, SITE_CONFIG_QUERY, SUBMIT_FORM_MUTATION, UnknownBlock, blocksToMeta, blocksToSchemas, buildBlockContext, buildBlockMap, collectFormIds, createCmssyClient, defineBlock, fetchLayouts, fetchPage, fields, getBlockContentForLanguage, graphqlRequest, isProtocolCompatible, normalizeOrigin, normalizeSlug, parseEditorMessage, postToEditor, resolveForms, resolveSiteLocales, resolveWorkspaceId, splitLocaleFromPath };
@@ -256,6 +256,11 @@ interface DragEndMessage {
256
256
  type EditorToAppMessage = SelectMessage | PatchMessage | ParentReadyMessage | InsertMessage | ReorderMessage | RemoveMessage | DragOverMessage | DragEndMessage;
257
257
  declare function isProtocolCompatible(version: number): boolean;
258
258
 
259
+ interface BlockLoaderArgs {
260
+ content: Record<string, unknown>;
261
+ context?: CmssyBlockContext;
262
+ }
263
+ type BlockLoader = (args: BlockLoaderArgs) => Promise<unknown> | unknown;
259
264
  interface BlockDefinition {
260
265
  type: string;
261
266
  label?: string;
@@ -263,26 +268,43 @@ interface BlockDefinition {
263
268
  icon?: string;
264
269
  layoutPositions?: string[];
265
270
  props: Record<string, FieldDefinition>;
271
+ /**
272
+ * Optional server-side data loader. Run by CmssyServerPage during SSR; its
273
+ * result is passed to the component as the `data` prop. Not run in the
274
+ * editor (the component receives `data: undefined` there).
275
+ *
276
+ * The result crosses the server→client boundary when the block component is a
277
+ * Client Component, so it must be RSC-serializable (plain objects, arrays and
278
+ * primitives - no functions, class instances, etc.).
279
+ */
280
+ loader?: BlockLoader;
266
281
  component: ComponentType<{
267
282
  content: Record<string, unknown>;
268
283
  context?: CmssyBlockContext;
284
+ data?: unknown;
269
285
  }>;
270
286
  }
271
- declare function defineBlock<C extends Record<string, unknown>>(def: {
287
+ declare function defineBlock<C extends Record<string, unknown>, D = unknown>(def: {
272
288
  type: string;
273
289
  label?: string;
274
290
  category?: string;
275
291
  icon?: string;
276
292
  layoutPositions?: string[];
277
293
  props: Record<string, FieldDefinition>;
294
+ loader?: (args: {
295
+ content: C;
296
+ context?: CmssyBlockContext;
297
+ }) => Promise<D> | D;
278
298
  component: ComponentType<{
279
299
  content: C;
280
300
  context?: CmssyBlockContext;
301
+ data?: D;
281
302
  }>;
282
303
  }): BlockDefinition;
283
304
  type BlockMap = Record<string, ComponentType<{
284
305
  content: Record<string, unknown>;
285
306
  context?: CmssyBlockContext;
307
+ data?: unknown;
286
308
  }>>;
287
309
  declare function buildBlockMap(blocks: BlockDefinition[]): BlockMap;
288
310
  declare function blocksToSchemas(blocks: BlockDefinition[]): Record<string, BlockSchema>;
@@ -256,6 +256,11 @@ interface DragEndMessage {
256
256
  type EditorToAppMessage = SelectMessage | PatchMessage | ParentReadyMessage | InsertMessage | ReorderMessage | RemoveMessage | DragOverMessage | DragEndMessage;
257
257
  declare function isProtocolCompatible(version: number): boolean;
258
258
 
259
+ interface BlockLoaderArgs {
260
+ content: Record<string, unknown>;
261
+ context?: CmssyBlockContext;
262
+ }
263
+ type BlockLoader = (args: BlockLoaderArgs) => Promise<unknown> | unknown;
259
264
  interface BlockDefinition {
260
265
  type: string;
261
266
  label?: string;
@@ -263,26 +268,43 @@ interface BlockDefinition {
263
268
  icon?: string;
264
269
  layoutPositions?: string[];
265
270
  props: Record<string, FieldDefinition>;
271
+ /**
272
+ * Optional server-side data loader. Run by CmssyServerPage during SSR; its
273
+ * result is passed to the component as the `data` prop. Not run in the
274
+ * editor (the component receives `data: undefined` there).
275
+ *
276
+ * The result crosses the server→client boundary when the block component is a
277
+ * Client Component, so it must be RSC-serializable (plain objects, arrays and
278
+ * primitives - no functions, class instances, etc.).
279
+ */
280
+ loader?: BlockLoader;
266
281
  component: ComponentType<{
267
282
  content: Record<string, unknown>;
268
283
  context?: CmssyBlockContext;
284
+ data?: unknown;
269
285
  }>;
270
286
  }
271
- declare function defineBlock<C extends Record<string, unknown>>(def: {
287
+ declare function defineBlock<C extends Record<string, unknown>, D = unknown>(def: {
272
288
  type: string;
273
289
  label?: string;
274
290
  category?: string;
275
291
  icon?: string;
276
292
  layoutPositions?: string[];
277
293
  props: Record<string, FieldDefinition>;
294
+ loader?: (args: {
295
+ content: C;
296
+ context?: CmssyBlockContext;
297
+ }) => Promise<D> | D;
278
298
  component: ComponentType<{
279
299
  content: C;
280
300
  context?: CmssyBlockContext;
301
+ data?: D;
281
302
  }>;
282
303
  }): BlockDefinition;
283
304
  type BlockMap = Record<string, ComponentType<{
284
305
  content: Record<string, unknown>;
285
306
  context?: CmssyBlockContext;
307
+ data?: unknown;
286
308
  }>>;
287
309
  declare function buildBlockMap(blocks: BlockDefinition[]): BlockMap;
288
310
  declare function blocksToSchemas(blocks: BlockDefinition[]): Record<string, BlockSchema>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmssy/react",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "React blocks, renderers, data client and editor bridge for cmssy headless sites",
5
5
  "keywords": [
6
6
  "cmssy",