visualifyjs 2.5.3 → 3.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 (252) hide show
  1. package/.claude/mem/TIMELINE.md +36 -0
  2. package/.claude/mem/notes/2026-02-11-3d-visualization-docs-fix-external-script-solution.md +24 -0
  3. package/.claude/mem/notes/2026-02-11-3d-visualization-docs-fix-session-summary.md +43 -0
  4. package/.claude/mem/notes/2026-02-11-cli-fix-editor-command-alias.md +26 -0
  5. package/.claude/mem/notes/2026-02-11-phase-3-developer-experience-completed.md +51 -0
  6. package/.claude/mem/notes/2026-02-11-phase-4-web-workers-implementation-complete.md +59 -0
  7. package/.claude/mem/notes/2026-02-11-visualify-phase-2-3d-visualization-complete.md +50 -0
  8. package/.claude/mem/notes/2026-02-11-visualify-phase-2-committed-ready-for-phase-3.md +33 -0
  9. package/.claude/mem/notes/2026-02-11-visualify-phase-3-complete-developer-experience.md +52 -0
  10. package/.claude/mem/notes/2026-02-11-visualify-repository-cleanup-complete.md +28 -0
  11. package/.claude/mem/notes/2026-02-18-codebase-cleanup-docsify-plugin-documentation.md +37 -0
  12. package/.claude/mem/notes/2026-02-19-css-grid-layout-fix-displaycontents-on-vcontroller.md +18 -0
  13. package/.claude/mem/notes/2026-02-19-docsify-plugin-fixes-latex-and-visualify-code-bloc.md +26 -0
  14. package/.claude/mem/notes/2026-02-19-page-mode-docs-update-decisions.md +23 -0
  15. package/.claude/mem/notes/2026-02-19-react-context-infinite-re-render-loop-fix-pattern.md +31 -0
  16. package/.claude/mem/notes/2026-02-19-version-300-bump-and-build-fixes.md +32 -0
  17. package/.claude/mem/notes/2026-02-19-visualify-build-deployment-architecture-bug-fixes.md +25 -0
  18. package/.claude/mem/notes/2026-02-19-visualify-dist-iife-self-contained-build-config.md +30 -0
  19. package/.claude/mem/notes/2026-02-19-visualify-infinite-loop-i18n-fixes.md +31 -0
  20. package/.claude/mem/notes/2026-02-19-visualify-v3-bundle-splitting-docs-restructuring.md +32 -0
  21. package/.claude/mem/notes/2026-02-20-bundle-externalization-final-architecture.md +29 -0
  22. package/.claude/mem/notes/2026-02-20-chromium-page-fix-unstable-keys-and-double-event-b.md +27 -0
  23. package/.claude/mem/notes/2026-02-20-console-cleanup-bundle-optimization-commit.md +20 -0
  24. package/.claude/mem/notes/2026-02-20-dotbio-dot-plot-fix-useeffect-dependency.md +21 -0
  25. package/.claude/mem/notes/2026-02-20-public-folder-cleanup-and-readme-rewrite.md +25 -0
  26. package/.claude/mem/notes/2026-02-20-v300-release-and-beta-channel-strategy.md +29 -0
  27. package/.claude/mem/notes/2026-02-20-visium-background-image-unknown-legend-fix.md +19 -0
  28. package/.claude/mem/notes/2026-02-20-visualify-cdn-loader-bundle-externalization.md +34 -0
  29. package/.claude/mem/sessions/session-2026-02-20-031524.md +54 -0
  30. package/.claude/settings.local.json +21 -0
  31. package/.github/workflows/static.yml.bak +51 -51
  32. package/.sisyphus/boulder.json +65 -0
  33. package/.sisyphus/plans/phase-4-advanced-optimizations.md +217 -0
  34. package/LICENSE +674 -674
  35. package/README.md +94 -59
  36. package/config-overrides.js +31 -31
  37. package/dist/stats.html +4949 -0
  38. package/dist/visualify-3d.esm.js +1 -0
  39. package/dist/visualify-3d.js +1 -0
  40. package/dist/visualify-core.esm.js +1 -0
  41. package/dist/visualify-core.js +1 -0
  42. package/dist/visualify-docs.esm.js +1 -0
  43. package/dist/visualify-docs.js +1 -0
  44. package/dist/visualify-loader.js +1 -0
  45. package/dist/visualify-pages.esm.js +1 -0
  46. package/dist/visualify-pages.js +1 -0
  47. package/dist/visualify-portal.esm.js +1 -0
  48. package/dist/visualify-portal.js +1 -0
  49. package/dist/visualify-shared.js +26571 -0
  50. package/dist/visualify.js +1 -188
  51. package/docs/CHANGELOG.md +148 -0
  52. package/docs/cli/commands.md +513 -0
  53. package/docs/configuration/visualify-json.md +474 -0
  54. package/docs/docs/3d-visualization.md +374 -0
  55. package/docs/docs/CLI.md +303 -34
  56. package/docs/docs/README.md +65 -65
  57. package/docs/docs/Rechart/bar.md +190 -190
  58. package/docs/docs/Rechart/funnel.md +241 -193
  59. package/docs/docs/Rechart/line.md +355 -355
  60. package/docs/docs/Rechart/pie.md +225 -225
  61. package/docs/docs/Rechart/radar.md +253 -253
  62. package/docs/docs/Rechart/scatter.md +298 -0
  63. package/docs/docs/_404.md +51 -51
  64. package/docs/docs/_coverpage.md +11 -11
  65. package/docs/docs/_sidebar.md +54 -43
  66. package/docs/docs/components/dotBio.md +87 -34
  67. package/docs/docs/components/echart.md +171 -82
  68. package/docs/docs/components/html.md +61 -34
  69. package/docs/docs/components/macaron.md +156 -145
  70. package/docs/docs/components/markdown.md +42 -0
  71. package/docs/docs/components/more.md +183 -142
  72. package/docs/docs/components/plotly.md +132 -62
  73. package/docs/docs/components/scatterL.md +171 -70
  74. package/docs/docs/components/visium.md +112 -57
  75. package/docs/docs/configuration.md +121 -123
  76. package/docs/docs/deploy.md +31 -31
  77. package/docs/docs/docsify-plugin.md +655 -0
  78. package/docs/docs/hmr.md +165 -0
  79. package/docs/docs/i18n.md +332 -0
  80. package/docs/docs/log.md +30 -1
  81. package/docs/docs/more-pages.md +23 -23
  82. package/docs/docs/quickstart.md +148 -119
  83. package/docs/docs/rechart-attributes.md +74 -74
  84. package/docs/docs/rechart-basic-usage.md +160 -162
  85. package/docs/docs/theme.md +5 -5
  86. package/docs/docs/typescript.md +306 -0
  87. package/docs/docs/visual-editor.md +359 -0
  88. package/docs/index.html +85 -71
  89. package/docs/manifest.json +23 -23
  90. package/docs/migration/v3-migration.md +392 -0
  91. package/docs/static/css/fluff-stuff.css +169 -169
  92. package/docs/static/css/font-awesome.min.css +4 -4
  93. package/docs/static/css/visualify.css +6 -25
  94. package/docs/static/js/3d-viz-examples.js +181 -0
  95. package/docs/static/js/configuration.js +630 -448
  96. package/docs/static/js/visualify.js +1 -188
  97. package/package.json +106 -84
  98. package/rollup.config.mjs +766 -76
  99. package/src/_css/404.css +115 -115
  100. package/src/_css/App.css +37 -37
  101. package/src/_css/autoSuggestion.css +26 -26
  102. package/src/_css/circular-progress.css +32 -32
  103. package/src/_css/index.css +36 -36
  104. package/src/_css/modern.css +350 -25
  105. package/src/_media/corner.svg +8 -8
  106. package/src/_media/download.svg +3 -3
  107. package/src/_media/logo.svg +14 -14
  108. package/src/_test/App.test.js +15 -15
  109. package/src/_utils/reportWebVitals.js +13 -13
  110. package/src/a11y/README.md +177 -0
  111. package/src/a11y/aria-labels.js +339 -0
  112. package/src/a11y/color-contrast.js +535 -0
  113. package/src/a11y/index.js +197 -0
  114. package/src/a11y/keyboard-nav.js +523 -0
  115. package/src/a11y/styles.css +165 -0
  116. package/src/cli/commands/dev.js +214 -0
  117. package/src/cli/commands/docs.js +521 -0
  118. package/src/cli/commands/edit.js +379 -0
  119. package/src/cli/commands/init.js +213 -0
  120. package/src/cli/commands/portal.js +236 -0
  121. package/src/cli/dev-server.js +530 -0
  122. package/src/cli/hmr.js +456 -0
  123. package/src/cli/index.js +180 -0
  124. package/src/cli/utils/config.js +207 -0
  125. package/src/cli/utils/logger.js +241 -0
  126. package/src/config/defaults.ts +122 -0
  127. package/src/config/index.ts +72 -0
  128. package/src/config/loader.ts +478 -0
  129. package/src/config/schema.ts +227 -0
  130. package/src/config/validator.ts +337 -0
  131. package/src/core/appContext.js +34 -27
  132. package/src/core/components/Bar.js +383 -0
  133. package/src/core/components/Bar3D.js +473 -0
  134. package/src/core/components/LargeDatasetChart.js +296 -0
  135. package/src/core/components/Line3D.js +310 -0
  136. package/src/core/components/Scatter.js +392 -188
  137. package/src/core/components/Scatter3D.js +455 -0
  138. package/src/core/components/ScatterBio.js +601 -572
  139. package/src/core/components/Surface3D.js +326 -0
  140. package/src/core/components/ThreeCustom.js +648 -0
  141. package/src/core/components/ThreeScene.js +459 -0
  142. package/src/core/components/VisiumPlot.js +191 -165
  143. package/src/core/components/browser.js +42 -42
  144. package/src/core/components/dotplot.js +413 -413
  145. package/src/core/components/html.js +29 -29
  146. package/src/core/components/list.js +178 -178
  147. package/src/core/components/macaron.js +206 -201
  148. package/src/core/components/markdown.js +56 -56
  149. package/src/core/components/parser.scatterBio.js +582 -579
  150. package/src/core/components/ratio.js +82 -80
  151. package/src/core/components/scatterL.js +206 -173
  152. package/src/core/components/searchbar.js +156 -131
  153. package/src/core/components/selection.js +310 -193
  154. package/src/core/components/timeline.js +236 -281
  155. package/src/core/components/visium.js +114 -97
  156. package/src/core/data-processor.js +413 -0
  157. package/src/core/fetch/condfetch.js +82 -82
  158. package/src/core/fetch/fetch.js +92 -92
  159. package/src/core/fetch/json.js +29 -29
  160. package/src/core/fetch/vfetch.js +42 -42
  161. package/src/core/hmr-client.js +724 -0
  162. package/src/core/liveEditor.js +44 -44
  163. package/src/core/modules/codeEditorWithPreview.js +104 -104
  164. package/src/core/modules/echarts/common.js +20 -20
  165. package/src/core/modules/echarts/gl.js +228 -0
  166. package/src/core/modules/echarts/presetHandler.js +41 -41
  167. package/src/core/modules/echarts/presets/esodev.chromium.js +172 -172
  168. package/src/core/modules/echarts/presets/esodev.codex.js +130 -130
  169. package/src/core/modules/echarts/presets/esodev.visium.js +123 -123
  170. package/src/core/modules/echarts/presets/mmtrbc.js +186 -186
  171. package/src/core/modules/echarts.js +70 -71
  172. package/src/core/modules/echartsUtils.js +43 -43
  173. package/src/core/modules/echartswitcher.js +227 -152
  174. package/src/core/modules/replotly/presetHandler.js +24 -24
  175. package/src/core/modules/replotly/presets/minimum.js +18 -18
  176. package/src/core/modules/replotly/presets/mmtrbc.dot.js +114 -114
  177. package/src/core/modules/replotly/presets/mmtrbc.violin.js +100 -100
  178. package/src/core/modules/replotly.js +74 -71
  179. package/src/core/modules/threejs/Camera.js +373 -0
  180. package/src/core/modules/threejs/Lighting.js +459 -0
  181. package/src/core/modules/threejs/Renderer.js +364 -0
  182. package/src/core/modules/threejs/Scene.js +266 -0
  183. package/src/core/modules/threejs/index.js +155 -0
  184. package/src/core/pages/404.js +50 -50
  185. package/src/core/pages/error.js +27 -27
  186. package/src/core/pages/jsonPage.js +62 -62
  187. package/src/core/pages/loading.js +44 -44
  188. package/src/core/parser/echart.data.js +204 -183
  189. package/src/core/parser/echart.features.js +125 -125
  190. package/src/core/parser/echart.general.js +147 -143
  191. package/src/core/parser/echart.hilbert.js +57 -57
  192. package/src/core/parser/echart.parser.js +210 -210
  193. package/src/core/parser/echart.series.js +67 -67
  194. package/src/core/parser/echart.types.js +76 -76
  195. package/src/core/parser/plotly.config.js +10 -10
  196. package/src/core/parser/plotly.data.js +132 -132
  197. package/src/core/parser/plotly.layout.js +9 -9
  198. package/src/core/parser/plotly.violin.js +18 -18
  199. package/src/core/recharts.js +361 -62
  200. package/src/core/router/alias.js +49 -49
  201. package/src/core/router/jsonRouter.js +31 -31
  202. package/src/core/themes/modern.js +32 -32
  203. package/src/core/themes/themeSelector.js +33 -33
  204. package/src/core/visualify.js +213 -47
  205. package/src/core/widgets/circularProgress.js +23 -23
  206. package/src/core/widgets/controller.js +116 -83
  207. package/src/core/widgets/errorBoundary.js +36 -36
  208. package/src/core/widgets/footer.js +185 -177
  209. package/src/core/widgets/header.js +238 -234
  210. package/src/core/widgets/layout/Grid.js +31 -31
  211. package/src/core/widgets/layout.js +36 -36
  212. package/src/core/widgets/mapping.js +56 -42
  213. package/src/core/workers/data-worker.js +349 -0
  214. package/src/core/workers/worker-pool.js +396 -0
  215. package/src/docsify/bundle.js +215 -0
  216. package/src/docsify/markdown.js +271 -0
  217. package/src/docsify/plugin.js +268 -0
  218. package/src/editor/README.md +172 -0
  219. package/src/editor/components/ChartBuilder.jsx +341 -0
  220. package/src/editor/components/ChartTypeSidebar.jsx +91 -0
  221. package/src/editor/components/Editor.jsx +367 -0
  222. package/src/editor/components/Preview.jsx +446 -0
  223. package/src/editor/components/PropertyPanel.jsx +468 -0
  224. package/src/editor/components/StatusBar.jsx +85 -0
  225. package/src/editor/context/EditorContext.js +248 -0
  226. package/src/editor/hooks/useDebounce.js +32 -0
  227. package/src/editor/index.js +315 -0
  228. package/src/editor/styles/editor.css +637 -0
  229. package/src/editor/utils/chartValidator.js +263 -0
  230. package/src/entries/charts3d.js +70 -0
  231. package/src/entries/core.js +78 -0
  232. package/src/entries/docs.js +154 -0
  233. package/src/entries/pages.js +93 -0
  234. package/src/entries/portal.js +204 -0
  235. package/src/entries/shared.js +50 -0
  236. package/src/i18n/formatters.js +455 -0
  237. package/src/i18n/index.js +169 -0
  238. package/src/i18n/locales/ar.json +137 -0
  239. package/src/i18n/locales/de.json +137 -0
  240. package/src/i18n/locales/en.json +137 -0
  241. package/src/i18n/locales/es.json +137 -0
  242. package/src/i18n/locales/he.json +137 -0
  243. package/src/i18n/locales/zh.json +137 -0
  244. package/src/i18n/rtl.css +183 -0
  245. package/src/index.js +82 -62
  246. package/src/loader.js +103 -0
  247. package/src/setupTests.js +5 -5
  248. package/tsconfig.json +51 -0
  249. package/types/charts.d.ts +569 -0
  250. package/types/components.d.ts +441 -0
  251. package/types/config.d.ts +199 -0
  252. package/types/index.d.ts +353 -0
