@kubb/ast 5.0.0-alpha.1 → 5.0.0-alpha.11
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/dist/index.cjs +633 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +80 -2
- package/dist/index.js +625 -58
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/{visitor-CmsfJzro.d.ts → visitor-D-l3dMbN.d.ts} +299 -49
- package/package.json +3 -2
- package/src/constants.ts +5 -1
- package/src/factory.ts +102 -2
- package/src/functionPrinter.ts +195 -0
- package/src/guards.ts +29 -1
- package/src/index.ts +25 -2
- package/src/mocks.ts +7 -1
- package/src/nodes/base.ts +10 -1
- package/src/nodes/function.ts +119 -0
- package/src/nodes/index.ts +4 -1
- package/src/nodes/operation.ts +4 -0
- package/src/nodes/response.ts +1 -1
- package/src/nodes/root.ts +13 -3
- package/src/nodes/schema.ts +29 -3
- package/src/printer.ts +111 -52
- package/src/types.ts +7 -1
- package/src/utils.ts +48 -0
- package/src/visitor.ts +32 -2
package/src/printer.ts
CHANGED
|
@@ -2,11 +2,12 @@ import type { SchemaNode, SchemaNodeByType, SchemaType } from './nodes/index.ts'
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Handler context for `definePrinter` — mirrors `PluginContext` from `@kubb/core`.
|
|
5
|
-
* Available as `this` inside each node handler
|
|
5
|
+
* Available as `this` inside each node handler and the optional root-level `print`.
|
|
6
|
+
* `this.print` always dispatches to the `nodes` handlers (node-level printer).
|
|
6
7
|
*/
|
|
7
8
|
export type PrinterHandlerContext<TOutput, TOptions extends object> = {
|
|
8
9
|
/**
|
|
9
|
-
* Recursively print a nested `SchemaNode
|
|
10
|
+
* Recursively print a nested `SchemaNode` using the node-level handlers.
|
|
10
11
|
*/
|
|
11
12
|
print: (node: SchemaNode) => TOutput | null | undefined
|
|
12
13
|
/**
|
|
@@ -28,19 +29,20 @@ export type PrinterHandler<TOutput, TOptions extends object, T extends SchemaTyp
|
|
|
28
29
|
* Shape of the type parameter passed to `definePrinter`.
|
|
29
30
|
* Mirrors `AdapterFactoryOptions` / `PluginFactoryOptions` from `@kubb/core`.
|
|
30
31
|
*
|
|
31
|
-
* - `TName`
|
|
32
|
-
* - `TOptions`
|
|
33
|
-
* - `TOutput`
|
|
32
|
+
* - `TName` — unique string identifier (e.g. `'zod'`, `'ts'`)
|
|
33
|
+
* - `TOptions` — options passed to and stored on the printer
|
|
34
|
+
* - `TOutput` — the type emitted by node handlers
|
|
35
|
+
* - `TPrintOutput` — the type emitted by the public `print` override (defaults to `TOutput`)
|
|
34
36
|
*/
|
|
35
|
-
export type PrinterFactoryOptions<TName extends string = string, TOptions extends object = object, TOutput = unknown> = {
|
|
37
|
+
export type PrinterFactoryOptions<TName extends string = string, TOptions extends object = object, TOutput = unknown, TPrintOutput = TOutput> = {
|
|
36
38
|
name: TName
|
|
37
39
|
options: TOptions
|
|
38
40
|
output: TOutput
|
|
41
|
+
printOutput: TPrintOutput
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
/**
|
|
42
45
|
* The object returned by calling a `definePrinter` instance.
|
|
43
|
-
* Mirrors the shape of `Adapter` from `@kubb/core`.
|
|
44
46
|
*/
|
|
45
47
|
export type Printer<T extends PrinterFactoryOptions = PrinterFactoryOptions> = {
|
|
46
48
|
/**
|
|
@@ -52,15 +54,18 @@ export type Printer<T extends PrinterFactoryOptions = PrinterFactoryOptions> = {
|
|
|
52
54
|
*/
|
|
53
55
|
options: T['options']
|
|
54
56
|
/**
|
|
55
|
-
*
|
|
57
|
+
* Public printer. If the builder provides a root-level `print`, this calls that
|
|
58
|
+
* higher-level function (which may produce full declarations). Otherwise falls back
|
|
59
|
+
* to the node-level dispatcher
|
|
56
60
|
*/
|
|
57
|
-
print: (node: SchemaNode) => T['
|
|
58
|
-
/**
|
|
59
|
-
* Maps `print` over an array of `SchemaNode`s.
|
|
60
|
-
*/
|
|
61
|
-
for: (nodes: Array<SchemaNode>) => Array<T['output'] | null | undefined>
|
|
61
|
+
print: (node: SchemaNode) => T['printOutput'] | null | undefined
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Builder function passed to `definePrinter`. Receives the resolved options and returns the
|
|
66
|
+
* printer configuration: a unique `name`, the stored `options`, node-level `nodes` handlers,
|
|
67
|
+
* and an optional root-level `print` override.
|
|
68
|
+
*/
|
|
64
69
|
type PrinterBuilder<T extends PrinterFactoryOptions> = (options: T['options']) => {
|
|
65
70
|
name: T['name']
|
|
66
71
|
/**
|
|
@@ -70,60 +75,114 @@ type PrinterBuilder<T extends PrinterFactoryOptions> = (options: T['options']) =
|
|
|
70
75
|
nodes: Partial<{
|
|
71
76
|
[K in SchemaType]: PrinterHandler<T['output'], T['options'], K>
|
|
72
77
|
}>
|
|
78
|
+
/**
|
|
79
|
+
* Optional root-level print override. When provided, becomes the public `printer.print`.
|
|
80
|
+
* `this.print(node)` inside this function calls the node-level dispatcher (`nodes` handlers),
|
|
81
|
+
* not the override itself — so recursion is safe.
|
|
82
|
+
*/
|
|
83
|
+
print?: (this: PrinterHandlerContext<T['output'], T['options']>, node: SchemaNode) => T['printOutput'] | null | undefined
|
|
73
84
|
}
|
|
74
85
|
|
|
75
86
|
/**
|
|
76
|
-
* Creates a named printer factory. Mirrors the `
|
|
87
|
+
* Creates a named printer factory. Mirrors the `createPlugin` / `createAdapter` pattern
|
|
77
88
|
* from `@kubb/core` — wraps a builder to make options optional and separates raw options
|
|
78
89
|
* from resolved options.
|
|
79
90
|
*
|
|
80
|
-
*
|
|
91
|
+
* The builder receives resolved options and returns:
|
|
92
|
+
* - `name` — a unique identifier for the printer
|
|
93
|
+
* - `options` — options stored on the returned printer instance
|
|
94
|
+
* - `nodes` — a map of `SchemaType` → handler functions that convert a `SchemaNode` to `TOutput`
|
|
95
|
+
* - `print` _(optional)_ — a root-level override that becomes the public `printer.print`.
|
|
96
|
+
* Inside it, `this.print(node)` still dispatches to the `nodes` map — safe recursion, no infinite loop.
|
|
97
|
+
*
|
|
98
|
+
* When no `print` override is provided, `printer.print` is the node-level dispatcher directly.
|
|
99
|
+
*
|
|
100
|
+
* @example Basic usage — Zod schema printer
|
|
81
101
|
* ```ts
|
|
82
|
-
* type ZodPrinter = PrinterFactoryOptions<'zod', { strict?: boolean },
|
|
102
|
+
* type ZodPrinter = PrinterFactoryOptions<'zod', { strict?: boolean }, string>
|
|
83
103
|
*
|
|
84
|
-
* export const zodPrinter = definePrinter<ZodPrinter>((options) => {
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* },
|
|
93
|
-
* object(node) {
|
|
94
|
-
* const props = node.properties
|
|
95
|
-
* ?.map(p => `${p.name}: ${this.print(p)}`)
|
|
96
|
-
* .join(', ') ?? ''
|
|
97
|
-
* return `z.object({ ${props} })`
|
|
98
|
-
* },
|
|
104
|
+
* export const zodPrinter = definePrinter<ZodPrinter>((options) => ({
|
|
105
|
+
* name: 'zod',
|
|
106
|
+
* options: { strict: options.strict ?? true },
|
|
107
|
+
* nodes: {
|
|
108
|
+
* string: () => 'z.string()',
|
|
109
|
+
* object(node) {
|
|
110
|
+
* const props = node.properties.map(p => `${p.name}: ${this.print(p.schema)}`).join(', ')
|
|
111
|
+
* return `z.object({ ${props} })`
|
|
99
112
|
* },
|
|
100
|
-
* }
|
|
101
|
-
* })
|
|
113
|
+
* },
|
|
114
|
+
* }))
|
|
115
|
+
* ```
|
|
102
116
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
117
|
+
* @example With a root-level `print` override to wrap output in a full declaration
|
|
118
|
+
* ```ts
|
|
119
|
+
* type TsPrinter = PrinterFactoryOptions<'ts', { typeName?: string }, ts.TypeNode, ts.Node>
|
|
120
|
+
*
|
|
121
|
+
* export const printerTs = definePrinter<TsPrinter>((options) => ({
|
|
122
|
+
* name: 'ts',
|
|
123
|
+
* options,
|
|
124
|
+
* nodes: { string: () => factory.keywordTypeNodes.string },
|
|
125
|
+
* print(node) {
|
|
126
|
+
* const type = this.print(node) // calls the node-level dispatcher
|
|
127
|
+
* if (!type || !this.options.typeName) return type
|
|
128
|
+
* return factory.createTypeAliasDeclaration(this.options.typeName, type)
|
|
129
|
+
* },
|
|
130
|
+
* }))
|
|
107
131
|
* ```
|
|
108
132
|
*/
|
|
109
133
|
export function definePrinter<T extends PrinterFactoryOptions = PrinterFactoryOptions>(build: PrinterBuilder<T>): (options?: T['options']) => Printer<T> {
|
|
110
|
-
return (options) =>
|
|
111
|
-
|
|
134
|
+
return createPrinterFactory<SchemaNode, SchemaType, SchemaNodeByType>((node) => node.type)(build) as (options?: T['options']) => Printer<T>
|
|
135
|
+
}
|
|
112
136
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
137
|
+
/**
|
|
138
|
+
* Generic printer factory. Extracts the core dispatch + context logic so it can be reused
|
|
139
|
+
* for any node type — not just `SchemaNode`. `definePrinter` is built on top of this.
|
|
140
|
+
*
|
|
141
|
+
* @param getKey — derives the handler-map key from a node. Return `undefined` to skip.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```ts
|
|
145
|
+
* export const defineFunctionPrinter = createPrinterFactory<FunctionNode, FunctionNodeType, FunctionNodeByType>(
|
|
146
|
+
* (node) => kindToHandlerKey[node.kind],
|
|
147
|
+
* )
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export function createPrinterFactory<TNode, TKey extends string, TNodeByKey extends Partial<Record<TKey, TNode>>>(getKey: (node: TNode) => TKey | undefined) {
|
|
151
|
+
return function <T extends PrinterFactoryOptions>(
|
|
152
|
+
build: (options: T['options']) => {
|
|
153
|
+
name: T['name']
|
|
154
|
+
options: T['options']
|
|
155
|
+
nodes: Partial<{
|
|
156
|
+
[K in TKey]: (
|
|
157
|
+
this: { print: (node: TNode) => T['output'] | null | undefined; options: T['options'] },
|
|
158
|
+
node: TNodeByKey[K],
|
|
159
|
+
) => T['output'] | null | undefined
|
|
160
|
+
}>
|
|
161
|
+
print?: (this: { print: (node: TNode) => T['output'] | null | undefined; options: T['options'] }, node: TNode) => T['printOutput'] | null | undefined
|
|
162
|
+
},
|
|
163
|
+
): (options?: T['options']) => { name: T['name']; options: T['options']; print: (node: TNode) => T['printOutput'] | null | undefined } {
|
|
164
|
+
return (options) => {
|
|
165
|
+
const { name, options: resolvedOptions, nodes, print: printOverride } = build(options ?? ({} as T['options']))
|
|
166
|
+
|
|
167
|
+
const context = {
|
|
168
|
+
options: resolvedOptions,
|
|
169
|
+
print: (node: TNode): T['output'] | null | undefined => {
|
|
170
|
+
const key = getKey(node)
|
|
171
|
+
if (key === undefined) return undefined
|
|
172
|
+
|
|
173
|
+
const handler = nodes[key]
|
|
174
|
+
|
|
175
|
+
if (!handler) return undefined
|
|
176
|
+
|
|
177
|
+
return (handler as (this: typeof context, node: TNode) => T['output'] | null | undefined).call(context, node)
|
|
178
|
+
},
|
|
179
|
+
}
|
|
121
180
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
181
|
+
return {
|
|
182
|
+
name,
|
|
183
|
+
options: resolvedOptions,
|
|
184
|
+
print: (printOverride ? printOverride.bind(context) : context.print) as (node: TNode) => T['printOutput'] | null | undefined,
|
|
185
|
+
}
|
|
127
186
|
}
|
|
128
187
|
}
|
|
129
188
|
}
|
package/src/types.ts
CHANGED
|
@@ -8,6 +8,10 @@ export type {
|
|
|
8
8
|
DatetimeSchemaNode,
|
|
9
9
|
EnumSchemaNode,
|
|
10
10
|
EnumValueNode,
|
|
11
|
+
FunctionNode,
|
|
12
|
+
FunctionNodeType,
|
|
13
|
+
FunctionParameterNode,
|
|
14
|
+
FunctionParametersNode,
|
|
11
15
|
HttpMethod,
|
|
12
16
|
HttpStatusCode,
|
|
13
17
|
IntersectionSchemaNode,
|
|
@@ -15,6 +19,7 @@ export type {
|
|
|
15
19
|
Node,
|
|
16
20
|
NodeKind,
|
|
17
21
|
NumberSchemaNode,
|
|
22
|
+
ObjectBindingParameterNode,
|
|
18
23
|
ObjectSchemaNode,
|
|
19
24
|
OperationNode,
|
|
20
25
|
ParameterLocation,
|
|
@@ -35,7 +40,8 @@ export type {
|
|
|
35
40
|
StringSchemaNode,
|
|
36
41
|
TimeSchemaNode,
|
|
37
42
|
UnionSchemaNode,
|
|
43
|
+
UrlSchemaNode,
|
|
38
44
|
} from './nodes/index.ts'
|
|
39
|
-
export type { Printer, PrinterFactoryOptions
|
|
45
|
+
export type { Printer, PrinterFactoryOptions } from './printer.ts'
|
|
40
46
|
export type { RefMap } from './refs.ts'
|
|
41
47
|
export type { AsyncVisitor, CollectVisitor, Visitor } from './visitor.ts'
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { camelCase, isValidVarName } from '@internals/utils'
|
|
2
|
+
|
|
3
|
+
import { narrowSchema } from './guards.ts'
|
|
4
|
+
import type { ParameterNode, SchemaNode } from './nodes/index.ts'
|
|
5
|
+
import type { SchemaType } from './nodes/schema.ts'
|
|
6
|
+
|
|
7
|
+
const plainStringTypes = new Set<SchemaType>(['string', 'uuid', 'email', 'url', 'datetime'] as const)
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Returns `true` when a schema node will be represented as a plain string in generated code.
|
|
11
|
+
*
|
|
12
|
+
* - `string`, `uuid`, `email`, `url`, `datetime` are always plain strings.
|
|
13
|
+
* - `date` and `time` are plain strings when their `representation` is `'string'` rather than `'date'`.
|
|
14
|
+
*/
|
|
15
|
+
export function isPlainStringType(node: SchemaNode): boolean {
|
|
16
|
+
if (plainStringTypes.has(node.type)) {
|
|
17
|
+
return true
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const temporal = narrowSchema(node, 'date') ?? narrowSchema(node, 'time')
|
|
21
|
+
if (temporal) {
|
|
22
|
+
return temporal.representation !== 'date'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Transforms the `name` field of each parameter node according to the given casing strategy.
|
|
30
|
+
*
|
|
31
|
+
* The original `params` array is never mutated — a new array of cloned nodes is returned.
|
|
32
|
+
* When no `casing` is provided the original array is returned as-is.
|
|
33
|
+
*
|
|
34
|
+
* Use this before passing parameters to schema builders so that property keys
|
|
35
|
+
* in the generated output match the desired casing while the original
|
|
36
|
+
* `OperationNode.parameters` array remains untouched for other consumers.
|
|
37
|
+
*/
|
|
38
|
+
export function applyParamsCasing(params: Array<ParameterNode>, casing: 'camelcase' | undefined): Array<ParameterNode> {
|
|
39
|
+
if (!casing) {
|
|
40
|
+
return params
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return params.map((param) => {
|
|
44
|
+
const transformed = casing === 'camelcase' || !isValidVarName(param.name) ? camelCase(param.name) : param.name
|
|
45
|
+
|
|
46
|
+
return { ...param, name: transformed }
|
|
47
|
+
})
|
|
48
|
+
}
|
package/src/visitor.ts
CHANGED
|
@@ -2,6 +2,10 @@ import type { VisitorDepth } from './constants.ts'
|
|
|
2
2
|
import { visitorDepths, WALK_CONCURRENCY } from './constants.ts'
|
|
3
3
|
import type { Node, OperationNode, ParameterNode, PropertyNode, ResponseNode, RootNode, SchemaNode } from './nodes/index.ts'
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Creates a concurrency-limiting wrapper. At most `concurrency` promises may be
|
|
7
|
+
* in-flight simultaneously; additional calls are queued and dispatched as slots free.
|
|
8
|
+
*/
|
|
5
9
|
function createLimit(concurrency: number) {
|
|
6
10
|
let active = 0
|
|
7
11
|
const queue: Array<() => void> = []
|
|
@@ -81,7 +85,10 @@ export type CollectVisitor<T> = {
|
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
/**
|
|
84
|
-
*
|
|
88
|
+
* Returns the immediate traversable children of `node`.
|
|
89
|
+
*
|
|
90
|
+
* For `Schema` nodes, children (properties, items, members) are only included
|
|
91
|
+
* when `recurse` is `true`; shallow traversal omits them entirely.
|
|
85
92
|
*/
|
|
86
93
|
function getChildren(node: Node, recurse: boolean): Array<Node> {
|
|
87
94
|
switch (node.kind) {
|
|
@@ -97,6 +104,7 @@ function getChildren(node: Node, recurse: boolean): Array<Node> {
|
|
|
97
104
|
if ('properties' in node && node.properties.length > 0) children.push(...node.properties)
|
|
98
105
|
if ('items' in node && node.items) children.push(...node.items)
|
|
99
106
|
if ('members' in node && node.members) children.push(...node.members)
|
|
107
|
+
if ('additionalProperties' in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties)
|
|
100
108
|
|
|
101
109
|
return children
|
|
102
110
|
}
|
|
@@ -106,6 +114,10 @@ function getChildren(node: Node, recurse: boolean): Array<Node> {
|
|
|
106
114
|
return [node.schema]
|
|
107
115
|
case 'Response':
|
|
108
116
|
return node.schema ? [node.schema] : []
|
|
117
|
+
case 'FunctionParameter':
|
|
118
|
+
case 'ObjectBindingParameter':
|
|
119
|
+
case 'FunctionParameters':
|
|
120
|
+
return []
|
|
109
121
|
}
|
|
110
122
|
}
|
|
111
123
|
|
|
@@ -119,6 +131,9 @@ export async function walk(node: Node, visitor: AsyncVisitor, options: VisitorOp
|
|
|
119
131
|
return _walk(node, visitor, recurse, limit)
|
|
120
132
|
}
|
|
121
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Internal recursive walk implementation — calls visitor then recurses into children.
|
|
136
|
+
*/
|
|
122
137
|
async function _walk(node: Node, visitor: AsyncVisitor, recurse: boolean, limit: LimitFn): Promise<void> {
|
|
123
138
|
switch (node.kind) {
|
|
124
139
|
case 'Root':
|
|
@@ -139,6 +154,10 @@ async function _walk(node: Node, visitor: AsyncVisitor, recurse: boolean, limit:
|
|
|
139
154
|
case 'Response':
|
|
140
155
|
await limit(() => visitor.response?.(node))
|
|
141
156
|
break
|
|
157
|
+
case 'FunctionParameter':
|
|
158
|
+
case 'ObjectBindingParameter':
|
|
159
|
+
case 'FunctionParameters':
|
|
160
|
+
break
|
|
142
161
|
}
|
|
143
162
|
|
|
144
163
|
const children = getChildren(node, recurse)
|
|
@@ -192,6 +211,9 @@ export function transform(node: Node, visitor: Visitor, options: VisitorOptions
|
|
|
192
211
|
...('properties' in schema && recurse ? { properties: schema.properties.map((p) => transform(p, visitor, options)) } : {}),
|
|
193
212
|
...('items' in schema && recurse ? { items: schema.items?.map((i) => transform(i, visitor, options)) } : {}),
|
|
194
213
|
...('members' in schema && recurse ? { members: schema.members?.map((m) => transform(m, visitor, options)) } : {}),
|
|
214
|
+
...('additionalProperties' in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true
|
|
215
|
+
? { additionalProperties: transform(schema.additionalProperties, visitor, options) }
|
|
216
|
+
: {}),
|
|
195
217
|
}
|
|
196
218
|
}
|
|
197
219
|
case 'Property': {
|
|
@@ -221,9 +243,13 @@ export function transform(node: Node, visitor: Visitor, options: VisitorOptions
|
|
|
221
243
|
|
|
222
244
|
return {
|
|
223
245
|
...response,
|
|
224
|
-
schema:
|
|
246
|
+
schema: transform(response.schema, visitor, options),
|
|
225
247
|
}
|
|
226
248
|
}
|
|
249
|
+
case 'FunctionParameter':
|
|
250
|
+
case 'ObjectBindingParameter':
|
|
251
|
+
case 'FunctionParameters':
|
|
252
|
+
return node
|
|
227
253
|
}
|
|
228
254
|
}
|
|
229
255
|
|
|
@@ -254,6 +280,10 @@ export function collect<T>(node: Node, visitor: CollectVisitor<T>, options: Visi
|
|
|
254
280
|
case 'Response':
|
|
255
281
|
v = visitor.response?.(node)
|
|
256
282
|
break
|
|
283
|
+
case 'FunctionParameter':
|
|
284
|
+
case 'ObjectBindingParameter':
|
|
285
|
+
case 'FunctionParameters':
|
|
286
|
+
break
|
|
257
287
|
}
|
|
258
288
|
if (v !== undefined) results.push(v)
|
|
259
289
|
|