dicebear 9.4.2 → 10.0.0-rc.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Florian Körner
3
+ Copyright (c) 2026 Florian Körner
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/lib/index.js CHANGED
@@ -1,16 +1,54 @@
1
1
  import updateNotifier from 'update-notifier';
2
- import * as collection from '@dicebear/collection';
3
2
  import yargs from 'yargs';
4
3
  import { hideBin } from 'yargs/helpers';
4
+ import chalk from 'chalk';
5
5
  import { getPackageJson } from './utils/getPackageJson.js';
6
6
  import { addStyleCommand } from './utils/addStyleCommand.js';
7
+ import { loadStyles } from './utils/loadStyles.js';
8
+ import { loadDefinition } from './utils/loadDefinition.js';
9
+ import { handleStyleCommand } from './utils/handleStyleCommand.js';
10
+ import { getStyleCommandOptions } from './utils/getStyleCommandOptions.js';
7
11
  (async () => {
8
12
  const pkg = await getPackageJson();
9
13
  updateNotifier({ pkg }).notify();
10
14
  const cli = yargs(hideBin(process.argv));
11
- for (let name of Object.keys(collection)) {
12
- const style = collection[name];
15
+ const styles = loadStyles();
16
+ for (const [name, style] of styles) {
13
17
  addStyleCommand(cli, name, style);
14
18
  }
19
+ cli.command({
20
+ command: '* <definition> [outputPath]',
21
+ describe: false,
22
+ builder: (yargs) => {
23
+ const args = hideBin(process.argv);
24
+ const filePath = args[0];
25
+ let options = {};
26
+ if (filePath) {
27
+ try {
28
+ const { style } = loadDefinition(filePath);
29
+ options = getStyleCommandOptions(style);
30
+ }
31
+ catch (error) {
32
+ const message = error instanceof Error ? error.message : String(error);
33
+ console.error(chalk.red(`\nError: ${message}`));
34
+ process.exit(1);
35
+ }
36
+ }
37
+ return yargs
38
+ .default('outputPath', '.')
39
+ .options(options);
40
+ },
41
+ handler: async (argv) => {
42
+ try {
43
+ const { style, name } = loadDefinition(argv.definition);
44
+ await handleStyleCommand(argv, name, style);
45
+ }
46
+ catch (error) {
47
+ const message = error instanceof Error ? error.message : String(error);
48
+ console.error(chalk.red(`\nError: ${message}`));
49
+ process.exit(1);
50
+ }
51
+ },
52
+ });
15
53
  cli.demandCommand().help().locale('en').parse();
16
54
  })();
@@ -1,3 +1,3 @@
1
1
  import type { Style } from '@dicebear/core';
2
2
  import yargs from 'yargs';
3
- export declare function addStyleCommand(cli: yargs.Argv<{}>, name: string, style: Style<any>): yargs.Argv<{}>;
3
+ export declare function addStyleCommand(cli: yargs.Argv<{}>, name: string, style: Style): yargs.Argv<{}>;
@@ -1,91 +1,25 @@
1
- import { createAvatar } from '@dicebear/core';
2
- import { toJpeg, toPng, toWebp, toAvif } from '@dicebear/converter';
3
- import cliProgress from 'cli-progress';
4
- import PQueue from 'p-queue';
5
- import os from 'node:os';
6
- import * as path from 'node:path';
7
- import fs from 'fs-extra';
8
- import { exiftool } from 'exiftool-vendored';
9
- import { getStyleCommandSchema } from './getStyleCommandSchema.js';
10
- import { getOptionsBySchema } from './getOptionsBySchema.js';
11
- import { validateInputBySchema } from './validateInputBySchema.js';
12
- import { outputStyleLicenseBanner } from './outputStyleLicenseBanner.js';
13
- import { createRandomSeed } from './createRandomSeed.js';
14
- import { writeFile } from './writeFile.js';
1
+ import chalk from 'chalk';
2
+ import { getStyleCommandOptions } from './getStyleCommandOptions.js';
3
+ import { handleStyleCommand } from './handleStyleCommand.js';
15
4
  export function addStyleCommand(cli, name, style) {
16
- const schema = getStyleCommandSchema(style);
5
+ const options = getStyleCommandOptions(style);
17
6
  return cli.command({
18
7
  command: `${name} [outputPath]`,
19
8
  describe: `Generate "${name}" avatar(s)`,
20
9
  builder: (yargs) => {
21
10
  return yargs
22
11
  .default('outputPath', '.')
23
- .options(getOptionsBySchema(schema));
12
+ .options(options);
24
13
  },
25
14
  handler: async (argv) => {
26
- const bar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);
27
- const validated = validateInputBySchema(argv, schema);
28
- const format = validated.format;
29
- const count = validated.count;
30
- const includeExif = validated.exif;
31
- const json = validated.json;
32
- outputStyleLicenseBanner(name, style);
33
- bar.start(count, 0);
34
- const queue = new PQueue({ concurrency: os.cpus().length || 1 });
35
- queue.on('next', () => {
36
- bar.update(count - queue.size - queue.pending);
37
- });
38
- const outputPath = path.resolve(process.cwd(), argv.outputPath);
39
- await fs.ensureDir(outputPath);
40
- for (let i = 0; i < count; i++) {
41
- queue.add(async () => {
42
- const fileName = path.resolve(process.cwd(), outputPath, `${name}-${i}.${format}`);
43
- const avatar = createAvatar(style, count <= 1
44
- ? validated
45
- : {
46
- ...validated,
47
- seed: createRandomSeed(),
48
- });
49
- switch (format) {
50
- case 'svg':
51
- await writeFile(fileName, avatar.toString());
52
- break;
53
- case 'png':
54
- await writeFile(fileName, await toPng(avatar.toString(), { includeExif, size: validated.size }).toArrayBuffer());
55
- break;
56
- case 'jpg':
57
- case 'jpeg':
58
- await writeFile(fileName, await toJpeg(avatar.toString(), {
59
- includeExif,
60
- size: validated.size,
61
- }).toArrayBuffer());
62
- break;
63
- case 'webp':
64
- await writeFile(fileName, await toWebp(avatar.toString(), {
65
- includeExif,
66
- size: validated.size,
67
- }).toArrayBuffer());
68
- break;
69
- case 'avif':
70
- await writeFile(fileName, await toAvif(avatar.toString(), {
71
- includeExif,
72
- size: validated.size,
73
- }).toArrayBuffer());
74
- break;
75
- case 'json':
76
- await writeFile(fileName, JSON.stringify(avatar.toJson(), null, 2));
77
- break;
78
- }
79
- if (json && 'json' !== format) {
80
- const jsonFileName = path.resolve(process.cwd(), outputPath, `${name}-${i}.json`);
81
- await fs.writeJSON(jsonFileName, avatar.toJson(), { spaces: 2 });
82
- }
83
- bar.increment();
84
- });
15
+ try {
16
+ await handleStyleCommand(argv, name, style);
17
+ }
18
+ catch (error) {
19
+ const message = error instanceof Error ? error.message : String(error);
20
+ console.error(chalk.red(`\nError: ${message}`));
21
+ process.exit(1);
85
22
  }
86
- await queue.onIdle();
87
- bar.stop();
88
- exiftool.end();
89
23
  },
90
24
  });
91
25
  }
@@ -0,0 +1,2 @@
1
+ import { Style } from '@dicebear/core';
2
+ export declare function extractStyleOptions(argv: Record<string, unknown>, style: Style): Record<string, unknown>;
@@ -0,0 +1,42 @@
1
+ import { OptionsDescriptor } from '@dicebear/core';
2
+ // Parses ['variant01:2', 'variant03:1'] (array from yargs) into { variant01: 2, variant03: 1 }
3
+ function parseWeightedValue(value) {
4
+ const result = {};
5
+ for (const pair of value) {
6
+ const [key, weight] = pair.split(':');
7
+ if (key) {
8
+ result[key.trim()] = weight !== undefined ? Number(weight) : 1;
9
+ }
10
+ }
11
+ return result;
12
+ }
13
+ // Parses "10,50" into [10, 50] for range values
14
+ function parseRangeValue(value) {
15
+ if (typeof value === 'string' && value.includes(',')) {
16
+ const parts = value.split(',').map(Number);
17
+ if (parts.length === 2 && parts.every((n) => !isNaN(n))) {
18
+ return parts;
19
+ }
20
+ }
21
+ return value;
22
+ }
23
+ // Extracts only the style-relevant options from the yargs argv object,
24
+ // filtering out CLI-specific keys (_, $0, outputPath, count, format, etc.).
25
+ export function extractStyleOptions(argv, style) {
26
+ const descriptor = new OptionsDescriptor(style).toJSON();
27
+ const result = {};
28
+ for (const [key, field] of Object.entries(descriptor)) {
29
+ if (!(key in argv) || argv[key] === undefined) {
30
+ continue;
31
+ }
32
+ let value = argv[key];
33
+ if (field.type === 'enum' && field.weighted && Array.isArray(value)) {
34
+ value = parseWeightedValue(value);
35
+ }
36
+ if (field.type === 'range') {
37
+ value = parseRangeValue(value);
38
+ }
39
+ result[key] = value;
40
+ }
41
+ return result;
42
+ }
@@ -0,0 +1,3 @@
1
+ import { Style } from '@dicebear/core';
2
+ import type { Options } from 'yargs';
3
+ export declare function getStyleCommandOptions(style: Style): Record<string, Options>;
@@ -0,0 +1,54 @@
1
+ import { OptionsDescriptor } from '@dicebear/core';
2
+ export function getStyleCommandOptions(style) {
3
+ const descriptor = new OptionsDescriptor(style).toJSON();
4
+ const result = {
5
+ count: {
6
+ description: 'Defines how many avatars to create.',
7
+ type: 'number',
8
+ default: 1,
9
+ },
10
+ format: {
11
+ type: 'string',
12
+ choices: ['svg', 'png', 'jpg', 'jpeg', 'webp', 'avif', 'json'],
13
+ default: 'svg',
14
+ },
15
+ exif: {
16
+ description: 'Include Exif Metadata',
17
+ type: 'boolean',
18
+ default: false,
19
+ },
20
+ json: {
21
+ description: 'Save JSON file in addition to image file',
22
+ type: 'boolean',
23
+ default: false,
24
+ },
25
+ };
26
+ for (const [key, field] of Object.entries(descriptor)) {
27
+ const option = {};
28
+ switch (field.type) {
29
+ case 'string':
30
+ option.type = 'string';
31
+ break;
32
+ case 'number':
33
+ option.type = 'number';
34
+ break;
35
+ case 'range':
36
+ option.type = 'string';
37
+ break;
38
+ case 'boolean':
39
+ option.type = 'boolean';
40
+ break;
41
+ case 'enum':
42
+ option.type = 'string';
43
+ if (!field.weighted) {
44
+ option.choices = field.values;
45
+ }
46
+ break;
47
+ case 'color':
48
+ option.type = 'string';
49
+ break;
50
+ }
51
+ result[key] = option;
52
+ }
53
+ return result;
54
+ }
@@ -0,0 +1,3 @@
1
+ import { Style } from '@dicebear/core';
2
+ import type { ArgumentsCamelCase } from 'yargs';
3
+ export declare function handleStyleCommand(argv: ArgumentsCamelCase<{}>, name: string, style: Style): Promise<void>;
@@ -0,0 +1,88 @@
1
+ import { Avatar } from '@dicebear/core';
2
+ import { toJpeg, toPng, toWebp, toAvif } from '@dicebear/converter';
3
+ import cliProgress from 'cli-progress';
4
+ import PQueue from 'p-queue';
5
+ import os from 'node:os';
6
+ import * as path from 'node:path';
7
+ import fs from 'fs-extra';
8
+ import { exiftool } from 'exiftool-vendored';
9
+ import { extractStyleOptions } from './extractStyleOptions.js';
10
+ import { outputStyleLicenseBanner } from './outputStyleLicenseBanner.js';
11
+ import { createRandomSeed } from './createRandomSeed.js';
12
+ import { writeFile } from './writeFile.js';
13
+ export async function handleStyleCommand(argv, name, style) {
14
+ var _a, _b, _c, _d;
15
+ const bar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);
16
+ const format = (_a = argv.format) !== null && _a !== void 0 ? _a : 'svg';
17
+ const count = (_b = argv.count) !== null && _b !== void 0 ? _b : 1;
18
+ const includeExif = (_c = argv.exif) !== null && _c !== void 0 ? _c : false;
19
+ const json = (_d = argv.json) !== null && _d !== void 0 ? _d : false;
20
+ outputStyleLicenseBanner(name, style);
21
+ bar.start(count, 0);
22
+ const queue = new PQueue({ concurrency: os.cpus().length || 1 });
23
+ const errors = [];
24
+ queue.on('next', () => {
25
+ bar.update(count - queue.size - queue.pending);
26
+ });
27
+ const outputPath = path.resolve(process.cwd(), argv.outputPath);
28
+ await fs.ensureDir(outputPath);
29
+ for (let i = 0; i < count; i++) {
30
+ queue.add(async () => {
31
+ var _a;
32
+ try {
33
+ const fileName = path.resolve(process.cwd(), outputPath, `${name}-${i}.${format}`);
34
+ const seed = count <= 1
35
+ ? (_a = argv.seed) !== null && _a !== void 0 ? _a : createRandomSeed()
36
+ : createRandomSeed();
37
+ const avatar = new Avatar(style, {
38
+ ...extractStyleOptions(argv, style),
39
+ seed,
40
+ });
41
+ switch (format) {
42
+ case 'svg':
43
+ await writeFile(fileName, avatar.toString());
44
+ break;
45
+ case 'png':
46
+ await writeFile(fileName, await toPng(avatar.toString(), { includeExif, size: argv.size }).toArrayBuffer());
47
+ break;
48
+ case 'jpg':
49
+ case 'jpeg':
50
+ await writeFile(fileName, await toJpeg(avatar.toString(), {
51
+ includeExif,
52
+ size: argv.size,
53
+ }).toArrayBuffer());
54
+ break;
55
+ case 'webp':
56
+ await writeFile(fileName, await toWebp(avatar.toString(), {
57
+ includeExif,
58
+ size: argv.size,
59
+ }).toArrayBuffer());
60
+ break;
61
+ case 'avif':
62
+ await writeFile(fileName, await toAvif(avatar.toString(), {
63
+ includeExif,
64
+ size: argv.size,
65
+ }).toArrayBuffer());
66
+ break;
67
+ case 'json':
68
+ await writeFile(fileName, JSON.stringify(avatar.toJSON(), null, 2));
69
+ break;
70
+ }
71
+ if (json && 'json' !== format) {
72
+ const jsonFileName = path.resolve(process.cwd(), outputPath, `${name}-${i}.json`);
73
+ await fs.writeJSON(jsonFileName, avatar.toJSON(), { spaces: 2 });
74
+ }
75
+ bar.increment();
76
+ }
77
+ catch (error) {
78
+ errors.push(error instanceof Error ? error : new Error(String(error)));
79
+ }
80
+ });
81
+ }
82
+ await queue.onIdle();
83
+ bar.stop();
84
+ exiftool.end();
85
+ if (errors.length > 0) {
86
+ throw errors[0];
87
+ }
88
+ }
@@ -0,0 +1,5 @@
1
+ import { Style } from '@dicebear/core';
2
+ export declare function loadDefinition(filePath: string): {
3
+ style: Style;
4
+ name: string;
5
+ };
@@ -0,0 +1,10 @@
1
+ import { Style } from '@dicebear/core';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ export function loadDefinition(filePath) {
5
+ const definitionPath = path.resolve(process.cwd(), filePath);
6
+ const definition = JSON.parse(fs.readFileSync(definitionPath, 'utf-8'));
7
+ const style = new Style(definition);
8
+ const name = path.basename(definitionPath, path.extname(definitionPath));
9
+ return { style, name };
10
+ }
@@ -0,0 +1,2 @@
1
+ import { Style } from '@dicebear/core';
2
+ export declare function loadStyles(): Map<string, Style>;
@@ -0,0 +1,18 @@
1
+ import { Style } from '@dicebear/core';
2
+ import { createRequire } from 'node:module';
3
+ import * as path from 'node:path';
4
+ import * as fs from 'node:fs';
5
+ const require = createRequire(import.meta.url);
6
+ export function loadStyles() {
7
+ const definitionsDir = path.dirname(require.resolve('@dicebear/definitions/initials.json'));
8
+ const styles = new Map();
9
+ for (const file of fs.readdirSync(definitionsDir)) {
10
+ if (!file.endsWith('.min.json')) {
11
+ continue;
12
+ }
13
+ const name = file.replace('.min.json', '');
14
+ const definition = JSON.parse(fs.readFileSync(path.join(definitionsDir, file), 'utf-8'));
15
+ styles.set(name, new Style(definition));
16
+ }
17
+ return styles;
18
+ }
@@ -1,2 +1,2 @@
1
1
  import type { Style } from '@dicebear/core';
