meno-core 1.0.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 (231) hide show
  1. package/bin/cli.ts +281 -0
  2. package/build-static.ts +298 -0
  3. package/bunfig.toml +39 -0
  4. package/entries/client-router.tsx +111 -0
  5. package/entries/server-router.tsx +71 -0
  6. package/lib/client/ClientInitializer.test.ts +9 -0
  7. package/lib/client/ClientInitializer.test.ts.skip +92 -0
  8. package/lib/client/ClientInitializer.ts +60 -0
  9. package/lib/client/ErrorBoundary.test.tsx +595 -0
  10. package/lib/client/ErrorBoundary.tsx +230 -0
  11. package/lib/client/componentRegistry.test.ts +165 -0
  12. package/lib/client/componentRegistry.ts +18 -0
  13. package/lib/client/contexts/ThemeContext.tsx +73 -0
  14. package/lib/client/core/ComponentBuilder.test.ts +677 -0
  15. package/lib/client/core/ComponentBuilder.ts +660 -0
  16. package/lib/client/core/ComponentRenderer.test.tsx +176 -0
  17. package/lib/client/core/ComponentRenderer.tsx +83 -0
  18. package/lib/client/core/cmsTemplateProcessor.ts +129 -0
  19. package/lib/client/elementRegistry.ts +81 -0
  20. package/lib/client/hmr/HMRManager.tsx +179 -0
  21. package/lib/client/hmr/index.ts +5 -0
  22. package/lib/client/hmrWebSocket.test.ts +9 -0
  23. package/lib/client/hmrWebSocket.ts +250 -0
  24. package/lib/client/hooks/useColorVariables.test.ts +166 -0
  25. package/lib/client/hooks/useColorVariables.ts +249 -0
  26. package/lib/client/hooks/usePropertyAutocomplete.test.ts +9 -0
  27. package/lib/client/hooks/usePropertyAutocomplete.ts +40 -0
  28. package/lib/client/hydration/HydrationUtils.test.ts +154 -0
  29. package/lib/client/hydration/HydrationUtils.ts +35 -0
  30. package/lib/client/i18nConfigService.test.ts +74 -0
  31. package/lib/client/i18nConfigService.ts +78 -0
  32. package/lib/client/index.ts +56 -0
  33. package/lib/client/navigation.test.ts +441 -0
  34. package/lib/client/navigation.ts +23 -0
  35. package/lib/client/responsiveStyleResolver.test.ts +491 -0
  36. package/lib/client/responsiveStyleResolver.ts +184 -0
  37. package/lib/client/routing/RouteLoader.test.ts +635 -0
  38. package/lib/client/routing/RouteLoader.ts +347 -0
  39. package/lib/client/routing/Router.tsx +382 -0
  40. package/lib/client/scripts/ScriptExecutor.test.ts +489 -0
  41. package/lib/client/scripts/ScriptExecutor.ts +171 -0
  42. package/lib/client/scripts/formHandler.ts +103 -0
  43. package/lib/client/styleProcessor.test.ts +126 -0
  44. package/lib/client/styleProcessor.ts +92 -0
  45. package/lib/client/styles/StyleInjector.test.ts +354 -0
  46. package/lib/client/styles/StyleInjector.ts +154 -0
  47. package/lib/client/templateEngine.test.ts +660 -0
  48. package/lib/client/templateEngine.ts +667 -0
  49. package/lib/client/theme.test.ts +173 -0
  50. package/lib/client/theme.ts +159 -0
  51. package/lib/client/utils/toast.ts +46 -0
  52. package/lib/server/createServer.ts +170 -0
  53. package/lib/server/cssGenerator.test.ts +172 -0
  54. package/lib/server/cssGenerator.ts +58 -0
  55. package/lib/server/fileWatcher.ts +134 -0
  56. package/lib/server/index.ts +55 -0
  57. package/lib/server/jsonLoader.test.ts +103 -0
  58. package/lib/server/jsonLoader.ts +350 -0
  59. package/lib/server/middleware/cors.test.ts +177 -0
  60. package/lib/server/middleware/cors.ts +69 -0
  61. package/lib/server/middleware/errorHandler.test.ts +208 -0
  62. package/lib/server/middleware/errorHandler.ts +63 -0
  63. package/lib/server/middleware/index.ts +9 -0
  64. package/lib/server/middleware/logger.test.ts +233 -0
  65. package/lib/server/middleware/logger.ts +99 -0
  66. package/lib/server/pageCache.test.ts +167 -0
  67. package/lib/server/pageCache.ts +97 -0
  68. package/lib/server/projectContext.ts +51 -0
  69. package/lib/server/providers/fileSystemCMSProvider.test.ts +292 -0
  70. package/lib/server/providers/fileSystemCMSProvider.ts +227 -0
  71. package/lib/server/providers/fileSystemPageProvider.ts +83 -0
  72. package/lib/server/routes/api/cms.test.ts +177 -0
  73. package/lib/server/routes/api/cms.ts +82 -0
  74. package/lib/server/routes/api/colors.ts +59 -0
  75. package/lib/server/routes/api/components.ts +70 -0
  76. package/lib/server/routes/api/config.test.ts +9 -0
  77. package/lib/server/routes/api/config.ts +28 -0
  78. package/lib/server/routes/api/core-routes.ts +182 -0
  79. package/lib/server/routes/api/functions.ts +170 -0
  80. package/lib/server/routes/api/index.ts +69 -0
  81. package/lib/server/routes/api/pages.ts +95 -0
  82. package/lib/server/routes/api/shared.test.ts +81 -0
  83. package/lib/server/routes/api/shared.ts +31 -0
  84. package/lib/server/routes/editor.test.ts +9 -0
  85. package/lib/server/routes/index.ts +104 -0
  86. package/lib/server/routes/pages.ts +161 -0
  87. package/lib/server/routes/static.ts +107 -0
  88. package/lib/server/services/ColorService.ts +193 -0
  89. package/lib/server/services/cmsService.test.ts +388 -0
  90. package/lib/server/services/cmsService.ts +296 -0
  91. package/lib/server/services/componentService.test.ts +276 -0
  92. package/lib/server/services/componentService.ts +346 -0
  93. package/lib/server/services/configService.ts +156 -0
  94. package/lib/server/services/fileWatcherService.ts +67 -0
  95. package/lib/server/services/index.ts +10 -0
  96. package/lib/server/services/pageService.test.ts +258 -0
  97. package/lib/server/services/pageService.ts +240 -0
  98. package/lib/server/ssrRenderer.test.ts +1005 -0
  99. package/lib/server/ssrRenderer.ts +878 -0
  100. package/lib/server/utilityClassGenerator.ts +11 -0
  101. package/lib/server/utils/index.ts +5 -0
  102. package/lib/server/utils/jsonLineMapper.test.ts +100 -0
  103. package/lib/server/utils/jsonLineMapper.ts +166 -0
  104. package/lib/server/validateStyleCoverage.test.ts +9 -0
  105. package/lib/server/validateStyleCoverage.ts +167 -0
  106. package/lib/server/websocketManager.test.ts +9 -0
  107. package/lib/server/websocketManager.ts +95 -0
  108. package/lib/shared/attributeNodeUtils.test.ts +152 -0
  109. package/lib/shared/attributeNodeUtils.ts +50 -0
  110. package/lib/shared/breakpoints.test.ts +166 -0
  111. package/lib/shared/breakpoints.ts +65 -0
  112. package/lib/shared/colorProperties.test.ts +111 -0
  113. package/lib/shared/colorProperties.ts +40 -0
  114. package/lib/shared/colorVariableUtils.test.ts +319 -0
  115. package/lib/shared/colorVariableUtils.ts +97 -0
  116. package/lib/shared/constants.test.ts +175 -0
  117. package/lib/shared/constants.ts +116 -0
  118. package/lib/shared/cssGeneration.ts +481 -0
  119. package/lib/shared/cssProperties.test.ts +252 -0
  120. package/lib/shared/cssProperties.ts +338 -0
  121. package/lib/shared/elementUtils.test.ts +245 -0
  122. package/lib/shared/elementUtils.ts +90 -0
  123. package/lib/shared/fontLoader.ts +97 -0
  124. package/lib/shared/i18n.test.ts +313 -0
  125. package/lib/shared/i18n.ts +286 -0
  126. package/lib/shared/index.ts +50 -0
  127. package/lib/shared/interfaces/contentProvider.test.ts +9 -0
  128. package/lib/shared/interfaces/contentProvider.ts +121 -0
  129. package/lib/shared/nodeUtils.test.ts +320 -0
  130. package/lib/shared/nodeUtils.ts +220 -0
  131. package/lib/shared/pathArrayUtils.test.ts +315 -0
  132. package/lib/shared/pathArrayUtils.ts +17 -0
  133. package/lib/shared/pathUtils.test.ts +260 -0
  134. package/lib/shared/pathUtils.ts +244 -0
  135. package/lib/shared/paths/Path.test.ts +74 -0
  136. package/lib/shared/paths/Path.ts +23 -0
  137. package/lib/shared/paths/PathConverter.test.ts +232 -0
  138. package/lib/shared/paths/PathConverter.ts +141 -0
  139. package/lib/shared/paths/PathUtils.ts +290 -0
  140. package/lib/shared/paths/PathValidator.test.ts +193 -0
  141. package/lib/shared/paths/PathValidator.ts +53 -0
  142. package/lib/shared/paths/index.ts +48 -0
  143. package/lib/shared/propResolver.test.ts +639 -0
  144. package/lib/shared/propResolver.ts +124 -0
  145. package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +190 -0
  146. package/lib/shared/registry/BaseNodeTypeRegistry.ts +200 -0
  147. package/lib/shared/registry/ClientNodeTypeRegistry.ts +34 -0
  148. package/lib/shared/registry/ClientRegistry.test.ts +26 -0
  149. package/lib/shared/registry/ClientRegistry.ts +15 -0
  150. package/lib/shared/registry/ComponentRegistry.test.ts +293 -0
  151. package/lib/shared/registry/ComponentRegistry.ts +100 -0
  152. package/lib/shared/registry/NodeTypeDefinition.ts +198 -0
  153. package/lib/shared/registry/NodeTypeManager.ts +94 -0
  154. package/lib/shared/registry/RegistryManager.test.ts +58 -0
  155. package/lib/shared/registry/RegistryManager.ts +60 -0
  156. package/lib/shared/registry/SSRNodeTypeRegistry.ts +33 -0
  157. package/lib/shared/registry/SSRRegistry.test.ts +26 -0
  158. package/lib/shared/registry/SSRRegistry.ts +15 -0
  159. package/lib/shared/registry/createNodeType.ts +175 -0
  160. package/lib/shared/registry/defineNodeType.ts +73 -0
  161. package/lib/shared/registry/fieldPresets.ts +109 -0
  162. package/lib/shared/registry/index.ts +50 -0
  163. package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +71 -0
  164. package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +61 -0
  165. package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +88 -0
  166. package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +66 -0
  167. package/lib/shared/registry/nodeTypes/ObjectLinkNodeType.ts +75 -0
  168. package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +49 -0
  169. package/lib/shared/registry/nodeTypes/TextNodeType.ts +52 -0
  170. package/lib/shared/registry/nodeTypes/index.ts +75 -0
  171. package/lib/shared/responsiveScaling.test.ts +268 -0
  172. package/lib/shared/responsiveScaling.ts +194 -0
  173. package/lib/shared/responsiveStyleUtils.test.ts +300 -0
  174. package/lib/shared/responsiveStyleUtils.ts +139 -0
  175. package/lib/shared/slugTranslator.test.ts +325 -0
  176. package/lib/shared/slugTranslator.ts +177 -0
  177. package/lib/shared/styleNodeUtils.test.ts +132 -0
  178. package/lib/shared/styleNodeUtils.ts +102 -0
  179. package/lib/shared/styleUtils.test.ts +238 -0
  180. package/lib/shared/styleUtils.ts +63 -0
  181. package/lib/shared/themeDefaults.test.ts +113 -0
  182. package/lib/shared/themeDefaults.ts +103 -0
  183. package/lib/shared/tree/PathBuilder.ts +383 -0
  184. package/lib/shared/treePathUtils.test.ts +539 -0
  185. package/lib/shared/treePathUtils.ts +339 -0
  186. package/lib/shared/types/api.ts +58 -0
  187. package/lib/shared/types/cms.ts +95 -0
  188. package/lib/shared/types/colors.ts +45 -0
  189. package/lib/shared/types/components.ts +121 -0
  190. package/lib/shared/types/errors.test.ts +103 -0
  191. package/lib/shared/types/errors.ts +69 -0
  192. package/lib/shared/types/index.ts +96 -0
  193. package/lib/shared/types/nodes.ts +20 -0
  194. package/lib/shared/types/rendering.ts +61 -0
  195. package/lib/shared/types/styles.ts +38 -0
  196. package/lib/shared/types.ts +11 -0
  197. package/lib/shared/utilityClassConfig.ts +287 -0
  198. package/lib/shared/utilityClassMapper.test.ts +140 -0
  199. package/lib/shared/utilityClassMapper.ts +229 -0
  200. package/lib/shared/utils/fileUtils.test.ts +99 -0
  201. package/lib/shared/utils/fileUtils.ts +56 -0
  202. package/lib/shared/utils.test.ts +261 -0
  203. package/lib/shared/utils.ts +84 -0
  204. package/lib/shared/validation/index.ts +7 -0
  205. package/lib/shared/validation/propValidator.test.ts +178 -0
  206. package/lib/shared/validation/propValidator.ts +238 -0
  207. package/lib/shared/validation/schemas.test.ts +177 -0
  208. package/lib/shared/validation/schemas.ts +401 -0
  209. package/lib/shared/validation/validators.test.ts +109 -0
  210. package/lib/shared/validation/validators.ts +304 -0
  211. package/lib/test-utils/dom-setup.ts +55 -0
  212. package/lib/test-utils/factories/ConsoleMockFactory.ts +200 -0
  213. package/lib/test-utils/factories/DomMockFactory.ts +487 -0
  214. package/lib/test-utils/factories/EventMockFactory.ts +244 -0
  215. package/lib/test-utils/factories/FetchMockFactory.ts +210 -0
  216. package/lib/test-utils/factories/ServerMockFactory.ts +223 -0
  217. package/lib/test-utils/factories/StoreMockFactory.ts +370 -0
  218. package/lib/test-utils/factories/index.ts +11 -0
  219. package/lib/test-utils/fixtures.ts +134 -0
  220. package/lib/test-utils/helpers/asyncHelpers.test.ts +112 -0
  221. package/lib/test-utils/helpers/asyncHelpers.ts +196 -0
  222. package/lib/test-utils/helpers/index.ts +6 -0
  223. package/lib/test-utils/helpers.test.ts +73 -0
  224. package/lib/test-utils/helpers.ts +90 -0
  225. package/lib/test-utils/index.ts +17 -0
  226. package/lib/test-utils/mockFactories.ts +92 -0
  227. package/lib/test-utils/mocks.ts +341 -0
  228. package/package.json +38 -0
  229. package/templates/index-router.html +34 -0
  230. package/tsconfig.json +14 -0
  231. package/vite.config.ts +43 -0
