@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.
- package/LICENSE +7 -0
- package/README.md +532 -0
- package/bun-plugin.mts +129 -0
- package/bunPlugin.cache +1 -0
- package/bunfig.toml +3 -0
- package/dist/bun-plugin.d.mts +4 -0
- package/dist/bun-plugin.d.mts.map +1 -0
- package/dist/e2e/bun/xml-stream.spec.d.ts +2 -0
- package/dist/e2e/bun/xml-stream.spec.d.ts.map +1 -0
- package/dist/e2e/fastify/xml-stream.spec.d.ts +2 -0
- package/dist/e2e/fastify/xml-stream.spec.d.ts.map +1 -0
- package/dist/src/adapters/index.d.mts +2 -0
- package/dist/src/adapters/index.d.mts.map +1 -0
- package/dist/src/adapters/xml-stream-adapter.service.d.mts +21 -0
- package/dist/src/adapters/xml-stream-adapter.service.d.mts.map +1 -0
- package/dist/src/decorators/component.decorator.d.mts +17 -0
- package/dist/src/decorators/component.decorator.d.mts.map +1 -0
- package/dist/src/decorators/component.decorator.spec.d.mts +2 -0
- package/dist/src/decorators/component.decorator.spec.d.mts.map +1 -0
- package/dist/src/decorators/index.d.mts +4 -0
- package/dist/src/decorators/index.d.mts.map +1 -0
- package/dist/src/decorators/xml-stream.decorator.d.mts +42 -0
- package/dist/src/decorators/xml-stream.decorator.d.mts.map +1 -0
- package/dist/src/define-environment.d.mts +31 -0
- package/dist/src/define-environment.d.mts.map +1 -0
- package/dist/src/handlers/index.d.mts +2 -0
- package/dist/src/handlers/index.d.mts.map +1 -0
- package/dist/src/handlers/xml-stream.d.mts +23 -0
- package/dist/src/handlers/xml-stream.d.mts.map +1 -0
- package/dist/src/index.d.mts +12 -0
- package/dist/src/index.d.mts.map +1 -0
- package/dist/src/jsx-dev-runtime.d.mts +5 -0
- package/dist/src/jsx-dev-runtime.d.mts.map +1 -0
- package/dist/src/jsx-runtime.d.mts +3 -0
- package/dist/src/jsx-runtime.d.mts.map +1 -0
- package/dist/src/jsx.d.mts +18 -0
- package/dist/src/jsx.d.mts.map +1 -0
- package/dist/src/runtime/create-element.d.mts +25 -0
- package/dist/src/runtime/create-element.d.mts.map +1 -0
- package/dist/src/runtime/fragment.d.mts +2 -0
- package/dist/src/runtime/fragment.d.mts.map +1 -0
- package/dist/src/runtime/index.d.mts +5 -0
- package/dist/src/runtime/index.d.mts.map +1 -0
- package/dist/src/runtime/render-to-xml.d.mts +20 -0
- package/dist/src/runtime/render-to-xml.d.mts.map +1 -0
- package/dist/src/runtime/render-to-xml.spec.d.mts +2 -0
- package/dist/src/runtime/render-to-xml.spec.d.mts.map +1 -0
- package/dist/src/runtime/special-nodes.d.mts +24 -0
- package/dist/src/runtime/special-nodes.d.mts.map +1 -0
- package/dist/src/tags/define-tag.d.mts +33 -0
- package/dist/src/tags/define-tag.d.mts.map +1 -0
- package/dist/src/tags/define-tag.spec.d.mts +2 -0
- package/dist/src/tags/define-tag.spec.d.mts.map +1 -0
- package/dist/src/tags/index.d.mts +3 -0
- package/dist/src/tags/index.d.mts.map +1 -0
- package/dist/src/types/component.d.mts +15 -0
- package/dist/src/types/component.d.mts.map +1 -0
- package/dist/src/types/config.d.mts +10 -0
- package/dist/src/types/config.d.mts.map +1 -0
- package/dist/src/types/index.d.mts +5 -0
- package/dist/src/types/index.d.mts.map +1 -0
- package/dist/src/types/xml-node.d.mts +35 -0
- package/dist/src/types/xml-node.d.mts.map +1 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/dist/tsconfig.spec.tsbuildinfo +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/tsup.config.d.mts +3 -0
- package/dist/tsup.config.d.mts.map +1 -0
- package/dist/vitest.config.d.mts +3 -0
- package/dist/vitest.config.d.mts.map +1 -0
- package/dist/vitest.e2e.fastify.config.d.mts +3 -0
- package/dist/vitest.e2e.fastify.config.d.mts.map +1 -0
- package/e2e/bun/xml-stream.spec.tsx +553 -0
- package/e2e/fastify/xml-stream.spec.tsx +569 -0
- package/jsx.d.ts +42 -0
- package/lib/_tsup-dts-rollup.d.mts +414 -0
- package/lib/_tsup-dts-rollup.d.ts +414 -0
- package/lib/chunk-6OR6LGJA.mjs +153 -0
- package/lib/chunk-6OR6LGJA.mjs.map +1 -0
- package/lib/index.d.mts +29 -0
- package/lib/index.d.ts +29 -0
- package/lib/index.js +376 -0
- package/lib/index.js.map +1 -0
- package/lib/index.mjs +256 -0
- package/lib/index.mjs.map +1 -0
- package/lib/jsx-dev-runtime.d.mts +4 -0
- package/lib/jsx-dev-runtime.d.ts +4 -0
- package/lib/jsx-dev-runtime.js +61 -0
- package/lib/jsx-dev-runtime.js.map +1 -0
- package/lib/jsx-dev-runtime.mjs +9 -0
- package/lib/jsx-dev-runtime.mjs.map +1 -0
- package/lib/jsx-runtime.d.mts +3 -0
- package/lib/jsx-runtime.d.ts +3 -0
- package/lib/jsx-runtime.js +57 -0
- package/lib/jsx-runtime.js.map +1 -0
- package/lib/jsx-runtime.mjs +3 -0
- package/lib/jsx-runtime.mjs.map +1 -0
- package/lib/jsx.d.mts +1 -0
- package/lib/jsx.d.ts +1 -0
- package/lib/jsx.js +4 -0
- package/lib/jsx.js.map +1 -0
- package/lib/jsx.mjs +3 -0
- package/lib/jsx.mjs.map +1 -0
- package/package.json +80 -0
- package/project.json +91 -0
- package/src/adapters/index.mts +1 -0
- package/src/adapters/xml-stream-adapter.service.mts +121 -0
- package/src/decorators/component.decorator.mts +102 -0
- package/src/decorators/component.decorator.spec.mts +345 -0
- package/src/decorators/index.mts +4 -0
- package/src/decorators/xml-stream.decorator.mts +93 -0
- package/src/define-environment.mts +40 -0
- package/src/handlers/index.mts +1 -0
- package/src/handlers/xml-stream.mts +31 -0
- package/src/index.mts +41 -0
- package/src/jsx-dev-runtime.mts +8 -0
- package/src/jsx-runtime.mts +2 -0
- package/src/jsx.mts +25 -0
- package/src/runtime/create-element.mts +113 -0
- package/src/runtime/fragment.mts +1 -0
- package/src/runtime/index.mts +4 -0
- package/src/runtime/render-to-xml.mts +214 -0
- package/src/runtime/render-to-xml.spec.mts +360 -0
- package/src/runtime/special-nodes.mts +32 -0
- package/src/tags/define-tag.mts +54 -0
- package/src/tags/define-tag.spec.mts +250 -0
- package/src/tags/index.mts +2 -0
- package/src/types/component.mts +16 -0
- package/src/types/config.mts +15 -0
- package/src/types/index.mts +23 -0
- package/src/types/jsx.d.ts +21 -0
- package/src/types/xml-node.mts +50 -0
- package/tsconfig.json +24 -0
- package/tsconfig.lib.json +8 -0
- package/tsconfig.spec.json +25 -0
- package/tsup.config.mts +18 -0
- package/vitest.config.mts +9 -0
- 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
|
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,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, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function escapeAttr(str: string): string {
|
|
209
|
+
return str
|
|
210
|
+
.replace(/&/g, '&')
|
|
211
|
+
.replace(/</g, '<')
|
|
212
|
+
.replace(/>/g, '>')
|
|
213
|
+
.replace(/"/g, '"')
|
|
214
|
+
}
|