@navios/adapter-xml 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +532 -0
  3. package/bun-plugin.mts +129 -0
  4. package/bunPlugin.cache +1 -0
  5. package/bunfig.toml +3 -0
  6. package/dist/bun-plugin.d.mts +4 -0
  7. package/dist/bun-plugin.d.mts.map +1 -0
  8. package/dist/e2e/bun/xml-stream.spec.d.ts +2 -0
  9. package/dist/e2e/bun/xml-stream.spec.d.ts.map +1 -0
  10. package/dist/e2e/fastify/xml-stream.spec.d.ts +2 -0
  11. package/dist/e2e/fastify/xml-stream.spec.d.ts.map +1 -0
  12. package/dist/src/adapters/index.d.mts +2 -0
  13. package/dist/src/adapters/index.d.mts.map +1 -0
  14. package/dist/src/adapters/xml-stream-adapter.service.d.mts +21 -0
  15. package/dist/src/adapters/xml-stream-adapter.service.d.mts.map +1 -0
  16. package/dist/src/decorators/component.decorator.d.mts +17 -0
  17. package/dist/src/decorators/component.decorator.d.mts.map +1 -0
  18. package/dist/src/decorators/component.decorator.spec.d.mts +2 -0
  19. package/dist/src/decorators/component.decorator.spec.d.mts.map +1 -0
  20. package/dist/src/decorators/index.d.mts +4 -0
  21. package/dist/src/decorators/index.d.mts.map +1 -0
  22. package/dist/src/decorators/xml-stream.decorator.d.mts +42 -0
  23. package/dist/src/decorators/xml-stream.decorator.d.mts.map +1 -0
  24. package/dist/src/define-environment.d.mts +31 -0
  25. package/dist/src/define-environment.d.mts.map +1 -0
  26. package/dist/src/handlers/index.d.mts +2 -0
  27. package/dist/src/handlers/index.d.mts.map +1 -0
  28. package/dist/src/handlers/xml-stream.d.mts +23 -0
  29. package/dist/src/handlers/xml-stream.d.mts.map +1 -0
  30. package/dist/src/index.d.mts +12 -0
  31. package/dist/src/index.d.mts.map +1 -0
  32. package/dist/src/jsx-dev-runtime.d.mts +5 -0
  33. package/dist/src/jsx-dev-runtime.d.mts.map +1 -0
  34. package/dist/src/jsx-runtime.d.mts +3 -0
  35. package/dist/src/jsx-runtime.d.mts.map +1 -0
  36. package/dist/src/jsx.d.mts +18 -0
  37. package/dist/src/jsx.d.mts.map +1 -0
  38. package/dist/src/runtime/create-element.d.mts +25 -0
  39. package/dist/src/runtime/create-element.d.mts.map +1 -0
  40. package/dist/src/runtime/fragment.d.mts +2 -0
  41. package/dist/src/runtime/fragment.d.mts.map +1 -0
  42. package/dist/src/runtime/index.d.mts +5 -0
  43. package/dist/src/runtime/index.d.mts.map +1 -0
  44. package/dist/src/runtime/render-to-xml.d.mts +20 -0
  45. package/dist/src/runtime/render-to-xml.d.mts.map +1 -0
  46. package/dist/src/runtime/render-to-xml.spec.d.mts +2 -0
  47. package/dist/src/runtime/render-to-xml.spec.d.mts.map +1 -0
  48. package/dist/src/runtime/special-nodes.d.mts +24 -0
  49. package/dist/src/runtime/special-nodes.d.mts.map +1 -0
  50. package/dist/src/tags/define-tag.d.mts +33 -0
  51. package/dist/src/tags/define-tag.d.mts.map +1 -0
  52. package/dist/src/tags/define-tag.spec.d.mts +2 -0
  53. package/dist/src/tags/define-tag.spec.d.mts.map +1 -0
  54. package/dist/src/tags/index.d.mts +3 -0
  55. package/dist/src/tags/index.d.mts.map +1 -0
  56. package/dist/src/types/component.d.mts +15 -0
  57. package/dist/src/types/component.d.mts.map +1 -0
  58. package/dist/src/types/config.d.mts +10 -0
  59. package/dist/src/types/config.d.mts.map +1 -0
  60. package/dist/src/types/index.d.mts +5 -0
  61. package/dist/src/types/index.d.mts.map +1 -0
  62. package/dist/src/types/xml-node.d.mts +35 -0
  63. package/dist/src/types/xml-node.d.mts.map +1 -0
  64. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  65. package/dist/tsconfig.spec.tsbuildinfo +1 -0
  66. package/dist/tsconfig.tsbuildinfo +1 -0
  67. package/dist/tsup.config.d.mts +3 -0
  68. package/dist/tsup.config.d.mts.map +1 -0
  69. package/dist/vitest.config.d.mts +3 -0
  70. package/dist/vitest.config.d.mts.map +1 -0
  71. package/dist/vitest.e2e.fastify.config.d.mts +3 -0
  72. package/dist/vitest.e2e.fastify.config.d.mts.map +1 -0
  73. package/e2e/bun/xml-stream.spec.tsx +553 -0
  74. package/e2e/fastify/xml-stream.spec.tsx +569 -0
  75. package/jsx.d.ts +42 -0
  76. package/lib/_tsup-dts-rollup.d.mts +414 -0
  77. package/lib/_tsup-dts-rollup.d.ts +414 -0
  78. package/lib/chunk-6OR6LGJA.mjs +153 -0
  79. package/lib/chunk-6OR6LGJA.mjs.map +1 -0
  80. package/lib/index.d.mts +29 -0
  81. package/lib/index.d.ts +29 -0
  82. package/lib/index.js +376 -0
  83. package/lib/index.js.map +1 -0
  84. package/lib/index.mjs +256 -0
  85. package/lib/index.mjs.map +1 -0
  86. package/lib/jsx-dev-runtime.d.mts +4 -0
  87. package/lib/jsx-dev-runtime.d.ts +4 -0
  88. package/lib/jsx-dev-runtime.js +61 -0
  89. package/lib/jsx-dev-runtime.js.map +1 -0
  90. package/lib/jsx-dev-runtime.mjs +9 -0
  91. package/lib/jsx-dev-runtime.mjs.map +1 -0
  92. package/lib/jsx-runtime.d.mts +3 -0
  93. package/lib/jsx-runtime.d.ts +3 -0
  94. package/lib/jsx-runtime.js +57 -0
  95. package/lib/jsx-runtime.js.map +1 -0
  96. package/lib/jsx-runtime.mjs +3 -0
  97. package/lib/jsx-runtime.mjs.map +1 -0
  98. package/lib/jsx.d.mts +1 -0
  99. package/lib/jsx.d.ts +1 -0
  100. package/lib/jsx.js +4 -0
  101. package/lib/jsx.js.map +1 -0
  102. package/lib/jsx.mjs +3 -0
  103. package/lib/jsx.mjs.map +1 -0
  104. package/package.json +80 -0
  105. package/project.json +91 -0
  106. package/src/adapters/index.mts +1 -0
  107. package/src/adapters/xml-stream-adapter.service.mts +121 -0
  108. package/src/decorators/component.decorator.mts +102 -0
  109. package/src/decorators/component.decorator.spec.mts +345 -0
  110. package/src/decorators/index.mts +4 -0
  111. package/src/decorators/xml-stream.decorator.mts +93 -0
  112. package/src/define-environment.mts +40 -0
  113. package/src/handlers/index.mts +1 -0
  114. package/src/handlers/xml-stream.mts +31 -0
  115. package/src/index.mts +41 -0
  116. package/src/jsx-dev-runtime.mts +8 -0
  117. package/src/jsx-runtime.mts +2 -0
  118. package/src/jsx.mts +25 -0
  119. package/src/runtime/create-element.mts +113 -0
  120. package/src/runtime/fragment.mts +1 -0
  121. package/src/runtime/index.mts +4 -0
  122. package/src/runtime/render-to-xml.mts +214 -0
  123. package/src/runtime/render-to-xml.spec.mts +360 -0
  124. package/src/runtime/special-nodes.mts +32 -0
  125. package/src/tags/define-tag.mts +54 -0
  126. package/src/tags/define-tag.spec.mts +250 -0
  127. package/src/tags/index.mts +2 -0
  128. package/src/types/component.mts +16 -0
  129. package/src/types/config.mts +15 -0
  130. package/src/types/index.mts +23 -0
  131. package/src/types/jsx.d.ts +21 -0
  132. package/src/types/xml-node.mts +50 -0
  133. package/tsconfig.json +24 -0
  134. package/tsconfig.lib.json +8 -0
  135. package/tsconfig.spec.json +25 -0
  136. package/tsup.config.mts +18 -0
  137. package/vitest.config.mts +9 -0
  138. package/vitest.e2e.fastify.config.mts +29 -0
