@strapi/strapi 4.9.0-alpha.0 → 4.9.0-beta.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.
@@ -1,56 +1,62 @@
1
1
  'use strict';
2
2
 
3
- const { createTransferEngine } = require('@strapi/data-transfer/lib/engine');
4
3
  const {
5
- providers: {
6
- createRemoteStrapiDestinationProvider,
7
- createLocalStrapiSourceProvider,
8
- createLocalStrapiDestinationProvider,
4
+ engine: { createTransferEngine },
5
+ strapi: {
6
+ providers: {
7
+ createRemoteStrapiDestinationProvider,
8
+ createLocalStrapiSourceProvider,
9
+ createLocalStrapiDestinationProvider,
10
+ createRemoteStrapiSourceProvider,
11
+ },
9
12
  },
10
- } = require('@strapi/data-transfer/lib/strapi');
13
+ } = require('@strapi/data-transfer');
11
14
  const { isObject } = require('lodash/fp');
12
- const chalk = require('chalk');
13
15
 
14
16
  const {
15
17
  buildTransferTable,
16
18
  createStrapiInstance,
17
19
  DEFAULT_IGNORED_CONTENT_TYPES,
18
20
  formatDiagnostic,
21
+ loadersFactory,
22
+ exitMessageText,
23
+ abortTransfer,
19
24
  } = require('./utils');
20
-
21
- const logger = console;
25
+ const { exitWith } = require('../utils/helpers');
22
26
 
23
27
  /**
24
28
  * @typedef TransferCommandOptions Options given to the CLI transfer command
25
29
  *
26
30
  * @property {URL|undefined} [to] The url of a remote Strapi to use as remote destination
27
31
  * @property {URL|undefined} [from] The url of a remote Strapi to use as remote source
32
+ * @property {string|undefined} [toToken] The transfer token for the remote Strapi destination
33
+ * @property {string|undefined} [fromToken] The transfer token for the remote Strapi source
34
+ * @property {(keyof import('@strapi/data-transfer/src/engine').TransferGroupFilter)[]} [only] If present, only include these filtered groups of data
35
+ * @property {(keyof import('@strapi/data-transfer/src/engine').TransferGroupFilter)[]} [exclude] If present, exclude these filtered groups of data
36
+ * @property {number|undefined} [throttle] Delay in ms after each record
28
37
  */
29
38
 
30
39
  /**
31
40
  * Transfer command.
32
41
  *
33
- * It transfers data from a local file to a local strapi instance
42
+ * Transfers data between local Strapi and remote Strapi instances
34
43
  *
35
44
  * @param {TransferCommandOptions} opts
36
45
  */
