@noego/wood 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/browser-test-setup.cjs +29 -0
- package/dist/browser-test-setup.cjs.map +1 -0
- package/dist/browser-test-setup.d.cts +8 -0
- package/dist/browser-test-setup.d.ts +8 -0
- package/dist/browser-test-setup.js +9 -0
- package/dist/browser-test-setup.js.map +1 -0
- package/dist/client/global_polyfill.d.cts +1 -0
- package/dist/client/global_polyfill.d.ts +1 -0
- package/dist/client/index.cjs +7 -0
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +7 -4
- package/dist/client/index.d.ts +7 -4
- package/dist/client/index.js +6 -1
- package/dist/client/index.js.map +1 -1
- package/dist/codegen/renderer_app_generator.cjs +97 -24
- package/dist/codegen/renderer_app_generator.cjs.map +1 -1
- package/dist/codegen/renderer_app_generator.js +97 -24
- package/dist/codegen/renderer_app_generator.js.map +1 -1
- package/dist/codegen/type_generator.cjs +9 -0
- package/dist/codegen/type_generator.cjs.map +1 -1
- package/dist/codegen/type_generator.js +9 -0
- package/dist/codegen/type_generator.js.map +1 -1
- package/dist/config/config_parser.cjs +10 -0
- package/dist/config/config_parser.cjs.map +1 -1
- package/dist/config/config_parser.js +10 -0
- package/dist/config/config_parser.js.map +1 -1
- package/dist/fetch.cjs +38 -0
- package/dist/fetch.cjs.map +1 -0
- package/dist/fetch.d.cts +7 -0
- package/dist/fetch.d.ts +7 -0
- package/dist/fetch.js +12 -0
- package/dist/fetch.js.map +1 -0
- package/dist/frontend/frontend_page.cjs +3 -3
- package/dist/frontend/frontend_page.cjs.map +1 -1
- package/dist/frontend/frontend_page.d.cts +1 -1
- package/dist/frontend/frontend_page.d.ts +1 -1
- package/dist/frontend/frontend_page.js +3 -3
- package/dist/frontend/frontend_page.js.map +1 -1
- package/dist/frontend/frontend_surface.cjs +31 -17
- package/dist/frontend/frontend_surface.cjs.map +1 -1
- package/dist/frontend/frontend_surface.d.cts +12 -2
- package/dist/frontend/frontend_surface.d.ts +12 -2
- package/dist/frontend/frontend_surface.js +37 -17
- package/dist/frontend/frontend_surface.js.map +1 -1
- package/dist/frontend/index.d.cts +2 -0
- package/dist/frontend/index.d.ts +2 -0
- package/dist/index.cjs +5 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/loader/index.cjs +19 -2
- package/dist/loader/index.cjs.map +1 -1
- package/dist/loader/index.d.cts +1 -0
- package/dist/loader/index.d.ts +1 -0
- package/dist/loader/index.js +19 -1
- package/dist/loader/index.js.map +1 -1
- package/dist/loader/loader_orchestration.cjs +82 -0
- package/dist/loader/loader_orchestration.cjs.map +1 -0
- package/dist/loader/loader_orchestration.d.cts +66 -0
- package/dist/loader/loader_orchestration.d.ts +66 -0
- package/dist/loader/loader_orchestration.js +51 -0
- package/dist/loader/loader_orchestration.js.map +1 -0
- package/dist/loader/loader_runner.cjs +46 -41
- package/dist/loader/loader_runner.cjs.map +1 -1
- package/dist/loader/loader_runner.d.cts +12 -6
- package/dist/loader/loader_runner.d.ts +12 -6
- package/dist/loader/loader_runner.js +50 -41
- package/dist/loader/loader_runner.js.map +1 -1
- package/dist/native_browser.types-BY2daUOS.d.ts +189 -0
- package/dist/native_browser.types-vWXtE4rM.d.cts +189 -0
- package/dist/navigation/index.cjs +15 -2
- package/dist/navigation/index.cjs.map +1 -1
- package/dist/navigation/index.d.cts +2 -0
- package/dist/navigation/index.d.ts +2 -0
- package/dist/navigation/index.js +8 -1
- package/dist/navigation/index.js.map +1 -1
- package/dist/runtime/get_container.cjs +2 -0
- package/dist/runtime/get_container.cjs.map +1 -1
- package/dist/runtime/get_container.js +2 -0
- package/dist/runtime/get_container.js.map +1 -1
- package/dist/runtime/index.cjs +5 -0
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.d.cts +2 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +3 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/runtime_capabilities.cjs +35 -0
- package/dist/runtime/runtime_capabilities.cjs.map +1 -0
- package/dist/runtime/runtime_capabilities.d.cts +6 -0
- package/dist/runtime/runtime_capabilities.d.ts +6 -0
- package/dist/runtime/runtime_capabilities.js +11 -0
- package/dist/runtime/runtime_capabilities.js.map +1 -0
- package/dist/runtime/runtime_module.cjs +2 -0
- package/dist/runtime/runtime_module.cjs.map +1 -1
- package/dist/runtime/runtime_module.js +2 -0
- package/dist/runtime/runtime_module.js.map +1 -1
- package/dist/test-wood.cjs +150 -0
- package/dist/test-wood.cjs.map +1 -0
- package/dist/test-wood.d.cts +62 -0
- package/dist/test-wood.d.ts +62 -0
- package/dist/test-wood.js +115 -0
- package/dist/test-wood.js.map +1 -0
- package/dist/testing/browser.cjs +92 -163
- package/dist/testing/browser.cjs.map +1 -1
- package/dist/testing/browser.d.cts +14 -13
- package/dist/testing/browser.d.ts +14 -13
- package/dist/testing/browser.js +88 -162
- package/dist/testing/browser.js.map +1 -1
- package/dist/testing/create_controller_harness.d.cts +2 -1
- package/dist/testing/create_controller_harness.d.ts +2 -1
- package/dist/testing/create_router_bridge.cjs +19 -15
- package/dist/testing/create_router_bridge.cjs.map +1 -1
- package/dist/testing/create_router_bridge.d.cts +8 -1
- package/dist/testing/create_router_bridge.d.ts +8 -1
- package/dist/testing/create_router_bridge.js +18 -15
- package/dist/testing/create_router_bridge.js.map +1 -1
- package/dist/testing/index.cjs +12 -4
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +11 -5
- package/dist/testing/index.d.ts +11 -5
- package/dist/testing/index.js +9 -3
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/native_browser/browser_pool.cjs +31 -0
- package/dist/testing/native_browser/browser_pool.cjs.map +1 -0
- package/dist/testing/native_browser/browser_pool.d.cts +1 -0
- package/dist/testing/native_browser/browser_pool.d.ts +1 -0
- package/dist/testing/native_browser/browser_pool.js +6 -0
- package/dist/testing/native_browser/browser_pool.js.map +1 -0
- package/dist/testing/native_browser/happy_dom_tab.cjs +107 -0
- package/dist/testing/native_browser/happy_dom_tab.cjs.map +1 -0
- package/dist/testing/native_browser/happy_dom_tab.d.cts +35 -0
- package/dist/testing/native_browser/happy_dom_tab.d.ts +35 -0
- package/dist/testing/native_browser/happy_dom_tab.js +73 -0
- package/dist/testing/native_browser/happy_dom_tab.js.map +1 -0
- package/dist/testing/native_browser/happy_dom_transfer.cjs +29 -0
- package/dist/testing/native_browser/happy_dom_transfer.cjs.map +1 -0
- package/dist/testing/native_browser/happy_dom_transfer.d.cts +1 -0
- package/dist/testing/native_browser/happy_dom_transfer.d.ts +1 -0
- package/dist/testing/native_browser/happy_dom_transfer.js +5 -0
- package/dist/testing/native_browser/happy_dom_transfer.js.map +1 -0
- package/dist/testing/native_browser/host_transport.cjs +89 -0
- package/dist/testing/native_browser/host_transport.cjs.map +1 -0
- package/dist/testing/native_browser/host_transport.d.cts +44 -0
- package/dist/testing/native_browser/host_transport.d.ts +44 -0
- package/dist/testing/native_browser/host_transport.js +65 -0
- package/dist/testing/native_browser/host_transport.js.map +1 -0
- package/dist/testing/native_browser/native_browser.types.cjs +17 -0
- package/dist/testing/native_browser/native_browser.types.cjs.map +1 -0
- package/dist/testing/native_browser/native_browser.types.d.cts +31 -0
- package/dist/testing/native_browser/native_browser.types.d.ts +31 -0
- package/dist/testing/native_browser/native_browser.types.js +1 -0
- package/dist/testing/native_browser/native_browser.types.js.map +1 -0
- package/dist/testing/native_browser/native_browser_environment.cjs +143 -0
- package/dist/testing/native_browser/native_browser_environment.cjs.map +1 -0
- package/dist/testing/native_browser/native_browser_environment.d.cts +36 -0
- package/dist/testing/native_browser/native_browser_environment.d.ts +36 -0
- package/dist/testing/native_browser/native_browser_environment.js +109 -0
- package/dist/testing/native_browser/native_browser_environment.js.map +1 -0
- package/dist/testing/native_browser/native_browser_lifecycle.cjs +31 -0
- package/dist/testing/native_browser/native_browser_lifecycle.cjs.map +1 -0
- package/dist/testing/native_browser/native_browser_lifecycle.d.cts +1 -0
- package/dist/testing/native_browser/native_browser_lifecycle.d.ts +1 -0
- package/dist/testing/native_browser/native_browser_lifecycle.js +6 -0
- package/dist/testing/native_browser/native_browser_lifecycle.js.map +1 -0
- package/dist/testing/native_browser/playwright_tab.cjs +124 -0
- package/dist/testing/native_browser/playwright_tab.cjs.map +1 -0
- package/dist/testing/native_browser/playwright_tab.d.cts +35 -0
- package/dist/testing/native_browser/playwright_tab.d.ts +35 -0
- package/dist/testing/native_browser/playwright_tab.js +100 -0
- package/dist/testing/native_browser/playwright_tab.js.map +1 -0
- package/dist/testing/native_browser/renderer_compilation.cjs +149 -0
- package/dist/testing/native_browser/renderer_compilation.cjs.map +1 -0
- package/dist/testing/native_browser/renderer_compilation.d.cts +35 -0
- package/dist/testing/native_browser/renderer_compilation.d.ts +35 -0
- package/dist/testing/native_browser/renderer_compilation.js +115 -0
- package/dist/testing/native_browser/renderer_compilation.js.map +1 -0
- package/dist/testing/native_browser/renderer_entry.cjs +148 -0
- package/dist/testing/native_browser/renderer_entry.cjs.map +1 -0
- package/dist/testing/native_browser/renderer_entry.d.cts +13 -0
- package/dist/testing/native_browser/renderer_entry.d.ts +13 -0
- package/dist/testing/native_browser/renderer_entry.js +114 -0
- package/dist/testing/native_browser/renderer_entry.js.map +1 -0
- package/dist/testing/registration_only_root.cjs +61 -5
- package/dist/testing/registration_only_root.cjs.map +1 -1
- package/dist/testing/registration_only_root.d.cts +25 -4
- package/dist/testing/registration_only_root.d.ts +25 -4
- package/dist/testing/registration_only_root.js +60 -5
- package/dist/testing/registration_only_root.js.map +1 -1
- package/dist/testing/runner_importer.cjs +75 -0
- package/dist/testing/runner_importer.cjs.map +1 -0
- package/dist/testing/runner_importer.d.cts +8 -0
- package/dist/testing/runner_importer.d.ts +8 -0
- package/dist/testing/runner_importer.js +41 -0
- package/dist/testing/runner_importer.js.map +1 -0
- package/dist/testing/test_wood_config.cjs +387 -272
- package/dist/testing/test_wood_config.cjs.map +1 -1
- package/dist/testing/test_wood_config.d.cts +18 -123
- package/dist/testing/test_wood_config.d.ts +18 -123
- package/dist/testing/test_wood_config.js +396 -270
- package/dist/testing/test_wood_config.js.map +1 -1
- package/dist/testing/wood_config_source.cjs +12 -0
- package/dist/testing/wood_config_source.cjs.map +1 -1
- package/dist/testing/wood_config_source.d.cts +10 -0
- package/dist/testing/wood_config_source.d.ts +10 -0
- package/dist/testing/wood_config_source.js +12 -0
- package/dist/testing/wood_config_source.js.map +1 -1
- package/dist/testing/wood_env_renderer.cjs +28 -3
- package/dist/testing/wood_env_renderer.cjs.map +1 -1
- package/dist/testing/wood_env_renderer.d.cts +12 -1
- package/dist/testing/wood_env_renderer.d.ts +12 -1
- package/dist/testing/wood_env_renderer.js +28 -3
- package/dist/testing/wood_env_renderer.js.map +1 -1
- package/dist/testing/wood_lazy_runtime.cjs +109 -0
- package/dist/testing/wood_lazy_runtime.cjs.map +1 -0
- package/dist/testing/wood_lazy_runtime.d.cts +36 -0
- package/dist/testing/wood_lazy_runtime.d.ts +36 -0
- package/dist/testing/wood_lazy_runtime.js +84 -0
- package/dist/testing/wood_lazy_runtime.js.map +1 -0
- package/dist/testing/wood_test_selection.cjs +34 -55
- package/dist/testing/wood_test_selection.cjs.map +1 -1
- package/dist/testing/wood_test_selection.d.cts +18 -24
- package/dist/testing/wood_test_selection.d.ts +18 -24
- package/dist/testing/wood_test_selection.js +30 -54
- package/dist/testing/wood_test_selection.js.map +1 -1
- package/dist/testing-api.cjs +17 -0
- package/dist/testing-api.cjs.map +1 -0
- package/dist/testing-api.d.cts +18 -0
- package/dist/testing-api.d.ts +18 -0
- package/dist/testing-api.js +1 -0
- package/dist/testing-api.js.map +1 -0
- package/dist/types/config.cjs.map +1 -1
- package/dist/types/config.d.cts +4 -0
- package/dist/types/config.d.ts +4 -0
- package/docs/native-browser-testing.md +164 -0
- package/package.json +31 -5
- package/src/components/NavigationShell.browser.test.ts +10 -10
- package/src/components/NavigationShell.cleanup.browser.test.ts +57 -0
- package/src/components/NavigationShell.scopes.browser.test.ts +5 -5
- package/src/components/NavigationShell.svelte +50 -24
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/codegen/renderer_app_generator.ts"],"sourcesContent":["import { existsSync } from 'fs';\nimport path from 'path';\nimport type { ViewDef } from '../types/index.js';\nimport { generatedFileNotice } from './generated_file_notice.js';\n\nconst RENDERER_APP_SOURCE_INSTRUCTIONS = [\n 'To change routes, page components, layouts, or renderer controllers, edit the views YAML',\n 'configured by wood.config.yml renderer.openapi and the matching UI source files.',\n] as const;\n\nexport interface RendererAppGeneratorOptions {\n /** Absolute path to renderer component root (e.g. /app/ui) */\n componentDir: string;\n /** Absolute output directory where generated file will be written */\n outDir: string;\n /** Optional explicit default view key (e.g. \"main.chat\") */\n entryViewKey?: string;\n}\n\nfunction computeImportPath(fromDir: string, toFile: string): string {\n let rel = path.relative(fromDir, toFile);\n\n if (!rel.startsWith('.')) {\n rel = './' + rel;\n }\n\n return rel.replace(/\\\\/g, '/');\n}\n\nfunction resolveFromComponentDir(componentDir: string, fileRef: string): string {\n if (path.isAbsolute(fileRef)) {\n return fileRef;\n }\n\n const normalizedRef = fileRef.replace(/\\\\/g, '/');\n const componentDirName = path.basename(componentDir).replace(/\\\\/g, '/');\n\n if (normalizedRef.startsWith(componentDirName + '/')) {\n const stripped = normalizedRef.slice(componentDirName.length + 1);\n return path.resolve(componentDir, stripped);\n }\n\n return path.resolve(componentDir, normalizedRef);\n}\n\nfunction viewKey(view: ViewDef): string {\n return `${view.windowName}.${view.pageName}`;\n}\n\nfunction toIdentifier(prefix: string, value: string): string {\n const core = value\n .replace(/[^a-zA-Z0-9]+/g, ' ')\n .trim()\n .split(/\\s+/)\n .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))\n .join('');\n\n return `${prefix}${core || 'Component'}`;\n}\n\nfunction escapeSingleQuoted(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n}\n\nconst CONTROLLER_FILE_SUFFIXES = [\n '.ts',\n '.js',\n '.controller.ts',\n '.controller.js',\n] as const;\n\nconst CONTROLLER_INDEX_CANDIDATES = [\n 'index.ts',\n 'index.js',\n] as const;\n\nfunction resolveControllerFile(controllerRef: string, componentDir: string): string | null {\n const normalized = controllerRef.trim();\n const basePath = resolveFromComponentDir(componentDir, normalized);\n\n if (existsSync(basePath)) {\n return basePath;\n }\n\n if (path.extname(basePath)) {\n return null;\n }\n\n for (const suffix of CONTROLLER_FILE_SUFFIXES) {\n const candidate = basePath + suffix;\n if (existsSync(candidate)) {\n return candidate;\n }\n }\n\n for (const indexFile of CONTROLLER_INDEX_CANDIDATES) {\n const candidate = path.resolve(basePath, indexFile);\n if (existsSync(candidate)) {\n return candidate;\n }\n }\n\n return null;\n}\n\nfunction resolveDefaultViewKey(sortedViews: ViewDef[], entryViewKey?: string): string {\n if (sortedViews.length === 0) return '';\n\n const configuredEntry = entryViewKey?.trim();\n if (!configuredEntry) {\n return viewKey(sortedViews[0]);\n }\n\n const availableKeys = sortedViews.map((view) => viewKey(view));\n if (availableKeys.includes(configuredEntry)) {\n return configuredEntry;\n }\n\n throw new Error(\n `Renderer entry \"${configuredEntry}\" was not found in parsed views. Available view keys: ${availableKeys.join(', ')}`,\n );\n}\n\nexport function generateRendererApp(\n views: ViewDef[],\n options: RendererAppGeneratorOptions,\n): string {\n const sortedViews = [...views].sort((a, b) => viewKey(a).localeCompare(viewKey(b)));\n const defaultViewKey = resolveDefaultViewKey(sortedViews, options.entryViewKey);\n\n const importLines: string[] = [];\n const viewIdentifiers = new Map<string, string>();\n const layoutIdentifiers = new Map<string, string>();\n const controllerIdentifiers = new Map<string, { identifier: string; importPath: string }>();\n const usedNames = new Set<string>();\n\n const allocateName = (prefix: string, filePath: string): string => {\n const base = toIdentifier(prefix, filePath);\n let name = base;\n let suffix = 2;\n while (usedNames.has(name)) {\n name = `${base}${suffix}`;\n suffix += 1;\n }\n usedNames.add(name);\n return name;\n };\n\n const ensureControllerImported = (controllerRef: string, owningViewKey: string): void => {\n if (controllerIdentifiers.has(controllerRef)) return;\n\n const resolvedControllerFile = resolveControllerFile(controllerRef, options.componentDir);\n if (!resolvedControllerFile) {\n throw new Error(\n `Renderer app codegen failed: controller \"${controllerRef}\" for view \"${owningViewKey}\" was not found. ` +\n 'Controller must point to an existing file with a default exported class.',\n );\n }\n\n const identifier = allocateName('PageController', controllerRef);\n const importPath = computeImportPath(options.outDir, resolvedControllerFile);\n controllerIdentifiers.set(controllerRef, { identifier, importPath });\n importLines.push(`import ${identifier} from '${importPath}';`);\n };\n\n for (const view of sortedViews) {\n if (!viewIdentifiers.has(view.viewPath)) {\n const identifier = allocateName('View', view.viewPath);\n viewIdentifiers.set(view.viewPath, identifier);\n const abs = resolveFromComponentDir(options.componentDir, view.viewPath);\n const importPath = computeImportPath(options.outDir, abs);\n importLines.push(`import ${identifier} from '${importPath}';`);\n }\n\n for (const layoutPath of view.layouts) {\n if (layoutIdentifiers.has(layoutPath)) continue;\n const identifier = allocateName('Layout', layoutPath);\n layoutIdentifiers.set(layoutPath, identifier);\n const abs = resolveFromComponentDir(options.componentDir, layoutPath);\n const importPath = computeImportPath(options.outDir, abs);\n importLines.push(`import ${identifier} from '${importPath}';`);\n }\n\n if (view.controller) {\n ensureControllerImported(view.controller, viewKey(view));\n }\n\n for (const layoutController of view.layoutControllers ?? []) {\n if (!layoutController) continue;\n ensureControllerImported(layoutController, viewKey(view));\n }\n }\n\n const manifestEntries = sortedViews.map((view) => {\n const key = viewKey(view);\n const viewIdentifier = viewIdentifiers.get(view.viewPath)!;\n const layoutList = view.layouts.map((layoutPath) => layoutIdentifiers.get(layoutPath)!);\n const controllerPath = view.controller ? `'${escapeSingleQuoted(view.controller)}'` : 'undefined';\n const controllerClass = view.controller\n ? controllerIdentifiers.get(view.controller)!.identifier\n : 'undefined';\n const layoutControllerRefs = view.layoutControllers;\n if (layoutControllerRefs && layoutControllerRefs.length !== view.layouts.length) {\n throw new Error(\n `Renderer app codegen failed: layout controller count for view \"${key}\" does not match layout count.`,\n );\n }\n const normalizedLayoutControllerRefs = layoutControllerRefs ?? Array.from(\n { length: view.layouts.length },\n () => undefined,\n );\n const layoutControllerPaths = `[${normalizedLayoutControllerRefs\n .map((controllerRef) => (controllerRef ? `'${escapeSingleQuoted(controllerRef)}'` : 'undefined'))\n .join(', ')}]`;\n const layoutControllerClasses = `[${normalizedLayoutControllerRefs\n .map((controllerRef) => (controllerRef ? controllerIdentifiers.get(controllerRef)!.identifier : 'undefined'))\n .join(', ')}]`;\n const layoutPreserve = view.layoutPreserve ?? Array.from(\n { length: view.layouts.length },\n () => false,\n );\n if (layoutPreserve.length !== view.layouts.length) {\n throw new Error(\n `Renderer app codegen failed: layout preserve count for view \"${key}\" does not match layout count.`,\n );\n }\n\n return ` '${escapeSingleQuoted(key)}': {\n key: '${escapeSingleQuoted(key)}',\n windowName: '${escapeSingleQuoted(view.windowName)}',\n pageName: '${escapeSingleQuoted(view.pageName)}',\n viewPath: '${escapeSingleQuoted(view.viewPath)}',\n layoutPaths: ${JSON.stringify(view.layouts)},\n layoutControllers: ${layoutControllerPaths},\n layoutPreserve: ${JSON.stringify(layoutPreserve)},\n controller: ${controllerPath},\n layoutControllerClasses: ${layoutControllerClasses},\n controllerClass: ${controllerClass},\n view: ${viewIdentifier},\n layouts: [${layoutList.join(', ')}],\n },`;\n });\n\n return `${generatedFileNotice(RENDERER_APP_SOURCE_INSTRUCTIONS)}\nimport { mount } from 'svelte';\nimport { App, getApp, getContainer } from '@noego/wood/client';\nimport { Navigation, getNavigation, installAutomationIntrospection } from '@noego/wood/navigation';\nimport { Ipc, getIpc } from '@noego/wood/ipc';\nimport NavigationShell from '@noego/wood/navigation-shell';\n${importLines.join('\\n')}\n\ninterface GeneratedAppViewEntry {\n key: string;\n windowName: string;\n pageName: string;\n viewPath: string;\n layoutPaths: string[];\n layoutControllers: Array<string | undefined>;\n layoutPreserve: boolean[];\n controller?: string;\n layoutControllerClasses: Array<any | undefined>;\n controllerClass?: any;\n view: any;\n layouts: any[];\n}\n\nconst VIEW_MANIFEST: Record<string, GeneratedAppViewEntry> = {\n${manifestEntries.join('\\n')}\n};\n\nfunction getViewManifestEntry(viewKey: string): GeneratedAppViewEntry | null {\n return VIEW_MANIFEST[viewKey] ?? null;\n}\n\ninterface WoodScopedContainer {\n get(token: any): unknown | Promise<unknown>;\n registerClass?(token: any): void;\n registerFunction?(\n token: any,\n resolver: (...args: any[]) => unknown,\n options?: Record<string, unknown>,\n ): void;\n}\n\ninterface WoodContainer extends WoodScopedContainer {\n extend?(): WoodScopedContainer;\n}\n\nfunction createScopedContainer(): WoodScopedContainer {\n const container = getContainer() as WoodContainer;\n if (typeof container.extend !== 'function') {\n throw new Error('Wood client container does not support extend().');\n }\n\n const scopedContainer = container.extend();\n\n if (!scopedContainer || typeof scopedContainer.get !== 'function') {\n throw new Error('Wood client container does not support get().');\n }\n\n // Renderer builtins belong to the root created by getContainer().\n // Page scopes inherit them; re-registering resolved ancestor singletons is invalid.\n\n return scopedContainer as WoodScopedContainer;\n}\n\nasync function resolveControllerInScope(\n scope: WoodScopedContainer,\n entry: GeneratedAppViewEntry,\n ControllerClass: any,\n contextLabel: string,\n): Promise<unknown> {\n if (typeof ControllerClass !== 'function') {\n throw new Error(\n \\`Controller for \\${contextLabel} in view \"\\${entry.key}\" is invalid. Expected a default exported class.\\`,\n );\n }\n\n if (typeof scope.registerClass === 'function') {\n scope.registerClass(ControllerClass);\n }\n\n return await scope.get(ControllerClass);\n}\n\nasync function disposeScopeQuietly(scope: { dispose?: () => Promise<void> } | null): Promise<void> {\n if (!scope || typeof scope.dispose !== 'function') return;\n try {\n await scope.dispose();\n } catch (error) {\n console.error('[wood] renderer scope disposal failed:', error);\n }\n}\n\n/**\n * One page scope per view lifecycle: the view controller and every\n * non-preservable layout controller resolve from the SAME scope, so Scoped\n * services/state are shared within the page (the NoEgo renderer model).\n * Layouts marked preservable in the manifest own dedicated scopes that\n * survive page transitions and dispose when the layout finally leaves.\n */\nasync function createControllerSet(\n entry: GeneratedAppViewEntry,\n preservedDepth = 0,\n): Promise<{\n controller: unknown;\n layoutControllers: Array<unknown>;\n pageScope: WoodScopedContainer;\n layoutScopes: Array<WoodScopedContainer | null>;\n}> {\n const pageScope = createScopedContainer();\n const layoutControllers: unknown[] = [];\n const layoutScopes: Array<WoodScopedContainer | null> = [];\n const createdLayoutScopes: WoodScopedContainer[] = [];\n\n try {\n for (let i = 0; i < preservedDepth; i += 1) {\n layoutControllers.push(undefined);\n layoutScopes.push(null);\n }\n\n for (let i = preservedDepth; i < entry.layoutControllerClasses.length; i += 1) {\n const LayoutController = entry.layoutControllerClasses[i];\n if (!LayoutController) {\n layoutControllers.push(undefined);\n layoutScopes.push(null);\n continue;\n }\n const preservable = entry.layoutPreserve?.[i] === true;\n let scope = pageScope;\n if (preservable) {\n scope = createScopedContainer();\n createdLayoutScopes.push(scope);\n }\n const controller = await resolveControllerInScope(\n scope,\n entry,\n LayoutController,\n \\`layout[\\${i}]\\`,\n );\n layoutControllers.push(controller);\n layoutScopes.push(preservable ? scope : null);\n }\n\n const controller = entry.controllerClass\n ? await resolveControllerInScope(pageScope, entry, entry.controllerClass, 'page')\n : undefined;\n\n return { controller, layoutControllers, pageScope, layoutScopes };\n } catch (error) {\n // Failed transitions must not orphan scopes: dispose everything this\n // set created before rethrowing.\n for (const scope of createdLayoutScopes) await disposeScopeQuietly(scope as any);\n await disposeScopeQuietly(pageScope as any);\n throw error;\n }\n}\n\nconst DEFAULT_VIEW_KEY = '${escapeSingleQuoted(defaultViewKey)}';\n\ntype WoodWindowBootstrapInfo = {\n ok?: boolean;\n windowId?: string;\n defaultRoute?: string;\n};\n\ntype WoodBootstrapBridgeLike = {\n __window?: {\n current?: () => Promise<WoodWindowBootstrapInfo>;\n };\n};\n\nfunction getWoodBridge(): WoodBootstrapBridgeLike | null {\n const globalObj = globalThis as typeof globalThis & {\n __NOEGO_WOOD_BRIDGE__?: unknown;\n window?: {\n __NOEGO_WOOD_BRIDGE__?: unknown;\n };\n };\n\n const bridge = globalObj.__NOEGO_WOOD_BRIDGE__ ?? globalObj.window?.__NOEGO_WOOD_BRIDGE__;\n if (!bridge || typeof bridge !== 'object') {\n return null;\n }\n\n return bridge as WoodBootstrapBridgeLike;\n}\n\nasync function resolveInitialEntry(): Promise<GeneratedAppViewEntry> {\n if (!DEFAULT_VIEW_KEY) {\n throw new Error('No views available in renderer manifest. Define at least one view in renderer OpenAPI YAML.');\n }\n\n const bridge = getWoodBridge();\n const bootstrapWindow = await bridge?.__window?.current?.();\n const requestedRoute = bootstrapWindow?.defaultRoute;\n\n if (requestedRoute) {\n const routeEntry = getViewManifestEntry(requestedRoute);\n if (routeEntry) {\n return routeEntry;\n }\n }\n\n const entry = getViewManifestEntry(DEFAULT_VIEW_KEY);\n if (!entry) {\n throw new Error(\\`Default view \"\\${DEFAULT_VIEW_KEY}\" is missing from generated view manifest.\\`);\n }\n\n return entry;\n}\n\nfunction isAutomationBuild(): boolean {\n try {\n const env = (import.meta as { env?: { DEV?: boolean; MODE?: string } }).env;\n return Boolean(env?.DEV) || env?.MODE === 'test';\n } catch {\n return false;\n }\n}\n\nexport async function app(target: Element) {\n const entry = await resolveInitialEntry();\n\n getNavigation().replace(entry.key, {}, {});\n\n // Dev/test-only Axe introspection surface; never present in production builds.\n if (isAutomationBuild()) {\n const bootstrapWindow = await getWoodBridge()?.__window?.current?.();\n installAutomationIntrospection({ windowId: bootstrapWindow?.windowId ?? null });\n }\n const initialSet = await createControllerSet(entry);\n\n return mount(NavigationShell, {\n target,\n props: {\n manifest: VIEW_MANIFEST,\n initialEntry: entry,\n initialControllerSet: initialSet,\n createControllerSetFn: createControllerSet,\n getViewManifestEntryFn: getViewManifestEntry,\n },\n });\n}\n`;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/codegen/renderer_app_generator.ts"],"sourcesContent":["import { existsSync } from 'fs';\nimport path from 'path';\nimport type { ViewDef } from '../types/index.js';\nimport { loaderCompanionCandidates } from '../loader/loader_orchestration.js';\nimport { generatedFileNotice } from './generated_file_notice.js';\n\nconst RENDERER_APP_SOURCE_INSTRUCTIONS = [\n 'To change routes, page components, layouts, or renderer controllers, edit the views YAML',\n 'configured by wood.config.yml renderer.openapi and the matching UI source files.',\n] as const;\n\nexport interface RendererAppGeneratorOptions {\n /** Absolute path to renderer component root (e.g. /app/ui) */\n componentDir: string;\n /** Absolute output directory where generated file will be written */\n outDir: string;\n /** Optional explicit default view key (e.g. \"main.chat\") */\n entryViewKey?: string;\n}\n\nfunction computeImportPath(fromDir: string, toFile: string): string {\n let rel = path.relative(fromDir, toFile);\n\n if (!rel.startsWith('.')) {\n rel = './' + rel;\n }\n\n return rel.replace(/\\\\/g, '/');\n}\n\nfunction resolveFromComponentDir(componentDir: string, fileRef: string): string {\n if (path.isAbsolute(fileRef)) {\n return fileRef;\n }\n\n const normalizedRef = fileRef.replace(/\\\\/g, '/');\n const componentDirName = path.basename(componentDir).replace(/\\\\/g, '/');\n\n if (normalizedRef.startsWith(componentDirName + '/')) {\n const stripped = normalizedRef.slice(componentDirName.length + 1);\n return path.resolve(componentDir, stripped);\n }\n\n return path.resolve(componentDir, normalizedRef);\n}\n\nfunction viewKey(view: ViewDef): string {\n return `${view.windowName}.${view.pageName}`;\n}\n\nfunction toIdentifier(prefix: string, value: string): string {\n const core = value\n .replace(/[^a-zA-Z0-9]+/g, ' ')\n .trim()\n .split(/\\s+/)\n .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))\n .join('');\n\n return `${prefix}${core || 'Component'}`;\n}\n\nfunction escapeSingleQuoted(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n}\n\nconst CONTROLLER_FILE_SUFFIXES = [\n '.ts',\n '.js',\n '.controller.ts',\n '.controller.js',\n] as const;\n\nconst CONTROLLER_INDEX_CANDIDATES = [\n 'index.ts',\n 'index.js',\n] as const;\n\nfunction resolveControllerFile(controllerRef: string, componentDir: string): string | null {\n const normalized = controllerRef.trim();\n const basePath = resolveFromComponentDir(componentDir, normalized);\n\n if (existsSync(basePath)) {\n return basePath;\n }\n\n if (path.extname(basePath)) {\n return null;\n }\n\n for (const suffix of CONTROLLER_FILE_SUFFIXES) {\n const candidate = basePath + suffix;\n if (existsSync(candidate)) {\n return candidate;\n }\n }\n\n for (const indexFile of CONTROLLER_INDEX_CANDIDATES) {\n const candidate = path.resolve(basePath, indexFile);\n if (existsSync(candidate)) {\n return candidate;\n }\n }\n\n return null;\n}\n\n/**\n * The companion data loader of a view/layout (`pages/chat.svelte` →\n * `pages/chat.load.ts`), discovered at build. None is fine (`null`); more\n * than one companion is ambiguous and fails the build.\n */\nfunction resolveLoaderFile(viewPath: string, componentDir: string): string | null {\n const present = loaderCompanionCandidates(viewPath)\n .map((candidate) => resolveFromComponentDir(componentDir, candidate))\n .filter((candidate) => existsSync(candidate));\n if (present.length > 1) {\n throw new Error(\n `Renderer app codegen failed: ambiguous data loader for \"${viewPath}\": ${present.join(', ')}. ` +\n 'Keep exactly one .load companion.',\n );\n }\n return present[0] ?? null;\n}\n\nfunction resolveDefaultViewKey(sortedViews: ViewDef[], entryViewKey?: string): string {\n if (sortedViews.length === 0) return '';\n\n const configuredEntry = entryViewKey?.trim();\n if (!configuredEntry) {\n return viewKey(sortedViews[0]);\n }\n\n const availableKeys = sortedViews.map((view) => viewKey(view));\n if (availableKeys.includes(configuredEntry)) {\n return configuredEntry;\n }\n\n throw new Error(\n `Renderer entry \"${configuredEntry}\" was not found in parsed views. Available view keys: ${availableKeys.join(', ')}`,\n );\n}\n\nexport function generateRendererApp(\n views: ViewDef[],\n options: RendererAppGeneratorOptions,\n): string {\n const sortedViews = [...views].sort((a, b) => viewKey(a).localeCompare(viewKey(b)));\n const defaultViewKey = resolveDefaultViewKey(sortedViews, options.entryViewKey);\n\n const importLines: string[] = [];\n const viewIdentifiers = new Map<string, string>();\n const layoutIdentifiers = new Map<string, string>();\n const controllerIdentifiers = new Map<string, { identifier: string; importPath: string }>();\n /** view/layout path → generated loader identifier (only when a companion exists). */\n const loaderIdentifiers = new Map<string, string>();\n const usedNames = new Set<string>();\n\n const allocateName = (prefix: string, filePath: string): string => {\n const base = toIdentifier(prefix, filePath);\n let name = base;\n let suffix = 2;\n while (usedNames.has(name)) {\n name = `${base}${suffix}`;\n suffix += 1;\n }\n usedNames.add(name);\n return name;\n };\n\n const ensureControllerImported = (controllerRef: string, owningViewKey: string): void => {\n if (controllerIdentifiers.has(controllerRef)) return;\n\n const resolvedControllerFile = resolveControllerFile(controllerRef, options.componentDir);\n if (!resolvedControllerFile) {\n throw new Error(\n `Renderer app codegen failed: controller \"${controllerRef}\" for view \"${owningViewKey}\" was not found. ` +\n 'Controller must point to an existing file with a default exported class.',\n );\n }\n\n const identifier = allocateName('PageController', controllerRef);\n const importPath = computeImportPath(options.outDir, resolvedControllerFile);\n controllerIdentifiers.set(controllerRef, { identifier, importPath });\n importLines.push(`import ${identifier} from '${importPath}';`);\n };\n\n // Build discovers and IMPORTS companion loaders; none runs until a page\n // opens (createControllerSet executes them lazily per navigation).\n const ensureLoaderImported = (viewPath: string): void => {\n if (loaderIdentifiers.has(viewPath)) return;\n const loaderFile = resolveLoaderFile(viewPath, options.componentDir);\n if (!loaderFile) return;\n const identifier = allocateName('Loader', viewPath);\n loaderIdentifiers.set(viewPath, identifier);\n importLines.push(`import ${identifier} from '${computeImportPath(options.outDir, loaderFile)}';`);\n };\n\n for (const view of sortedViews) {\n if (!viewIdentifiers.has(view.viewPath)) {\n const identifier = allocateName('View', view.viewPath);\n viewIdentifiers.set(view.viewPath, identifier);\n const abs = resolveFromComponentDir(options.componentDir, view.viewPath);\n const importPath = computeImportPath(options.outDir, abs);\n importLines.push(`import ${identifier} from '${importPath}';`);\n }\n ensureLoaderImported(view.viewPath);\n\n for (const layoutPath of view.layouts) {\n ensureLoaderImported(layoutPath);\n if (layoutIdentifiers.has(layoutPath)) continue;\n const identifier = allocateName('Layout', layoutPath);\n layoutIdentifiers.set(layoutPath, identifier);\n const abs = resolveFromComponentDir(options.componentDir, layoutPath);\n const importPath = computeImportPath(options.outDir, abs);\n importLines.push(`import ${identifier} from '${importPath}';`);\n }\n\n if (view.controller) {\n ensureControllerImported(view.controller, viewKey(view));\n }\n\n for (const layoutController of view.layoutControllers ?? []) {\n if (!layoutController) continue;\n ensureControllerImported(layoutController, viewKey(view));\n }\n }\n\n const manifestEntries = sortedViews.map((view) => {\n const key = viewKey(view);\n const viewIdentifier = viewIdentifiers.get(view.viewPath)!;\n const layoutList = view.layouts.map((layoutPath) => layoutIdentifiers.get(layoutPath)!);\n const layoutLoaders = view.layouts.map((layoutPath) => loaderIdentifiers.get(layoutPath) ?? 'null');\n const viewLoader = loaderIdentifiers.get(view.viewPath) ?? 'null';\n const controllerPath = view.controller ? `'${escapeSingleQuoted(view.controller)}'` : 'undefined';\n const controllerClass = view.controller\n ? controllerIdentifiers.get(view.controller)!.identifier\n : 'undefined';\n const layoutControllerRefs = view.layoutControllers;\n if (layoutControllerRefs && layoutControllerRefs.length !== view.layouts.length) {\n throw new Error(\n `Renderer app codegen failed: layout controller count for view \"${key}\" does not match layout count.`,\n );\n }\n const normalizedLayoutControllerRefs = layoutControllerRefs ?? Array.from(\n { length: view.layouts.length },\n () => undefined,\n );\n const layoutControllerPaths = `[${normalizedLayoutControllerRefs\n .map((controllerRef) => (controllerRef ? `'${escapeSingleQuoted(controllerRef)}'` : 'undefined'))\n .join(', ')}]`;\n const layoutControllerClasses = `[${normalizedLayoutControllerRefs\n .map((controllerRef) => (controllerRef ? controllerIdentifiers.get(controllerRef)!.identifier : 'undefined'))\n .join(', ')}]`;\n const layoutPreserve = view.layoutPreserve ?? Array.from(\n { length: view.layouts.length },\n () => false,\n );\n if (layoutPreserve.length !== view.layouts.length) {\n throw new Error(\n `Renderer app codegen failed: layout preserve count for view \"${key}\" does not match layout count.`,\n );\n }\n\n return ` '${escapeSingleQuoted(key)}': {\n key: '${escapeSingleQuoted(key)}',\n windowName: '${escapeSingleQuoted(view.windowName)}',\n pageName: '${escapeSingleQuoted(view.pageName)}',\n viewPath: '${escapeSingleQuoted(view.viewPath)}',\n layoutPaths: ${JSON.stringify(view.layouts)},\n layoutControllers: ${layoutControllerPaths},\n layoutPreserve: ${JSON.stringify(layoutPreserve)},\n controller: ${controllerPath},\n layoutControllerClasses: ${layoutControllerClasses},\n controllerClass: ${controllerClass},\n view: ${viewIdentifier},\n layouts: [${layoutList.join(', ')}],\n viewLoader: ${viewLoader},\n layoutLoaders: [${layoutLoaders.join(', ')}],\n },`;\n });\n\n return `${generatedFileNotice(RENDERER_APP_SOURCE_INSTRUCTIONS)}\nimport { mount } from 'svelte';\nimport { App, getApp, getContainer } from '@noego/wood/client';\nimport { Navigation, getNavigation, installAutomationIntrospection } from '@noego/wood/navigation';\nimport { createLoaderRequest, initializeArgument, initializeController, runViewLoaders } from '@noego/wood/navigation';\nimport { Ipc, getIpc } from '@noego/wood/ipc';\nimport NavigationShell from '@noego/wood/navigation-shell';\n${importLines.join('\\n')}\n\ninterface GeneratedAppViewEntry {\n key: string;\n windowName: string;\n pageName: string;\n viewPath: string;\n layoutPaths: string[];\n layoutControllers: Array<string | undefined>;\n layoutPreserve: boolean[];\n controller?: string;\n layoutControllerClasses: Array<any | undefined>;\n controllerClass?: any;\n view: any;\n layouts: any[];\n /** Companion data loader of the view (\\`pages/x.svelte\\` → \\`pages/x.load.ts\\`), discovered at build. */\n viewLoader: ((request: any) => unknown) | null;\n /** Index-aligned companion loaders of the layout stack. */\n layoutLoaders: Array<((request: any) => unknown) | null>;\n}\n\ntype RouteInput = { params: Record<string, string>; query: Record<string, string> };\n\nconst VIEW_MANIFEST: Record<string, GeneratedAppViewEntry> = {\n${manifestEntries.join('\\n')}\n};\n\nfunction getViewManifestEntry(viewKey: string): GeneratedAppViewEntry | null {\n return VIEW_MANIFEST[viewKey] ?? null;\n}\n\ninterface WoodScopedContainer {\n get(token: any): unknown | Promise<unknown>;\n registerClass?(token: any): void;\n registerFunction?(\n token: any,\n resolver: (...args: any[]) => unknown,\n options?: Record<string, unknown>,\n ): void;\n}\n\ninterface WoodContainer extends WoodScopedContainer {\n extend?(): WoodScopedContainer;\n}\n\nfunction createScopedContainer(): WoodScopedContainer {\n const container = getContainer() as WoodContainer;\n if (typeof container.extend !== 'function') {\n throw new Error('Wood client container does not support extend().');\n }\n\n const scopedContainer = container.extend();\n\n if (!scopedContainer || typeof scopedContainer.get !== 'function') {\n throw new Error('Wood client container does not support get().');\n }\n\n // Renderer builtins belong to the root created by getContainer().\n // Page scopes inherit them; re-registering resolved ancestor singletons is invalid.\n\n return scopedContainer as WoodScopedContainer;\n}\n\nasync function resolveControllerInScope(\n scope: WoodScopedContainer,\n entry: GeneratedAppViewEntry,\n ControllerClass: any,\n contextLabel: string,\n): Promise<unknown> {\n if (typeof ControllerClass !== 'function') {\n throw new Error(\n \\`Controller for \\${contextLabel} in view \"\\${entry.key}\" is invalid. Expected a default exported class.\\`,\n );\n }\n\n if (typeof scope.registerClass === 'function') {\n scope.registerClass(ControllerClass);\n }\n\n return await scope.get(ControllerClass);\n}\n\ntype RollbackStep = () => unknown | Promise<unknown>;\n\nasync function disposeScope(scope: { dispose?: () => Promise<void> } | null): Promise<void> {\n if (!scope || typeof scope.dispose !== 'function') return;\n await scope.dispose();\n}\n\n/**\n * Roll back after \\`primary\\` failed: every step is attempted exactly once and\n * every cleanup failure is retained as its original error object next to the\n * primary failure (\\`errors\\`, with \\`cause\\` = primary). Never swallows.\n */\nasync function failWithRollback(primary: unknown, steps: RollbackStep[], message: string): Promise<never> {\n const errors: unknown[] = [primary];\n for (const step of steps) {\n try { await step(); } catch (error) { errors.push(error); }\n }\n if (errors.length === 1) throw primary;\n throw Object.assign(new Error(message), { errors, cause: primary });\n}\n\n/**\n * One page scope per view lifecycle: the view controller and every\n * non-preservable layout controller resolve from the SAME scope, so Scoped\n * services/state are shared within the page (the NoEgo renderer model).\n * Layouts marked preservable in the manifest own dedicated scopes that\n * survive page transitions and dispose when the layout finally leaves.\n *\n * Data loaders (shared orchestration) run first for this navigation —\n * non-preserved layouts in stack order, then the view — and each\n * controller's optional \\`initialize({ params, query[, data] })\\` receives\n * its loader's data. A loader failure disposes the scopes created here.\n */\nasync function createControllerSet(\n entry: GeneratedAppViewEntry,\n preservedDepth = 0,\n route: RouteInput = { params: {}, query: {} },\n): Promise<{\n controller: unknown;\n layoutControllers: Array<unknown>;\n pageScope: WoodScopedContainer;\n layoutScopes: Array<WoodScopedContainer | null>;\n}> {\n const pageScope = createScopedContainer();\n const layoutControllers: unknown[] = [];\n const layoutScopes: Array<WoodScopedContainer | null> = [];\n const createdLayoutScopes: WoodScopedContainer[] = [];\n\n try {\n const loaded = await runViewLoaders(\n { layouts: entry.layoutLoaders, view: entry.viewLoader },\n createLoaderRequest(entry.key, route),\n preservedDepth,\n );\n\n for (let i = 0; i < preservedDepth; i += 1) {\n layoutControllers.push(undefined);\n layoutScopes.push(null);\n }\n\n for (let i = preservedDepth; i < entry.layoutControllerClasses.length; i += 1) {\n const LayoutController = entry.layoutControllerClasses[i];\n if (!LayoutController) {\n layoutControllers.push(undefined);\n layoutScopes.push(null);\n continue;\n }\n const preservable = entry.layoutPreserve?.[i] === true;\n let scope = pageScope;\n if (preservable) {\n scope = createScopedContainer();\n createdLayoutScopes.push(scope);\n }\n const controller = await resolveControllerInScope(\n scope,\n entry,\n LayoutController,\n \\`layout[\\${i}]\\`,\n );\n await initializeController(controller, initializeArgument(route, loaded.layouts[i] ?? null));\n layoutControllers.push(controller);\n layoutScopes.push(preservable ? scope : null);\n }\n\n const controller = entry.controllerClass\n ? await resolveControllerInScope(pageScope, entry, entry.controllerClass, 'page')\n : undefined;\n if (controller !== undefined) {\n await initializeController(controller, initializeArgument(route, loaded.view));\n }\n\n return { controller, layoutControllers, pageScope, layoutScopes };\n } catch (error) {\n // Failed transitions must not orphan scopes: every scope this set created\n // is disposed once, and every disposal failure is retained with the\n // original initialization failure instead of being swallowed.\n return failWithRollback(error, [\n ...createdLayoutScopes.map((scope) => () => disposeScope(scope as any)),\n () => disposeScope(pageScope as any),\n ], 'Wood controller-set rollback failed');\n }\n}\n\nconst DEFAULT_VIEW_KEY = '${escapeSingleQuoted(defaultViewKey)}';\n\ntype WoodWindowBootstrapInfo = {\n ok?: boolean;\n windowId?: string;\n defaultRoute?: string;\n};\n\ntype WoodBootstrapBridgeLike = {\n __window?: {\n current?: () => Promise<WoodWindowBootstrapInfo>;\n };\n};\n\nfunction getWoodBridge(): WoodBootstrapBridgeLike | null {\n const globalObj = globalThis as typeof globalThis & {\n __NOEGO_WOOD_BRIDGE__?: unknown;\n window?: {\n __NOEGO_WOOD_BRIDGE__?: unknown;\n };\n };\n\n const bridge = globalObj.__NOEGO_WOOD_BRIDGE__ ?? globalObj.window?.__NOEGO_WOOD_BRIDGE__;\n if (!bridge || typeof bridge !== 'object') {\n return null;\n }\n\n return bridge as WoodBootstrapBridgeLike;\n}\n\nasync function resolveInitialEntry(): Promise<GeneratedAppViewEntry> {\n if (!DEFAULT_VIEW_KEY) {\n throw new Error('No views available in renderer manifest. Define at least one view in renderer OpenAPI YAML.');\n }\n\n const bridge = getWoodBridge();\n const bootstrapWindow = await bridge?.__window?.current?.();\n const requestedRoute = bootstrapWindow?.defaultRoute;\n\n if (requestedRoute) {\n const routeEntry = getViewManifestEntry(requestedRoute);\n if (routeEntry) {\n return routeEntry;\n }\n }\n\n const entry = getViewManifestEntry(DEFAULT_VIEW_KEY);\n if (!entry) {\n throw new Error(\\`Default view \"\\${DEFAULT_VIEW_KEY}\" is missing from generated view manifest.\\`);\n }\n\n return entry;\n}\n\nfunction isAutomationBuild(): boolean {\n try {\n const env = (import.meta as { env?: { DEV?: boolean; MODE?: string } }).env;\n return Boolean(env?.DEV) || env?.MODE === 'test';\n } catch {\n return false;\n }\n}\n\nexport const supportsInitialRoute = true;\nexport async function app(target: Element, route: RouteInput = { params: {}, query: {} }) {\n const entry = await resolveInitialEntry();\n\n getNavigation().replace(entry.key, route.params, route.query);\n\n // Dev/test-only Axe introspection surface; never present in production builds.\n if (isAutomationBuild()) {\n const bootstrapWindow = await getWoodBridge()?.__window?.current?.();\n installAutomationIntrospection({ windowId: bootstrapWindow?.windowId ?? null });\n }\n const initialSet = await createControllerSet(entry, 0, route);\n\n try {\n return mount(NavigationShell, {\n target,\n props: {\n manifest: VIEW_MANIFEST,\n initialEntry: entry,\n initialRoute: route,\n initialControllerSet: initialSet,\n createControllerSetFn: createControllerSet,\n getViewManifestEntryFn: getViewManifestEntry,\n },\n });\n } catch (error) {\n // A failed initial mount must not orphan the initial controller set: the\n // shell never took ownership, so its scopes are disposed here once.\n return failWithRollback(error, [\n ...initialSet.layoutScopes.map((scope) => () => disposeScope(scope as any)),\n () => disposeScope(initialSet.pageScope as any),\n ], 'Wood initial mount rollback failed');\n }\n}\n`;\n}\n"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4BAiKA,mBAAmB,cAAc,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkG9D;","names":[]}
|
|
@@ -181,6 +181,15 @@ export interface GeneratedWoodBridge {
|
|
|
181
181
|
${typesCode}
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Project-generated typing for testWood's object-shaped operation API.
|
|
186
|
+
* The runtime surface is built from the selected operation aperture and still
|
|
187
|
+
* dispatches through the real WoodRouter.
|
|
188
|
+
*/
|
|
189
|
+
declare module '@noego/wood/testing-api' {
|
|
190
|
+
interface WoodTestApi extends GeneratedWoodBridge {}
|
|
191
|
+
}
|
|
192
|
+
|
|
184
193
|
export interface WoodApi extends GeneratedWoodBridge {
|
|
185
194
|
__rpcManifest: WoodRpcManifestEntry[];
|
|
186
195
|
__load(route: string, params: Record<string, string>): Promise<unknown>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/codegen/type_generator.ts"],"sourcesContent":["import type { OperationDef } from '../types/index.cjs';\nimport { generatedFileNotice } from './generated_file_notice.cjs';\n\nconst TYPE_SOURCE_INSTRUCTIONS = [\n 'To change IPC channels, input schemas, controller/action names, or renderer bridge types,',\n 'edit the operations YAML configured by wood.config.yml main.openapi and the matching controller methods.',\n] as const;\n\nfunction formatPropertyKey(key: string): string {\n if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {\n return key;\n }\n\n return JSON.stringify(key);\n}\n\nfunction operationHasInput(op: OperationDef): boolean {\n return Boolean(\n op.inputSchema &&\n (op.inputSchema as Record<string, unknown>).type === 'object' &&\n Object.keys((op.inputSchema as Record<string, Record<string, unknown>>).properties ?? {}).length > 0,\n );\n}\n\nfunction jsonSchemaToTS(schema: Record<string, unknown> | undefined, indent: string = ''): string {\n if (!schema) return 'unknown';\n\n const type = schema.type as string | undefined;\n\n if (type === 'string') {\n if (schema.enum) {\n return (schema.enum as string[]).map((v) => `'${v}'`).join(' | ');\n }\n return 'string';\n }\n if (type === 'number' || type === 'integer') return 'number';\n if (type === 'boolean') return 'boolean';\n if (type === 'array') {\n const itemType = jsonSchemaToTS(schema.items as Record<string, unknown>, indent);\n return `Array<${itemType}>`;\n }\n if (type === 'object') {\n const props = (schema.properties ?? {}) as Record<string, Record<string, unknown>>;\n const required = new Set((schema.required as string[]) ?? []);\n const entries = Object.entries(props);\n if (entries.length === 0) return 'Record<string, unknown>';\n\n const lines = entries.map(([key, val]) => {\n const optional = required.has(key) ? '' : '?';\n const valType = jsonSchemaToTS(val, indent + ' ');\n return `${indent} ${formatPropertyKey(key)}${optional}: ${valType};`;\n });\n return `{\\n${lines.join('\\n')}\\n${indent}}`;\n }\n\n return 'unknown';\n}\n\nfunction buildNestedTypeTree(operations: Map<string, OperationDef>): Record<string, unknown> {\n const tree: Record<string, unknown> = {};\n for (const [channel, op] of operations) {\n const parts = channel.split('.');\n let current: Record<string, unknown> = tree;\n for (let i = 0; i < parts.length - 1; i++) {\n if (!current[parts[i]]) current[parts[i]] = {};\n current = current[parts[i]] as Record<string, unknown>;\n }\n current[parts[parts.length - 1]] = { __operation: op };\n }\n return tree;\n}\n\nfunction generateTypeTree(node: Record<string, unknown>, indent: string = ' '): string {\n const lines: string[] = [];\n for (const [key, value] of Object.entries(node)) {\n const entry = value as Record<string, unknown>;\n if (entry.__operation) {\n const op = entry.__operation as OperationDef;\n const inputType = jsonSchemaToTS(op.inputSchema as Record<string, unknown>, indent + ' ');\n const outputType = jsonSchemaToTS(op.outputSchema as Record<string, unknown>, indent + ' ');\n const hasInput = operationHasInput(op);\n\n if (hasInput) {\n lines.push(`${indent}${formatPropertyKey(key)}(data: ${inputType}): Promise<${outputType}>;`);\n } else {\n lines.push(`${indent}${formatPropertyKey(key)}(): Promise<${outputType}>;`);\n }\n } else {\n lines.push(`${indent}${formatPropertyKey(key)}: {`);\n lines.push(generateTypeTree(entry, indent + ' '));\n lines.push(`${indent}};`);\n }\n }\n return lines.join('\\n');\n}\n\ntype RpcActionTypeEntry = {\n channel: string;\n inputType: string;\n outputType: string;\n hasInput: boolean;\n};\n\nfunction buildRpcControllerTree(\n operations: Map<string, OperationDef>,\n): Record<string, Record<string, RpcActionTypeEntry>> {\n const tree: Record<string, Record<string, RpcActionTypeEntry>> = {};\n\n for (const operation of operations.values()) {\n const controller = operation.controller;\n const action = operation.action;\n\n if (!tree[controller]) {\n tree[controller] = {};\n }\n\n if (tree[controller][action]) {\n throw new Error(\n `Type codegen failed: duplicate RPC action \"${controller}.${action}\" found in channel \"${operation.channel}\".`,\n );\n }\n\n tree[controller][action] = {\n channel: operation.channel,\n inputType: jsonSchemaToTS(operation.inputSchema as Record<string, unknown>, ' '),\n outputType: jsonSchemaToTS(operation.outputSchema as Record<string, unknown>, ' '),\n hasInput: operationHasInput(operation),\n };\n }\n\n return tree;\n}\n\nfunction generateRpcTypeTree(operations: Map<string, OperationDef>): string {\n const controllerTree = buildRpcControllerTree(operations);\n const controllerNames = Object.keys(controllerTree).sort((a, b) => a.localeCompare(b));\n const lines: string[] = [];\n\n for (const controllerName of controllerNames) {\n lines.push(` ${formatPropertyKey(controllerName)}: {`);\n const actions = controllerTree[controllerName];\n const actionNames = Object.keys(actions).sort((a, b) => a.localeCompare(b));\n for (const actionName of actionNames) {\n const action = actions[actionName];\n if (action.hasInput) {\n lines.push(` ${formatPropertyKey(actionName)}(data: ${action.inputType}): Promise<${action.outputType}>;`);\n } else {\n lines.push(` ${formatPropertyKey(actionName)}(): Promise<${action.outputType}>;`);\n }\n }\n lines.push(' };');\n }\n\n return lines.join('\\n');\n}\n\nexport function generateTypes(operations: Map<string, OperationDef>): string {\n const tree = buildNestedTypeTree(operations);\n const typesCode = generateTypeTree(tree);\n const rpcTypeCode = generateRpcTypeTree(operations);\n\n return `${generatedFileNotice(TYPE_SOURCE_INSTRUCTIONS)}\nexport interface WoodRpcManifestEntry {\n channel: string;\n controller: string;\n action: string;\n path: string[];\n hasInput: boolean;\n}\n\n/**\n * Project-specific renderer App facade contract.\n *\n * This matches @noego/wood/client getApp() and injected App instances:\n * controller name -> controller action.\n */\nexport interface GeneratedApp {\n${rpcTypeCode}\n}\n\n/** Backward-compatible name for the generated client App facade contract. */\nexport interface WoodRpcApi extends GeneratedApp {}\n\n/**\n * Project-specific raw preload bridge contract.\n *\n * This matches the channel path exposed by preload.generated.ts:\n * operation channel segments -> invoke function.\n */\nexport interface GeneratedWoodBridge {\n${typesCode}\n}\n\nexport interface WoodApi extends GeneratedWoodBridge {\n __rpcManifest: WoodRpcManifestEntry[];\n __load(route: string, params: Record<string, string>): Promise<unknown>;\n __navigate(windowId: string, route: string): Promise<void>;\n __window: {\n current(): Promise<{ ok: boolean; windowId?: string; defaultRoute?: string }>;\n open(windowId: string, options?: Record<string, unknown>): Promise<void>;\n close(windowId?: string): Promise<void>;\n focus(windowId: string): Promise<void>;\n minimize(windowId?: string): Promise<void>;\n maximize(windowId?: string): Promise<void>;\n };\n __debug: {\n current(): Promise<{\n ok: boolean;\n enabled: boolean;\n mode: 'development' | 'production';\n port?: number;\n windows: Array<{\n windowId: string;\n title: string;\n webContentsId: number;\n url: string;\n focused: boolean;\n rendererHealth?: 'ready' | 'loading' | 'unresponsive' | 'gone' | 'destroyed';\n }>;\n }>;\n };\n __contextMenu: {\n show(data: {\n items: Array<{ label?: string; action?: string; data?: string; _callbackId?: string; disabled?: boolean; type?: 'separator' }>;\n position: { x: number; y: number };\n }): Promise<void>;\n };\n on(channel: string, callback: (...args: unknown[]) => void): () => void;\n __log(level: string, logger: string, message: string, context?: unknown): Promise<{\n ok: boolean;\n forwarded: boolean;\n configured: boolean;\n reason?: 'unconfigured' | 'callback-error';\n errorMessage?: string;\n }>;\n __logBatch(batch: unknown): void;\n}\n`;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/codegen/type_generator.ts"],"sourcesContent":["import type { OperationDef } from '../types/index.cjs';\nimport { generatedFileNotice } from './generated_file_notice.cjs';\n\nconst TYPE_SOURCE_INSTRUCTIONS = [\n 'To change IPC channels, input schemas, controller/action names, or renderer bridge types,',\n 'edit the operations YAML configured by wood.config.yml main.openapi and the matching controller methods.',\n] as const;\n\nfunction formatPropertyKey(key: string): string {\n if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {\n return key;\n }\n\n return JSON.stringify(key);\n}\n\nfunction operationHasInput(op: OperationDef): boolean {\n return Boolean(\n op.inputSchema &&\n (op.inputSchema as Record<string, unknown>).type === 'object' &&\n Object.keys((op.inputSchema as Record<string, Record<string, unknown>>).properties ?? {}).length > 0,\n );\n}\n\nfunction jsonSchemaToTS(schema: Record<string, unknown> | undefined, indent: string = ''): string {\n if (!schema) return 'unknown';\n\n const type = schema.type as string | undefined;\n\n if (type === 'string') {\n if (schema.enum) {\n return (schema.enum as string[]).map((v) => `'${v}'`).join(' | ');\n }\n return 'string';\n }\n if (type === 'number' || type === 'integer') return 'number';\n if (type === 'boolean') return 'boolean';\n if (type === 'array') {\n const itemType = jsonSchemaToTS(schema.items as Record<string, unknown>, indent);\n return `Array<${itemType}>`;\n }\n if (type === 'object') {\n const props = (schema.properties ?? {}) as Record<string, Record<string, unknown>>;\n const required = new Set((schema.required as string[]) ?? []);\n const entries = Object.entries(props);\n if (entries.length === 0) return 'Record<string, unknown>';\n\n const lines = entries.map(([key, val]) => {\n const optional = required.has(key) ? '' : '?';\n const valType = jsonSchemaToTS(val, indent + ' ');\n return `${indent} ${formatPropertyKey(key)}${optional}: ${valType};`;\n });\n return `{\\n${lines.join('\\n')}\\n${indent}}`;\n }\n\n return 'unknown';\n}\n\nfunction buildNestedTypeTree(operations: Map<string, OperationDef>): Record<string, unknown> {\n const tree: Record<string, unknown> = {};\n for (const [channel, op] of operations) {\n const parts = channel.split('.');\n let current: Record<string, unknown> = tree;\n for (let i = 0; i < parts.length - 1; i++) {\n if (!current[parts[i]]) current[parts[i]] = {};\n current = current[parts[i]] as Record<string, unknown>;\n }\n current[parts[parts.length - 1]] = { __operation: op };\n }\n return tree;\n}\n\nfunction generateTypeTree(node: Record<string, unknown>, indent: string = ' '): string {\n const lines: string[] = [];\n for (const [key, value] of Object.entries(node)) {\n const entry = value as Record<string, unknown>;\n if (entry.__operation) {\n const op = entry.__operation as OperationDef;\n const inputType = jsonSchemaToTS(op.inputSchema as Record<string, unknown>, indent + ' ');\n const outputType = jsonSchemaToTS(op.outputSchema as Record<string, unknown>, indent + ' ');\n const hasInput = operationHasInput(op);\n\n if (hasInput) {\n lines.push(`${indent}${formatPropertyKey(key)}(data: ${inputType}): Promise<${outputType}>;`);\n } else {\n lines.push(`${indent}${formatPropertyKey(key)}(): Promise<${outputType}>;`);\n }\n } else {\n lines.push(`${indent}${formatPropertyKey(key)}: {`);\n lines.push(generateTypeTree(entry, indent + ' '));\n lines.push(`${indent}};`);\n }\n }\n return lines.join('\\n');\n}\n\ntype RpcActionTypeEntry = {\n channel: string;\n inputType: string;\n outputType: string;\n hasInput: boolean;\n};\n\nfunction buildRpcControllerTree(\n operations: Map<string, OperationDef>,\n): Record<string, Record<string, RpcActionTypeEntry>> {\n const tree: Record<string, Record<string, RpcActionTypeEntry>> = {};\n\n for (const operation of operations.values()) {\n const controller = operation.controller;\n const action = operation.action;\n\n if (!tree[controller]) {\n tree[controller] = {};\n }\n\n if (tree[controller][action]) {\n throw new Error(\n `Type codegen failed: duplicate RPC action \"${controller}.${action}\" found in channel \"${operation.channel}\".`,\n );\n }\n\n tree[controller][action] = {\n channel: operation.channel,\n inputType: jsonSchemaToTS(operation.inputSchema as Record<string, unknown>, ' '),\n outputType: jsonSchemaToTS(operation.outputSchema as Record<string, unknown>, ' '),\n hasInput: operationHasInput(operation),\n };\n }\n\n return tree;\n}\n\nfunction generateRpcTypeTree(operations: Map<string, OperationDef>): string {\n const controllerTree = buildRpcControllerTree(operations);\n const controllerNames = Object.keys(controllerTree).sort((a, b) => a.localeCompare(b));\n const lines: string[] = [];\n\n for (const controllerName of controllerNames) {\n lines.push(` ${formatPropertyKey(controllerName)}: {`);\n const actions = controllerTree[controllerName];\n const actionNames = Object.keys(actions).sort((a, b) => a.localeCompare(b));\n for (const actionName of actionNames) {\n const action = actions[actionName];\n if (action.hasInput) {\n lines.push(` ${formatPropertyKey(actionName)}(data: ${action.inputType}): Promise<${action.outputType}>;`);\n } else {\n lines.push(` ${formatPropertyKey(actionName)}(): Promise<${action.outputType}>;`);\n }\n }\n lines.push(' };');\n }\n\n return lines.join('\\n');\n}\n\nexport function generateTypes(operations: Map<string, OperationDef>): string {\n const tree = buildNestedTypeTree(operations);\n const typesCode = generateTypeTree(tree);\n const rpcTypeCode = generateRpcTypeTree(operations);\n\n return `${generatedFileNotice(TYPE_SOURCE_INSTRUCTIONS)}\nexport interface WoodRpcManifestEntry {\n channel: string;\n controller: string;\n action: string;\n path: string[];\n hasInput: boolean;\n}\n\n/**\n * Project-specific renderer App facade contract.\n *\n * This matches @noego/wood/client getApp() and injected App instances:\n * controller name -> controller action.\n */\nexport interface GeneratedApp {\n${rpcTypeCode}\n}\n\n/** Backward-compatible name for the generated client App facade contract. */\nexport interface WoodRpcApi extends GeneratedApp {}\n\n/**\n * Project-specific raw preload bridge contract.\n *\n * This matches the channel path exposed by preload.generated.ts:\n * operation channel segments -> invoke function.\n */\nexport interface GeneratedWoodBridge {\n${typesCode}\n}\n\n/**\n * Project-generated typing for testWood's object-shaped operation API.\n * The runtime surface is built from the selected operation aperture and still\n * dispatches through the real WoodRouter.\n */\ndeclare module '@noego/wood/testing-api' {\n interface WoodTestApi extends GeneratedWoodBridge {}\n}\n\nexport interface WoodApi extends GeneratedWoodBridge {\n __rpcManifest: WoodRpcManifestEntry[];\n __load(route: string, params: Record<string, string>): Promise<unknown>;\n __navigate(windowId: string, route: string): Promise<void>;\n __window: {\n current(): Promise<{ ok: boolean; windowId?: string; defaultRoute?: string }>;\n open(windowId: string, options?: Record<string, unknown>): Promise<void>;\n close(windowId?: string): Promise<void>;\n focus(windowId: string): Promise<void>;\n minimize(windowId?: string): Promise<void>;\n maximize(windowId?: string): Promise<void>;\n };\n __debug: {\n current(): Promise<{\n ok: boolean;\n enabled: boolean;\n mode: 'development' | 'production';\n port?: number;\n windows: Array<{\n windowId: string;\n title: string;\n webContentsId: number;\n url: string;\n focused: boolean;\n rendererHealth?: 'ready' | 'loading' | 'unresponsive' | 'gone' | 'destroyed';\n }>;\n }>;\n };\n __contextMenu: {\n show(data: {\n items: Array<{ label?: string; action?: string; data?: string; _callbackId?: string; disabled?: boolean; type?: 'separator' }>;\n position: { x: number; y: number };\n }): Promise<void>;\n };\n on(channel: string, callback: (...args: unknown[]) => void): () => void;\n __log(level: string, logger: string, message: string, context?: unknown): Promise<{\n ok: boolean;\n forwarded: boolean;\n configured: boolean;\n reason?: 'unconfigured' | 'callback-error';\n errorMessage?: string;\n }>;\n __logBatch(batch: unknown): void;\n}\n`;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -158,6 +158,15 @@ export interface GeneratedWoodBridge {
|
|
|
158
158
|
${typesCode}
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Project-generated typing for testWood's object-shaped operation API.
|
|
163
|
+
* The runtime surface is built from the selected operation aperture and still
|
|
164
|
+
* dispatches through the real WoodRouter.
|
|
165
|
+
*/
|
|
166
|
+
declare module '@noego/wood/testing-api' {
|
|
167
|
+
interface WoodTestApi extends GeneratedWoodBridge {}
|
|
168
|
+
}
|
|
169
|
+
|
|
161
170
|
export interface WoodApi extends GeneratedWoodBridge {
|
|
162
171
|
__rpcManifest: WoodRpcManifestEntry[];
|
|
163
172
|
__load(route: string, params: Record<string, string>): Promise<unknown>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/codegen/type_generator.ts"],"sourcesContent":["import type { OperationDef } from '../types/index.js';\nimport { generatedFileNotice } from './generated_file_notice.js';\n\nconst TYPE_SOURCE_INSTRUCTIONS = [\n 'To change IPC channels, input schemas, controller/action names, or renderer bridge types,',\n 'edit the operations YAML configured by wood.config.yml main.openapi and the matching controller methods.',\n] as const;\n\nfunction formatPropertyKey(key: string): string {\n if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {\n return key;\n }\n\n return JSON.stringify(key);\n}\n\nfunction operationHasInput(op: OperationDef): boolean {\n return Boolean(\n op.inputSchema &&\n (op.inputSchema as Record<string, unknown>).type === 'object' &&\n Object.keys((op.inputSchema as Record<string, Record<string, unknown>>).properties ?? {}).length > 0,\n );\n}\n\nfunction jsonSchemaToTS(schema: Record<string, unknown> | undefined, indent: string = ''): string {\n if (!schema) return 'unknown';\n\n const type = schema.type as string | undefined;\n\n if (type === 'string') {\n if (schema.enum) {\n return (schema.enum as string[]).map((v) => `'${v}'`).join(' | ');\n }\n return 'string';\n }\n if (type === 'number' || type === 'integer') return 'number';\n if (type === 'boolean') return 'boolean';\n if (type === 'array') {\n const itemType = jsonSchemaToTS(schema.items as Record<string, unknown>, indent);\n return `Array<${itemType}>`;\n }\n if (type === 'object') {\n const props = (schema.properties ?? {}) as Record<string, Record<string, unknown>>;\n const required = new Set((schema.required as string[]) ?? []);\n const entries = Object.entries(props);\n if (entries.length === 0) return 'Record<string, unknown>';\n\n const lines = entries.map(([key, val]) => {\n const optional = required.has(key) ? '' : '?';\n const valType = jsonSchemaToTS(val, indent + ' ');\n return `${indent} ${formatPropertyKey(key)}${optional}: ${valType};`;\n });\n return `{\\n${lines.join('\\n')}\\n${indent}}`;\n }\n\n return 'unknown';\n}\n\nfunction buildNestedTypeTree(operations: Map<string, OperationDef>): Record<string, unknown> {\n const tree: Record<string, unknown> = {};\n for (const [channel, op] of operations) {\n const parts = channel.split('.');\n let current: Record<string, unknown> = tree;\n for (let i = 0; i < parts.length - 1; i++) {\n if (!current[parts[i]]) current[parts[i]] = {};\n current = current[parts[i]] as Record<string, unknown>;\n }\n current[parts[parts.length - 1]] = { __operation: op };\n }\n return tree;\n}\n\nfunction generateTypeTree(node: Record<string, unknown>, indent: string = ' '): string {\n const lines: string[] = [];\n for (const [key, value] of Object.entries(node)) {\n const entry = value as Record<string, unknown>;\n if (entry.__operation) {\n const op = entry.__operation as OperationDef;\n const inputType = jsonSchemaToTS(op.inputSchema as Record<string, unknown>, indent + ' ');\n const outputType = jsonSchemaToTS(op.outputSchema as Record<string, unknown>, indent + ' ');\n const hasInput = operationHasInput(op);\n\n if (hasInput) {\n lines.push(`${indent}${formatPropertyKey(key)}(data: ${inputType}): Promise<${outputType}>;`);\n } else {\n lines.push(`${indent}${formatPropertyKey(key)}(): Promise<${outputType}>;`);\n }\n } else {\n lines.push(`${indent}${formatPropertyKey(key)}: {`);\n lines.push(generateTypeTree(entry, indent + ' '));\n lines.push(`${indent}};`);\n }\n }\n return lines.join('\\n');\n}\n\ntype RpcActionTypeEntry = {\n channel: string;\n inputType: string;\n outputType: string;\n hasInput: boolean;\n};\n\nfunction buildRpcControllerTree(\n operations: Map<string, OperationDef>,\n): Record<string, Record<string, RpcActionTypeEntry>> {\n const tree: Record<string, Record<string, RpcActionTypeEntry>> = {};\n\n for (const operation of operations.values()) {\n const controller = operation.controller;\n const action = operation.action;\n\n if (!tree[controller]) {\n tree[controller] = {};\n }\n\n if (tree[controller][action]) {\n throw new Error(\n `Type codegen failed: duplicate RPC action \"${controller}.${action}\" found in channel \"${operation.channel}\".`,\n );\n }\n\n tree[controller][action] = {\n channel: operation.channel,\n inputType: jsonSchemaToTS(operation.inputSchema as Record<string, unknown>, ' '),\n outputType: jsonSchemaToTS(operation.outputSchema as Record<string, unknown>, ' '),\n hasInput: operationHasInput(operation),\n };\n }\n\n return tree;\n}\n\nfunction generateRpcTypeTree(operations: Map<string, OperationDef>): string {\n const controllerTree = buildRpcControllerTree(operations);\n const controllerNames = Object.keys(controllerTree).sort((a, b) => a.localeCompare(b));\n const lines: string[] = [];\n\n for (const controllerName of controllerNames) {\n lines.push(` ${formatPropertyKey(controllerName)}: {`);\n const actions = controllerTree[controllerName];\n const actionNames = Object.keys(actions).sort((a, b) => a.localeCompare(b));\n for (const actionName of actionNames) {\n const action = actions[actionName];\n if (action.hasInput) {\n lines.push(` ${formatPropertyKey(actionName)}(data: ${action.inputType}): Promise<${action.outputType}>;`);\n } else {\n lines.push(` ${formatPropertyKey(actionName)}(): Promise<${action.outputType}>;`);\n }\n }\n lines.push(' };');\n }\n\n return lines.join('\\n');\n}\n\nexport function generateTypes(operations: Map<string, OperationDef>): string {\n const tree = buildNestedTypeTree(operations);\n const typesCode = generateTypeTree(tree);\n const rpcTypeCode = generateRpcTypeTree(operations);\n\n return `${generatedFileNotice(TYPE_SOURCE_INSTRUCTIONS)}\nexport interface WoodRpcManifestEntry {\n channel: string;\n controller: string;\n action: string;\n path: string[];\n hasInput: boolean;\n}\n\n/**\n * Project-specific renderer App facade contract.\n *\n * This matches @noego/wood/client getApp() and injected App instances:\n * controller name -> controller action.\n */\nexport interface GeneratedApp {\n${rpcTypeCode}\n}\n\n/** Backward-compatible name for the generated client App facade contract. */\nexport interface WoodRpcApi extends GeneratedApp {}\n\n/**\n * Project-specific raw preload bridge contract.\n *\n * This matches the channel path exposed by preload.generated.ts:\n * operation channel segments -> invoke function.\n */\nexport interface GeneratedWoodBridge {\n${typesCode}\n}\n\nexport interface WoodApi extends GeneratedWoodBridge {\n __rpcManifest: WoodRpcManifestEntry[];\n __load(route: string, params: Record<string, string>): Promise<unknown>;\n __navigate(windowId: string, route: string): Promise<void>;\n __window: {\n current(): Promise<{ ok: boolean; windowId?: string; defaultRoute?: string }>;\n open(windowId: string, options?: Record<string, unknown>): Promise<void>;\n close(windowId?: string): Promise<void>;\n focus(windowId: string): Promise<void>;\n minimize(windowId?: string): Promise<void>;\n maximize(windowId?: string): Promise<void>;\n };\n __debug: {\n current(): Promise<{\n ok: boolean;\n enabled: boolean;\n mode: 'development' | 'production';\n port?: number;\n windows: Array<{\n windowId: string;\n title: string;\n webContentsId: number;\n url: string;\n focused: boolean;\n rendererHealth?: 'ready' | 'loading' | 'unresponsive' | 'gone' | 'destroyed';\n }>;\n }>;\n };\n __contextMenu: {\n show(data: {\n items: Array<{ label?: string; action?: string; data?: string; _callbackId?: string; disabled?: boolean; type?: 'separator' }>;\n position: { x: number; y: number };\n }): Promise<void>;\n };\n on(channel: string, callback: (...args: unknown[]) => void): () => void;\n __log(level: string, logger: string, message: string, context?: unknown): Promise<{\n ok: boolean;\n forwarded: boolean;\n configured: boolean;\n reason?: 'unconfigured' | 'callback-error';\n errorMessage?: string;\n }>;\n __logBatch(batch: unknown): void;\n}\n`;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/codegen/type_generator.ts"],"sourcesContent":["import type { OperationDef } from '../types/index.js';\nimport { generatedFileNotice } from './generated_file_notice.js';\n\nconst TYPE_SOURCE_INSTRUCTIONS = [\n 'To change IPC channels, input schemas, controller/action names, or renderer bridge types,',\n 'edit the operations YAML configured by wood.config.yml main.openapi and the matching controller methods.',\n] as const;\n\nfunction formatPropertyKey(key: string): string {\n if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {\n return key;\n }\n\n return JSON.stringify(key);\n}\n\nfunction operationHasInput(op: OperationDef): boolean {\n return Boolean(\n op.inputSchema &&\n (op.inputSchema as Record<string, unknown>).type === 'object' &&\n Object.keys((op.inputSchema as Record<string, Record<string, unknown>>).properties ?? {}).length > 0,\n );\n}\n\nfunction jsonSchemaToTS(schema: Record<string, unknown> | undefined, indent: string = ''): string {\n if (!schema) return 'unknown';\n\n const type = schema.type as string | undefined;\n\n if (type === 'string') {\n if (schema.enum) {\n return (schema.enum as string[]).map((v) => `'${v}'`).join(' | ');\n }\n return 'string';\n }\n if (type === 'number' || type === 'integer') return 'number';\n if (type === 'boolean') return 'boolean';\n if (type === 'array') {\n const itemType = jsonSchemaToTS(schema.items as Record<string, unknown>, indent);\n return `Array<${itemType}>`;\n }\n if (type === 'object') {\n const props = (schema.properties ?? {}) as Record<string, Record<string, unknown>>;\n const required = new Set((schema.required as string[]) ?? []);\n const entries = Object.entries(props);\n if (entries.length === 0) return 'Record<string, unknown>';\n\n const lines = entries.map(([key, val]) => {\n const optional = required.has(key) ? '' : '?';\n const valType = jsonSchemaToTS(val, indent + ' ');\n return `${indent} ${formatPropertyKey(key)}${optional}: ${valType};`;\n });\n return `{\\n${lines.join('\\n')}\\n${indent}}`;\n }\n\n return 'unknown';\n}\n\nfunction buildNestedTypeTree(operations: Map<string, OperationDef>): Record<string, unknown> {\n const tree: Record<string, unknown> = {};\n for (const [channel, op] of operations) {\n const parts = channel.split('.');\n let current: Record<string, unknown> = tree;\n for (let i = 0; i < parts.length - 1; i++) {\n if (!current[parts[i]]) current[parts[i]] = {};\n current = current[parts[i]] as Record<string, unknown>;\n }\n current[parts[parts.length - 1]] = { __operation: op };\n }\n return tree;\n}\n\nfunction generateTypeTree(node: Record<string, unknown>, indent: string = ' '): string {\n const lines: string[] = [];\n for (const [key, value] of Object.entries(node)) {\n const entry = value as Record<string, unknown>;\n if (entry.__operation) {\n const op = entry.__operation as OperationDef;\n const inputType = jsonSchemaToTS(op.inputSchema as Record<string, unknown>, indent + ' ');\n const outputType = jsonSchemaToTS(op.outputSchema as Record<string, unknown>, indent + ' ');\n const hasInput = operationHasInput(op);\n\n if (hasInput) {\n lines.push(`${indent}${formatPropertyKey(key)}(data: ${inputType}): Promise<${outputType}>;`);\n } else {\n lines.push(`${indent}${formatPropertyKey(key)}(): Promise<${outputType}>;`);\n }\n } else {\n lines.push(`${indent}${formatPropertyKey(key)}: {`);\n lines.push(generateTypeTree(entry, indent + ' '));\n lines.push(`${indent}};`);\n }\n }\n return lines.join('\\n');\n}\n\ntype RpcActionTypeEntry = {\n channel: string;\n inputType: string;\n outputType: string;\n hasInput: boolean;\n};\n\nfunction buildRpcControllerTree(\n operations: Map<string, OperationDef>,\n): Record<string, Record<string, RpcActionTypeEntry>> {\n const tree: Record<string, Record<string, RpcActionTypeEntry>> = {};\n\n for (const operation of operations.values()) {\n const controller = operation.controller;\n const action = operation.action;\n\n if (!tree[controller]) {\n tree[controller] = {};\n }\n\n if (tree[controller][action]) {\n throw new Error(\n `Type codegen failed: duplicate RPC action \"${controller}.${action}\" found in channel \"${operation.channel}\".`,\n );\n }\n\n tree[controller][action] = {\n channel: operation.channel,\n inputType: jsonSchemaToTS(operation.inputSchema as Record<string, unknown>, ' '),\n outputType: jsonSchemaToTS(operation.outputSchema as Record<string, unknown>, ' '),\n hasInput: operationHasInput(operation),\n };\n }\n\n return tree;\n}\n\nfunction generateRpcTypeTree(operations: Map<string, OperationDef>): string {\n const controllerTree = buildRpcControllerTree(operations);\n const controllerNames = Object.keys(controllerTree).sort((a, b) => a.localeCompare(b));\n const lines: string[] = [];\n\n for (const controllerName of controllerNames) {\n lines.push(` ${formatPropertyKey(controllerName)}: {`);\n const actions = controllerTree[controllerName];\n const actionNames = Object.keys(actions).sort((a, b) => a.localeCompare(b));\n for (const actionName of actionNames) {\n const action = actions[actionName];\n if (action.hasInput) {\n lines.push(` ${formatPropertyKey(actionName)}(data: ${action.inputType}): Promise<${action.outputType}>;`);\n } else {\n lines.push(` ${formatPropertyKey(actionName)}(): Promise<${action.outputType}>;`);\n }\n }\n lines.push(' };');\n }\n\n return lines.join('\\n');\n}\n\nexport function generateTypes(operations: Map<string, OperationDef>): string {\n const tree = buildNestedTypeTree(operations);\n const typesCode = generateTypeTree(tree);\n const rpcTypeCode = generateRpcTypeTree(operations);\n\n return `${generatedFileNotice(TYPE_SOURCE_INSTRUCTIONS)}\nexport interface WoodRpcManifestEntry {\n channel: string;\n controller: string;\n action: string;\n path: string[];\n hasInput: boolean;\n}\n\n/**\n * Project-specific renderer App facade contract.\n *\n * This matches @noego/wood/client getApp() and injected App instances:\n * controller name -> controller action.\n */\nexport interface GeneratedApp {\n${rpcTypeCode}\n}\n\n/** Backward-compatible name for the generated client App facade contract. */\nexport interface WoodRpcApi extends GeneratedApp {}\n\n/**\n * Project-specific raw preload bridge contract.\n *\n * This matches the channel path exposed by preload.generated.ts:\n * operation channel segments -> invoke function.\n */\nexport interface GeneratedWoodBridge {\n${typesCode}\n}\n\n/**\n * Project-generated typing for testWood's object-shaped operation API.\n * The runtime surface is built from the selected operation aperture and still\n * dispatches through the real WoodRouter.\n */\ndeclare module '@noego/wood/testing-api' {\n interface WoodTestApi extends GeneratedWoodBridge {}\n}\n\nexport interface WoodApi extends GeneratedWoodBridge {\n __rpcManifest: WoodRpcManifestEntry[];\n __load(route: string, params: Record<string, string>): Promise<unknown>;\n __navigate(windowId: string, route: string): Promise<void>;\n __window: {\n current(): Promise<{ ok: boolean; windowId?: string; defaultRoute?: string }>;\n open(windowId: string, options?: Record<string, unknown>): Promise<void>;\n close(windowId?: string): Promise<void>;\n focus(windowId: string): Promise<void>;\n minimize(windowId?: string): Promise<void>;\n maximize(windowId?: string): Promise<void>;\n };\n __debug: {\n current(): Promise<{\n ok: boolean;\n enabled: boolean;\n mode: 'development' | 'production';\n port?: number;\n windows: Array<{\n windowId: string;\n title: string;\n webContentsId: number;\n url: string;\n focused: boolean;\n rendererHealth?: 'ready' | 'loading' | 'unresponsive' | 'gone' | 'destroyed';\n }>;\n }>;\n };\n __contextMenu: {\n show(data: {\n items: Array<{ label?: string; action?: string; data?: string; _callbackId?: string; disabled?: boolean; type?: 'separator' }>;\n position: { x: number; y: number };\n }): Promise<void>;\n };\n on(channel: string, callback: (...args: unknown[]) => void): () => void;\n __log(level: string, logger: string, message: string, context?: unknown): Promise<{\n ok: boolean;\n forwarded: boolean;\n configured: boolean;\n reason?: 'unconfigured' | 'callback-error';\n errorMessage?: string;\n }>;\n __logBatch(batch: unknown): void;\n}\n`;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -77,6 +77,14 @@ function validateRequired(doc) {
|
|
|
77
77
|
if (rendererEntry !== void 0) {
|
|
78
78
|
requireString(doc, "renderer.entry", rendererEntry);
|
|
79
79
|
}
|
|
80
|
+
const rendererCompose = doc.renderer?.compose;
|
|
81
|
+
if (rendererCompose !== void 0) {
|
|
82
|
+
requireString(doc, "renderer.compose", rendererCompose);
|
|
83
|
+
}
|
|
84
|
+
const rendererCss = doc.renderer?.css;
|
|
85
|
+
if (rendererCss !== void 0 && (!Array.isArray(rendererCss) || rendererCss.some((value) => typeof value !== "string" || !value.trim()))) {
|
|
86
|
+
throw new Error('Config validation error: "renderer.css" must be an array of nonempty paths');
|
|
87
|
+
}
|
|
80
88
|
const windows = doc.windows;
|
|
81
89
|
if (!windows || typeof windows !== "object") {
|
|
82
90
|
throw new Error('Config validation error: "windows" is required and must be an object with at least one window');
|
|
@@ -165,7 +173,9 @@ function applyDefaults(doc) {
|
|
|
165
173
|
renderer: {
|
|
166
174
|
openapi: renderer.openapi,
|
|
167
175
|
componentDir: renderer.componentDir,
|
|
176
|
+
...Array.isArray(renderer.css) ? { css: [...renderer.css] } : {},
|
|
168
177
|
controllers: renderer.controllers,
|
|
178
|
+
...typeof renderer.compose === "string" ? { compose: renderer.compose } : {},
|
|
169
179
|
...typeof renderer.entry === "string" ? { entry: renderer.entry } : {},
|
|
170
180
|
...Array.isArray(renderer.watch) ? { watch: renderer.watch } : {},
|
|
171
181
|
...parseTestingSection("renderer", renderer.testing, ["host"])
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/config/config_parser.ts"],"sourcesContent":["import yaml from 'js-yaml';\nimport { readFileSync, existsSync } from 'fs';\nimport path from 'path';\nimport { getLogger } from '@noego/logger';\nimport type {\n WoodConfig,\n WindowDef,\n DevConfig,\n RendererTraceBatchingConfig,\n} from '../types/config.cjs';\n\nconst logger = getLogger('wood:config');\n\n/** Default values applied when optional fields are omitted. */\nconst WINDOW_DEFAULTS: Partial<WindowDef> = {\n frame: true,\n resizable: true,\n show: true,\n modal: false,\n};\n\nconst DEV_DEFAULTS: DevConfig = {\n watch: true,\n splitServe: true,\n port: 5173,\n};\n\n/**\n * Parse and validate a wood.config.yml file, returning a fully-typed WoodConfig\n * with defaults applied for optional fields.\n */\nexport function parseConfig(configPath: string): WoodConfig {\n const resolved = path.resolve(configPath);\n\n if (!existsSync(resolved)) {\n throw new Error(`Config file not found: ${resolved}`);\n }\n\n const raw = readFileSync(resolved, 'utf-8');\n const doc = yaml.load(raw) as Record<string, unknown> | null;\n\n if (!doc || typeof doc !== 'object') {\n throw new Error(`Invalid YAML in config file: ${resolved}`);\n }\n\n validateRequired(doc);\n\n const config = applyDefaults(doc as RawConfig);\n\n logger.info('Parsed config', { path: resolved, windows: Object.keys(config.windows) });\n\n return config;\n}\n\n/** Shape of the raw YAML before defaults are applied. */\ntype RawConfig = Record<string, unknown>;\n\nfunction validateRequired(doc: Record<string, unknown>): void {\n requireString(doc, 'app.boot', (doc as Record<string, Record<string, unknown>>).app?.boot);\n requireString(doc, 'main.entry', (doc as Record<string, Record<string, unknown>>).main?.entry);\n requireString(doc, 'main.controllers', (doc as Record<string, Record<string, unknown>>).main?.controllers);\n requireString(doc, 'main.middleware', (doc as Record<string, Record<string, unknown>>).main?.middleware);\n requireString(doc, 'main.openapi', (doc as Record<string, Record<string, unknown>>).main?.openapi);\n requireString(doc, 'renderer.openapi', (doc as Record<string, Record<string, unknown>>).renderer?.openapi);\n requireString(doc, 'renderer.componentDir', (doc as Record<string, Record<string, unknown>>).renderer?.componentDir);\n requireString(doc, 'renderer.controllers', (doc as Record<string, Record<string, unknown>>).renderer?.controllers);\n const rendererEntry = (doc as Record<string, Record<string, unknown>>).renderer?.entry;\n if (rendererEntry !== undefined) {\n requireString(doc, 'renderer.entry', rendererEntry);\n }\n\n // Windows validation\n const windows = doc.windows;\n if (!windows || typeof windows !== 'object') {\n throw new Error('Config validation error: \"windows\" is required and must be an object with at least one window');\n }\n\n const windowKeys = Object.keys(windows as Record<string, unknown>);\n if (windowKeys.length === 0) {\n throw new Error('Config validation error: \"windows\" must contain at least one window definition');\n }\n\n for (const key of windowKeys) {\n const win = (windows as Record<string, Record<string, unknown>>)[key];\n if (!win || typeof win !== 'object') {\n throw new Error(`Config validation error: window \"${key}\" must be an object`);\n }\n requireString(doc, `windows.${key}.title`, win.title);\n requireNumber(doc, `windows.${key}.width`, win.width);\n requireNumber(doc, `windows.${key}.height`, win.height);\n requireString(doc, `windows.${key}.defaultRoute`, win.defaultRoute);\n\n if (win.contextMenu !== undefined) {\n if (typeof win.contextMenu !== 'string') {\n throw new Error(`Config validation error: \"windows.${key}.contextMenu\" must be a string`);\n }\n if (win.contextMenu !== 'native' && win.contextMenu !== 'custom') {\n throw new Error(`Config validation error: \"windows.${key}.contextMenu\" must be \"native\" or \"custom\"`);\n }\n }\n }\n}\n\nfunction requireString(_doc: Record<string, unknown>, field: string, value: unknown): asserts value is string {\n if (typeof value !== 'string' || value.length === 0) {\n throw new Error(`Config validation error: \"${field}\" is required and must be a non-empty string`);\n }\n}\n\nfunction requireNumber(_doc: Record<string, unknown>, field: string, value: unknown): asserts value is number {\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new Error(`Config validation error: \"${field}\" is required and must be a number`);\n }\n}\n\n/**\n * Parse an optional `<section>.testing` mapping: every allowed key is an\n * optional non-empty string (a config-relative module path); anything else\n * is a validation error so a typo cannot silently drop a hook.\n */\nfunction parseTestingSection(\n section: 'main' | 'renderer',\n raw: unknown,\n allowedKeys: readonly string[],\n): { testing?: Record<string, string> } {\n if (raw === undefined) return {};\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new Error(`Config validation error: \"${section}.testing\" must be an object`);\n }\n const testing: Record<string, string> = {};\n for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {\n if (!allowedKeys.includes(key)) {\n throw new Error(\n `Config validation error: \"${section}.testing.${key}\" is not a known hook (allowed: ${allowedKeys.join(', ')})`,\n );\n }\n requireString(raw as Record<string, unknown>, `${section}.testing.${key}`, value);\n testing[key] = value;\n }\n return { testing };\n}\n\nfunction applyDefaults(doc: RawConfig): WoodConfig {\n const app = doc.app as Record<string, unknown>;\n const main = doc.main as Record<string, unknown>;\n const renderer = doc.renderer as Record<string, unknown>;\n const rawWindows = doc.windows as Record<string, Record<string, unknown>>;\n\n const windows: Record<string, WindowDef> = {};\n for (const [key, rawWin] of Object.entries(rawWindows)) {\n windows[key] = {\n ...WINDOW_DEFAULTS,\n ...rawWin,\n } as WindowDef;\n }\n\n const dev: DevConfig = {\n ...DEV_DEFAULTS,\n ...(doc.dev as Record<string, unknown> | undefined),\n };\n\n const config: WoodConfig = {\n root: typeof doc.root === 'string' ? doc.root : '.',\n outDir: typeof doc.outDir === 'string' ? doc.outDir : 'dist',\n app: {\n boot: app.boot as string,\n watch: Array.isArray(app.watch) ? (app.watch as string[]) : [],\n },\n main: {\n entry: main.entry as string,\n controllers: main.controllers as string,\n middleware: main.middleware as string,\n openapi: main.openapi as string,\n watch: Array.isArray(main.watch) ? (main.watch as string[]) : [],\n ...parseTestingSection('main', main.testing, ['compose']),\n },\n renderer: {\n openapi: renderer.openapi as string,\n componentDir: renderer.componentDir as string,\n controllers: renderer.controllers as string,\n ...(typeof renderer.entry === 'string' ? { entry: renderer.entry as string } : {}),\n ...(Array.isArray(renderer.watch) ? { watch: renderer.watch as string[] } : {}),\n ...parseTestingSection('renderer', renderer.testing, ['host']),\n },\n windows,\n dev,\n };\n\n // Optional database section (preserve :userData: token for runtime expansion)\n if (doc.database && typeof doc.database === 'object') {\n const db = doc.database as Record<string, unknown>;\n if (typeof db.path === 'string') {\n config.database = {\n path: db.path,\n ...(typeof db.migrations === 'string' ? { migrations: db.migrations } : {}),\n };\n }\n }\n\n // Optional assets\n if (Array.isArray(doc.assets)) {\n config.assets = doc.assets as string[];\n }\n\n // Optional build section\n if (doc.build && typeof doc.build === 'object') {\n config.build = doc.build as WoodConfig['build'];\n }\n\n // Optional observability section (renderer→main trace batching policy)\n if (doc.observability && typeof doc.observability === 'object') {\n config.observability = parseObservability(doc.observability as Record<string, unknown>);\n }\n\n return config;\n}\n\n/**\n * Default renderer→main IPC trace batching policy. These values match the\n * policy previously hard-coded in the generated preload; wood.config.yml\n * `observability.trace.rendererToMain.batching` is now the owner.\n */\nexport const RENDERER_TRACE_BATCHING_DEFAULTS: RendererTraceBatchingConfig = Object.freeze({\n maxQueuedEvents: 256,\n maxBatchEvents: 32,\n acknowledgementTimeoutMs: 1_000,\n retryLimit: 3,\n retryBaseDelayMs: 25,\n});\n\n/** Resolves the renderer trace batching policy from a parsed config. */\nexport function resolveRendererTraceBatching(\n config: Pick<WoodConfig, 'observability'>,\n): RendererTraceBatchingConfig {\n return {\n ...RENDERER_TRACE_BATCHING_DEFAULTS,\n ...config.observability?.trace?.rendererToMain?.batching,\n };\n}\n\nfunction parseObservability(raw: Record<string, unknown>): WoodConfig['observability'] {\n const trace = raw.trace;\n if (trace === undefined) {\n return {};\n }\n if (typeof trace !== 'object' || trace === null) {\n throw new Error('Config validation error: \"observability.trace\" must be an object');\n }\n const rendererToMain = (trace as Record<string, unknown>).rendererToMain;\n if (rendererToMain === undefined) {\n return { trace: {} };\n }\n if (typeof rendererToMain !== 'object' || rendererToMain === null) {\n throw new Error('Config validation error: \"observability.trace.rendererToMain\" must be an object');\n }\n const batching = (rendererToMain as Record<string, unknown>).batching;\n if (batching === undefined) {\n return { trace: { rendererToMain: {} } };\n }\n if (typeof batching !== 'object' || batching === null) {\n throw new Error('Config validation error: \"observability.trace.rendererToMain.batching\" must be an object');\n }\n\n const resolved: RendererTraceBatchingConfig = {\n ...RENDERER_TRACE_BATCHING_DEFAULTS,\n ...(batching as Partial<RendererTraceBatchingConfig>),\n };\n\n const positiveIntFields = ['maxQueuedEvents', 'maxBatchEvents', 'acknowledgementTimeoutMs'] as const;\n for (const field of positiveIntFields) {\n const value = resolved[field];\n if (!Number.isSafeInteger(value) || value <= 0) {\n throw new Error(\n `Config validation error: \"observability.trace.rendererToMain.batching.${field}\" must be a positive integer`,\n );\n }\n }\n if (!Number.isSafeInteger(resolved.retryLimit) || resolved.retryLimit < 0) {\n throw new Error(\n 'Config validation error: \"observability.trace.rendererToMain.batching.retryLimit\" must be a non-negative integer',\n );\n }\n if (!Number.isFinite(resolved.retryBaseDelayMs) || resolved.retryBaseDelayMs < 0) {\n throw new Error(\n 'Config validation error: \"observability.trace.rendererToMain.batching.retryBaseDelayMs\" must be a non-negative number',\n );\n }\n if (resolved.maxBatchEvents > resolved.maxQueuedEvents) {\n throw new Error(\n 'Config validation error: \"observability.trace.rendererToMain.batching.maxBatchEvents\" cannot exceed maxQueuedEvents',\n );\n }\n\n return { trace: { rendererToMain: { batching: resolved } } };\n}\n"],"mappings":"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","names":["path","yaml"]}
|
|
1
|
+
{"version":3,"sources":["../../src/config/config_parser.ts"],"sourcesContent":["import yaml from 'js-yaml';\nimport { readFileSync, existsSync } from 'fs';\nimport path from 'path';\nimport { getLogger } from '@noego/logger';\nimport type {\n WoodConfig,\n WindowDef,\n DevConfig,\n RendererTraceBatchingConfig,\n} from '../types/config.cjs';\n\nconst logger = getLogger('wood:config');\n\n/** Default values applied when optional fields are omitted. */\nconst WINDOW_DEFAULTS: Partial<WindowDef> = {\n frame: true,\n resizable: true,\n show: true,\n modal: false,\n};\n\nconst DEV_DEFAULTS: DevConfig = {\n watch: true,\n splitServe: true,\n port: 5173,\n};\n\n/**\n * Parse and validate a wood.config.yml file, returning a fully-typed WoodConfig\n * with defaults applied for optional fields.\n */\nexport function parseConfig(configPath: string): WoodConfig {\n const resolved = path.resolve(configPath);\n\n if (!existsSync(resolved)) {\n throw new Error(`Config file not found: ${resolved}`);\n }\n\n const raw = readFileSync(resolved, 'utf-8');\n const doc = yaml.load(raw) as Record<string, unknown> | null;\n\n if (!doc || typeof doc !== 'object') {\n throw new Error(`Invalid YAML in config file: ${resolved}`);\n }\n\n validateRequired(doc);\n\n const config = applyDefaults(doc as RawConfig);\n\n logger.info('Parsed config', { path: resolved, windows: Object.keys(config.windows) });\n\n return config;\n}\n\n/** Shape of the raw YAML before defaults are applied. */\ntype RawConfig = Record<string, unknown>;\n\nfunction validateRequired(doc: Record<string, unknown>): void {\n requireString(doc, 'app.boot', (doc as Record<string, Record<string, unknown>>).app?.boot);\n requireString(doc, 'main.entry', (doc as Record<string, Record<string, unknown>>).main?.entry);\n requireString(doc, 'main.controllers', (doc as Record<string, Record<string, unknown>>).main?.controllers);\n requireString(doc, 'main.middleware', (doc as Record<string, Record<string, unknown>>).main?.middleware);\n requireString(doc, 'main.openapi', (doc as Record<string, Record<string, unknown>>).main?.openapi);\n requireString(doc, 'renderer.openapi', (doc as Record<string, Record<string, unknown>>).renderer?.openapi);\n requireString(doc, 'renderer.componentDir', (doc as Record<string, Record<string, unknown>>).renderer?.componentDir);\n requireString(doc, 'renderer.controllers', (doc as Record<string, Record<string, unknown>>).renderer?.controllers);\n const rendererEntry = (doc as Record<string, Record<string, unknown>>).renderer?.entry;\n if (rendererEntry !== undefined) {\n requireString(doc, 'renderer.entry', rendererEntry);\n }\n const rendererCompose = (doc as Record<string, Record<string, unknown>>).renderer?.compose;\n if (rendererCompose !== undefined) {\n requireString(doc, 'renderer.compose', rendererCompose);\n }\n\n const rendererCss = (doc as Record<string, Record<string, unknown>>).renderer?.css;\n if (rendererCss !== undefined && (!Array.isArray(rendererCss) || rendererCss.some(value => typeof value !== 'string' || !value.trim()))) {\n throw new Error('Config validation error: \"renderer.css\" must be an array of nonempty paths');\n }\n\n // Windows validation\n const windows = doc.windows;\n if (!windows || typeof windows !== 'object') {\n throw new Error('Config validation error: \"windows\" is required and must be an object with at least one window');\n }\n\n const windowKeys = Object.keys(windows as Record<string, unknown>);\n if (windowKeys.length === 0) {\n throw new Error('Config validation error: \"windows\" must contain at least one window definition');\n }\n\n for (const key of windowKeys) {\n const win = (windows as Record<string, Record<string, unknown>>)[key];\n if (!win || typeof win !== 'object') {\n throw new Error(`Config validation error: window \"${key}\" must be an object`);\n }\n requireString(doc, `windows.${key}.title`, win.title);\n requireNumber(doc, `windows.${key}.width`, win.width);\n requireNumber(doc, `windows.${key}.height`, win.height);\n requireString(doc, `windows.${key}.defaultRoute`, win.defaultRoute);\n\n if (win.contextMenu !== undefined) {\n if (typeof win.contextMenu !== 'string') {\n throw new Error(`Config validation error: \"windows.${key}.contextMenu\" must be a string`);\n }\n if (win.contextMenu !== 'native' && win.contextMenu !== 'custom') {\n throw new Error(`Config validation error: \"windows.${key}.contextMenu\" must be \"native\" or \"custom\"`);\n }\n }\n }\n}\n\nfunction requireString(_doc: Record<string, unknown>, field: string, value: unknown): asserts value is string {\n if (typeof value !== 'string' || value.length === 0) {\n throw new Error(`Config validation error: \"${field}\" is required and must be a non-empty string`);\n }\n}\n\nfunction requireNumber(_doc: Record<string, unknown>, field: string, value: unknown): asserts value is number {\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new Error(`Config validation error: \"${field}\" is required and must be a number`);\n }\n}\n\n/**\n * Parse an optional `<section>.testing` mapping: every allowed key is an\n * optional non-empty string (a config-relative module path); anything else\n * is a validation error so a typo cannot silently drop a hook.\n */\nfunction parseTestingSection(\n section: 'main' | 'renderer',\n raw: unknown,\n allowedKeys: readonly string[],\n): { testing?: Record<string, string> } {\n if (raw === undefined) return {};\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new Error(`Config validation error: \"${section}.testing\" must be an object`);\n }\n const testing: Record<string, string> = {};\n for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {\n if (!allowedKeys.includes(key)) {\n throw new Error(\n `Config validation error: \"${section}.testing.${key}\" is not a known hook (allowed: ${allowedKeys.join(', ')})`,\n );\n }\n requireString(raw as Record<string, unknown>, `${section}.testing.${key}`, value);\n testing[key] = value;\n }\n return { testing };\n}\n\nfunction applyDefaults(doc: RawConfig): WoodConfig {\n const app = doc.app as Record<string, unknown>;\n const main = doc.main as Record<string, unknown>;\n const renderer = doc.renderer as Record<string, unknown>;\n const rawWindows = doc.windows as Record<string, Record<string, unknown>>;\n\n const windows: Record<string, WindowDef> = {};\n for (const [key, rawWin] of Object.entries(rawWindows)) {\n windows[key] = {\n ...WINDOW_DEFAULTS,\n ...rawWin,\n } as WindowDef;\n }\n\n const dev: DevConfig = {\n ...DEV_DEFAULTS,\n ...(doc.dev as Record<string, unknown> | undefined),\n };\n\n const config: WoodConfig = {\n root: typeof doc.root === 'string' ? doc.root : '.',\n outDir: typeof doc.outDir === 'string' ? doc.outDir : 'dist',\n app: {\n boot: app.boot as string,\n watch: Array.isArray(app.watch) ? (app.watch as string[]) : [],\n },\n main: {\n entry: main.entry as string,\n controllers: main.controllers as string,\n middleware: main.middleware as string,\n openapi: main.openapi as string,\n watch: Array.isArray(main.watch) ? (main.watch as string[]) : [],\n ...parseTestingSection('main', main.testing, ['compose']),\n },\n renderer: {\n openapi: renderer.openapi as string,\n componentDir: renderer.componentDir as string,\n ...(Array.isArray(renderer.css) ? {css: [...renderer.css] as string[]} : {}),\n controllers: renderer.controllers as string,\n ...(typeof renderer.compose === 'string' ? { compose: renderer.compose as string } : {}),\n ...(typeof renderer.entry === 'string' ? { entry: renderer.entry as string } : {}),\n ...(Array.isArray(renderer.watch) ? { watch: renderer.watch as string[] } : {}),\n ...parseTestingSection('renderer', renderer.testing, ['host']),\n },\n windows,\n dev,\n };\n\n // Optional database section (preserve :userData: token for runtime expansion)\n if (doc.database && typeof doc.database === 'object') {\n const db = doc.database as Record<string, unknown>;\n if (typeof db.path === 'string') {\n config.database = {\n path: db.path,\n ...(typeof db.migrations === 'string' ? { migrations: db.migrations } : {}),\n };\n }\n }\n\n // Optional assets\n if (Array.isArray(doc.assets)) {\n config.assets = doc.assets as string[];\n }\n\n // Optional build section\n if (doc.build && typeof doc.build === 'object') {\n config.build = doc.build as WoodConfig['build'];\n }\n\n // Optional observability section (renderer→main trace batching policy)\n if (doc.observability && typeof doc.observability === 'object') {\n config.observability = parseObservability(doc.observability as Record<string, unknown>);\n }\n\n return config;\n}\n\n/**\n * Default renderer→main IPC trace batching policy. These values match the\n * policy previously hard-coded in the generated preload; wood.config.yml\n * `observability.trace.rendererToMain.batching` is now the owner.\n */\nexport const RENDERER_TRACE_BATCHING_DEFAULTS: RendererTraceBatchingConfig = Object.freeze({\n maxQueuedEvents: 256,\n maxBatchEvents: 32,\n acknowledgementTimeoutMs: 1_000,\n retryLimit: 3,\n retryBaseDelayMs: 25,\n});\n\n/** Resolves the renderer trace batching policy from a parsed config. */\nexport function resolveRendererTraceBatching(\n config: Pick<WoodConfig, 'observability'>,\n): RendererTraceBatchingConfig {\n return {\n ...RENDERER_TRACE_BATCHING_DEFAULTS,\n ...config.observability?.trace?.rendererToMain?.batching,\n };\n}\n\nfunction parseObservability(raw: Record<string, unknown>): WoodConfig['observability'] {\n const trace = raw.trace;\n if (trace === undefined) {\n return {};\n }\n if (typeof trace !== 'object' || trace === null) {\n throw new Error('Config validation error: \"observability.trace\" must be an object');\n }\n const rendererToMain = (trace as Record<string, unknown>).rendererToMain;\n if (rendererToMain === undefined) {\n return { trace: {} };\n }\n if (typeof rendererToMain !== 'object' || rendererToMain === null) {\n throw new Error('Config validation error: \"observability.trace.rendererToMain\" must be an object');\n }\n const batching = (rendererToMain as Record<string, unknown>).batching;\n if (batching === undefined) {\n return { trace: { rendererToMain: {} } };\n }\n if (typeof batching !== 'object' || batching === null) {\n throw new Error('Config validation error: \"observability.trace.rendererToMain.batching\" must be an object');\n }\n\n const resolved: RendererTraceBatchingConfig = {\n ...RENDERER_TRACE_BATCHING_DEFAULTS,\n ...(batching as Partial<RendererTraceBatchingConfig>),\n };\n\n const positiveIntFields = ['maxQueuedEvents', 'maxBatchEvents', 'acknowledgementTimeoutMs'] as const;\n for (const field of positiveIntFields) {\n const value = resolved[field];\n if (!Number.isSafeInteger(value) || value <= 0) {\n throw new Error(\n `Config validation error: \"observability.trace.rendererToMain.batching.${field}\" must be a positive integer`,\n );\n }\n }\n if (!Number.isSafeInteger(resolved.retryLimit) || resolved.retryLimit < 0) {\n throw new Error(\n 'Config validation error: \"observability.trace.rendererToMain.batching.retryLimit\" must be a non-negative integer',\n );\n }\n if (!Number.isFinite(resolved.retryBaseDelayMs) || resolved.retryBaseDelayMs < 0) {\n throw new Error(\n 'Config validation error: \"observability.trace.rendererToMain.batching.retryBaseDelayMs\" must be a non-negative number',\n );\n }\n if (resolved.maxBatchEvents > resolved.maxQueuedEvents) {\n throw new Error(\n 'Config validation error: \"observability.trace.rendererToMain.batching.maxBatchEvents\" cannot exceed maxQueuedEvents',\n );\n }\n\n return { trace: { rendererToMain: { batching: resolved } } };\n}\n"],"mappings":"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","names":["path","yaml"]}
|
|
@@ -42,6 +42,14 @@ function validateRequired(doc) {
|
|
|
42
42
|
if (rendererEntry !== void 0) {
|
|
43
43
|
requireString(doc, "renderer.entry", rendererEntry);
|
|
44
44
|
}
|
|
45
|
+
const rendererCompose = doc.renderer?.compose;
|
|
46
|
+
if (rendererCompose !== void 0) {
|
|
47
|
+
requireString(doc, "renderer.compose", rendererCompose);
|
|
48
|
+
}
|
|
49
|
+
const rendererCss = doc.renderer?.css;
|
|
50
|
+
if (rendererCss !== void 0 && (!Array.isArray(rendererCss) || rendererCss.some((value) => typeof value !== "string" || !value.trim()))) {
|
|
51
|
+
throw new Error('Config validation error: "renderer.css" must be an array of nonempty paths');
|
|
52
|
+
}
|
|
45
53
|
const windows = doc.windows;
|
|
46
54
|
if (!windows || typeof windows !== "object") {
|
|
47
55
|
throw new Error('Config validation error: "windows" is required and must be an object with at least one window');
|
|
@@ -130,7 +138,9 @@ function applyDefaults(doc) {
|
|
|
130
138
|
renderer: {
|
|
131
139
|
openapi: renderer.openapi,
|
|
132
140
|
componentDir: renderer.componentDir,
|
|
141
|
+
...Array.isArray(renderer.css) ? { css: [...renderer.css] } : {},
|
|
133
142
|
controllers: renderer.controllers,
|
|
143
|
+
...typeof renderer.compose === "string" ? { compose: renderer.compose } : {},
|
|
134
144
|
...typeof renderer.entry === "string" ? { entry: renderer.entry } : {},
|
|
135
145
|
...Array.isArray(renderer.watch) ? { watch: renderer.watch } : {},
|
|
136
146
|
...parseTestingSection("renderer", renderer.testing, ["host"])
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/config/config_parser.ts"],"sourcesContent":["import yaml from 'js-yaml';\nimport { readFileSync, existsSync } from 'fs';\nimport path from 'path';\nimport { getLogger } from '@noego/logger';\nimport type {\n WoodConfig,\n WindowDef,\n DevConfig,\n RendererTraceBatchingConfig,\n} from '../types/config.js';\n\nconst logger = getLogger('wood:config');\n\n/** Default values applied when optional fields are omitted. */\nconst WINDOW_DEFAULTS: Partial<WindowDef> = {\n frame: true,\n resizable: true,\n show: true,\n modal: false,\n};\n\nconst DEV_DEFAULTS: DevConfig = {\n watch: true,\n splitServe: true,\n port: 5173,\n};\n\n/**\n * Parse and validate a wood.config.yml file, returning a fully-typed WoodConfig\n * with defaults applied for optional fields.\n */\nexport function parseConfig(configPath: string): WoodConfig {\n const resolved = path.resolve(configPath);\n\n if (!existsSync(resolved)) {\n throw new Error(`Config file not found: ${resolved}`);\n }\n\n const raw = readFileSync(resolved, 'utf-8');\n const doc = yaml.load(raw) as Record<string, unknown> | null;\n\n if (!doc || typeof doc !== 'object') {\n throw new Error(`Invalid YAML in config file: ${resolved}`);\n }\n\n validateRequired(doc);\n\n const config = applyDefaults(doc as RawConfig);\n\n logger.info('Parsed config', { path: resolved, windows: Object.keys(config.windows) });\n\n return config;\n}\n\n/** Shape of the raw YAML before defaults are applied. */\ntype RawConfig = Record<string, unknown>;\n\nfunction validateRequired(doc: Record<string, unknown>): void {\n requireString(doc, 'app.boot', (doc as Record<string, Record<string, unknown>>).app?.boot);\n requireString(doc, 'main.entry', (doc as Record<string, Record<string, unknown>>).main?.entry);\n requireString(doc, 'main.controllers', (doc as Record<string, Record<string, unknown>>).main?.controllers);\n requireString(doc, 'main.middleware', (doc as Record<string, Record<string, unknown>>).main?.middleware);\n requireString(doc, 'main.openapi', (doc as Record<string, Record<string, unknown>>).main?.openapi);\n requireString(doc, 'renderer.openapi', (doc as Record<string, Record<string, unknown>>).renderer?.openapi);\n requireString(doc, 'renderer.componentDir', (doc as Record<string, Record<string, unknown>>).renderer?.componentDir);\n requireString(doc, 'renderer.controllers', (doc as Record<string, Record<string, unknown>>).renderer?.controllers);\n const rendererEntry = (doc as Record<string, Record<string, unknown>>).renderer?.entry;\n if (rendererEntry !== undefined) {\n requireString(doc, 'renderer.entry', rendererEntry);\n }\n\n // Windows validation\n const windows = doc.windows;\n if (!windows || typeof windows !== 'object') {\n throw new Error('Config validation error: \"windows\" is required and must be an object with at least one window');\n }\n\n const windowKeys = Object.keys(windows as Record<string, unknown>);\n if (windowKeys.length === 0) {\n throw new Error('Config validation error: \"windows\" must contain at least one window definition');\n }\n\n for (const key of windowKeys) {\n const win = (windows as Record<string, Record<string, unknown>>)[key];\n if (!win || typeof win !== 'object') {\n throw new Error(`Config validation error: window \"${key}\" must be an object`);\n }\n requireString(doc, `windows.${key}.title`, win.title);\n requireNumber(doc, `windows.${key}.width`, win.width);\n requireNumber(doc, `windows.${key}.height`, win.height);\n requireString(doc, `windows.${key}.defaultRoute`, win.defaultRoute);\n\n if (win.contextMenu !== undefined) {\n if (typeof win.contextMenu !== 'string') {\n throw new Error(`Config validation error: \"windows.${key}.contextMenu\" must be a string`);\n }\n if (win.contextMenu !== 'native' && win.contextMenu !== 'custom') {\n throw new Error(`Config validation error: \"windows.${key}.contextMenu\" must be \"native\" or \"custom\"`);\n }\n }\n }\n}\n\nfunction requireString(_doc: Record<string, unknown>, field: string, value: unknown): asserts value is string {\n if (typeof value !== 'string' || value.length === 0) {\n throw new Error(`Config validation error: \"${field}\" is required and must be a non-empty string`);\n }\n}\n\nfunction requireNumber(_doc: Record<string, unknown>, field: string, value: unknown): asserts value is number {\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new Error(`Config validation error: \"${field}\" is required and must be a number`);\n }\n}\n\n/**\n * Parse an optional `<section>.testing` mapping: every allowed key is an\n * optional non-empty string (a config-relative module path); anything else\n * is a validation error so a typo cannot silently drop a hook.\n */\nfunction parseTestingSection(\n section: 'main' | 'renderer',\n raw: unknown,\n allowedKeys: readonly string[],\n): { testing?: Record<string, string> } {\n if (raw === undefined) return {};\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new Error(`Config validation error: \"${section}.testing\" must be an object`);\n }\n const testing: Record<string, string> = {};\n for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {\n if (!allowedKeys.includes(key)) {\n throw new Error(\n `Config validation error: \"${section}.testing.${key}\" is not a known hook (allowed: ${allowedKeys.join(', ')})`,\n );\n }\n requireString(raw as Record<string, unknown>, `${section}.testing.${key}`, value);\n testing[key] = value;\n }\n return { testing };\n}\n\nfunction applyDefaults(doc: RawConfig): WoodConfig {\n const app = doc.app as Record<string, unknown>;\n const main = doc.main as Record<string, unknown>;\n const renderer = doc.renderer as Record<string, unknown>;\n const rawWindows = doc.windows as Record<string, Record<string, unknown>>;\n\n const windows: Record<string, WindowDef> = {};\n for (const [key, rawWin] of Object.entries(rawWindows)) {\n windows[key] = {\n ...WINDOW_DEFAULTS,\n ...rawWin,\n } as WindowDef;\n }\n\n const dev: DevConfig = {\n ...DEV_DEFAULTS,\n ...(doc.dev as Record<string, unknown> | undefined),\n };\n\n const config: WoodConfig = {\n root: typeof doc.root === 'string' ? doc.root : '.',\n outDir: typeof doc.outDir === 'string' ? doc.outDir : 'dist',\n app: {\n boot: app.boot as string,\n watch: Array.isArray(app.watch) ? (app.watch as string[]) : [],\n },\n main: {\n entry: main.entry as string,\n controllers: main.controllers as string,\n middleware: main.middleware as string,\n openapi: main.openapi as string,\n watch: Array.isArray(main.watch) ? (main.watch as string[]) : [],\n ...parseTestingSection('main', main.testing, ['compose']),\n },\n renderer: {\n openapi: renderer.openapi as string,\n componentDir: renderer.componentDir as string,\n controllers: renderer.controllers as string,\n ...(typeof renderer.entry === 'string' ? { entry: renderer.entry as string } : {}),\n ...(Array.isArray(renderer.watch) ? { watch: renderer.watch as string[] } : {}),\n ...parseTestingSection('renderer', renderer.testing, ['host']),\n },\n windows,\n dev,\n };\n\n // Optional database section (preserve :userData: token for runtime expansion)\n if (doc.database && typeof doc.database === 'object') {\n const db = doc.database as Record<string, unknown>;\n if (typeof db.path === 'string') {\n config.database = {\n path: db.path,\n ...(typeof db.migrations === 'string' ? { migrations: db.migrations } : {}),\n };\n }\n }\n\n // Optional assets\n if (Array.isArray(doc.assets)) {\n config.assets = doc.assets as string[];\n }\n\n // Optional build section\n if (doc.build && typeof doc.build === 'object') {\n config.build = doc.build as WoodConfig['build'];\n }\n\n // Optional observability section (renderer→main trace batching policy)\n if (doc.observability && typeof doc.observability === 'object') {\n config.observability = parseObservability(doc.observability as Record<string, unknown>);\n }\n\n return config;\n}\n\n/**\n * Default renderer→main IPC trace batching policy. These values match the\n * policy previously hard-coded in the generated preload; wood.config.yml\n * `observability.trace.rendererToMain.batching` is now the owner.\n */\nexport const RENDERER_TRACE_BATCHING_DEFAULTS: RendererTraceBatchingConfig = Object.freeze({\n maxQueuedEvents: 256,\n maxBatchEvents: 32,\n acknowledgementTimeoutMs: 1_000,\n retryLimit: 3,\n retryBaseDelayMs: 25,\n});\n\n/** Resolves the renderer trace batching policy from a parsed config. */\nexport function resolveRendererTraceBatching(\n config: Pick<WoodConfig, 'observability'>,\n): RendererTraceBatchingConfig {\n return {\n ...RENDERER_TRACE_BATCHING_DEFAULTS,\n ...config.observability?.trace?.rendererToMain?.batching,\n };\n}\n\nfunction parseObservability(raw: Record<string, unknown>): WoodConfig['observability'] {\n const trace = raw.trace;\n if (trace === undefined) {\n return {};\n }\n if (typeof trace !== 'object' || trace === null) {\n throw new Error('Config validation error: \"observability.trace\" must be an object');\n }\n const rendererToMain = (trace as Record<string, unknown>).rendererToMain;\n if (rendererToMain === undefined) {\n return { trace: {} };\n }\n if (typeof rendererToMain !== 'object' || rendererToMain === null) {\n throw new Error('Config validation error: \"observability.trace.rendererToMain\" must be an object');\n }\n const batching = (rendererToMain as Record<string, unknown>).batching;\n if (batching === undefined) {\n return { trace: { rendererToMain: {} } };\n }\n if (typeof batching !== 'object' || batching === null) {\n throw new Error('Config validation error: \"observability.trace.rendererToMain.batching\" must be an object');\n }\n\n const resolved: RendererTraceBatchingConfig = {\n ...RENDERER_TRACE_BATCHING_DEFAULTS,\n ...(batching as Partial<RendererTraceBatchingConfig>),\n };\n\n const positiveIntFields = ['maxQueuedEvents', 'maxBatchEvents', 'acknowledgementTimeoutMs'] as const;\n for (const field of positiveIntFields) {\n const value = resolved[field];\n if (!Number.isSafeInteger(value) || value <= 0) {\n throw new Error(\n `Config validation error: \"observability.trace.rendererToMain.batching.${field}\" must be a positive integer`,\n );\n }\n }\n if (!Number.isSafeInteger(resolved.retryLimit) || resolved.retryLimit < 0) {\n throw new Error(\n 'Config validation error: \"observability.trace.rendererToMain.batching.retryLimit\" must be a non-negative integer',\n );\n }\n if (!Number.isFinite(resolved.retryBaseDelayMs) || resolved.retryBaseDelayMs < 0) {\n throw new Error(\n 'Config validation error: \"observability.trace.rendererToMain.batching.retryBaseDelayMs\" must be a non-negative number',\n );\n }\n if (resolved.maxBatchEvents > resolved.maxQueuedEvents) {\n throw new Error(\n 'Config validation error: \"observability.trace.rendererToMain.batching.maxBatchEvents\" cannot exceed maxQueuedEvents',\n );\n }\n\n return { trace: { rendererToMain: { batching: resolved } } };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/config/config_parser.ts"],"sourcesContent":["import yaml from 'js-yaml';\nimport { readFileSync, existsSync } from 'fs';\nimport path from 'path';\nimport { getLogger } from '@noego/logger';\nimport type {\n WoodConfig,\n WindowDef,\n DevConfig,\n RendererTraceBatchingConfig,\n} from '../types/config.js';\n\nconst logger = getLogger('wood:config');\n\n/** Default values applied when optional fields are omitted. */\nconst WINDOW_DEFAULTS: Partial<WindowDef> = {\n frame: true,\n resizable: true,\n show: true,\n modal: false,\n};\n\nconst DEV_DEFAULTS: DevConfig = {\n watch: true,\n splitServe: true,\n port: 5173,\n};\n\n/**\n * Parse and validate a wood.config.yml file, returning a fully-typed WoodConfig\n * with defaults applied for optional fields.\n */\nexport function parseConfig(configPath: string): WoodConfig {\n const resolved = path.resolve(configPath);\n\n if (!existsSync(resolved)) {\n throw new Error(`Config file not found: ${resolved}`);\n }\n\n const raw = readFileSync(resolved, 'utf-8');\n const doc = yaml.load(raw) as Record<string, unknown> | null;\n\n if (!doc || typeof doc !== 'object') {\n throw new Error(`Invalid YAML in config file: ${resolved}`);\n }\n\n validateRequired(doc);\n\n const config = applyDefaults(doc as RawConfig);\n\n logger.info('Parsed config', { path: resolved, windows: Object.keys(config.windows) });\n\n return config;\n}\n\n/** Shape of the raw YAML before defaults are applied. */\ntype RawConfig = Record<string, unknown>;\n\nfunction validateRequired(doc: Record<string, unknown>): void {\n requireString(doc, 'app.boot', (doc as Record<string, Record<string, unknown>>).app?.boot);\n requireString(doc, 'main.entry', (doc as Record<string, Record<string, unknown>>).main?.entry);\n requireString(doc, 'main.controllers', (doc as Record<string, Record<string, unknown>>).main?.controllers);\n requireString(doc, 'main.middleware', (doc as Record<string, Record<string, unknown>>).main?.middleware);\n requireString(doc, 'main.openapi', (doc as Record<string, Record<string, unknown>>).main?.openapi);\n requireString(doc, 'renderer.openapi', (doc as Record<string, Record<string, unknown>>).renderer?.openapi);\n requireString(doc, 'renderer.componentDir', (doc as Record<string, Record<string, unknown>>).renderer?.componentDir);\n requireString(doc, 'renderer.controllers', (doc as Record<string, Record<string, unknown>>).renderer?.controllers);\n const rendererEntry = (doc as Record<string, Record<string, unknown>>).renderer?.entry;\n if (rendererEntry !== undefined) {\n requireString(doc, 'renderer.entry', rendererEntry);\n }\n const rendererCompose = (doc as Record<string, Record<string, unknown>>).renderer?.compose;\n if (rendererCompose !== undefined) {\n requireString(doc, 'renderer.compose', rendererCompose);\n }\n\n const rendererCss = (doc as Record<string, Record<string, unknown>>).renderer?.css;\n if (rendererCss !== undefined && (!Array.isArray(rendererCss) || rendererCss.some(value => typeof value !== 'string' || !value.trim()))) {\n throw new Error('Config validation error: \"renderer.css\" must be an array of nonempty paths');\n }\n\n // Windows validation\n const windows = doc.windows;\n if (!windows || typeof windows !== 'object') {\n throw new Error('Config validation error: \"windows\" is required and must be an object with at least one window');\n }\n\n const windowKeys = Object.keys(windows as Record<string, unknown>);\n if (windowKeys.length === 0) {\n throw new Error('Config validation error: \"windows\" must contain at least one window definition');\n }\n\n for (const key of windowKeys) {\n const win = (windows as Record<string, Record<string, unknown>>)[key];\n if (!win || typeof win !== 'object') {\n throw new Error(`Config validation error: window \"${key}\" must be an object`);\n }\n requireString(doc, `windows.${key}.title`, win.title);\n requireNumber(doc, `windows.${key}.width`, win.width);\n requireNumber(doc, `windows.${key}.height`, win.height);\n requireString(doc, `windows.${key}.defaultRoute`, win.defaultRoute);\n\n if (win.contextMenu !== undefined) {\n if (typeof win.contextMenu !== 'string') {\n throw new Error(`Config validation error: \"windows.${key}.contextMenu\" must be a string`);\n }\n if (win.contextMenu !== 'native' && win.contextMenu !== 'custom') {\n throw new Error(`Config validation error: \"windows.${key}.contextMenu\" must be \"native\" or \"custom\"`);\n }\n }\n }\n}\n\nfunction requireString(_doc: Record<string, unknown>, field: string, value: unknown): asserts value is string {\n if (typeof value !== 'string' || value.length === 0) {\n throw new Error(`Config validation error: \"${field}\" is required and must be a non-empty string`);\n }\n}\n\nfunction requireNumber(_doc: Record<string, unknown>, field: string, value: unknown): asserts value is number {\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new Error(`Config validation error: \"${field}\" is required and must be a number`);\n }\n}\n\n/**\n * Parse an optional `<section>.testing` mapping: every allowed key is an\n * optional non-empty string (a config-relative module path); anything else\n * is a validation error so a typo cannot silently drop a hook.\n */\nfunction parseTestingSection(\n section: 'main' | 'renderer',\n raw: unknown,\n allowedKeys: readonly string[],\n): { testing?: Record<string, string> } {\n if (raw === undefined) return {};\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new Error(`Config validation error: \"${section}.testing\" must be an object`);\n }\n const testing: Record<string, string> = {};\n for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {\n if (!allowedKeys.includes(key)) {\n throw new Error(\n `Config validation error: \"${section}.testing.${key}\" is not a known hook (allowed: ${allowedKeys.join(', ')})`,\n );\n }\n requireString(raw as Record<string, unknown>, `${section}.testing.${key}`, value);\n testing[key] = value;\n }\n return { testing };\n}\n\nfunction applyDefaults(doc: RawConfig): WoodConfig {\n const app = doc.app as Record<string, unknown>;\n const main = doc.main as Record<string, unknown>;\n const renderer = doc.renderer as Record<string, unknown>;\n const rawWindows = doc.windows as Record<string, Record<string, unknown>>;\n\n const windows: Record<string, WindowDef> = {};\n for (const [key, rawWin] of Object.entries(rawWindows)) {\n windows[key] = {\n ...WINDOW_DEFAULTS,\n ...rawWin,\n } as WindowDef;\n }\n\n const dev: DevConfig = {\n ...DEV_DEFAULTS,\n ...(doc.dev as Record<string, unknown> | undefined),\n };\n\n const config: WoodConfig = {\n root: typeof doc.root === 'string' ? doc.root : '.',\n outDir: typeof doc.outDir === 'string' ? doc.outDir : 'dist',\n app: {\n boot: app.boot as string,\n watch: Array.isArray(app.watch) ? (app.watch as string[]) : [],\n },\n main: {\n entry: main.entry as string,\n controllers: main.controllers as string,\n middleware: main.middleware as string,\n openapi: main.openapi as string,\n watch: Array.isArray(main.watch) ? (main.watch as string[]) : [],\n ...parseTestingSection('main', main.testing, ['compose']),\n },\n renderer: {\n openapi: renderer.openapi as string,\n componentDir: renderer.componentDir as string,\n ...(Array.isArray(renderer.css) ? {css: [...renderer.css] as string[]} : {}),\n controllers: renderer.controllers as string,\n ...(typeof renderer.compose === 'string' ? { compose: renderer.compose as string } : {}),\n ...(typeof renderer.entry === 'string' ? { entry: renderer.entry as string } : {}),\n ...(Array.isArray(renderer.watch) ? { watch: renderer.watch as string[] } : {}),\n ...parseTestingSection('renderer', renderer.testing, ['host']),\n },\n windows,\n dev,\n };\n\n // Optional database section (preserve :userData: token for runtime expansion)\n if (doc.database && typeof doc.database === 'object') {\n const db = doc.database as Record<string, unknown>;\n if (typeof db.path === 'string') {\n config.database = {\n path: db.path,\n ...(typeof db.migrations === 'string' ? { migrations: db.migrations } : {}),\n };\n }\n }\n\n // Optional assets\n if (Array.isArray(doc.assets)) {\n config.assets = doc.assets as string[];\n }\n\n // Optional build section\n if (doc.build && typeof doc.build === 'object') {\n config.build = doc.build as WoodConfig['build'];\n }\n\n // Optional observability section (renderer→main trace batching policy)\n if (doc.observability && typeof doc.observability === 'object') {\n config.observability = parseObservability(doc.observability as Record<string, unknown>);\n }\n\n return config;\n}\n\n/**\n * Default renderer→main IPC trace batching policy. These values match the\n * policy previously hard-coded in the generated preload; wood.config.yml\n * `observability.trace.rendererToMain.batching` is now the owner.\n */\nexport const RENDERER_TRACE_BATCHING_DEFAULTS: RendererTraceBatchingConfig = Object.freeze({\n maxQueuedEvents: 256,\n maxBatchEvents: 32,\n acknowledgementTimeoutMs: 1_000,\n retryLimit: 3,\n retryBaseDelayMs: 25,\n});\n\n/** Resolves the renderer trace batching policy from a parsed config. */\nexport function resolveRendererTraceBatching(\n config: Pick<WoodConfig, 'observability'>,\n): RendererTraceBatchingConfig {\n return {\n ...RENDERER_TRACE_BATCHING_DEFAULTS,\n ...config.observability?.trace?.rendererToMain?.batching,\n };\n}\n\nfunction parseObservability(raw: Record<string, unknown>): WoodConfig['observability'] {\n const trace = raw.trace;\n if (trace === undefined) {\n return {};\n }\n if (typeof trace !== 'object' || trace === null) {\n throw new Error('Config validation error: \"observability.trace\" must be an object');\n }\n const rendererToMain = (trace as Record<string, unknown>).rendererToMain;\n if (rendererToMain === undefined) {\n return { trace: {} };\n }\n if (typeof rendererToMain !== 'object' || rendererToMain === null) {\n throw new Error('Config validation error: \"observability.trace.rendererToMain\" must be an object');\n }\n const batching = (rendererToMain as Record<string, unknown>).batching;\n if (batching === undefined) {\n return { trace: { rendererToMain: {} } };\n }\n if (typeof batching !== 'object' || batching === null) {\n throw new Error('Config validation error: \"observability.trace.rendererToMain.batching\" must be an object');\n }\n\n const resolved: RendererTraceBatchingConfig = {\n ...RENDERER_TRACE_BATCHING_DEFAULTS,\n ...(batching as Partial<RendererTraceBatchingConfig>),\n };\n\n const positiveIntFields = ['maxQueuedEvents', 'maxBatchEvents', 'acknowledgementTimeoutMs'] as const;\n for (const field of positiveIntFields) {\n const value = resolved[field];\n if (!Number.isSafeInteger(value) || value <= 0) {\n throw new Error(\n `Config validation error: \"observability.trace.rendererToMain.batching.${field}\" must be a positive integer`,\n );\n }\n }\n if (!Number.isSafeInteger(resolved.retryLimit) || resolved.retryLimit < 0) {\n throw new Error(\n 'Config validation error: \"observability.trace.rendererToMain.batching.retryLimit\" must be a non-negative integer',\n );\n }\n if (!Number.isFinite(resolved.retryBaseDelayMs) || resolved.retryBaseDelayMs < 0) {\n throw new Error(\n 'Config validation error: \"observability.trace.rendererToMain.batching.retryBaseDelayMs\" must be a non-negative number',\n );\n }\n if (resolved.maxBatchEvents > resolved.maxQueuedEvents) {\n throw new Error(\n 'Config validation error: \"observability.trace.rendererToMain.batching.maxBatchEvents\" cannot exceed maxQueuedEvents',\n );\n }\n\n return { trace: { rendererToMain: { batching: resolved } } };\n}\n"],"mappings":"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","names":[]}
|
package/dist/fetch.cjs
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var fetch_exports = {};
|
|
20
|
+
__export(fetch_exports, {
|
|
21
|
+
FETCH_RUNTIME: () => import_fetch2.FETCH_RUNTIME,
|
|
22
|
+
getFetchRuntime: () => getFetchRuntime,
|
|
23
|
+
nativeFetchRuntime: () => import_fetch2.nativeFetchRuntime
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(fetch_exports);
|
|
26
|
+
var import_ioc = require("@noego/ioc");
|
|
27
|
+
var import_fetch = require("@noego/runtime/fetch");
|
|
28
|
+
var import_fetch2 = require("@noego/runtime/fetch");
|
|
29
|
+
function getFetchRuntime() {
|
|
30
|
+
return (0, import_fetch.resolveFetchRuntime)(import_ioc.ExecutionContext.current());
|
|
31
|
+
}
|
|
32
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
33
|
+
0 && (module.exports = {
|
|
34
|
+
FETCH_RUNTIME,
|
|
35
|
+
getFetchRuntime,
|
|
36
|
+
nativeFetchRuntime
|
|
37
|
+
});
|
|
38
|
+
//# sourceMappingURL=fetch.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/fetch.ts"],"sourcesContent":["import { ExecutionContext } from '@noego/ioc';\nimport { resolveFetchRuntime, type FetchRuntime } from '@noego/runtime/fetch';\nexport { FETCH_RUNTIME, nativeFetchRuntime } from '@noego/runtime/fetch';\nexport type { FetchRuntime } from '@noego/runtime/fetch';\n\n/** Frontend and backend: capture during IoC construction, then call runtime.fetch(). */\nexport function getFetchRuntime(): FetchRuntime {\n return resolveFetchRuntime(ExecutionContext.current());\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAAiC;AACjC,mBAAuD;AACvD,IAAAA,gBAAkD;AAI3C,SAAS,kBAAgC;AAC9C,aAAO,kCAAoB,4BAAiB,QAAQ,CAAC;AACvD;","names":["import_fetch"]}
|
package/dist/fetch.d.cts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { FetchRuntime } from '@noego/runtime/fetch';
|
|
2
|
+
export { FETCH_RUNTIME, FetchRuntime, nativeFetchRuntime } from '@noego/runtime/fetch';
|
|
3
|
+
|
|
4
|
+
/** Frontend and backend: capture during IoC construction, then call runtime.fetch(). */
|
|
5
|
+
declare function getFetchRuntime(): FetchRuntime;
|
|
6
|
+
|
|
7
|
+
export { getFetchRuntime };
|
package/dist/fetch.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { FetchRuntime } from '@noego/runtime/fetch';
|
|
2
|
+
export { FETCH_RUNTIME, FetchRuntime, nativeFetchRuntime } from '@noego/runtime/fetch';
|
|
3
|
+
|
|
4
|
+
/** Frontend and backend: capture during IoC construction, then call runtime.fetch(). */
|
|
5
|
+
declare function getFetchRuntime(): FetchRuntime;
|
|
6
|
+
|
|
7
|
+
export { getFetchRuntime };
|
package/dist/fetch.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ExecutionContext } from "@noego/ioc";
|
|
2
|
+
import { resolveFetchRuntime } from "@noego/runtime/fetch";
|
|
3
|
+
import { FETCH_RUNTIME, nativeFetchRuntime } from "@noego/runtime/fetch";
|
|
4
|
+
function getFetchRuntime() {
|
|
5
|
+
return resolveFetchRuntime(ExecutionContext.current());
|
|
6
|
+
}
|
|
7
|
+
export {
|
|
8
|
+
FETCH_RUNTIME,
|
|
9
|
+
getFetchRuntime,
|
|
10
|
+
nativeFetchRuntime
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/fetch.ts"],"sourcesContent":["import { ExecutionContext } from '@noego/ioc';\nimport { resolveFetchRuntime, type FetchRuntime } from '@noego/runtime/fetch';\nexport { FETCH_RUNTIME, nativeFetchRuntime } from '@noego/runtime/fetch';\nexport type { FetchRuntime } from '@noego/runtime/fetch';\n\n/** Frontend and backend: capture during IoC construction, then call runtime.fetch(). */\nexport function getFetchRuntime(): FetchRuntime {\n return resolveFetchRuntime(ExecutionContext.current());\n}\n"],"mappings":"AAAA,SAAS,wBAAwB;AACjC,SAAS,2BAA8C;AACvD,SAAS,eAAe,0BAA0B;AAI3C,SAAS,kBAAgC;AAC9C,SAAO,oBAAoB,iBAAiB,QAAQ,CAAC;AACvD;","names":[]}
|