2
- export declare function outputStyleLicenseBanner(name: string, style: Style<any>): void;
2
+ export declare function outputStyleLicenseBanner(name: string, style: Style): void;
@@ -1,29 +1,28 @@
1
1
  import chalk from 'chalk';
2
2
  import chalkTemplate from 'chalk-template';
3
3
  export function outputStyleLicenseBanner(name, style) {
4
- var _a, _b, _c, _d, _e, _f, _g, _h;
5
- let banner = ['-'.repeat(64)];
6
- let creator = Array.isArray((_a = style.meta) === null || _a === void 0 ? void 0 : _a.creator)
7
- ? (_b = style.meta) === null || _b === void 0 ? void 0 : _b.creator.join(', ')
8
- : (_c = style.meta) === null || _c === void 0 ? void 0 : _c.creator;
9
- if (((_d = style.meta) === null || _d === void 0 ? void 0 : _d.title) && creator) {
10
- banner.push(chalkTemplate `{bold ${style.meta.title}} by {bold ${creator}}`);
4
+ const meta = style.meta();
5
+ const sourceName = meta.source().name();
6
+ const creatorName = meta.creator().name();
7
+ const sourceUrl = meta.source().url();
8
+ const licenseName = meta.license().name();
9
+ const licenseUrl = meta.license().url();
10
+ const banner = ['-'.repeat(64)];
11
+ if (sourceName && creatorName) {
12
+ banner.push(chalkTemplate `{bold ${sourceName}} by {bold ${creatorName}}`);
11
13
  }
12
- else if ((_e = style.meta) === null || _e === void 0 ? void 0 : _e.title) {
13
- banner.push(chalkTemplate `{bold ${style.meta.title}}`);
14
+ else if (sourceName) {
15
+ banner.push(chalkTemplate `{bold ${sourceName}}`);
14
16
  }
15
- else if (creator) {
16
- banner.push(chalkTemplate `{bold ${name}} by {bold ${creator}}`);
17
+ else if (creatorName) {
18
+ banner.push(chalkTemplate `{bold ${name}} by {bold ${creatorName}}`);
17
19
  }
18
20
  banner.push('');
19
- if ((_f = style.meta) === null || _f === void 0 ? void 0 : _f.homepage) {
20
- banner.push(`Homepage: ${style.meta.homepage}`);
21
+ if (sourceUrl) {
22
+ banner.push(`Source: ${sourceUrl}`);
21
23
  }
22
- if ((_g = style.meta) === null || _g === void 0 ? void 0 : _g.source) {
23
- banner.push(`Source: ${style.meta.source}`);
24
- }
25
- if ((_h = style.meta) === null || _h === void 0 ? void 0 : _h.license) {
26
- banner.push(`License: ${style.meta.license.name} - ${style.meta.license.url}`);
24
+ if (licenseName) {
25
+ banner.push(`License: ${licenseName}${licenseUrl ? ` - ${licenseUrl}` : ''}`);
27
26
  }
28
27
  banner.push('-'.repeat(64));
29
28
  banner.push('');
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "dicebear",
3
- "version": "9.4.2",
3
+ "version": "10.0.0-rc.2",
4
4
  "private": false,
5
- "description": "CLI for DiceBear - An avatar library for designers and developers",
5
+ "description": "CLI for DiceBear unique avatars from dozens of styles.",
6
6
  "homepage": "https://github.com/dicebear/dicebear",
7
7
  "repository": {
8
8
  "type": "git",
@@ -25,9 +25,9 @@
25
25
  "prepublishOnly": "npm run build"
26
26
  },
27
27
  "dependencies": {
28
- "@dicebear/collection": "9.4.2",
29
- "@dicebear/converter": "9.4.2",
30
- "@dicebear/core": "9.4.2",
28
+ "@dicebear/converter": "10.0.0-rc.2",
29
+ "@dicebear/core": "10.0.0-rc.2",
30
+ "@dicebear/definitions": "^0.1.0",
31
31
  "ajv": "^8.17.1",
32
32
  "chalk": "^5.4.1",
33
33
  "chalk-template": "^1.1.2",
@@ -1,2 +0,0 @@
1
- import { JSONSchema7 } from 'json-schema';
2
- export declare function getOptionsBySchema(schema: JSONSchema7): Record<string, any>;
@@ -1,34 +0,0 @@
1
- export function getOptionsBySchema(schema) {
2
- const result = {};
3
- for (var key in schema.properties) {
4
- if (false === schema.properties.hasOwnProperty(key)) {
5
- continue;
6
- }
7
- const property = schema.properties[key];
8
- if (typeof property === 'object') {
9
- const option = {
10
- type: property.type,
11
- };
12
- if (option.type === 'integer') {
13
- option.type = 'number';
14
- }
15
- option.choices = [];
16
- if (property.enum) {
17
- option.choices.push(...property.enum.filter((v) => typeof v === 'string'));
18
- }
19
- if (typeof property.items === 'object' &&
20
- 'enum' in property.items &&
21
- property.items.enum) {
22
- option.choices.push(...property.items.enum.filter((v) => typeof v === 'string'));
23
- }
24
- if (option.choices.length === 0) {
25
- delete option.choices;
26
- }
27
- if (property.description) {
28
- option.description = property.description;
29
- }
30
- result[key] = option;
31
- }
32
- }
33
- return result;
34
- }
@@ -1,3 +0,0 @@
1
- import type { Style } from '@dicebear/core';
2
- import type { JSONSchema7 } from 'json-schema';
3
- export declare function getStyleCommandSchema(style: Style<any>): JSONSchema7;
@@ -1,32 +0,0 @@
1
- import { schema as coreSchema } from '@dicebear/core';
2
- export function getStyleCommandSchema(style) {
3
- var _a;
4
- return {
5
- $schema: 'http://json-schema.org/draft-07/schema#',
6
- type: 'object',
7
- properties: {
8
- count: {
9
- description: 'Defines how many avatars to create.',
10
- type: 'number',
11
- default: 1,
12
- },
13
- format: {
14
- type: 'string',
15
- enum: ['svg', 'png', 'jpg', 'jpeg', 'webp', 'avif', 'json'],
16
- default: 'svg',
17
- },
18
- exif: {
19
- description: 'Include Exif Metadata',
20
- type: 'boolean',
21
- default: false,
22
- },
23
- json: {
24
- description: 'Save JSON file in addition to image file',
25
- type: 'boolean',
26
- default: false,
27
- },
28
- ...coreSchema.properties,
29
- ...(_a = style.schema) === null || _a === void 0 ? void 0 : _a.properties,
30
- },
31
- };
32
- }
@@ -1,5 +0,0 @@
1
- import { JSONSchema7 } from 'json-schema';
2
- import { ArgumentsCamelCase } from 'yargs';
3
- export declare function validateInputBySchema(input: ArgumentsCamelCase<unknown>, schema: JSONSchema7): {
4
- [x: string]: {};
5
- };
@@ -1,20 +0,0 @@
1
- import Ajv from 'ajv';
2
- export function validateInputBySchema(input, schema) {
3
- const validator = new Ajv({
4
- strict: false,
5
- coerceTypes: true,
6
- useDefaults: true,
7
- removeAdditional: 'all',
8
- });
9
- const validate = validator.compile(schema);
10
- const data = JSON.parse(JSON.stringify(input));
11
- if (false === validate(data)) {
12
- if (validate.errors) {
13
- for (var error of validate.errors) {
14
- throw new Error(`${error.keyword} - ${error.message}`);
15
- }
16
- }
17
- throw new Error('Unknown error');
18
- }
19
- return data;
20
- }