@tinacms/schema-tools 0.1.2 → 0.1.4
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.d.ts +1 -0
- package/dist/index.es.js +73 -8
- package/dist/index.js +73 -7
- package/dist/schema/TinaSchema.d.ts +7 -0
- package/dist/schema/resolveField.d.ts +6 -1
- package/dist/schema/resolveForm.d.ts +6 -1
- package/dist/types/SchemaTypes.d.ts +9 -1
- package/dist/types/SchemaTypes2.d.ts +101 -0
- package/dist/types/config.d.ts +9 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/schema2.d.ts +13 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
package/dist/index.es.js
CHANGED
|
@@ -165,7 +165,7 @@ class TinaSchema {
|
|
|
165
165
|
};
|
|
166
166
|
this.getCollectionByFullPath = (filepath) => {
|
|
167
167
|
const collection = this.getCollections().find((collection2) => {
|
|
168
|
-
return filepath.replace(
|
|
168
|
+
return filepath.replace(/\\/g, "/").startsWith(collection2.path.replace(/\/?$/, "/"));
|
|
169
169
|
});
|
|
170
170
|
if (!collection) {
|
|
171
171
|
throw new Error(`Unable to find collection for file at ${filepath}`);
|
|
@@ -211,6 +211,72 @@ class TinaSchema {
|
|
|
211
211
|
return template;
|
|
212
212
|
}
|
|
213
213
|
};
|
|
214
|
+
this.transformPayload = (collectionName, payload) => {
|
|
215
|
+
const collection = this.getCollection(collectionName);
|
|
216
|
+
if (collection.templates) {
|
|
217
|
+
const template = collection.templates.find((template2) => {
|
|
218
|
+
if (typeof template2 === "string") {
|
|
219
|
+
throw new Error("Global templates not supported");
|
|
220
|
+
}
|
|
221
|
+
return payload["_template"] === template2.name;
|
|
222
|
+
});
|
|
223
|
+
if (!template) {
|
|
224
|
+
console.error(payload);
|
|
225
|
+
throw new Error(`Unable to find template for payload`);
|
|
226
|
+
}
|
|
227
|
+
if (typeof template === "string") {
|
|
228
|
+
throw new Error("Global templates not supported");
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
[collectionName]: {
|
|
232
|
+
[template.name]: this.transformCollectablePayload(payload, template)
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
} else {
|
|
236
|
+
return {
|
|
237
|
+
[collectionName]: this.transformCollectablePayload(payload, collection)
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
this.transformCollectablePayload = (payload, collection) => {
|
|
242
|
+
const accumulator = {};
|
|
243
|
+
Object.entries(payload).forEach(([key, value]) => {
|
|
244
|
+
if (typeof collection.fields === "string") {
|
|
245
|
+
throw new Error("Global templates not supported");
|
|
246
|
+
}
|
|
247
|
+
const field = collection.fields.find((field2) => {
|
|
248
|
+
if (typeof field2 === "string") {
|
|
249
|
+
throw new Error("Global templates not supported");
|
|
250
|
+
}
|
|
251
|
+
return field2.name === key;
|
|
252
|
+
});
|
|
253
|
+
if (field) {
|
|
254
|
+
accumulator[key] = this.transformField(field, value);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
return accumulator;
|
|
258
|
+
};
|
|
259
|
+
this.transformField = (field, value) => {
|
|
260
|
+
if (field.type === "object")
|
|
261
|
+
if (field.templates) {
|
|
262
|
+
if (field.list) {
|
|
263
|
+
assertShape(value, (yup2) => yup2.array(yup2.object({ _template: yup2.string().required() })));
|
|
264
|
+
return value.map((item) => {
|
|
265
|
+
const { _template, ...rest } = item;
|
|
266
|
+
return { [_template]: rest };
|
|
267
|
+
});
|
|
268
|
+
} else {
|
|
269
|
+
assertShape(value, (yup2) => yup2.object({ _template: yup2.string().required() }));
|
|
270
|
+
const { _template, ...rest } = value;
|
|
271
|
+
return { [_template]: rest };
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
return value;
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
return value;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
214
280
|
this.isMarkdownCollection = (collectionName) => {
|
|
215
281
|
const collection = this.getCollection(collectionName);
|
|
216
282
|
const format = collection.format;
|
|
@@ -261,9 +327,9 @@ class TinaSchema {
|
|
|
261
327
|
this.schema = config;
|
|
262
328
|
}
|
|
263
329
|
}
|
|
264
|
-
const resolveField = (
|
|
330
|
+
const resolveField = (field, schema) => {
|
|
265
331
|
var _a;
|
|
266
|
-
field.parentTypename = NAMER.dataTypeName(namespace.filter((_, i) => i < namespace.length - 1));
|
|
332
|
+
field.parentTypename = NAMER.dataTypeName(field.namespace.filter((_, i) => i < field.namespace.length - 1));
|
|
267
333
|
const extraFields = field.ui || {};
|
|
268
334
|
switch (field.type) {
|
|
269
335
|
case "number":
|
|
@@ -324,10 +390,7 @@ const resolveField = ({ namespace, ...field }, schema) => {
|
|
|
324
390
|
...extraFields
|
|
325
391
|
};
|
|
326
392
|
case "object":
|
|
327
|
-
const templateInfo = schema.getTemplatesForCollectable(
|
|
328
|
-
...field,
|
|
329
|
-
namespace
|
|
330
|
-
});
|
|
393
|
+
const templateInfo = schema.getTemplatesForCollectable(field);
|
|
331
394
|
if (templateInfo.type === "object") {
|
|
332
395
|
return {
|
|
333
396
|
...field,
|
|
@@ -345,6 +408,7 @@ const resolveField = ({ namespace, ...field }, schema) => {
|
|
|
345
408
|
templates2[lastItem(template.namespace)] = {
|
|
346
409
|
label: template.label || templateName,
|
|
347
410
|
key: templateName,
|
|
411
|
+
namespace: [...field.namespace, templateName],
|
|
348
412
|
fields: template.fields.map((field2) => resolveField(field2, schema)),
|
|
349
413
|
...extraFields2
|
|
350
414
|
};
|
|
@@ -353,6 +417,7 @@ const resolveField = ({ namespace, ...field }, schema) => {
|
|
|
353
417
|
return {
|
|
354
418
|
...field,
|
|
355
419
|
typeMap: typeMap2,
|
|
420
|
+
namespace: field.namespace,
|
|
356
421
|
component: field.list ? "blocks" : "not-implemented",
|
|
357
422
|
templates: templates2,
|
|
358
423
|
...extraFields
|
|
@@ -761,4 +826,4 @@ const validateSchema = ({
|
|
|
761
826
|
}
|
|
762
827
|
}
|
|
763
828
|
};
|
|
764
|
-
export { TinaSchema, TinaSchemaValidationError, addNamespaceToSchema, resolveField, resolveForm, validateSchema, validateTinaCloudSchemaConfig };
|
|
829
|
+
export { NAMER, TinaSchema, TinaSchemaValidationError, addNamespaceToSchema, resolveField, resolveForm, validateSchema, validateTinaCloudSchemaConfig };
|
package/dist/index.js
CHANGED
|
@@ -192,7 +192,7 @@
|
|
|
192
192
|
};
|
|
193
193
|
this.getCollectionByFullPath = (filepath) => {
|
|
194
194
|
const collection = this.getCollections().find((collection2) => {
|
|
195
|
-
return filepath.replace(
|
|
195
|
+
return filepath.replace(/\\/g, "/").startsWith(collection2.path.replace(/\/?$/, "/"));
|
|
196
196
|
});
|
|
197
197
|
if (!collection) {
|
|
198
198
|
throw new Error(`Unable to find collection for file at ${filepath}`);
|
|
@@ -238,6 +238,72 @@
|
|
|
238
238
|
return template;
|
|
239
239
|
}
|
|
240
240
|
};
|
|
241
|
+
this.transformPayload = (collectionName, payload) => {
|
|
242
|
+
const collection = this.getCollection(collectionName);
|
|
243
|
+
if (collection.templates) {
|
|
244
|
+
const template = collection.templates.find((template2) => {
|
|
245
|
+
if (typeof template2 === "string") {
|
|
246
|
+
throw new Error("Global templates not supported");
|
|
247
|
+
}
|
|
248
|
+
return payload["_template"] === template2.name;
|
|
249
|
+
});
|
|
250
|
+
if (!template) {
|
|
251
|
+
console.error(payload);
|
|
252
|
+
throw new Error(`Unable to find template for payload`);
|
|
253
|
+
}
|
|
254
|
+
if (typeof template === "string") {
|
|
255
|
+
throw new Error("Global templates not supported");
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
[collectionName]: {
|
|
259
|
+
[template.name]: this.transformCollectablePayload(payload, template)
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
} else {
|
|
263
|
+
return {
|
|
264
|
+
[collectionName]: this.transformCollectablePayload(payload, collection)
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
this.transformCollectablePayload = (payload, collection) => {
|
|
269
|
+
const accumulator = {};
|
|
270
|
+
Object.entries(payload).forEach(([key, value]) => {
|
|
271
|
+
if (typeof collection.fields === "string") {
|
|
272
|
+
throw new Error("Global templates not supported");
|
|
273
|
+
}
|
|
274
|
+
const field = collection.fields.find((field2) => {
|
|
275
|
+
if (typeof field2 === "string") {
|
|
276
|
+
throw new Error("Global templates not supported");
|
|
277
|
+
}
|
|
278
|
+
return field2.name === key;
|
|
279
|
+
});
|
|
280
|
+
if (field) {
|
|
281
|
+
accumulator[key] = this.transformField(field, value);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
return accumulator;
|
|
285
|
+
};
|
|
286
|
+
this.transformField = (field, value) => {
|
|
287
|
+
if (field.type === "object")
|
|
288
|
+
if (field.templates) {
|
|
289
|
+
if (field.list) {
|
|
290
|
+
assertShape(value, (yup2) => yup2.array(yup2.object({ _template: yup2.string().required() })));
|
|
291
|
+
return value.map((item) => {
|
|
292
|
+
const { _template, ...rest } = item;
|
|
293
|
+
return { [_template]: rest };
|
|
294
|
+
});
|
|
295
|
+
} else {
|
|
296
|
+
assertShape(value, (yup2) => yup2.object({ _template: yup2.string().required() }));
|
|
297
|
+
const { _template, ...rest } = value;
|
|
298
|
+
return { [_template]: rest };
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
return value;
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
return value;
|
|
305
|
+
}
|
|
306
|
+
};
|
|
241
307
|
this.isMarkdownCollection = (collectionName) => {
|
|
242
308
|
const collection = this.getCollection(collectionName);
|
|
243
309
|
const format = collection.format;
|
|
@@ -288,9 +354,9 @@
|
|
|
288
354
|
this.schema = config;
|
|
289
355
|
}
|
|
290
356
|
}
|
|
291
|
-
const resolveField = (
|
|
357
|
+
const resolveField = (field, schema) => {
|
|
292
358
|
var _a;
|
|
293
|
-
field.parentTypename = NAMER.dataTypeName(namespace.filter((_, i) => i < namespace.length - 1));
|
|
359
|
+
field.parentTypename = NAMER.dataTypeName(field.namespace.filter((_, i) => i < field.namespace.length - 1));
|
|
294
360
|
const extraFields = field.ui || {};
|
|
295
361
|
switch (field.type) {
|
|
296
362
|
case "number":
|
|
@@ -351,10 +417,7 @@
|
|
|
351
417
|
...extraFields
|
|
352
418
|
};
|
|
353
419
|
case "object":
|
|
354
|
-
const templateInfo = schema.getTemplatesForCollectable(
|
|
355
|
-
...field,
|
|
356
|
-
namespace
|
|
357
|
-
});
|
|
420
|
+
const templateInfo = schema.getTemplatesForCollectable(field);
|
|
358
421
|
if (templateInfo.type === "object") {
|
|
359
422
|
return {
|
|
360
423
|
...field,
|
|
@@ -372,6 +435,7 @@
|
|
|
372
435
|
templates2[lastItem(template.namespace)] = {
|
|
373
436
|
label: template.label || templateName,
|
|
374
437
|
key: templateName,
|
|
438
|
+
namespace: [...field.namespace, templateName],
|
|
375
439
|
fields: template.fields.map((field2) => resolveField(field2, schema)),
|
|
376
440
|
...extraFields2
|
|
377
441
|
};
|
|
@@ -380,6 +444,7 @@
|
|
|
380
444
|
return {
|
|
381
445
|
...field,
|
|
382
446
|
typeMap: typeMap2,
|
|
447
|
+
namespace: field.namespace,
|
|
383
448
|
component: field.list ? "blocks" : "not-implemented",
|
|
384
449
|
templates: templates2,
|
|
385
450
|
...extraFields
|
|
@@ -788,6 +853,7 @@ ${JSON.stringify(val, null, 2)}
|
|
|
788
853
|
}
|
|
789
854
|
}
|
|
790
855
|
};
|
|
856
|
+
exports2.NAMER = NAMER;
|
|
791
857
|
exports2.TinaSchema = TinaSchema;
|
|
792
858
|
exports2.TinaSchemaValidationError = TinaSchemaValidationError;
|
|
793
859
|
exports2.addNamespaceToSchema = addNamespaceToSchema;
|
|
@@ -64,6 +64,13 @@ export declare class TinaSchema {
|
|
|
64
64
|
data?: unknown;
|
|
65
65
|
collection: Collectable;
|
|
66
66
|
}) => Templateable;
|
|
67
|
+
transformPayload: (collectionName: string, payload: object) => {
|
|
68
|
+
[x: string]: {
|
|
69
|
+
[x: string]: {};
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
private transformCollectablePayload;
|
|
73
|
+
private transformField;
|
|
67
74
|
isMarkdownCollection: (collectionName: string) => boolean;
|
|
68
75
|
/**
|
|
69
76
|
* Gets the template or templates from the item.
|
|
@@ -21,4 +21,9 @@ import { TinaSchema } from './TinaSchema';
|
|
|
21
21
|
* @param {TinaSchema} schema the entireT Tina Schema
|
|
22
22
|
* @returns unknown
|
|
23
23
|
*/
|
|
24
|
-
export declare const resolveField: (
|
|
24
|
+
export declare const resolveField: (field: TinaFieldEnriched, schema: TinaSchema) => {
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
name: string;
|
|
27
|
+
component: string;
|
|
28
|
+
type: string;
|
|
29
|
+
};
|
|
@@ -18,5 +18,10 @@ export declare const resolveForm: ({ collection, basename, template, schema, }:
|
|
|
18
18
|
id: string;
|
|
19
19
|
label: string;
|
|
20
20
|
name: string;
|
|
21
|
-
fields:
|
|
21
|
+
fields: {
|
|
22
|
+
[key: string]: unknown;
|
|
23
|
+
name: string;
|
|
24
|
+
component: string;
|
|
25
|
+
type: string;
|
|
26
|
+
}[];
|
|
22
27
|
};
|
|
@@ -56,10 +56,17 @@ declare type Document = {
|
|
|
56
56
|
extension: string;
|
|
57
57
|
};
|
|
58
58
|
};
|
|
59
|
+
export declare type TinaIndex = {
|
|
60
|
+
name: string;
|
|
61
|
+
fields: {
|
|
62
|
+
name: string;
|
|
63
|
+
}[];
|
|
64
|
+
};
|
|
59
65
|
interface BaseCollection {
|
|
60
66
|
label?: string;
|
|
61
67
|
name: string;
|
|
62
68
|
path: string;
|
|
69
|
+
indexes?: TinaIndex[];
|
|
63
70
|
format?: FormatType;
|
|
64
71
|
ui?: {
|
|
65
72
|
/**
|
|
@@ -117,11 +124,12 @@ export declare type TinaFieldEnriched = TinaFieldInner<true> & {
|
|
|
117
124
|
*/
|
|
118
125
|
parentTypename?: string;
|
|
119
126
|
};
|
|
120
|
-
interface TinaField {
|
|
127
|
+
export interface TinaField {
|
|
121
128
|
name: string;
|
|
122
129
|
label?: string;
|
|
123
130
|
description?: string;
|
|
124
131
|
required?: boolean;
|
|
132
|
+
indexed?: boolean;
|
|
125
133
|
/**
|
|
126
134
|
* Any items passed to the UI field will be passed to the underlying field.
|
|
127
135
|
* NOTE: only serializable values are supported, so functions like `validate`
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Copyright 2021 Forestry.io Holdings, Inc.
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
See the License for the specific language governing permissions and
|
|
11
|
+
limitations under the License.
|
|
12
|
+
*/
|
|
13
|
+
import { TinaCloudSchema } from './SchemaTypes';
|
|
14
|
+
/**
|
|
15
|
+
* Used with `defineStaticConfig`
|
|
16
|
+
*
|
|
17
|
+
* These are mostly similar types as whats in `schemaTypes`
|
|
18
|
+
* but since those have gone through several iterations
|
|
19
|
+
* they're pretty messy. These should act as the happy path
|
|
20
|
+
* for iframe/standalone setups which we hope to eventually
|
|
21
|
+
* make the default/only path for all Tina users.
|
|
22
|
+
*/
|
|
23
|
+
export declare type TinaCMSConfig<CMSCallback = undefined, FormifyCallback = undefined, DocumentCreatorCallback = undefined, Store = undefined> = {
|
|
24
|
+
schema: TinaCloudSchema<false>;
|
|
25
|
+
/**
|
|
26
|
+
* The base branch to pull content from. Note that this is ignored for local development
|
|
27
|
+
*/
|
|
28
|
+
branch: string | null;
|
|
29
|
+
/**
|
|
30
|
+
* Your clientId from app.tina.io
|
|
31
|
+
*/
|
|
32
|
+
clientId: string | null;
|
|
33
|
+
/**
|
|
34
|
+
* Your read only token from app.tina.io
|
|
35
|
+
*/
|
|
36
|
+
token: string | null;
|
|
37
|
+
/**
|
|
38
|
+
* Configurations for the autogenerated GraphQL HTTP client
|
|
39
|
+
*/
|
|
40
|
+
client?: {
|
|
41
|
+
/**
|
|
42
|
+
* Autogenerated queries will traverse references to a given depth
|
|
43
|
+
* @default 2
|
|
44
|
+
*/
|
|
45
|
+
referenceDepth?: number;
|
|
46
|
+
};
|
|
47
|
+
build: {
|
|
48
|
+
/**
|
|
49
|
+
* The folder where your application stores assets, eg. `"public"`
|
|
50
|
+
*/
|
|
51
|
+
publicFolder: string;
|
|
52
|
+
/**
|
|
53
|
+
* TinaCMS is shipped as a single-page app, the value specified here will
|
|
54
|
+
* determine the path when visiting the TinaCMS dashboard.
|
|
55
|
+
*
|
|
56
|
+
* Eg. `"admin"` will be viewable at `[your-development-url]/admin/index.html`
|
|
57
|
+
*/
|
|
58
|
+
outputFolder: string;
|
|
59
|
+
};
|
|
60
|
+
media?: {
|
|
61
|
+
/**
|
|
62
|
+
* Load a media store like Cloudinary
|
|
63
|
+
*
|
|
64
|
+
* ```ts
|
|
65
|
+
* loadCustomStore = async () => {
|
|
66
|
+
* const pack = await import("next-tinacms-cloudinary");
|
|
67
|
+
* return pack.TinaCloudCloudinaryMediaStore;
|
|
68
|
+
* }
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
loadCustomStore: () => Promise<Store>;
|
|
72
|
+
tina?: never;
|
|
73
|
+
} | {
|
|
74
|
+
/**
|
|
75
|
+
* Use Git-backed assets for media, these values will
|
|
76
|
+
* [Learn more](https://tina.io/docs/reference/media/repo-based/)
|
|
77
|
+
*/
|
|
78
|
+
tina: {
|
|
79
|
+
/**
|
|
80
|
+
* The folder where your application stores assets, eg. `"public"`
|
|
81
|
+
*/
|
|
82
|
+
publicFolder: string;
|
|
83
|
+
/**
|
|
84
|
+
* The root folder for media managed by Tina. For example, `"uploads"`
|
|
85
|
+
* would store content in `"<my-public-folder>/uploads"`
|
|
86
|
+
*/
|
|
87
|
+
mediaRoot: string;
|
|
88
|
+
};
|
|
89
|
+
loadCustomStore?: never;
|
|
90
|
+
};
|
|
91
|
+
tinaioConfig?: {
|
|
92
|
+
assetsApiUrlOverride?: string;
|
|
93
|
+
frontendUrlOverride?: string;
|
|
94
|
+
identityApiUrlOverride?: string;
|
|
95
|
+
contentApiUrlOverride?: string;
|
|
96
|
+
};
|
|
97
|
+
cmsCallback?: CMSCallback;
|
|
98
|
+
formifyCallback?: FormifyCallback;
|
|
99
|
+
documentCreatorCallback?: DocumentCreatorCallback;
|
|
100
|
+
};
|
|
101
|
+
export {};
|
package/dist/types/config.d.ts
CHANGED
|
@@ -37,4 +37,13 @@ export interface TinaCloudSchemaConfig<Store = any> {
|
|
|
37
37
|
mediaRoot: string;
|
|
38
38
|
};
|
|
39
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* Used to override the default Tina Cloud API URL
|
|
42
|
+
*/
|
|
43
|
+
tinaioConfig?: {
|
|
44
|
+
assetsApiUrlOverride?: string;
|
|
45
|
+
frontendUrlOverride?: string;
|
|
46
|
+
identityApiUrlOverride?: string;
|
|
47
|
+
contentApiUrlOverride?: string;
|
|
48
|
+
};
|
|
40
49
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Copyright 2021 Forestry.io Holdings, Inc.
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
See the License for the specific language governing permissions and
|
|
11
|
+
limitations under the License.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|