@mkja/o-data 0.0.1
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 +416 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +13 -0
- package/dist/filter.d.ts +40 -0
- package/dist/filter.js +278 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.js +419 -0
- package/dist/operations.d.ts +75 -0
- package/dist/operations.js +3 -0
- package/dist/parser/config.d.ts +152 -0
- package/dist/parser/config.js +3 -0
- package/dist/parser/index.d.ts +1 -0
- package/dist/parser/index.js +1157 -0
- package/dist/query.d.ts +43 -0
- package/dist/query.js +3 -0
- package/dist/response.d.ts +132 -0
- package/dist/response.js +3 -0
- package/dist/runtime.d.ts +4 -0
- package/dist/runtime.js +113 -0
- package/dist/schema.d.ts +128 -0
- package/dist/schema.js +11 -0
- package/dist/serialization.d.ts +42 -0
- package/dist/serialization.js +533 -0
- package/dist/types.d.ts +155 -0
- package/dist/types.js +3 -0
- package/package.json +34 -0
package/dist/filter.js
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Filter Types
|
|
3
|
+
// ============================================================================
|
|
4
|
+
import { buildQueryableEntity } from './runtime.js';
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// Filter Builder Runtime Implementation
|
|
7
|
+
// ============================================================================
|
|
8
|
+
class FilterBuilderImpl {
|
|
9
|
+
state;
|
|
10
|
+
constructor(initialState) {
|
|
11
|
+
this.state = initialState;
|
|
12
|
+
}
|
|
13
|
+
and(expr) {
|
|
14
|
+
return new FilterBuilderImpl([...this.state, 'and', expr.state]);
|
|
15
|
+
}
|
|
16
|
+
or(expr) {
|
|
17
|
+
return new FilterBuilderImpl([...this.state, 'or', expr.state]);
|
|
18
|
+
}
|
|
19
|
+
__brand = 'FilterBuilder';
|
|
20
|
+
}
|
|
21
|
+
export function createFilterHelpers(entityDef, schema) {
|
|
22
|
+
const clause = (property, operator, value) => {
|
|
23
|
+
return new FilterBuilderImpl([[property, operator, value]]);
|
|
24
|
+
};
|
|
25
|
+
// Helper to recursively update paths in the builder state
|
|
26
|
+
const prependPathToState = (state, prefix) => {
|
|
27
|
+
return state.map((item) => {
|
|
28
|
+
if (Array.isArray(item)) {
|
|
29
|
+
// Check if it's a clause tuple [property, operator, value]
|
|
30
|
+
const ops = [
|
|
31
|
+
'eq',
|
|
32
|
+
'ne',
|
|
33
|
+
'gt',
|
|
34
|
+
'ge',
|
|
35
|
+
'lt',
|
|
36
|
+
'le',
|
|
37
|
+
'contains',
|
|
38
|
+
'startswith',
|
|
39
|
+
'endswith',
|
|
40
|
+
'in',
|
|
41
|
+
];
|
|
42
|
+
if (item.length === 3 &&
|
|
43
|
+
typeof item[0] === 'string' &&
|
|
44
|
+
typeof item[1] === 'string' &&
|
|
45
|
+
ops.includes(item[1])) {
|
|
46
|
+
return [`${prefix}/${item[0]}`, item[1], item[2]];
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
return prependPathToState(item, prefix);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else if (typeof item === 'object' && item !== null && item.kind === 'lambda') {
|
|
53
|
+
// Update lambda navigation path
|
|
54
|
+
return { ...item, nav: `${prefix}/${item.nav}` };
|
|
55
|
+
}
|
|
56
|
+
return item;
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
const nav = (navKey, cb) => {
|
|
60
|
+
const navDef = entityDef.navigations[navKey];
|
|
61
|
+
if (!navDef) {
|
|
62
|
+
throw new Error(`Navigation ${String(navKey)} not found`);
|
|
63
|
+
}
|
|
64
|
+
// At runtime, target is a string (entitytype name), need to resolve to QueryableEntity
|
|
65
|
+
if (!schema) {
|
|
66
|
+
throw new Error('Schema required for navigation filters');
|
|
67
|
+
}
|
|
68
|
+
const targetEntitytypeName = navDef.target;
|
|
69
|
+
const targetEntitysetKey = navDef.targetEntitysetKey;
|
|
70
|
+
const targetEntity = buildQueryableEntity(schema, targetEntitysetKey);
|
|
71
|
+
const innerHelpers = createFilterHelpers(targetEntity, schema);
|
|
72
|
+
const innerBuilder = cb(innerHelpers);
|
|
73
|
+
// Transform the inner state by prepending the navigation key
|
|
74
|
+
const innerState = innerBuilder.state;
|
|
75
|
+
const scopedState = prependPathToState(innerState, String(navKey));
|
|
76
|
+
return new FilterBuilderImpl(scopedState);
|
|
77
|
+
};
|
|
78
|
+
const any = (nav, cb) => {
|
|
79
|
+
const navDef = entityDef.navigations[nav];
|
|
80
|
+
if (!navDef) {
|
|
81
|
+
throw new Error(`Navigation ${String(nav)} not found`);
|
|
82
|
+
}
|
|
83
|
+
if (!schema) {
|
|
84
|
+
throw new Error('Schema required for navigation filters');
|
|
85
|
+
}
|
|
86
|
+
const targetEntitysetKey = navDef.targetEntitysetKey;
|
|
87
|
+
const targetEntity = buildQueryableEntity(schema, targetEntitysetKey);
|
|
88
|
+
const innerHelpers = createFilterHelpers(targetEntity, schema);
|
|
89
|
+
const innerBuilder = cb(innerHelpers);
|
|
90
|
+
const lambdaState = {
|
|
91
|
+
kind: 'lambda',
|
|
92
|
+
op: 'any',
|
|
93
|
+
nav: nav,
|
|
94
|
+
predicate: innerBuilder.state,
|
|
95
|
+
};
|
|
96
|
+
return new FilterBuilderImpl([lambdaState]);
|
|
97
|
+
};
|
|
98
|
+
const all = (nav, cb) => {
|
|
99
|
+
const navDef = entityDef.navigations[nav];
|
|
100
|
+
if (!navDef) {
|
|
101
|
+
throw new Error(`Navigation ${String(nav)} not found`);
|
|
102
|
+
}
|
|
103
|
+
if (!schema) {
|
|
104
|
+
throw new Error('Schema required for navigation filters');
|
|
105
|
+
}
|
|
106
|
+
const targetEntitysetKey = navDef.targetEntitysetKey;
|
|
107
|
+
const targetEntity = buildQueryableEntity(schema, targetEntitysetKey);
|
|
108
|
+
const innerHelpers = createFilterHelpers(targetEntity, schema);
|
|
109
|
+
const innerBuilder = cb(innerHelpers);
|
|
110
|
+
const lambdaState = {
|
|
111
|
+
kind: 'lambda',
|
|
112
|
+
op: 'all',
|
|
113
|
+
nav: nav,
|
|
114
|
+
predicate: innerBuilder.state,
|
|
115
|
+
};
|
|
116
|
+
return new FilterBuilderImpl([lambdaState]);
|
|
117
|
+
};
|
|
118
|
+
return { clause, nav, any, all };
|
|
119
|
+
}
|
|
120
|
+
// ============================================================================
|
|
121
|
+
// Filter Serialization
|
|
122
|
+
// ============================================================================
|
|
123
|
+
export function serializeFilter(filterState, depth = 0, lambdaVar, entityDef, schema) {
|
|
124
|
+
if (filterState.length === 0) {
|
|
125
|
+
return '';
|
|
126
|
+
}
|
|
127
|
+
// Handle lambda
|
|
128
|
+
if (filterState.length === 1 &&
|
|
129
|
+
typeof filterState[0] === 'object' &&
|
|
130
|
+
filterState[0] !== null &&
|
|
131
|
+
filterState[0].kind === 'lambda') {
|
|
132
|
+
const lambda = filterState[0];
|
|
133
|
+
const varName = lambdaVar || `p${depth}`;
|
|
134
|
+
let lambdaEntityDef;
|
|
135
|
+
if (entityDef && lambda.nav in entityDef.navigations) {
|
|
136
|
+
// For lambda navigation, we need to resolve the target entity
|
|
137
|
+
// The nav might be a path (e.g., A/B/C), so we take the first part
|
|
138
|
+
const firstPart = lambda.nav.split('/')[0];
|
|
139
|
+
const nav = entityDef.navigations[firstPart];
|
|
140
|
+
if (nav) {
|
|
141
|
+
// At runtime, target is a string, but we don't have schema here
|
|
142
|
+
// We'll pass undefined and let serializeClause handle it
|
|
143
|
+
lambdaEntityDef = undefined;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const predicate = serializeFilter(lambda.predicate, depth + 1, varName, lambdaEntityDef, schema);
|
|
147
|
+
return `${lambda.nav}/${lambda.op}(${varName}:${predicate})`;
|
|
148
|
+
}
|
|
149
|
+
// Handle clause
|
|
150
|
+
if (filterState.length === 3 && typeof filterState[0] === 'string') {
|
|
151
|
+
const [property, operator, value] = filterState;
|
|
152
|
+
const qualifiedProperty = lambdaVar ? `${lambdaVar}/${property}` : property;
|
|
153
|
+
return serializeClause(qualifiedProperty, operator, value, entityDef, property, schema);
|
|
154
|
+
}
|
|
155
|
+
// Handle logical operators
|
|
156
|
+
let result = '';
|
|
157
|
+
let i = 0;
|
|
158
|
+
while (i < filterState.length) {
|
|
159
|
+
const part = filterState[i];
|
|
160
|
+
if (part === 'and' || part === 'or') {
|
|
161
|
+
const operator = part;
|
|
162
|
+
i++;
|
|
163
|
+
if (i < filterState.length) {
|
|
164
|
+
const right = serializeFilter(Array.isArray(filterState[i]) ? filterState[i] : [filterState[i]], depth, lambdaVar, entityDef, schema);
|
|
165
|
+
result = `(${result}) ${operator} (${right})`;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
const partStr = serializeFilter(Array.isArray(part) ? part : [part], depth, lambdaVar, entityDef, schema);
|
|
170
|
+
result = result ? `${result} ${partStr}` : partStr;
|
|
171
|
+
}
|
|
172
|
+
i++;
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
// Helper function to resolve enum member name from numeric value
|
|
177
|
+
function resolveEnumMemberName(schema, enumTypeName, value) {
|
|
178
|
+
if (!schema.enumtypes) {
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
const enumType = schema.enumtypes[enumTypeName];
|
|
182
|
+
if (!enumType || !enumType.members) {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
const entry = Object.entries(enumType.members).find(([_, val]) => val === value);
|
|
186
|
+
return entry ? entry[0] : undefined;
|
|
187
|
+
}
|
|
188
|
+
// Helper function to format enum value as FQN
|
|
189
|
+
function formatEnumValue(schema, enumTypeName, memberName) {
|
|
190
|
+
if (!schema || !schema.namespace) {
|
|
191
|
+
// Fallback to plain member name if schema/namespace not available
|
|
192
|
+
return `'${memberName}'`;
|
|
193
|
+
}
|
|
194
|
+
return `${schema.namespace}.${enumTypeName}'${memberName}'`;
|
|
195
|
+
}
|
|
196
|
+
function serializeClause(property, operator, value, entityDef, originalProperty, schema) {
|
|
197
|
+
// Check if this is an enum property
|
|
198
|
+
const isEnumProperty = () => {
|
|
199
|
+
if (!originalProperty || !entityDef || !entityDef.properties) {
|
|
200
|
+
return { isEnum: false };
|
|
201
|
+
}
|
|
202
|
+
const propDef = entityDef.properties[originalProperty];
|
|
203
|
+
if (propDef && typeof propDef === 'object' && 'type' in propDef && propDef.type === 'enum') {
|
|
204
|
+
const enumTypeName = propDef.target;
|
|
205
|
+
return { isEnum: true, enumTypeName };
|
|
206
|
+
}
|
|
207
|
+
return { isEnum: false };
|
|
208
|
+
};
|
|
209
|
+
const enumInfo = isEnumProperty();
|
|
210
|
+
const formatValue = (val) => {
|
|
211
|
+
if (val === null || val === undefined) {
|
|
212
|
+
return 'null';
|
|
213
|
+
}
|
|
214
|
+
// Handle enum values
|
|
215
|
+
if (enumInfo.isEnum && enumInfo.enumTypeName && schema) {
|
|
216
|
+
let memberName;
|
|
217
|
+
if (typeof val === 'string') {
|
|
218
|
+
// Use string value as member name
|
|
219
|
+
memberName = val;
|
|
220
|
+
}
|
|
221
|
+
else if (typeof val === 'number') {
|
|
222
|
+
// Look up member name from numeric value
|
|
223
|
+
const resolved = resolveEnumMemberName(schema, enumInfo.enumTypeName, val);
|
|
224
|
+
if (!resolved) {
|
|
225
|
+
// Fallback to number if member not found
|
|
226
|
+
return String(val);
|
|
227
|
+
}
|
|
228
|
+
memberName = resolved;
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
// Not a string or number, fall through to normal formatting
|
|
232
|
+
return formatValueNonEnum(val);
|
|
233
|
+
}
|
|
234
|
+
return formatEnumValue(schema, enumInfo.enumTypeName, memberName);
|
|
235
|
+
}
|
|
236
|
+
return formatValueNonEnum(val);
|
|
237
|
+
};
|
|
238
|
+
const formatValueNonEnum = (val) => {
|
|
239
|
+
if (val === null || val === undefined) {
|
|
240
|
+
return 'null';
|
|
241
|
+
}
|
|
242
|
+
// Handle Date values
|
|
243
|
+
if (val instanceof Date || (typeof val === 'string' && /^\d{4}-\d{2}-\d{2}/.test(val))) {
|
|
244
|
+
let dateValue;
|
|
245
|
+
if (val instanceof Date) {
|
|
246
|
+
dateValue = val;
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
dateValue = new Date(val);
|
|
250
|
+
}
|
|
251
|
+
// Default to ISO format (DateTimeOffset)
|
|
252
|
+
return dateValue.toISOString();
|
|
253
|
+
}
|
|
254
|
+
// Handle strings
|
|
255
|
+
if (typeof val === 'string') {
|
|
256
|
+
return `'${val.replace(/'/g, "''")}'`;
|
|
257
|
+
}
|
|
258
|
+
// Handle arrays (for 'in' operator)
|
|
259
|
+
if (Array.isArray(val)) {
|
|
260
|
+
return `(${val.map(formatValue).join(',')})`;
|
|
261
|
+
}
|
|
262
|
+
// Handle numbers and booleans
|
|
263
|
+
return String(val);
|
|
264
|
+
};
|
|
265
|
+
// Handle string functions
|
|
266
|
+
if (operator === 'contains' || operator === 'startswith' || operator === 'endswith') {
|
|
267
|
+
return `${operator}(${property},${formatValue(value)})`;
|
|
268
|
+
}
|
|
269
|
+
// Handle 'in' operator
|
|
270
|
+
if (operator === 'in') {
|
|
271
|
+
if (!Array.isArray(value)) {
|
|
272
|
+
throw new Error(`'in' operator requires an array value`);
|
|
273
|
+
}
|
|
274
|
+
return `${property} in ${formatValue(value)}`;
|
|
275
|
+
}
|
|
276
|
+
// Standard comparison operators
|
|
277
|
+
return `${property} ${operator} ${formatValue(value)}`;
|
|
278
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { Schema } from './schema';
|
|
2
|
+
import type { QueryableEntity, EntitySetToQueryableEntity, EntitySetToQueryableEntity as ResolveEntitySet, ImportedActionKeys, ImportedFunctionKeys, ResolveActionFromImport, ResolveFunctionFromImport, BoundActionKeysForEntitySet, BoundFunctionKeysForEntitySet } from './types';
|
|
3
|
+
import type { CollectionQueryResponse, SingleQueryResponse, CreateResponse, UpdateResponse, DeleteResponse, ActionResponse, FunctionResponse } from './response';
|
|
4
|
+
import type { CollectionQueryObject, SingleQueryObject, QueryOperationOptions } from './query';
|
|
5
|
+
import type { CreateObject, UpdateObject, CreateOperationOptions, UpdateOperationOptions, OperationParameters } from './operations';
|
|
6
|
+
type Fetch = (input: Request, init?: RequestInit) => Promise<Response>;
|
|
7
|
+
export type OdataClientOptions = {
|
|
8
|
+
baseUrl: string;
|
|
9
|
+
transport: Fetch;
|
|
10
|
+
};
|
|
11
|
+
type EntitySetNames<S extends Schema<S>> = keyof S['entitysets'];
|
|
12
|
+
type EntitySetToQE<S extends Schema<S>, ES extends EntitySetNames<S>> = EntitySetToQueryableEntity<S, ES>;
|
|
13
|
+
export declare class OdataClient<S extends Schema<S>> {
|
|
14
|
+
#private;
|
|
15
|
+
constructor(schema: S, options: OdataClientOptions);
|
|
16
|
+
/**
|
|
17
|
+
* Access an entityset collection.
|
|
18
|
+
*/
|
|
19
|
+
entitysets<E extends EntitySetNames<S>>(entityset: E): CollectionOperation<S, EntitySetToQE<S, E>, E>;
|
|
20
|
+
/**
|
|
21
|
+
* Execute an unbound global action.
|
|
22
|
+
*/
|
|
23
|
+
action<A extends ImportedActionKeys<S>>(name: A, payload: {
|
|
24
|
+
parameters: OperationParameters<S, NonNullable<S['actions']>[ResolveActionFromImport<S, A>]['parameters']>;
|
|
25
|
+
}): Promise<ActionResponse<S, NonNullable<S['actions']>[ResolveActionFromImport<S, A>]['returnType']>>;
|
|
26
|
+
/**
|
|
27
|
+
* Execute an unbound global function.
|
|
28
|
+
*/
|
|
29
|
+
function<F extends ImportedFunctionKeys<S>>(name: F, payload: {
|
|
30
|
+
parameters: OperationParameters<S, NonNullable<S['functions']>[ResolveFunctionFromImport<S, F>]['parameters']>;
|
|
31
|
+
}): Promise<FunctionResponse<S, NonNullable<S['functions']>[ResolveFunctionFromImport<S, F>]['returnType']>>;
|
|
32
|
+
}
|
|
33
|
+
declare class CollectionOperation<S extends Schema<S>, QE extends QueryableEntity, E extends EntitySetNames<S> = EntitySetNames<S>> {
|
|
34
|
+
#private;
|
|
35
|
+
constructor(schema: S, entityset: QE, entitysetName: E, path: string, options: OdataClientOptions);
|
|
36
|
+
/**
|
|
37
|
+
* Query a collection of entities.
|
|
38
|
+
*/
|
|
39
|
+
query<Q extends CollectionQueryObject<QE, S>, O extends QueryOperationOptions>(q: Q, o?: O): Promise<CollectionQueryResponse<QE, Q, O>>;
|
|
40
|
+
/**
|
|
41
|
+
* Build the full URL for this operation.
|
|
42
|
+
*/
|
|
43
|
+
private buildUrl;
|
|
44
|
+
/**
|
|
45
|
+
* Create a new entity.
|
|
46
|
+
*/
|
|
47
|
+
create<O extends CreateOperationOptions<QE>>(c: CreateObject<QE>, o?: O): Promise<CreateResponse<QE, O>>;
|
|
48
|
+
/**
|
|
49
|
+
* Access a single entity by key.
|
|
50
|
+
*/
|
|
51
|
+
key(key: string): SingleOperation<S, QE, E>;
|
|
52
|
+
/**
|
|
53
|
+
* Execute a bound action on the collection.
|
|
54
|
+
*/
|
|
55
|
+
action<K extends BoundActionKeysForEntitySet<S, E, 'collection'>>(name: K, payload: {
|
|
56
|
+
parameters: OperationParameters<S, NonNullable<S['actions']>[K]['parameters']>;
|
|
57
|
+
}): Promise<ActionResponse<S, NonNullable<S['actions']>[K]['returnType']>>;
|
|
58
|
+
/**
|
|
59
|
+
* Execute a bound function on the collection.
|
|
60
|
+
*/
|
|
61
|
+
function<K extends BoundFunctionKeysForEntitySet<S, E, 'collection'>>(name: K, payload: {
|
|
62
|
+
parameters: OperationParameters<S, NonNullable<S['functions']>[K]['parameters']>;
|
|
63
|
+
}): Promise<FunctionResponse<S, NonNullable<S['functions']>[K]['returnType']>>;
|
|
64
|
+
}
|
|
65
|
+
declare class SingleOperation<S extends Schema<S>, QE extends QueryableEntity, E extends EntitySetNames<S> = EntitySetNames<S>> {
|
|
66
|
+
#private;
|
|
67
|
+
constructor(schema: S, entityset: QE, entitysetName: E, path: string, options: OdataClientOptions);
|
|
68
|
+
/**
|
|
69
|
+
* Query a single entity.
|
|
70
|
+
*/
|
|
71
|
+
query<Q extends SingleQueryObject<QE, S>, O extends QueryOperationOptions>(q: Q, o?: O): Promise<SingleQueryResponse<QE, Q, O>>;
|
|
72
|
+
/**
|
|
73
|
+
* Build the full URL for this operation.
|
|
74
|
+
*/
|
|
75
|
+
private buildUrl;
|
|
76
|
+
/**
|
|
77
|
+
* Update an entity.
|
|
78
|
+
*/
|
|
79
|
+
update<O extends UpdateOperationOptions<QE>>(u: UpdateObject<QE>, o?: O): Promise<UpdateResponse<QE, O>>;
|
|
80
|
+
/**
|
|
81
|
+
* Delete an entity.
|
|
82
|
+
*/
|
|
83
|
+
delete(): Promise<DeleteResponse>;
|
|
84
|
+
/**
|
|
85
|
+
* Navigate to a related entity or collection.
|
|
86
|
+
*/
|
|
87
|
+
navigate<N extends keyof QE['navigations']>(navigation_property: N): QE['navigations'][N]['targetEntitysetKey'] extends string ? QE['navigations'][N]['collection'] extends true ? CollectionOperation<S, ResolveEntitySet<S, QE['navigations'][N]['targetEntitysetKey']>> : SingleOperation<S, ResolveEntitySet<S, QE['navigations'][N]['targetEntitysetKey']>> : QE['navigations'][N]['collection'] extends true ? CollectionOperation<S, QueryableEntity> : SingleOperation<S, QueryableEntity>;
|
|
88
|
+
/**
|
|
89
|
+
* Execute a bound action on the entity.
|
|
90
|
+
*/
|
|
91
|
+
action<K extends BoundActionKeysForEntitySet<S, E, 'entity'>>(name: K, payload: {
|
|
92
|
+
parameters: OperationParameters<S, NonNullable<S['actions']>[K]['parameters']>;
|
|
93
|
+
}): Promise<ActionResponse<S, NonNullable<S['actions']>[K]['returnType']>>;
|
|
94
|
+
/**
|
|
95
|
+
* Execute a bound function on the entity.
|
|
96
|
+
*/
|
|
97
|
+
function<K extends BoundFunctionKeysForEntitySet<S, E, 'entity'>>(name: K, payload: {
|
|
98
|
+
parameters: OperationParameters<S, NonNullable<S['functions']>[K]['parameters']>;
|
|
99
|
+
}): Promise<FunctionResponse<S, NonNullable<S['functions']>[K]['returnType']>>;
|
|
100
|
+
}
|
|
101
|
+
export {};
|