@@ -0,0 +1,58 @@
1
+ import { describe, test, expect } from 'bun:test';
2
+ import { RegistryManager } from './RegistryManager';
3
+
4
+ describe('RegistryManager', () => {
5
+ test('creates client and SSR registries', () => {
6
+ const manager = new RegistryManager();
7
+ expect(manager.getClient()).toBeDefined();
8
+ expect(manager.getSSR()).toBeDefined();
9
+ });
10
+
11
+ test('registerAll adds to both registries', () => {
12
+ const manager = new RegistryManager();
13
+ const def = { component: { structure: { type: 'node', tag: 'div' } } };
14
+
15
+ manager.registerAll('TestComponent', def);
16
+
17
+ expect(manager.getClient().get('TestComponent')).toEqual(def);
18
+ expect(manager.getSSR().get('TestComponent')).toEqual(def);
19
+ });
20
+
21
+ test('sync copies from client to SSR', () => {
22
+ const manager = new RegistryManager();
23
+ const def = { component: { structure: { type: 'node', tag: 'div' } } };
24
+
25
+ manager.getClient().register('TestComponent', def);
26
+ manager.sync('client', 'ssr');
27
+
28
+ expect(manager.getSSR().get('TestComponent')).toEqual(def);
29
+ });
30
+
31
+ test('sync copies from SSR to client', () => {
32
+ const manager = new RegistryManager();
33
+ const def = { component: { structure: { type: 'node', tag: 'div' } } };
34
+
35
+ manager.getSSR().register('TestComponent', def);
36
+ manager.sync('ssr', 'client');
37
+
38
+ expect(manager.getClient().get('TestComponent')).toEqual(def);
39
+ });
40
+
41
+ test('clearAll clears both registries', () => {
42
+ const manager = new RegistryManager();
43
+ const def = { component: { structure: { type: 'node', tag: 'div' } } };
44
+
45
+ manager.registerAll('TestComponent', def);
46
+ manager.clearAll();
47
+
48
+ expect(manager.getClient().get('TestComponent')).toBeUndefined();
49
+ expect(manager.getSSR().get('TestComponent')).toBeUndefined();
50
+ });
51
+
52
+ test('returns same registry instances', () => {
53
+ const manager = new RegistryManager();
54
+ const client1 = manager.getClient();
55
+ const client2 = manager.getClient();
56
+ expect(client1).toBe(client2);
57
+ });
58
+ });
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Registry Manager
3
+ * Manages multiple component registries (client, SSR, etc.)
4
+ */
5
+
6
+ import type { ComponentDefinition } from '../types';
7
+ import { ClientRegistry } from './ClientRegistry';
8
+ import { SSRRegistry } from './SSRRegistry';
9
+
10
+ export class RegistryManager {
11
+ private clientRegistry: ClientRegistry;
12
+ private ssrRegistry: SSRRegistry;
13
+
14
+ constructor() {
15
+ this.clientRegistry = new ClientRegistry();
16
+ this.ssrRegistry = new SSRRegistry();
17
+ }
18
+
19
+ /**
20
+ * Get the client registry
21
+ */
22
+ getClient(): ClientRegistry {
23
+ return this.clientRegistry;
24
+ }
25
+
26
+ /**
27
+ * Get the SSR registry
28
+ */
29
+ getSSR(): SSRRegistry {
30
+ return this.ssrRegistry;
31
+ }
32
+
33
+ /**
34
+ * Sync components from one registry to another
35
+ */
36
+ sync(from: 'client' | 'ssr', to: 'client' | 'ssr'): void {
37
+ const source = from === 'client' ? this.clientRegistry : this.ssrRegistry;
38
+ const target = to === 'client' ? this.clientRegistry : this.ssrRegistry;
39
+
40
+ const components = source.getAll();
41
+ target.merge(components);
42
+ }
43
+
44
+ /**
45
+ * Register a component in all registries
46
+ */
47
+ registerAll(name: string, definition: ComponentDefinition): void {
48
+ this.clientRegistry.register(name, definition);
49
+ this.ssrRegistry.register(name, definition);
50
+ }
51
+
52
+ /**
53
+ * Clear all registries
54
+ */
55
+ clearAll(): void {
56
+ this.clientRegistry.clear();
57
+ this.ssrRegistry.clear();
58
+ }
59
+ }
60
+
@@ -0,0 +1,33 @@
1
+ /**
2
+ * SSR Node Type Registry
3
+ * Registry specialized for server-side rendering (HTML strings)
4
+ */
5
+
6
+ import type { ComponentNode } from '../types/nodes';
7
+ import { BaseNodeTypeRegistry } from './BaseNodeTypeRegistry';
8
+ import type { SSRRenderContext } from './NodeTypeDefinition';
9
+
10
+ /**
11
+ * Server-side node type registry
12
+ * Extends base registry with SSR rendering capabilities
13
+ */
14
+ export class SSRNodeTypeRegistry extends BaseNodeTypeRegistry {
15
+ /**
16
+ * Render a node to HTML string using the registered renderer
17
+ * Returns empty string if no renderer is found for the node type
18
+ */
19
+ renderNode(node: ComponentNode, context: SSRRenderContext): string {
20
+ const definition = this.findByNode(node);
21
+ if (!definition) {
22
+ return '';
23
+ }
24
+ return definition.ssrRenderer(node as any, context);
25
+ }
26
+
27
+ /**
28
+ * Check if a node can be rendered by this registry
29
+ */
30
+ canRender(node: ComponentNode): boolean {
31
+ return this.findByNode(node) !== undefined;
32
+ }
33
+ }
@@ -0,0 +1,26 @@
1
+ import { describe, test, expect } from 'bun:test';
2
+ import { SSRRegistry } from './SSRRegistry';
3
+
4
+ describe('SSRRegistry', () => {
5
+ test('can be instantiated', () => {
6
+ const registry = new SSRRegistry();
7
+ expect(registry).toBeDefined();
8
+ });
9
+
10
+ test('inherits from BaseComponentRegistry', () => {
11
+ const registry = new SSRRegistry();
12
+ const def = { component: { structure: { type: 'node', tag: 'div' } } };
13
+
14
+ registry.register('Test', def);
15
+ expect(registry.get('Test')).toEqual(def);
16
+ });
17
+
18
+ test('supports all base registry methods', () => {
19
+ const registry = new SSRRegistry();
20
+ expect(typeof registry.register).toBe('function');
21
+ expect(typeof registry.get).toBe('function');
22
+ expect(typeof registry.has).toBe('function');
23
+ expect(typeof registry.getAll).toBe('function');
24
+ expect(typeof registry.clear).toBe('function');
25
+ });
26
+ });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * SSR Component Registry
3
+ * Extends base registry with SSR-specific functionality
4
+ */
5
+
6
+ import { BaseComponentRegistry } from './ComponentRegistry';
7
+ import type { ComponentDefinition } from '../types';
8
+
9
+ export class SSRRegistry extends BaseComponentRegistry {
10
+ /**
11
+ * SSR-specific methods can be added here
12
+ * For now, it's the same as base registry
13
+ */
14
+ }
15
+
@@ -0,0 +1,175 @@
1
+ /**
2
+ * createNodeType Helper
3
+ * Simplified API for creating node type definitions with auto-generated type guards
4
+ * and inferred TypeScript types from Zod schemas
5
+ */
6
+
7
+ import type { ReactElement } from 'react';
8
+ import type { ZodType, z } from 'zod';
9
+ import type {
10
+ NodeTypeDefinition,
11
+ NodeCapabilities,
12
+ NodeCategory,
13
+ TreeIcon,
14
+ ClientRenderContext,
15
+ SSRRenderContext,
16
+ EditableField,
17
+ } from './NodeTypeDefinition';
18
+
19
+ /**
20
+ * Default capabilities for node types
21
+ */
22
+ const DEFAULT_CAPABILITIES: NodeCapabilities = {
23
+ canHaveChildren: true,
24
+ canHaveStyle: true,
25
+ canHaveAttributes: true,
26
+ canBeNested: true,
27
+ requiresProps: [],
28
+ };
29
+
30
+ /**
31
+ * Create a type guard function from a type string value
32
+ * Automatically generates the type guard based on the node's type field
33
+ */
34
+ function createTypeGuard<T>(typeValue: string) {
35
+ return (node: unknown): node is T =>
36
+ node !== null &&
37
+ typeof node === 'object' &&
38
+ 'type' in node &&
39
+ (node as { type: unknown }).type === typeValue;
40
+ }
41
+
42
+ /**
43
+ * Input configuration for createNodeType
44
+ * Schema is the single source of truth - TypeScript types are inferred from it
45
+ */
46
+ export interface CreateNodeTypeInput<TSchema extends ZodType> {
47
+ /** The type discriminator value (e.g., 'image', 'video') */
48
+ type: string;
49
+
50
+ /** Human-readable display name (e.g., 'Image', 'Video') */
51
+ displayName: string;
52
+
53
+ /** Category for grouping in command palette */
54
+ category?: NodeCategory;
55
+
56
+ /** Zod schema - single source of truth for validation and type inference */
57
+ schema: TSchema;
58
+
59
+ /** Default values for creating new nodes (type field is auto-added) */
60
+ defaultValues: Omit<Partial<z.infer<TSchema>>, 'type'>;
61
+
62
+ /** Tree display configuration */
63
+ treeDisplay: {
64
+ icon: TreeIcon;
65
+ getLabel?: (node: z.infer<TSchema>) => string;
66
+ };
67
+
68
+ /** Client-side React renderer */
69
+ clientRenderer: (node: z.infer<TSchema>, context: ClientRenderContext) => ReactElement | null;
70
+
71
+ /** Server-side HTML string renderer */
72
+ ssrRenderer: (node: z.infer<TSchema>, context: SSRRenderContext) => string;
73
+
74
+ /** Optional capabilities override */
75
+ capabilities?: Partial<NodeCapabilities>;
76
+
77
+ /** Optional custom props editor component */
78
+ propsEditor?: React.ComponentType<{
79
+ node: z.infer<TSchema>;
80
+ selectedPath: string | null;
81
+ onPropChange?: (path: string, propName: string, newValue: unknown) => void;
82
+ themeColors?: Record<string, string>;
83
+ }>;
84
+
85
+ /** Editable fields for auto-generated props editor UI */
86
+ editableFields?: EditableField[];
87
+ }
88
+
89
+ /**
90
+ * Create a complete node type definition from a simplified configuration
91
+ *
92
+ * Benefits over defineNodeType:
93
+ * - Type guards are auto-generated from the type string
94
+ * - Default factory is auto-generated from defaultValues
95
+ * - TypeScript types are inferred from the Zod schema
96
+ * - Less boilerplate, single source of truth
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * const schema = z.object({
101
+ * type: z.literal('image'),
102
+ * src: z.string(),
103
+ * alt: z.string().optional(),
104
+ * });
105
+ *
106
+ * export type ImageNode = z.infer<typeof schema>;
107
+ * export const ImageNodeSchema = schema;
108
+ *
109
+ * export const ImageNodeType = createNodeType({
110
+ * type: 'image',
111
+ * displayName: 'Image',
112
+ * schema,
113
+ * defaultValues: { src: '', alt: '' },
114
+ * treeDisplay: { icon: 'IMAGE' },
115
+ * clientRenderer: (node, ctx) => h('img', { src: node.src, alt: node.alt }),
116
+ * ssrRenderer: (node, ctx) => `<img src="${node.src}" alt="${node.alt || ''}" />`,
117
+ * });
118
+ * ```
119
+ */
120
+ export function createNodeType<TSchema extends ZodType>(
121
+ input: CreateNodeTypeInput<TSchema>
122
+ ): NodeTypeDefinition<z.infer<TSchema>> {
123
+ type TNode = z.infer<TSchema>;
124
+
125
+ // Validate required fields
126
+ if (!input.type) {
127
+ throw new Error('Node type definition must have a type');
128
+ }
129
+ if (!input.displayName) {
130
+ throw new Error('Node type definition must have a displayName');
131
+ }
132
+ if (!input.schema) {
133
+ throw new Error('Node type definition must have a schema');
134
+ }
135
+ if (!input.clientRenderer) {
136
+ throw new Error('Node type definition must have a clientRenderer');
137
+ }
138
+ if (!input.ssrRenderer) {
139
+ throw new Error('Node type definition must have an ssrRenderer');
140
+ }
141
+ if (!input.treeDisplay) {
142
+ throw new Error('Node type definition must have treeDisplay');
143
+ }
144
+
145
+ return {
146
+ type: input.type,
147
+ displayName: input.displayName,
148
+ category: input.category ?? 'core',
149
+ schema: input.schema as ZodType<TNode>,
150
+
151
+ // Auto-generated type guard
152
+ typeGuard: createTypeGuard<TNode>(input.type),
153
+
154
+ // Auto-generated default factory
155
+ defaultFactory: (): TNode => ({
156
+ type: input.type,
157
+ ...input.defaultValues,
158
+ } as TNode),
159
+
160
+ treeDisplay: {
161
+ icon: input.treeDisplay.icon,
162
+ getLabel: input.treeDisplay.getLabel ?? (() => input.displayName),
163
+ },
164
+
165
+ clientRenderer: input.clientRenderer,
166
+ ssrRenderer: input.ssrRenderer,
167
+ propsEditor: input.propsEditor,
168
+ editableFields: input.editableFields,
169
+
170
+ capabilities: {
171
+ ...DEFAULT_CAPABILITIES,
172
+ ...input.capabilities,
173
+ },
174
+ };
175
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * defineNodeType Helper
3
+ * Type-safe helper function to create node type definitions
4
+ */
5
+
6
+ import type { ComponentNode } from '../types/nodes';
7
+ import type {
8
+ NodeTypeDefinition,
9
+ NodeTypeDefinitionInput,
10
+ NodeCapabilities,
11
+ } from './NodeTypeDefinition';
12
+
13
+ /**
14
+ * Default capabilities for node types
15
+ */
16
+ const DEFAULT_CAPABILITIES: NodeCapabilities = {
17
+ canHaveChildren: true,
18
+ canHaveStyle: true,
19
+ canHaveAttributes: true,
20
+ canBeNested: true,
21
+ requiresProps: [],
22
+ };
23
+
24
+ /**
25
+ * Create a node type definition with sensible defaults
26
+ * Provides type safety and validation at definition time
27
+ */
28
+ export function defineNodeType<TNode extends ComponentNode>(
29
+ input: NodeTypeDefinitionInput<TNode>
30
+ ): NodeTypeDefinition<TNode> {
31
+ // Validate required fields
32
+ if (!input.type) {
33
+ throw new Error('Node type definition must have a type');
34
+ }
35
+ if (!input.displayName) {
36
+ throw new Error('Node type definition must have a displayName');
37
+ }
38
+ if (!input.schema) {
39
+ throw new Error('Node type definition must have a schema');
40
+ }
41
+ if (!input.typeGuard) {
42
+ throw new Error('Node type definition must have a typeGuard');
43
+ }
44
+ if (!input.clientRenderer) {
45
+ throw new Error('Node type definition must have a clientRenderer');
46
+ }
47
+ if (!input.ssrRenderer) {
48
+ throw new Error('Node type definition must have an ssrRenderer');
49
+ }
50
+ if (!input.treeDisplay) {
51
+ throw new Error('Node type definition must have treeDisplay');
52
+ }
53
+ if (!input.defaultFactory) {
54
+ throw new Error('Node type definition must have a defaultFactory');
55
+ }
56
+
57
+ return {
58
+ type: input.type,
59
+ displayName: input.displayName,
60
+ category: input.category ?? 'core',
61
+ schema: input.schema,
62
+ typeGuard: input.typeGuard,
63
+ clientRenderer: input.clientRenderer,
64
+ ssrRenderer: input.ssrRenderer,
65
+ treeDisplay: input.treeDisplay,
66
+ propsEditor: input.propsEditor,
67
+ defaultFactory: input.defaultFactory,
68
+ capabilities: {
69
+ ...DEFAULT_CAPABILITIES,
70
+ ...input.capabilities,
71
+ },
72
+ };
73
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Generic Field Type Factories for Node Types
3
+ *
4
+ * Configurable field factories that can be used for any property.
5
+ * Each factory creates an EditableField with the specified configuration.
6
+ *
7
+ * @example
8
+ * import { textField, urlField, imageField, booleanField, selectField } from '../fieldPresets';
9
+ *
10
+ * editableFields: [
11
+ * urlField({ name: 'src', label: 'Video Source', required: true }),
12
+ * imageField({ name: 'poster', label: 'Poster Image' }),
13
+ * selectField({ name: 'preload', label: 'Preload', options: ['auto', 'metadata', 'none'] }),
14
+ * booleanField({ name: 'controls', label: 'Show Controls', group: 'Playback' }),
15
+ * ],
16
+ */
17
+
18
+ import type { EditableField } from './NodeTypeDefinition';
19
+
20
+ // ============================================
21
+ // Field Configuration Types
22
+ // ============================================
23
+
24
+ interface BaseFieldConfig {
25
+ name: string;
26
+ label: string;
27
+ required?: boolean;
28
+ group?: string;
29
+ }
30
+
31
+ interface TextFieldConfig extends BaseFieldConfig {
32
+ placeholder?: string;
33
+ }
34
+
35
+ interface SelectFieldConfig extends BaseFieldConfig {
36
+ options: string[];
37
+ }
38
+
39
+ // ============================================
40
+ // Generic Field Type Factories
41
+ // ============================================
42
+
43
+ /** Text input field */
44
+ export const textField = (config: TextFieldConfig): EditableField => ({
45
+ name: config.name,
46
+ label: config.label,
47
+ type: 'string',
48
+ required: config.required,
49
+ placeholder: config.placeholder,
50
+ group: config.group,
51
+ });
52
+
53
+ /** URL input field */
54
+ export const urlField = (config: TextFieldConfig): EditableField => ({
55
+ name: config.name,
56
+ label: config.label,
57
+ type: 'url',
58
+ required: config.required,
59
+ placeholder: config.placeholder ?? 'https://...',
60
+ group: config.group,
61
+ });
62
+
63
+ /** Image URL field (can add picker UI later) */
64
+ export const imageField = (config: BaseFieldConfig): EditableField => ({
65
+ name: config.name,
66
+ label: config.label,
67
+ type: 'url',
68
+ required: config.required,
69
+ placeholder: 'https://example.com/image.jpg',
70
+ group: config.group,
71
+ });
72
+
73
+ /** Number input field */
74
+ export const numberField = (config: TextFieldConfig): EditableField => ({
75
+ name: config.name,
76
+ label: config.label,
77
+ type: 'number',
78
+ required: config.required,
79
+ placeholder: config.placeholder,
80
+ group: config.group,
81
+ });
82
+
83
+ /** Boolean toggle field */
84
+ export const booleanField = (config: BaseFieldConfig): EditableField => ({
85
+ name: config.name,
86
+ label: config.label,
87
+ type: 'boolean',
88
+ group: config.group,
89
+ });
90
+
91
+ /** Select dropdown field */
92
+ export const selectField = (config: SelectFieldConfig): EditableField => ({
93
+ name: config.name,
94
+ label: config.label,
95
+ type: 'select',
96
+ options: config.options,
97
+ required: config.required,
98
+ group: config.group,
99
+ });
100
+
101
+ /** Textarea field (multiline text) */
102
+ export const textareaField = (config: TextFieldConfig): EditableField => ({
103
+ name: config.name,
104
+ label: config.label,
105
+ type: 'text',
106
+ required: config.required,
107
+ placeholder: config.placeholder,
108
+ group: config.group,
109
+ });
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Component Registry System
3
+ * Unified registry system for client and SSR
4
+ */
5
+
6
+ // Component registries
7
+ export { BaseComponentRegistry } from './ComponentRegistry';
8
+ export { ClientRegistry } from './ClientRegistry';
9
+ export { SSRRegistry } from './SSRRegistry';
10
+ export { RegistryManager } from './RegistryManager';
11
+
12
+ // Node type registries
13
+ export { BaseNodeTypeRegistry } from './BaseNodeTypeRegistry';
14
+ export type { NodeLabelInfo } from './BaseNodeTypeRegistry';
15
+ export { ClientNodeTypeRegistry } from './ClientNodeTypeRegistry';
16
+ export { SSRNodeTypeRegistry } from './SSRNodeTypeRegistry';
17
+ export { NodeTypeManager, globalNodeTypeManager } from './NodeTypeManager';
18
+ export { defineNodeType } from './defineNodeType';
19
+ export { createNodeType } from './createNodeType';
20
+ export type { CreateNodeTypeInput } from './createNodeType';
21
+ export type {
22
+ NodeTypeDefinition,
23
+ NodeTypeDefinitionInput,
24
+ TreeIcon,
25
+ NodeCategory,
26
+ TreeDisplayConfig,
27
+ NodeCapabilities,
28
+ ClientRenderContext,
29
+ SSRRenderContext,
30
+ ClientNodeRenderer,
31
+ SSRNodeRenderer,
32
+ PropsEditorProps,
33
+ } from './NodeTypeDefinition';
34
+
35
+ // Field presets for node types
36
+ export * from './fieldPresets';
37
+
38
+ // Built-in node types
39
+ export {
40
+ registerBuiltInNodeTypes,
41
+ builtInNodeTypes,
42
+ HtmlNodeType,
43
+ ComponentInstanceNodeType,
44
+ SlotMarkerType,
45
+ EmbedNodeType,
46
+ ObjectLinkNodeType,
47
+ LocaleListNodeType,
48
+ TextNodeType,
49
+ } from './nodeTypes';
50
+
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Component Instance Node Type Definition
3
+ * Instances of registered components
4
+ */
5
+
6
+ import { z } from 'zod';
7
+ import { createElement as h } from 'react';
8
+ import { StyleValueSchema } from '../../validation/schemas';
9
+ import { createNodeType } from '../createNodeType';
10
+
11
+ // Forward reference for ComponentNode schema (recursive)
12
+ const ComponentNodeSchemaRef: z.ZodType<any> = z.lazy(() => z.any());
13
+
14
+ // Schema is the SINGLE source of truth
15
+ const ComponentInstanceNodeSchemaInternal = z.object({
16
+ type: z.literal('component'),
17
+ component: z.string(),
18
+ props: z.record(z.string(), z.any()).optional(),
19
+ style: StyleValueSchema.optional(),
20
+ attributes: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional(),
21
+ children: z.union([
22
+ z.array(z.union([ComponentNodeSchemaRef, z.string()])),
23
+ z.string(),
24
+ ]).optional(),
25
+ }).passthrough();
26
+
27
+ // TypeScript type inferred from schema
28
+ export type ComponentInstanceNode = z.infer<typeof ComponentInstanceNodeSchemaInternal>;
29
+
30
+ // Export schema for validation/schemas.ts
31
+ export const ComponentInstanceNodeSchema = ComponentInstanceNodeSchemaInternal;
32
+
33
+ export const ComponentInstanceNodeType = createNodeType({
34
+ type: 'component',
35
+ displayName: 'Component',
36
+ category: 'core',
37
+ schema: ComponentInstanceNodeSchemaInternal,
38
+
39
+ defaultValues: {
40
+ component: '',
41
+ props: {},
42
+ style: { base: {} },
43
+ children: [],
44
+ },
45
+
46
+ treeDisplay: {
47
+ icon: 'COMPONENT',
48
+ getLabel: (node) => node.component,
49
+ },
50
+
51
+ clientRenderer: (node, context) => {
52
+ const definition = context.getComponentDefinition(node.component);
53
+ if (!definition) {
54
+ return h('div', { key: context.key }, `[Unknown component: ${node.component}]`);
55
+ }
56
+ return h('div', { key: context.key }, `[Component: ${node.component}]`);
57
+ },
58
+
59
+ ssrRenderer: (node, context) => {
60
+ const definition = context.getComponentDefinition(node.component);
61
+ if (!definition) {
62
+ return `<!-- Unknown component: ${context.escapeHtml(node.component)} -->`;
63
+ }
64
+ return `<!-- Component: ${context.escapeHtml(node.component)} -->`;
65
+ },
66
+
67
+ capabilities: {
68
+ canHaveChildren: true,
69
+ requiresProps: ['component'],
70
+ },
71
+ });