37
46
  module.exports = async (opts) => {
38
47
  // Validate inputs from Commander
39
48
  if (!isObject(opts)) {
40
- logger.error('Could not parse command arguments');
41
- process.exit(1);
49
+ exitWith(1, 'Could not parse command arguments');
42
50
  }
43
51
 
44
- const strapi = await createStrapiInstance();
52
+ if (!(opts.from || opts.to) || (opts.from && opts.to)) {
53
+ exitWith(1, 'Exactly one source (from) or destination (to) option must be provided');
54
+ }
45
55
 
56
+ const strapi = await createStrapiInstance();
46
57
  let source;
47
58
  let destination;
48
59
 
49
- if (!opts.from && !opts.to) {
50
- logger.error('At least one source (from) or destination (to) option must be provided');
51
- process.exit(1);
52
- }
53
-
54
60
  // if no URL provided, use local Strapi
55
61
  if (!opts.from) {
56
62
  source = createLocalStrapiSourceProvider({
@@ -59,21 +65,42 @@ module.exports = async (opts) => {
59
65
  }
60
66
  // if URL provided, set up a remote source provider
61
67
  else {
62
- logger.error(`Remote Strapi source provider not yet implemented`);
63
- process.exit(1);
68
+ if (!opts.fromToken) {
69
+ exitWith(1, 'Missing token for remote destination');
70
+ }
71
+
72
+ source = createRemoteStrapiSourceProvider({
73
+ getStrapi: () => strapi,
74
+ url: opts.from,
75
+ auth: {
76
+ type: 'token',
77
+ token: opts.fromToken,
78
+ },
79
+ });
64
80
  }
65
81
 
66
82
  // if no URL provided, use local Strapi
67
83
  if (!opts.to) {
68
84
  destination = createLocalStrapiDestinationProvider({
69
85
  getStrapi: () => strapi,
86
+ strategy: 'restore',
87
+ restore: {
88
+ entities: { exclude: DEFAULT_IGNORED_CONTENT_TYPES },
89
+ },
70
90
  });
71
91
  }
72
92
  // if URL provided, set up a remote destination provider
73
93
  else {
94
+ if (!opts.toToken) {
95
+ exitWith(1, 'Missing token for remote destination');
96
+ }
97
+
74
98
  destination = createRemoteStrapiDestinationProvider({
75
99
  url: opts.to,
76
- auth: false,
100
+ auth: {
101
+ type: 'token',
102
+ token: opts.toToken,
103
+ },
77
104
  strategy: 'restore',
78
105
  restore: {
79
106
  entities: { exclude: DEFAULT_IGNORED_CONTENT_TYPES },
@@ -82,13 +109,15 @@ module.exports = async (opts) => {
82
109
  }
83
110
 
84
111
  if (!source || !destination) {
85
- logger.error('Could not create providers');
86
- process.exit(1);
112
+ exitWith(1, 'Could not create providers');
87
113
  }
88
114
 
89
115
  const engine = createTransferEngine(source, destination, {
90
- versionStrategy: 'strict',
116
+ versionStrategy: 'exact',
91
117
  schemaStrategy: 'strict',
118
+ exclude: opts.exclude,
119
+ only: opts.only,
120
+ throttle: opts.throttle,
92
121
  transforms: {
93
122
  links: [
94
123
  {
@@ -112,18 +141,42 @@ module.exports = async (opts) => {
112
141
 
113
142
  engine.diagnostics.onDiagnostic(formatDiagnostic('transfer'));
114
143
 
115
- try {
116
- logger.log(`Starting transfer...`);
144
+ const progress = engine.progress.stream;
145
+
146
+ const { updateLoader } = loadersFactory();
117
147
 
118
- const results = await engine.transfer();
148
+ progress.on(`stage::start`, ({ stage, data }) => {
149
+ updateLoader(stage, data).start();
150
+ });
151
+
152
+ progress.on('stage::finish', ({ stage, data }) => {
153
+ updateLoader(stage, data).succeed();
154
+ });
119
155
 
120
- const table = buildTransferTable(results.engine);
121
- logger.log(table.toString());
156
+ progress.on('stage::progress', ({ stage, data }) => {
157
+ updateLoader(stage, data);
158
+ });
122
159
 
123
- logger.log(`${chalk.bold('Transfer process has been completed successfully!')}`);
124
- process.exit(0);
160
+ progress.on('stage::error', ({ stage, data }) => {
161
+ updateLoader(stage, data).fail();
162
+ });
163
+
164
+ let results;
165
+ try {
166
+ console.log(`Starting transfer...`);
167
+
168
+ // Abort transfer if user interrupts process
169
+ ['SIGTERM', 'SIGINT', 'SIGQUIT'].forEach((signal) => {
170
+ process.removeAllListeners(signal);
171
+ process.on(signal, () => abortTransfer({ engine, strapi }));
172
+ });
173
+
174
+ results = await engine.transfer();
125
175
  } catch (e) {
126
- logger.error('Transfer process failed.');
127
- process.exit(1);
176
+ exitWith(1, exitMessageText('transfer', true));
128
177
  }
178
+
179
+ const table = buildTransferTable(results.engine);
180
+ console.log(table.toString());
181
+ exitWith(0, exitMessageText('transfer'));
129
182
  };
@@ -3,15 +3,30 @@
3
3
  const chalk = require('chalk');
4
4
  const Table = require('cli-table3');
5
5
  const { Option } = require('commander');
6
- const { TransferGroupPresets } = require('@strapi/data-transfer/lib/engine');
6
+ const {
7
+ engine: { TransferGroupPresets },
8
+ } = require('@strapi/data-transfer');
7
9
 
8
10
  const {
9
11
  configs: { createOutputFileConfiguration },
10
12
  createLogger,
11
13
  } = require('@strapi/logger');
14
+ const ora = require('ora');
12
15
  const { readableBytes, exitWith } = require('../utils/helpers');
13
16
  const strapi = require('../../index');
14
- const { getParseListWithChoices } = require('../utils/commander');
17
+ const { getParseListWithChoices, parseInteger } = require('../utils/commander');
18
+
19
+ const exitMessageText = (process, error = false) => {
20
+ const processCapitalized = process[0].toUpperCase() + process.slice(1);
21
+
22
+ if (!error) {
23
+ return chalk.bold(
24
+ chalk.green(`${processCapitalized} process has been completed successfully!`)
25
+ );
26
+ }
27
+
28
+ return chalk.bold(chalk.red(`${processCapitalized} process failed.`));
29
+ };
15
30
 
16
31
  const pad = (n) => {
17
32
  return (n < 10 ? '0' : '') + String(n);
@@ -82,15 +97,28 @@ const DEFAULT_IGNORED_CONTENT_TYPES = [
82
97
  'admin::role',
83
98
  'admin::api-token',
84
99
  'admin::api-token-permission',
100
+ 'admin::transfer-token',
101
+ 'admin::transfer-token-permission',
85
102
  'admin::audit-log',
86
103
  ];
87
104
 
88
- const createStrapiInstance = async (logLevel = 'error') => {
105
+ const abortTransfer = async ({ engine, strapi }) => {
106
+ try {
107
+ await engine.abortTransfer();
108
+ await strapi.destroy();
109
+ } catch (e) {
110
+ // ignore because there's not much else we can do
111
+ return false;
112
+ }
113
+ return true;
114
+ };
115
+
116
+ const createStrapiInstance = async (opts = {}) => {
89
117
  try {
90
118
  const appContext = await strapi.compile();
91
- const app = strapi(appContext);
119
+ const app = strapi({ ...opts, ...appContext });
92
120
 
93
- app.log.level = logLevel;
121
+ app.log.level = opts.logLevel || 'error';
94
122
  return await app.load();
95
123
  } catch (err) {
96
124
  if (err.code === 'ECONNREFUSED') {
@@ -102,6 +130,13 @@ const createStrapiInstance = async (logLevel = 'error') => {
102
130
 
103
131
  const transferDataTypes = Object.keys(TransferGroupPresets);
104
132
 
133
+ const throttleOption = new Option(
134
+ '--throttle <delay after each entity>',
135
+ `Add a delay in milliseconds between each transferred entity`
136
+ )
137
+ .argParser(parseInteger)
138
+ .hideHelp(); // This option is not publicly documented
139
+
105
140
  const excludeOption = new Option(
106
141
  '--exclude <comma-separated data types>',
107
142
  `Exclude data using comma-separated types. Available types: ${transferDataTypes.join(',')}`
@@ -169,13 +204,55 @@ const formatDiagnostic =
169
204
  }
170
205
  };
171
206
 
207
+ const loadersFactory = (defaultLoaders = {}) => {
208
+ const loaders = defaultLoaders;
209
+ const updateLoader = (stage, data) => {
210
+ if (!(stage in loaders)) {
211
+ createLoader(stage);
212
+ }
213
+ const stageData = data[stage];
214
+ const elapsedTime = stageData?.startTime
215
+ ? (stageData?.endTime || Date.now()) - stageData.startTime
216
+ : 0;
217
+ const size = `size: ${readableBytes(stageData?.bytes ?? 0)}`;
218
+ const elapsed = `elapsed: ${elapsedTime} ms`;
219
+ const speed =
220
+ elapsedTime > 0 ? `(${readableBytes(((stageData?.bytes ?? 0) * 1000) / elapsedTime)}/s)` : '';
221
+
222
+ loaders[stage].text = `${stage}: ${stageData?.count ?? 0} transfered (${size}) (${elapsed}) ${
223
+ !stageData?.endTime ? speed : ''
224
+ }`;
225
+
226
+ return loaders[stage];
227
+ };
228
+
229
+ const createLoader = (stage) => {
230
+ Object.assign(loaders, { [stage]: ora() });
231
+ return loaders[stage];
232
+ };
233
+
234
+ const getLoader = (stage) => {
235
+ return loaders[stage];
236
+ };
237
+
238
+ return {
239
+ updateLoader,
240
+ createLoader,
241
+ getLoader,
242
+ };
243
+ };
244
+
172
245
  module.exports = {
246
+ loadersFactory,
173
247
  buildTransferTable,
174
248
  getDefaultExportName,
175
249
  DEFAULT_IGNORED_CONTENT_TYPES,
176
250
  createStrapiInstance,
177
251
  excludeOption,
252
+ exitMessageText,
178
253
  onlyOption,
254
+ throttleOption,
179
255
  validateExcludeOnly,
180
256
  formatDiagnostic,
257
+ abortTransfer,
181
258
  };
@@ -7,6 +7,7 @@
7
7
  const inquirer = require('inquirer');
8
8
  const { InvalidOptionArgumentError, Option } = require('commander');
9
9
  const { bold, green, cyan } = require('chalk');
10
+ const { isNaN } = require('lodash/fp');
10
11
  const { exitWith } = require('./helpers');
11
12
 
12
13
  /**
@@ -40,6 +41,18 @@ const getParseListWithChoices = (choices, errorMessage = 'Invalid options:') =>
40
41
  };
41
42
  };
42
43
 
44
+ /**
45
+ * argParser: Parse a string as an integer
46
+ */
47
+ const parseInteger = (value) => {
48
+ // parseInt takes a string and a radix
49
+ const parsedValue = parseInt(value, 10);
50
+ if (isNaN(parsedValue)) {
51
+ throw new InvalidOptionArgumentError(`Not an integer: ${value}`);
52
+ }
53
+ return parsedValue;
54
+ };
55
+
43
56
  /**
44
57
  * argParser: Parse a string as a URL object
45
58
  */
@@ -96,8 +109,9 @@ const promptEncryptionKey = async (thisCommand) => {
96
109
  *
97
110
  * @param {string} message The message to confirm with user
98
111
  * @param {object} options Additional options
112
+ * @param {string|undefined} options.failMessage The message to display when prompt is not confirmed
99
113
  */
100
- const confirmMessage = (message) => {
114
+ const confirmMessage = (message, { failMessage } = {}) => {
101
115
  return async (command) => {
102
116
  // if we have a force option, assume yes
103
117
  const opts = command.opts();
@@ -116,7 +130,7 @@ const confirmMessage = (message) => {
116
130
  },
117
131
  ]);
118
132
  if (!answers.confirm) {
119
- exitWith(0);
133
+ exitWith(1, failMessage);
120
134
  }
121
135
  };
122
136
  };
@@ -130,6 +144,7 @@ module.exports = {
130
144
  getParseListWithChoices,
131
145
  parseList,
132
146
  parseURL,
147
+ parseInteger,
133
148
  promptEncryptionKey,
134
149
  confirmMessage,
135
150
  forceOption,
@@ -36,22 +36,29 @@ const readableBytes = (bytes, decimals = 1, padStart = 0) => {
36
36
  *
37
37
  * @param {number} code Code to exit process with
38
38
  * @param {string | Array} message Message(s) to display before exiting
39
+ * @param {Object} options
40
+ * @param {console} options.logger - logger object, defaults to console
41
+ * @param {process} options.prc - process object, defaults to process
42
+ *
39
43
  */
40
- const exitWith = (code, message = undefined) => {
41
- const logger = (message) => {
44
+ const exitWith = (code, message = undefined, options = {}) => {
45
+ const { logger = console, prc = process } = options;
46
+
47
+ const log = (message) => {
42
48
  if (code === 0) {
43
- console.log(chalk.green(message));
49
+ logger.log(chalk.green(message));
44
50
  } else {
45
- console.error(chalk.red(message));
51
+ logger.error(chalk.red(message));
46
52
  }
47
53
  };
48
54
 
49
55
  if (isString(message)) {
50
- logger(message);
56
+ log(message);
51
57
  } else if (isArray(message)) {
52
- message.forEach((msg) => logger(msg));
58
+ message.forEach((msg) => log(msg));
53
59
  }
54
- process.exit(code);
60
+
61
+ prc.exit(code);
55
62
  };
56
63
 
57
64
  /**
@@ -1,4 +1,3 @@
1
-
2
1
  type Handler = (context: any) => any;
3
2
 
4
3
  type AsyncHook = {
@@ -8,7 +7,6 @@ type AsyncHook = {
8
7
  call(): Promise<void>;
9
8
  };
10
9
 
11
-
12
10
  type SyncHook = {
13
11
  get handlers(): Handler[];
14
12
  register(handler: Handler): this;
@@ -16,5 +14,4 @@ type SyncHook = {
16
14
  call(): void;
17
15
  };
18
16
 
19
-
20
- export type Hook = AsyncHook|SyncHook
17
+ export type Hook = AsyncHook | SyncHook;
@@ -6,4 +6,8 @@ interface PolicyContext extends BaseContext {
6
6
  is(name): boolean;
7
7
  }
8
8
 
9
- export type Policy<T=unknown> = (ctx: PolicyContext,cfg:T, { strapi: Strapi }) => boolean | undefined;
9
+ export type Policy<T = unknown> = (
10
+ ctx: PolicyContext,
11
+ cfg: T,
12
+ { strapi: Strapi }
13
+ ) => boolean | undefined;
@@ -19,9 +19,8 @@ const createCollectionTypeController = ({ contentType }) => {
19
19
  * @return {Object|Array}
20
20
  */
21
21
  async find(ctx) {
22
- const { query } = ctx;
23
-
24
- const { results, pagination } = await strapi.service(uid).find(query);
22
+ const sanitizedQuery = await this.sanitizeQuery(ctx);
23
+ const { results, pagination } = await strapi.service(uid).find(sanitizedQuery);
25
24
  const sanitizedResults = await this.sanitizeOutput(results, ctx);
26
25
 
27
26
  return this.transformResponse(sanitizedResults, { pagination });
@@ -29,6 +29,12 @@ const createController = ({ contentType }) => {
29
29
 
30
30
  return sanitize.contentAPI.input(data, contentType, { auth });
31
31
  },
32
+
33
+ sanitizeQuery(ctx) {
34
+ const auth = getAuthFromKoaContext(ctx);
35
+
36
+ return sanitize.contentAPI.query(ctx.query, contentType, { auth });
37
+ },
32
38
  };
33
39
 
34
40
  let ctrl;
@@ -18,9 +18,9 @@ const createSingleTypeController = ({ contentType }) => {
18
18
  * @return {Object|Array}
19
19
  */
20
20
  async find(ctx) {
21
- const { query } = ctx;
21
+ const sanitizedQuery = await this.sanitizeQuery(ctx);
22
+ const entity = await strapi.service(uid).find(sanitizedQuery);
22
23
 
23
- const entity = await strapi.service(uid).find(query);
24
24
  const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
25
25
 
26
26
  return this.transformResponse(sanitizedEntity);
package/lib/global.d.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  import type { Strapi as StrapiInterface } from './types/core';
2
- import type { CollectionTypeSchema, SingleTypeSchema, ComponentSchema, ContentTypeSchema } from './types/core/schemas';
2
+ import type {
3
+ CollectionTypeSchema,
4
+ SingleTypeSchema,
5
+ ComponentSchema,
6
+ ContentTypeSchema,
7
+ } from './types/core/schemas';
3
8
  import type { KeysBy } from './types/utils';
4
9
 
5
10
  declare global {
@@ -7,7 +12,7 @@ declare global {
7
12
  /**
8
13
  * Map of UID / schemas used as a schemas database for other types.
9
14
  * It must be extended by the user application or plugins.
10
- *
15
+ *
11
16
  * @example
12
17
  * ```ts
13
18
  * declare global {
@@ -39,12 +44,12 @@ declare global {
39
44
  /**
40
45
  * Literal union type of every component registered in Strapi.Schemas
41
46
  */
42
- type ComponentUIDs = KeysBy<Schemas, ComponentSchema>;
47
+ type ComponentUIDs = KeysBy<Schemas, ComponentSchema>;
43
48
 
44
- /**
45
- * Global shorthand to access the `StrapiInterface` type
46
- */
47
- type Strapi = StrapiInterface;
49
+ /**
50
+ * Global shorthand to access the `StrapiInterface` type
51
+ */
52
+ type Strapi = StrapiInterface;
48
53
  }
49
54
 
50
55
  /**
package/lib/index.d.ts CHANGED
@@ -2,4 +2,4 @@ import './global';
2
2
 
3
3
  export * from './types';
4
4
 
5
- export default function(opts): Strapi;
5
+ export default function (opts): Strapi;
@@ -43,7 +43,28 @@ const createAuthentication = () => {
43
43
  return next();
44
44
  }
45
45
 
46
- const strategiesToUse = strategies[route.info.type];
46
+ const routeStrategies = strategies[route.info.type];
47
+ const configStrategies = config?.strategies ?? routeStrategies ?? [];
48
+
49
+ const strategiesToUse = configStrategies.reduce((acc, strategy) => {
50
+ // Resolve by strategy name
51
+ if (typeof strategy === 'string') {
52
+ const routeStrategy = routeStrategies.find((rs) => rs.name === strategy);
53
+
54
+ if (routeStrategy) {
55
+ acc.push(routeStrategy);
56
+ }
57
+ }
58
+
59
+ // Use the given strategy as is
60
+ else if (typeof strategy === 'object') {
61
+ validStrategy(strategy);
62
+
63
+ acc.push(strategy);
64
+ }
65
+
66
+ return acc;
67
+ }, []);
47
68
 
48
69
  for (const strategy of strategiesToUse) {
49
70
  const result = await strategy.authenticate(ctx);
@@ -14,10 +14,15 @@ const createCronService = () => {
14
14
 
15
15
  let fn;
16
16
  let options;
17
+ let taskName;
17
18
  if (isFunction(taskValue)) {
19
+ // don't use task name if key is the rule
20
+ taskName = null;
18
21
  fn = taskValue.bind(tasks);
19
22
  options = taskExpression;
20
23
  } else if (isFunction(taskValue.task)) {
24
+ // set task name if key is not the rule
25
+ taskName = taskExpression;
21
26
  fn = taskValue.task.bind(taskValue);
22
27
  options = taskValue.options;
23
28
  } else {
@@ -29,7 +34,7 @@ const createCronService = () => {
29
34
  const fnWithStrapi = (...args) => fn({ strapi }, ...args);
30
35
 
31
36
  const job = new Job(null, fnWithStrapi);
32
- jobsSpecs.push({ job, options });
37
+ jobsSpecs.push({ job, options, name: taskName });
33
38
 
34
39
  if (running) {
35
40
  job.schedule(options);
@@ -37,6 +42,13 @@ const createCronService = () => {
37
42
  }
38
43
  return this;
39
44
  },
45
+ remove(name) {
46
+ if (!name) throw new Error('You must provide a name to remove a cron job.');
47
+ const matchingJobsSpecs = jobsSpecs.filter(({ name: jobSpecName }) => jobSpecName === name);
48
+ matchingJobsSpecs.forEach(({ job }) => job.cancel());
49
+ jobsSpecs = jobsSpecs.filter(({ name: jobSpecName }) => jobSpecName !== name);
50
+ return this;
51
+ },
40
52
  start() {
41
53
  jobsSpecs.forEach(({ job, options }) => job.schedule(options));
42
54
  running = true;
@@ -52,6 +64,7 @@ const createCronService = () => {
52
64
  jobsSpecs = [];
53
65
  return this;
54
66
  },
67
+ jobs: jobsSpecs,
55
68
  };
56
69
  };
57
70
 
@@ -88,7 +88,7 @@ export interface EntityService {
88
88
  ): Promise<any>;
89
89
  }
90
90
 
91
- export default function(opts: {
91
+ export default function (opts: {
92
92
  strapi: Strapi;
93
93
  db: Database;
94
94
  // TODO: define types
@@ -25,13 +25,10 @@ export type ComponentAttribute<
25
25
  PrivateOption &
26
26
  RequiredOption;
27
27
 
28
- export type ComponentValue<T extends Strapi.ComponentUIDs, R extends boolean> = GetAttributesValues<
29
- T
30
- > extends infer V
31
- ? R extends true
32
- ? V[]
33
- : V
34
- : never;
28
+ export type ComponentValue<
29
+ T extends Strapi.ComponentUIDs,
30
+ R extends boolean
31
+ > = GetAttributesValues<T> extends infer V ? (R extends true ? V[] : V) : never;
35
32
 
36
33
  export type GetComponentAttributeValue<T extends Attribute> = T extends ComponentAttribute<
37
34
  infer U,
@@ -83,11 +83,10 @@ export type GetAttributeValueByKey<
83
83
  export type GetAttributesValues<T extends SchemaUID> = {
84
84
  // Handle required attributes
85
85
  [key in GetAttributesRequiredKeys<T>]-?: GetAttributeValueByKey<T, key>;
86
- } &
87
- {
88
- // Handle optional attributes
89
- [key in GetAttributesOptionalKeys<T>]?: GetAttributeValueByKey<T, key>;
90
- };
86
+ } & {
87
+ // Handle optional attributes
88
+ [key in GetAttributesOptionalKeys<T>]?: GetAttributeValueByKey<T, key>;
89
+ };
91
90
 
92
91
  export type GetAttributesRequiredKeys<T extends SchemaUID> = KeysBy<
93
92
  GetAttributes<T>,
@@ -1,3 +1,3 @@
1
1
  export * from './attributes';
2
- export * from './schemas';
3
- export * from './strapi'
2
+ export * from './schemas';
3
+ export * from './strapi';
@@ -1,4 +1,4 @@
1
1
  export * from './core';
2
2
 
3
- export * as utils from './utils'
4
- export * as factories from './factories';
3
+ export * as utils from './utils';
4
+ export * as factories from './factories';