@@ -0,0 +1,478 @@
1
+ /**
2
+ * Configuration loading with merging logic
3
+ * @module loader
4
+ */
5
+
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
8
+ import defaults from './defaults';
9
+ import { validateConfig } from './validator';
10
+ import {
11
+ VisualifyConfig,
12
+ PartialConfig,
13
+ LoadConfigOptions,
14
+ ConfigCacheEntry,
15
+ CacheStats,
16
+ ConfigWatcher,
17
+ ConfigReloadCallback,
18
+ } from '../../types';
19
+
20
+ /**
21
+ * Cache for parsed configurations to avoid re-reading files
22
+ */
23
+ const configCache = new Map<string, ConfigCacheEntry>();
24
+
25
+ /**
26
+ * File watchers for development mode
27
+ */
28
+ const fileWatchers = new Map<string, fs.FSWatcher>();
29
+
30
+ /**
31
+ * Callbacks to invoke on config reload
32
+ */
33
+ const reloadCallbacks = new Map<string, Set<ConfigReloadCallback>>();
34
+
35
+ /**
36
+ * Configuration file names to search for
37
+ */
38
+ const CONFIG_FILES: readonly string[] = ['visualify.json', '.visualify.json'];
39
+
40
+ /**
41
+ * Environment variable prefix for Visualify config
42
+ */
43
+ const ENV_PREFIX = 'VISUALIFY_';
44
+
45
+ /**
46
+ * Error thrown when configuration loading fails
47
+ */
48
+ class ConfigLoadError extends Error {
49
+ code: string;
50
+ filepath?: string;
51
+ originalError?: Error;
52
+
53
+ constructor(message: string, code: string, filepath?: string, originalError?: Error) {
54
+ super(message);
55
+ this.name = 'ConfigLoadError';
56
+ this.code = code;
57
+ this.filepath = filepath;
58
+ this.originalError = originalError;
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Find configuration file in the given directory
64
+ * @param cwd - Current working directory to search
65
+ * @returns Path to config file or null if not found
66
+ */
67
+ function findConfigFile(cwd: string): string | null {
68
+ for (const filename of CONFIG_FILES) {
69
+ const filepath = path.join(cwd, filename);
70
+ if (fs.existsSync(filepath)) {
71
+ return filepath;
72
+ }
73
+ }
74
+ return null;
75
+ }
76
+
77
+ /**
78
+ * Read and parse a JSON file
79
+ * @param filepath - Path to the JSON file
80
+ * @returns Parsed JSON content
81
+ * @throws ConfigLoadError if file cannot be read or parsed
82
+ */
83
+ function readJsonFile(filepath: string): unknown {
84
+ const content = fs.readFileSync(filepath, 'utf-8');
85
+ try {
86
+ return JSON.parse(content);
87
+ } catch (parseError) {
88
+ throw new ConfigLoadError(
89
+ `Invalid JSON in ${filepath}: ${(parseError as Error).message}`,
90
+ 'INVALID_JSON',
91
+ filepath,
92
+ parseError as Error
93
+ );
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Load configuration from file
99
+ * @param cwd - Current working directory
100
+ * @returns Parsed configuration or null if no file found
101
+ */
102
+ function loadConfigFile(cwd: string): PartialConfig | null {
103
+ const filepath = findConfigFile(cwd);
104
+
105
+ if (!filepath) {
106
+ return null;
107
+ }
108
+
109
+ // Check cache first
110
+ const cached = configCache.get(filepath);
111
+ if (cached) {
112
+ const stats = fs.statSync(filepath);
113
+ if (cached.mtime >= stats.mtime) {
114
+ return cached.config as PartialConfig;
115
+ }
116
+ }
117
+
118
+ const config = readJsonFile(filepath) as PartialConfig;
119
+
120
+ // Update cache
121
+ const stats = fs.statSync(filepath);
122
+ configCache.set(filepath, {
123
+ config: config as VisualifyConfig,
124
+ mtime: stats.mtime,
125
+ filepath,
126
+ });
127
+
128
+ return config;
129
+ }
130
+
131
+ /**
132
+ * Parse environment variable value
133
+ * @param value - Raw environment variable value
134
+ * @returns Parsed value (boolean, number, null, or string)
135
+ */
136
+ function parseEnvValue(value: string): unknown {
137
+ if (value === 'true') return true;
138
+ if (value === 'false') return false;
139
+ if (value === 'null') return null;
140
+ if (value !== '' && !isNaN(Number(value))) return Number(value);
141
+ return value;
142
+ }
143
+
144
+ /**
145
+ * Load configuration from environment variables
146
+ * Environment variables should be prefixed with VISUALIFY_
147
+ * Nested properties use double underscore: VISUALIFY_DOCS__THEME
148
+ * @returns Configuration from environment
149
+ */
150
+ function loadEnvironmentConfig(): PartialConfig {
151
+ const config: PartialConfig = {};
152
+
153
+ for (const [key, value] of Object.entries(process.env)) {
154
+ if (!key || !value || !key.startsWith(ENV_PREFIX)) {
155
+ continue;
156
+ }
157
+
158
+ // Remove prefix and split by double underscore for nesting
159
+ const pathParts = key
160
+ .slice(ENV_PREFIX.length)
161
+ .toLowerCase()
162
+ .split('__');
163
+
164
+ // Parse value
165
+ const parsedValue = parseEnvValue(value);
166
+
167
+ // Build nested object
168
+ pathParts.reduce((acc: Record<string, unknown>, part, index) => {
169
+ if (index === pathParts.length - 1) {
170
+ acc[part] = parsedValue;
171
+ return acc;
172
+ }
173
+ if (!acc[part]) acc[part] = {};
174
+ return acc[part] as Record<string, unknown>;
175
+ }, config as Record<string, unknown>);
176
+ }
177
+
178
+ return config;
179
+ }
180
+
181
+ /**
182
+ * Check if value is a plain object
183
+ * @param value - Value to check
184
+ * @returns True if value is a plain object
185
+ */
186
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
187
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
188
+ }
189
+
190
+ /**
191
+ * Deep merge objects (arrays are concatenated, not merged)
192
+ * @param target - Target object
193
+ * @param sources - Source objects to merge
194
+ * @returns Merged object
195
+ */
196
+ function deepMerge<T extends Record<string, unknown>>(
197
+ target: T,
198
+ ...sources: Array<Partial<T> | undefined | null>
199
+ ): T {
200
+ if (!sources.length) return target;
201
+
202
+ const source = sources.shift();
203
+ if (source === undefined || source === null) {
204
+ return deepMerge(target, ...sources);
205
+ }
206
+
207
+ if (!isPlainObject(target) || !isPlainObject(source)) {
208
+ return deepMerge(target, ...sources);
209
+ }
210
+
211
+ for (const key of Object.keys(source)) {
212
+ const sourceValue = source[key];
213
+ if (sourceValue === undefined) continue;
214
+
215
+ const targetValue = target[key];
216
+
217
+ if (Array.isArray(sourceValue)) {
218
+ (target as Record<string, unknown>)[key] = Array.isArray(targetValue)
219
+ ? [...targetValue, ...sourceValue]
220
+ : [...sourceValue];
221
+ } else if (isPlainObject(sourceValue)) {
222
+ (target as Record<string, unknown>)[key] = deepMerge(
223
+ isPlainObject(targetValue) ? targetValue : {},
224
+ sourceValue
225
+ );
226
+ } else {
227
+ (target as Record<string, unknown>)[key] = sourceValue;
228
+ }
229
+ }
230
+
231
+ return deepMerge(target, ...sources);
232
+ }
233
+
234
+ /**
235
+ * Normalize dot-notation keys to nested objects
236
+ * @param overrides - Object with potential dot-notation keys
237
+ * @returns Normalized object with nested structure
238
+ */
239
+ function normalizeOverrides(
240
+ overrides: Record<string, unknown>
241
+ ): Record<string, unknown> {
242
+ const normalized: Record<string, unknown> = {};
243
+
244
+ for (const [key, value] of Object.entries(overrides)) {
245
+ if (key.includes('.')) {
246
+ const parts = key.split('.');
247
+ parts.reduce((acc, part, index) => {
248
+ if (index === parts.length - 1) {
249
+ acc[part] = value;
250
+ return acc;
251
+ }
252
+ if (!acc[part]) acc[part] = {};
253
+ return acc[part] as Record<string, unknown>;
254
+ }, normalized);
255
+ } else {
256
+ normalized[key] = value;
257
+ }
258
+ }
259
+
260
+ return normalized;
261
+ }
262
+
263
+ /**
264
+ * Load and merge configuration from all sources
265
+ * Merging order: defaults < file < environment < CLI args
266
+ *
267
+ * @param cwd - Current working directory (default: process.cwd())
268
+ * @param overrides - CLI argument overrides
269
+ * @param options - Loading options
270
+ * @returns Merged configuration object
271
+ *
272
+ * @example
273
+ * ```typescript
274
+ * // Load with defaults
275
+ * const config = await loadConfig();
276
+ *
277
+ * // Load with CLI overrides
278
+ * const config = await loadConfig(process.cwd(), {
279
+ * mode: 'docs',
280
+ * 'docs.theme': 'modern'
281
+ * });
282
+ *
283
+ * // Load without validation (for partial configs)
284
+ * const config = await loadConfig(cwd, {}, { validate: false });
285
+ * ```
286
+ */
287
+ async function loadConfig(
288
+ cwd: string = process.cwd(),
289
+ overrides: Record<string, unknown> = {},
290
+ options: LoadConfigOptions = {}
291
+ ): Promise<VisualifyConfig> {
292
+ const {
293
+ validate = true,
294
+ skipFile = false,
295
+ skipEnvironment = false,
296
+ } = options;
297
+
298
+ // Start with defaults
299
+ let config: Record<string, unknown> = { ...defaults };
300
+
301
+ // Merge with file config
302
+ if (!skipFile) {
303
+ const fileConfig = loadConfigFile(cwd);
304
+ if (fileConfig) {
305
+ config = deepMerge(config, fileConfig);
306
+ }
307
+ }
308
+
309
+ // Merge with environment config
310
+ if (!skipEnvironment) {
311
+ const envConfig = loadEnvironmentConfig();
312
+ config = deepMerge(config, envConfig);
313
+ }
314
+
315
+ // Merge with CLI overrides
316
+ if (overrides && Object.keys(overrides).length > 0) {
317
+ const normalizedOverrides = normalizeOverrides(overrides);
318
+ config = deepMerge(config, normalizedOverrides);
319
+ }
320
+
321
+ // Validate final config
322
+ if (validate) {
323
+ const validation = validateConfig(config, { source: 'merged' });
324
+ if (!validation.valid) {
325
+ const error = new ConfigLoadError(
326
+ validation.summary || 'Configuration validation failed',
327
+ 'CONFIG_VALIDATION_ERROR'
328
+ );
329
+ // Attach errors to the error object for programmatic access
330
+ Object.defineProperty(error, 'errors', {
331
+ value: validation.errors,
332
+ enumerable: false,
333
+ writable: false,
334
+ configurable: false,
335
+ });
336
+ throw error;
337
+ }
338
+ }
339
+
340
+ return config as VisualifyConfig;
341
+ }
342
+
343
+ /**
344
+ * Watch configuration file for changes
345
+ * @param cwd - Current working directory (default: process.cwd())
346
+ * @param callback - Callback to invoke on change
347
+ * @returns Watcher control object with stop() method
348
+ *
349
+ * @example
350
+ * ```typescript
351
+ * const watcher = watchConfig(process.cwd(), (config, error) => {
352
+ * if (error) {
353
+ * console.error('Config reload error:', error);
354
+ * } else {
355
+ * console.log('Config reloaded:', config);
356
+ * }
357
+ * });
358
+ *
359
+ * // Stop watching
360
+ * watcher.stop();
361
+ * ```
362
+ */
363
+ function watchConfig(
364
+ cwd: string = process.cwd(),
365
+ callback: ConfigReloadCallback
366
+ ): ConfigWatcher {
367
+ const filepath = findConfigFile(cwd);
368
+
369
+ if (!filepath) {
370
+ throw new ConfigLoadError(
371
+ `No config file found in ${cwd}`,
372
+ 'CONFIG_FILE_NOT_FOUND'
373
+ );
374
+ }
375
+
376
+ // Store callback
377
+ if (!reloadCallbacks.has(filepath)) {
378
+ reloadCallbacks.set(filepath, new Set());
379
+ }
380
+ reloadCallbacks.get(filepath)!.add(callback);
381
+
382
+ // Create watcher if not exists
383
+ if (!fileWatchers.has(filepath)) {
384
+ const watcher = fs.watch(filepath, (eventType) => {
385
+ if (eventType === 'change') {
386
+ // Clear cache for this file
387
+ configCache.delete(filepath);
388
+
389
+ // Reload config
390
+ loadConfig(cwd)
391
+ .then((newConfig) => {
392
+ const callbacks = reloadCallbacks.get(filepath);
393
+ if (callbacks) {
394
+ callbacks.forEach((cb) => {
395
+ try {
396
+ cb(newConfig, null);
397
+ } catch (err) {
398
+ console.error('Config reload callback error:', err);
399
+ }
400
+ });
401
+ }
402
+ })
403
+ .catch((error) => {
404
+ const callbacks = reloadCallbacks.get(filepath);
405
+ if (callbacks) {
406
+ callbacks.forEach((cb) => cb(null, error as Error));
407
+ }
408
+ });
409
+ }
410
+ });
411
+
412
+ fileWatchers.set(filepath, watcher);
413
+ }
414
+
415
+ return {
416
+ stop: () => {
417
+ const callbacks = reloadCallbacks.get(filepath);
418
+ if (callbacks) {
419
+ callbacks.delete(callback);
420
+ if (callbacks.size === 0) {
421
+ // No more callbacks, stop watcher
422
+ const watcher = fileWatchers.get(filepath);
423
+ if (watcher) {
424
+ watcher.close();
425
+ fileWatchers.delete(filepath);
426
+ }
427
+ reloadCallbacks.delete(filepath);
428
+ }
429
+ }
430
+ },
431
+ filepath,
432
+ };
433
+ }
434
+
435
+ /**
436
+ * Clear the configuration cache
437
+ */
438
+ function clearCache(): void {
439
+ configCache.clear();
440
+ }
441
+
442
+ /**
443
+ * Get cache statistics
444
+ * @returns Cache statistics
445
+ */
446
+ function getCacheStats(): CacheStats {
447
+ return {
448
+ size: configCache.size,
449
+ files: Array.from(configCache.keys()),
450
+ };
451
+ }
452
+
453
+ export {
454
+ loadConfig,
455
+ watchConfig,
456
+ findConfigFile,
457
+ loadConfigFile,
458
+ loadEnvironmentConfig,
459
+ clearCache,
460
+ getCacheStats,
461
+ deepMerge,
462
+ ConfigLoadError,
463
+ CONFIG_FILES,
464
+ ENV_PREFIX,
465
+ };
466
+
467
+ export default {
468
+ loadConfig,
469
+ watchConfig,
470
+ findConfigFile,
471
+ loadConfigFile,
472
+ loadEnvironmentConfig,
473
+ clearCache,
474
+ getCacheStats,
475
+ deepMerge,
476
+ CONFIG_FILES,
477
+ ENV_PREFIX,
478
+ };
@@ -0,0 +1,227 @@
1
+ /**
2
+ * JSON Schema for visualify.json validation
3
+ * @module schema
4
+ */
5
+
6
+ /**
7
+ * JSON Schema definition for Visualify.js configuration
8
+ * Used with AJV for validation
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import schema from './schema';
13
+ * import Ajv from 'ajv';
14
+ *
15
+ * const ajv = new Ajv();
16
+ * const validate = ajv.compile(schema);
17
+ * const valid = validate(config);
18
+ * ```
19
+ */
20
+ const schema = {
21
+ $schema: 'http://json-schema.org/draft-07/schema#',
22
+ $id: 'https://visualify.js.org/schema/config.json',
23
+ title: 'Visualify Configuration',
24
+ description: 'Configuration schema for Visualify.js applications',
25
+ type: 'object' as const,
26
+ required: ['version'] as const,
27
+ additionalProperties: false,
28
+ properties: {
29
+ /**
30
+ * Schema version for compatibility checking
31
+ */
32
+ version: {
33
+ type: 'string' as const,
34
+ enum: ['3.0.0'] as const,
35
+ description: 'Configuration schema version',
36
+ default: '3.0.0',
37
+ },
38
+
39
+ /**
40
+ * Application mode
41
+ */
42
+ mode: {
43
+ type: 'string' as const,
44
+ enum: ['docs', 'portal', 'hybrid', 'auto'] as const,
45
+ description: 'Application operating mode',
46
+ default: 'auto',
47
+ },
48
+
49
+ /**
50
+ * Documentation configuration
51
+ */
52
+ docs: {
53
+ type: 'object' as const,
54
+ description: 'Documentation settings',
55
+ additionalProperties: false,
56
+ properties: {
57
+ basePath: {
58
+ type: 'string' as const,
59
+ description: 'Base path for documentation files',
60
+ default: './docs',
61
+ minLength: 1,
62
+ },
63
+ theme: {
64
+ type: 'string' as const,
65
+ description: 'Documentation theme',
66
+ default: 'vue',
67
+ minLength: 1,
68
+ },
69
+ plugins: {
70
+ type: 'array' as const,
71
+ description: 'List of plugin modules to load',
72
+ default: [] as const,
73
+ items: {
74
+ type: 'string' as const,
75
+ minLength: 1,
76
+ },
77
+ },
78
+ },
79
+ },
80
+
81
+ /**
82
+ * Portal configuration
83
+ */
84
+ portal: {
85
+ type: 'object' as const,
86
+ description: 'Portal settings',
87
+ additionalProperties: false,
88
+ properties: {
89
+ homepage: {
90
+ type: 'string' as const,
91
+ description: 'Homepage configuration file',
92
+ default: 'home.json',
93
+ minLength: 1,
94
+ },
95
+ theme: {
96
+ type: 'string' as const,
97
+ description: 'Portal theme',
98
+ default: 'modern',
99
+ minLength: 1,
100
+ },
101
+ dataSources: {
102
+ type: 'array' as const,
103
+ description: 'Data sources for the portal',
104
+ default: [] as const,
105
+ items: {
106
+ type: 'object' as const,
107
+ required: ['name', 'type'] as const,
108
+ properties: {
109
+ name: {
110
+ type: 'string' as const,
111
+ minLength: 1,
112
+ },
113
+ type: {
114
+ type: 'string' as const,
115
+ enum: ['json', 'csv', 'api', 'websocket'] as const,
116
+ },
117
+ url: {
118
+ type: 'string' as const,
119
+ format: 'uri',
120
+ },
121
+ path: {
122
+ type: 'string' as const,
123
+ },
124
+ },
125
+ additionalProperties: true,
126
+ },
127
+ },
128
+ },
129
+ },
130
+
131
+ /**
132
+ * Visualization configuration
133
+ */
134
+ visualization: {
135
+ type: 'object' as const,
136
+ description: 'Visualization settings',
137
+ additionalProperties: false,
138
+ properties: {
139
+ defaultLibrary: {
140
+ type: 'string' as const,
141
+ enum: ['echarts', 'plotly'] as const,
142
+ description: 'Default charting library',
143
+ default: 'echarts',
144
+ },
145
+ enable3D: {
146
+ type: 'boolean' as const,
147
+ description: 'Enable 3D visualization capabilities',
148
+ default: false,
149
+ },
150
+ webWorkers: {
151
+ type: 'boolean' as const,
152
+ description: 'Use Web Workers for rendering',
153
+ default: false,
154
+ },
155
+ },
156
+ },
157
+
158
+ /**
159
+ * Internationalization (i18n) configuration
160
+ */
161
+ i18n: {
162
+ type: 'object' as const,
163
+ description: 'Internationalization settings',
164
+ additionalProperties: false,
165
+ properties: {
166
+ locale: {
167
+ type: 'string' as const,
168
+ description: 'Locale setting (auto for browser detection)',
169
+ default: 'auto',
170
+ enum: ['auto', 'en', 'zh', 'es', 'de', 'ar', 'he'] as const,
171
+ },
172
+ fallbackLocale: {
173
+ type: 'string' as const,
174
+ description: 'Fallback locale when translation is missing',
175
+ default: 'en',
176
+ enum: ['en', 'zh', 'es', 'de', 'ar', 'he'] as const,
177
+ },
178
+ enableRTL: {
179
+ type: 'boolean' as const,
180
+ description: 'Enable RTL (Right-to-Left) layout support',
181
+ default: true,
182
+ },
183
+ },
184
+ },
185
+ },
186
+ } as const;
187
+
188
+ /**
189
+ * Type derived from the schema for compile-time validation
190
+ * This ensures the schema matches the VisualifyConfig interface
191
+ */
192
+ export type SchemaType = typeof schema;
193
+
194
+ /**
195
+ * Schema default values extracted for programmatic use
196
+ */
197
+ export const SCHEMA_DEFAULTS = {
198
+ version: '3.0.0',
199
+ mode: 'auto',
200
+ docs: {
201
+ basePath: './docs',
202
+ theme: 'vue',
203
+ plugins: [] as string[],
204
+ },
205
+ portal: {
206
+ homepage: 'home.json',
207
+ theme: 'modern',
208
+ dataSources: [] as Array<{
209
+ name: string;
210
+ type: 'json' | 'csv' | 'api' | 'websocket';
211
+ url?: string;
212
+ path?: string;
213
+ }>,
214
+ },
215
+ visualization: {
216
+ defaultLibrary: 'echarts' as const,
217
+ enable3D: false,
218
+ webWorkers: false,
219
+ },
220
+ i18n: {
221
+ locale: 'auto' as const,
222
+ fallbackLocale: 'en' as const,
223
+ enableRTL: true,
224
+ },
225
+ } as const;
226
+
227
+ export default schema;