@@ -0,0 +1,93 @@
1
+ import type { EndpointFunctionArgs, HttpMethod, Util_FlatObject } from '@navios/builder'
2
+ import type { ZodObject, ZodType } from 'zod/v4'
3
+
4
+ import { getEndpointMetadata, XmlStreamAdapterToken } from '@navios/core'
5
+
6
+ import type { BaseXmlStreamConfig } from '../types/config.mjs'
7
+
8
+ export type XmlStreamParams<
9
+ EndpointDeclaration extends {
10
+ config: BaseXmlStreamConfig<any, any, any, any>
11
+ },
12
+ Url extends string = EndpointDeclaration['config']['url'],
13
+ QuerySchema = EndpointDeclaration['config']['querySchema'],
14
+ > = QuerySchema extends ZodObject
15
+ ? EndpointDeclaration['config']['requestSchema'] extends ZodType
16
+ ? Util_FlatObject<EndpointFunctionArgs<Url, QuerySchema, EndpointDeclaration['config']['requestSchema'], true>>
17
+ : Util_FlatObject<EndpointFunctionArgs<Url, QuerySchema, undefined, true>>
18
+ : EndpointDeclaration['config']['requestSchema'] extends ZodType
19
+ ? Util_FlatObject<EndpointFunctionArgs<Url, undefined, EndpointDeclaration['config']['requestSchema'], true>>
20
+ : Util_FlatObject<EndpointFunctionArgs<Url, undefined, undefined, true>>
21
+
22
+ /**
23
+ * Decorator for XML Stream endpoints that return JSX-based XML responses.
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * import { XmlStream } from '@navios/adapter-xml'
28
+ * import { Controller } from '@navios/core'
29
+ *
30
+ * const getRssFeed = declareXmlStream({
31
+ * method: 'GET',
32
+ * url: '/feed.xml',
33
+ * querySchema: undefined,
34
+ * requestSchema: undefined,
35
+ * contentType: 'application/rss+xml',
36
+ * })
37
+ *
38
+ * @Controller('/api')
39
+ * class FeedController {
40
+ * @XmlStream(getRssFeed)
41
+ * async getFeed() {
42
+ * return (
43
+ * <rss version="2.0">
44
+ * <channel>
45
+ * <title>My Feed</title>
46
+ * </channel>
47
+ * </rss>
48
+ * )
49
+ * }
50
+ * }
51
+ * ```
52
+ */
53
+ export function XmlStream<
54
+ Method extends HttpMethod = HttpMethod,
55
+ Url extends string = string,
56
+ QuerySchema = undefined,
57
+ RequestSchema = ZodType,
58
+ >(endpoint: { config: BaseXmlStreamConfig<Method, Url, QuerySchema, RequestSchema> }) {
59
+ return (
60
+ target: (
61
+ params: QuerySchema extends ZodObject
62
+ ? RequestSchema extends ZodType
63
+ ? Util_FlatObject<EndpointFunctionArgs<Url, QuerySchema, RequestSchema, true>>
64
+ : Util_FlatObject<EndpointFunctionArgs<Url, QuerySchema, undefined, true>>
65
+ : RequestSchema extends ZodType
66
+ ? Util_FlatObject<EndpointFunctionArgs<Url, undefined, RequestSchema, true>>
67
+ : Util_FlatObject<EndpointFunctionArgs<Url, undefined, undefined, true>>,
68
+ ) => Promise<any>, // Returns XmlNode
69
+ context: ClassMethodDecoratorContext,
70
+ ) => {
71
+ if (typeof target !== 'function') {
72
+ throw new Error('[Navios] XmlStream decorator can only be used on functions.')
73
+ }
74
+ if (context.kind !== 'method') {
75
+ throw new Error('[Navios] XmlStream decorator can only be used on methods.')
76
+ }
77
+
78
+ const config = endpoint.config
79
+ if (context.metadata) {
80
+ const endpointMetadata = getEndpointMetadata<BaseXmlStreamConfig>(target, context)
81
+ if (endpointMetadata.config && endpointMetadata.config.url) {
82
+ throw new Error(`[Navios] Endpoint ${config.method} ${config.url} already exists.`)
83
+ }
84
+ // @ts-expect-error We don't need to set correctly in the metadata
85
+ endpointMetadata.config = config
86
+ endpointMetadata.adapterToken = XmlStreamAdapterToken
87
+ endpointMetadata.classMethod = target.name
88
+ endpointMetadata.httpMethod = config.method
89
+ endpointMetadata.url = config.url
90
+ }
91
+ return target
92
+ }
93
+ }
@@ -0,0 +1,40 @@
1
+ import type { AnyInjectableType } from '@navios/di'
2
+
3
+ import { XmlStreamAdapterToken } from '@navios/core'
4
+ import { InjectionToken } from '@navios/di'
5
+
6
+ import { XmlStreamAdapterService } from './adapters/index.mjs'
7
+
8
+ /**
9
+ * Creates the XML environment configuration to be merged with base adapter (Fastify/Bun).
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { defineFastifyEnvironment } from '@navios/adapter-fastify'
14
+ * import { defineXmlEnvironment } from '@navios/adapter-xml'
15
+ * import { NaviosFactory } from '@navios/core'
16
+ *
17
+ * const fastifyEnv = defineFastifyEnvironment()
18
+ * const xmlEnv = defineXmlEnvironment()
19
+ *
20
+ * // Merge environments
21
+ * const mergedEnv = {
22
+ * httpTokens: new Map([
23
+ * ...fastifyEnv.httpTokens,
24
+ * ...xmlEnv.httpTokens,
25
+ * ]),
26
+ * }
27
+ *
28
+ * const app = await NaviosFactory.create(AppModule, {
29
+ * adapter: mergedEnv,
30
+ * })
31
+ * ```
32
+ */
33
+ export function defineXmlEnvironment() {
34
+ const httpTokens = new Map<InjectionToken<any, undefined>, AnyInjectableType>([
35
+ [XmlStreamAdapterToken, XmlStreamAdapterService],
36
+ ])
37
+ return {
38
+ httpTokens,
39
+ }
40
+ }
@@ -0,0 +1 @@
1
+ export { declareXmlStream } from './xml-stream.mjs'
@@ -0,0 +1,31 @@
1
+ import type { HttpMethod } from '@navios/builder'
2
+
3
+ import type { BaseXmlStreamConfig } from '../types/config.mjs'
4
+
5
+ /**
6
+ * Declares an XML Stream endpoint configuration for use with @XmlStream decorator.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { declareXmlStream } from '@navios/adapter-xml'
11
+ *
12
+ * export const getRssFeed = declareXmlStream({
13
+ * method: 'GET',
14
+ * url: '/feed.xml',
15
+ * querySchema: undefined,
16
+ * requestSchema: undefined,
17
+ * contentType: 'application/rss+xml',
18
+ * xmlDeclaration: true,
19
+ * })
20
+ * ```
21
+ */
22
+ export function declareXmlStream<
23
+ Method extends HttpMethod,
24
+ Url extends string,
25
+ QuerySchema = undefined,
26
+ RequestSchema = undefined,
27
+ >(
28
+ config: BaseXmlStreamConfig<Method, Url, QuerySchema, RequestSchema>,
29
+ ): { config: BaseXmlStreamConfig<Method, Url, QuerySchema, RequestSchema> } {
30
+ return { config }
31
+ }
package/src/index.mts ADDED
@@ -0,0 +1,41 @@
1
+ // Types
2
+ export type {
3
+ XmlNode,
4
+ AsyncXmlNode,
5
+ CDataNode,
6
+ RawXmlNode,
7
+ ClassComponentNode,
8
+ AnyXmlNode,
9
+ BaseXmlStreamConfig,
10
+ XmlComponent,
11
+ ComponentClass,
12
+ } from './types/index.mjs'
13
+
14
+ export {
15
+ Fragment,
16
+ AsyncComponent,
17
+ CDataSymbol,
18
+ RawXmlSymbol,
19
+ ClassComponent,
20
+ } from './types/index.mjs'
21
+
22
+ // Runtime
23
+ export { createElement, CData, DangerouslyInsertRawXml, renderToXml, MissingContainerError } from './runtime/index.mjs'
24
+ export type { RenderOptions } from './runtime/index.mjs'
25
+
26
+ // Tags
27
+ export { defineTag } from './tags/index.mjs'
28
+ export type { TagComponent } from './tags/index.mjs'
29
+
30
+ // Decorators
31
+ export { XmlStream, Component, isComponentClass } from './decorators/index.mjs'
32
+ export type { XmlStreamParams } from './decorators/index.mjs'
33
+
34
+ // Handlers
35
+ export { declareXmlStream } from './handlers/index.mjs'
36
+
37
+ // Adapters
38
+ export { XmlStreamAdapterService } from './adapters/index.mjs'
39
+
40
+ // Environment
41
+ export { defineXmlEnvironment } from './define-environment.mjs'
@@ -0,0 +1,8 @@
1
+ // Dev runtime is the same as the production runtime for XML
2
+ export { jsx, jsxs } from './runtime/create-element.mjs'
3
+ export { Fragment } from './types/xml-node.mjs'
4
+
5
+ // jsxDEV is called by development builds of React/JSX transformers
6
+ // It has additional debugging parameters that we ignore for XML
7
+ import { jsx } from './runtime/create-element.mjs'
8
+ export const jsxDEV = jsx
@@ -0,0 +1,2 @@
1
+ export { jsx, jsxs } from './runtime/create-element.mjs'
2
+ export { Fragment } from './types/xml-node.mjs'
package/src/jsx.mts ADDED
@@ -0,0 +1,25 @@
1
+ // JSX type definitions
2
+ // This file serves as an entry point for TypeScript to include JSX types
3
+ // Users can add "@navios/adapter-xml/jsx" to their tsconfig.json "types" array
4
+ import type { AnyXmlNode, XmlNode } from './types/xml-node.mjs'
5
+
6
+ declare global {
7
+ namespace JSX {
8
+ type Element = XmlNode
9
+
10
+ interface ElementChildrenAttribute {
11
+ children: {}
12
+ }
13
+
14
+ interface IntrinsicElements {
15
+ // Allow any XML tag name with any props
16
+ [tagName: string]: {
17
+ [prop: string]: string | number | boolean | null | undefined
18
+ } & {
19
+ children?: AnyXmlNode | AnyXmlNode[]
20
+ }
21
+ }
22
+ }
23
+ }
24
+
25
+ export {}
@@ -0,0 +1,113 @@
1
+ import type { AnyXmlNode, AsyncXmlNode, ClassComponentNode, XmlNode } from '../types/xml-node.mjs'
2
+ import type { ComponentClass } from '../types/component.mjs'
3
+
4
+ import { isComponentClass } from '../decorators/component.decorator.mjs'
5
+ import { AsyncComponent, ClassComponent, Fragment } from '../types/xml-node.mjs'
6
+
7
+ type SyncComponent = (props: any) => XmlNode | AsyncXmlNode | ClassComponentNode
8
+ type AsyncComponentFn = (props: any) => Promise<XmlNode | AsyncXmlNode | ClassComponentNode>
9
+ type FunctionalComponent = SyncComponent | AsyncComponentFn
10
+ type ComponentType = FunctionalComponent | ComponentClass
11
+
12
+ function flattenChildren(children: any): AnyXmlNode[] {
13
+ if (children == null || children === false) {
14
+ return []
15
+ }
16
+ if (Array.isArray(children)) {
17
+ return children.flat(Infinity).filter((c) => c != null && c !== false)
18
+ }
19
+ return [children]
20
+ }
21
+
22
+ /**
23
+ * JSX automatic runtime function.
24
+ * Used by the JSX transformer when jsxImportSource is set.
25
+ * Children are passed as part of props.children.
26
+ */
27
+ export function jsx(
28
+ type: string | typeof Fragment | ComponentType,
29
+ props: Record<string, unknown> | null,
30
+ ): XmlNode | AsyncXmlNode | ClassComponentNode {
31
+ const { children, ...restProps } = props ?? {}
32
+ const flatChildren = flattenChildren(children)
33
+
34
+ // Handle class components - create ClassComponentNode for later resolution
35
+ if (isComponentClass(type)) {
36
+ return {
37
+ type: ClassComponent,
38
+ componentClass: type,
39
+ props: { ...restProps, children: flatChildren },
40
+ }
41
+ }
42
+
43
+ // Handle function components (sync or async)
44
+ if (typeof type === 'function') {
45
+ const result = type({ ...restProps, children: flatChildren })
46
+
47
+ // If component returns a Promise, wrap it in AsyncXmlNode
48
+ if (result instanceof Promise) {
49
+ return {
50
+ type: AsyncComponent,
51
+ promise: result,
52
+ }
53
+ }
54
+
55
+ return result
56
+ }
57
+
58
+ return {
59
+ type,
60
+ props: restProps,
61
+ children: flatChildren,
62
+ }
63
+ }
64
+
65
+ /**
66
+ * JSX automatic runtime function for static children.
67
+ * Identical to jsx() for XML - React uses this for optimization hints.
68
+ */
69
+ export const jsxs = jsx
70
+
71
+ /**
72
+ * Classic createElement for manual usage.
73
+ * Children are passed as rest arguments.
74
+ */
75
+ export function createElement(
76
+ type: string | typeof Fragment | ComponentType,
77
+ props: Record<string, unknown> | null,
78
+ ...children: any[]
79
+ ): XmlNode | AsyncXmlNode | ClassComponentNode {
80
+ const flatChildren = flattenChildren(children)
81
+
82
+ // Handle class components - create ClassComponentNode for later resolution
83
+ if (isComponentClass(type)) {
84
+ return {
85
+ type: ClassComponent,
86
+ componentClass: type,
87
+ props: { ...props, children: flatChildren },
88
+ }
89
+ }
90
+
91
+ // Handle function components (sync or async)
92
+ if (typeof type === 'function') {
93
+ const result = type({ ...props, children: flatChildren })
94
+
95
+ // If component returns a Promise, wrap it in AsyncXmlNode
96
+ if (result instanceof Promise) {
97
+ return {
98
+ type: AsyncComponent,
99
+ promise: result,
100
+ }
101
+ }
102
+
103
+ return result
104
+ }
105
+
106
+ return {
107
+ type,
108
+ props: props ?? {},
109
+ children: flatChildren,
110
+ }
111
+ }
112
+
113
+ export { Fragment }
@@ -0,0 +1 @@
1
+ export { Fragment } from '../types/xml-node.mjs'
@@ -0,0 +1,4 @@
1
+ export { createElement, Fragment } from './create-element.mjs'
2
+ export { CData, DangerouslyInsertRawXml } from './special-nodes.mjs'
3
+ export { renderToXml, MissingContainerError } from './render-to-xml.mjs'
4
+ export type { RenderOptions } from './render-to-xml.mjs'
@@ -0,0 +1,214 @@
1
+ import type { Container } from '@navios/di'
2
+
3
+ import type {
4
+ AnyXmlNode,
5
+ AsyncXmlNode,
6
+ CDataNode,
7
+ ClassComponentNode,
8
+ RawXmlNode,
9
+ } from '../types/xml-node.mjs'
10
+ import type { XmlComponent } from '../types/component.mjs'
11
+
12
+ import {
13
+ AsyncComponent,
14
+ CDataSymbol,
15
+ ClassComponent,
16
+ Fragment,
17
+ RawXmlSymbol,
18
+ } from '../types/xml-node.mjs'
19
+
20
+ export interface RenderOptions {
21
+ /** Include XML declaration (<?xml version="1.0"?>) - defaults to true */
22
+ declaration?: boolean
23
+ /** XML encoding, defaults to 'UTF-8' */
24
+ encoding?: string
25
+ /** Pretty print with indentation */
26
+ pretty?: boolean
27
+ /**
28
+ * DI container for resolving class components.
29
+ * Required if the tree contains any class components.
30
+ */
31
+ container?: Container
32
+ }
33
+
34
+ export class MissingContainerError extends Error {
35
+ constructor(componentName: string) {
36
+ super(
37
+ `[@navios/adapter-xml] Cannot render class component "${componentName}" without a container. ` +
38
+ `Pass a container to renderToXml options: renderToXml(node, { container })`,
39
+ )
40
+ this.name = 'MissingContainerError'
41
+ }
42
+ }
43
+
44
+ export async function renderToXml(
45
+ node: AnyXmlNode,
46
+ options: RenderOptions = {},
47
+ ): Promise<string> {
48
+ const {
49
+ declaration = true,
50
+ encoding = 'UTF-8',
51
+ pretty = false,
52
+ container,
53
+ } = options
54
+
55
+ let xml = ''
56
+ if (declaration) {
57
+ xml += `<?xml version="1.0" encoding="${encoding}"?>`
58
+ if (pretty) xml += '\n'
59
+ }
60
+
61
+ xml += await renderNode(node, pretty ? 0 : -1, container)
62
+ return xml
63
+ }
64
+
65
+ async function renderNode(
66
+ node: AnyXmlNode,
67
+ indent: number,
68
+ container: Container | undefined,
69
+ ): Promise<string> {
70
+ if (node == null) return ''
71
+ if (typeof node === 'string') return escapeXml(node)
72
+ if (typeof node === 'number') return String(node)
73
+
74
+ // Handle class components - resolve via DI container
75
+ if (isClassComponentNode(node)) {
76
+ if (!container) {
77
+ throw new MissingContainerError(node.componentClass.name)
78
+ }
79
+
80
+ // Resolve the component instance from the container, passing props as schema args
81
+ // This validates props via Zod schema if defined on the component
82
+ const instance = (await container.get(
83
+ node.componentClass as any,
84
+ node.props,
85
+ )) as XmlComponent
86
+
87
+ // Call render() - no arguments, props are already in the instance
88
+ const result = instance.render()
89
+
90
+ // Handle async render methods
91
+ const resolved = result instanceof Promise ? await result : result
92
+
93
+ // Recursively render the result
94
+ return renderNode(resolved, indent, container)
95
+ }
96
+
97
+ // Handle async components - resolve the promise first
98
+ if (isAsyncNode(node)) {
99
+ const resolved = await node.promise
100
+ return renderNode(resolved, indent, container)
101
+ }
102
+
103
+ // Handle CDATA nodes
104
+ if (isCDataNode(node)) {
105
+ return renderCData(node.content)
106
+ }
107
+
108
+ // Handle Raw XML nodes - no escaping
109
+ if (isRawXmlNode(node)) {
110
+ return node.content
111
+ }
112
+
113
+ const { type, props, children } = node
114
+
115
+ if (type === Fragment) {
116
+ const renderedChildren = await Promise.all(
117
+ children
118
+ .filter((c) => c != null)
119
+ .map((c) => renderNode(c, indent, container)),
120
+ )
121
+ return renderedChildren.join('')
122
+ }
123
+
124
+ const prefix = indent >= 0 ? ' '.repeat(indent) : ''
125
+ const newline = indent >= 0 ? '\n' : ''
126
+
127
+ const attrs = Object.entries(props)
128
+ .filter(([_, v]) => v != null)
129
+ .map(([k, v]) => ` ${k}="${escapeAttr(String(v))}"`)
130
+ .join('')
131
+
132
+ if (children.length === 0) {
133
+ return `${prefix}<${type}${attrs}/>${newline}`
134
+ }
135
+
136
+ const childIndent = indent >= 0 ? indent + 1 : -1
137
+
138
+ // Resolve all children (including async and class components) in parallel
139
+ const resolvedChildren = await Promise.all(
140
+ children
141
+ .filter((c) => c != null)
142
+ .map((c) => renderNode(c, childIndent, container)),
143
+ )
144
+ const childContent = resolvedChildren.join('')
145
+
146
+ // Check if children are simple (text, numbers, CDATA, or raw XML)
147
+ const hasOnlySimpleContent = children.every(
148
+ (c) =>
149
+ typeof c === 'string' ||
150
+ typeof c === 'number' ||
151
+ isCDataNode(c) ||
152
+ isRawXmlNode(c),
153
+ )
154
+ if (hasOnlySimpleContent) {
155
+ return `${prefix}<${type}${attrs}>${childContent}</${type}>${newline}`
156
+ }
157
+
158
+ return `${prefix}<${type}${attrs}>${newline}${childContent}${prefix}</${type}>${newline}`
159
+ }
160
+
161
+ function isClassComponentNode(node: unknown): node is ClassComponentNode {
162
+ return (
163
+ node !== null &&
164
+ typeof node === 'object' &&
165
+ 'type' in node &&
166
+ node.type === ClassComponent
167
+ )
168
+ }
169
+
170
+ function isAsyncNode(node: unknown): node is AsyncXmlNode {
171
+ return (
172
+ node !== null &&
173
+ typeof node === 'object' &&
174
+ 'type' in node &&
175
+ node.type === AsyncComponent
176
+ )
177
+ }
178
+
179
+ function isCDataNode(node: any): node is CDataNode {
180
+ return node && typeof node === 'object' && node.type === CDataSymbol
181
+ }
182
+
183
+ function isRawXmlNode(node: any): node is RawXmlNode {
184
+ return node && typeof node === 'object' && node.type === RawXmlSymbol
185
+ }
186
+
187
+ /**
188
+ * Renders content as CDATA section.
189
+ * If content contains "]]>", splits into multiple CDATA sections.
190
+ * The technique is to end the CDATA section before ]]>, then start a new one.
191
+ */
192
+ function renderCData(content: string): string {
193
+ // Handle the edge case where content contains "]]>"
194
+ // We split on "]]>" and join with "]]]]><![CDATA[>" which effectively
195
+ // ends the CDATA section after "]]" and starts a new one for ">"
196
+ if (content.includes(']]>')) {
197
+ // Replace ]]> with ]]]]><![CDATA[> which closes CDATA before > and reopens it
198
+ const escaped = content.replace(/]]>/g, ']]]]><![CDATA[>')
199
+ return `<![CDATA[${escaped}]]>`
200
+ }
201
+ return `<![CDATA[${content}]]>`
202
+ }
203
+
204
+ function escapeXml(str: string): string {
205
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
206
+ }
207
+
208
+ function escapeAttr(str: string): string {
209
+ return str
210
+ .replace(/&/g, '&amp;')
211
+ .replace(/</g, '&lt;')
212
+ .replace(/>/g, '&gt;')
213
+ .replace(/"/g, '&quot;')
214
+ }