@strapi/data-transfer 4.6.0-beta.2 → 4.6.0

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.
Files changed (42) hide show
  1. package/lib/engine/diagnostic.d.ts +40 -0
  2. package/lib/engine/diagnostic.js +50 -0
  3. package/lib/engine/errors.d.ts +28 -0
  4. package/lib/engine/errors.js +29 -0
  5. package/lib/engine/index.d.ts +11 -3
  6. package/lib/engine/index.js +140 -30
  7. package/lib/engine/validation/index.d.ts +2 -1
  8. package/lib/engine/validation/index.js +4 -13
  9. package/lib/engine/validation/provider.d.ts +3 -0
  10. package/lib/engine/validation/provider.js +18 -0
  11. package/lib/errors/base.d.ts +8 -0
  12. package/lib/errors/base.js +13 -0
  13. package/lib/errors/constants.d.ts +3 -0
  14. package/lib/errors/constants.js +9 -0
  15. package/lib/errors/index.d.ts +2 -0
  16. package/lib/errors/index.js +19 -0
  17. package/lib/errors/providers.d.ts +21 -0
  18. package/lib/errors/providers.js +32 -0
  19. package/lib/file/providers/source/index.js +9 -11
  20. package/lib/strapi/providers/local-destination/index.d.ts +3 -1
  21. package/lib/strapi/providers/local-destination/index.js +51 -31
  22. package/lib/strapi/providers/local-destination/strategies/restore/configuration.d.ts +2 -2
  23. package/lib/strapi/providers/local-destination/strategies/restore/configuration.js +17 -10
  24. package/lib/strapi/providers/local-destination/strategies/restore/entities.d.ts +2 -0
  25. package/lib/strapi/providers/local-destination/strategies/restore/entities.js +57 -54
  26. package/lib/strapi/providers/local-destination/strategies/restore/index.js +2 -1
  27. package/lib/strapi/providers/local-destination/strategies/restore/links.d.ts +2 -1
  28. package/lib/strapi/providers/local-destination/strategies/restore/links.js +18 -15
  29. package/lib/strapi/providers/local-source/index.js +6 -21
  30. package/lib/strapi/providers/remote-destination/index.js +21 -6
  31. package/lib/strapi/queries/link.d.ts +1 -1
  32. package/lib/strapi/queries/link.js +17 -3
  33. package/lib/strapi/remote/constants.d.ts +1 -0
  34. package/lib/strapi/remote/constants.js +2 -1
  35. package/lib/strapi/remote/handlers.js +28 -12
  36. package/lib/utils/index.d.ts +1 -0
  37. package/lib/utils/index.js +2 -1
  38. package/lib/utils/providers.d.ts +2 -0
  39. package/lib/utils/providers.js +11 -0
  40. package/lib/utils/transaction.d.ts +3 -0
  41. package/lib/utils/transaction.js +70 -0
  42. package/package.json +6 -5
@@ -12,6 +12,7 @@ const uuid_1 = require("uuid");
12
12
  const stream_1 = require("stream");
13
13
  const utils_1 = require("./utils");
14
14
  const constants_1 = require("../../remote/constants");
15
+ const providers_1 = require("../../../errors/providers");
15
16
  class RemoteStrapiDestinationProvider {
16
17
  constructor(options) {
17
18
  _RemoteStrapiDestinationProvider_instances.add(this);
@@ -33,7 +34,7 @@ class RemoteStrapiDestinationProvider {
33
34
  });
34
35
  const res = (await query);
35
36
  if (!res?.transferID) {
36
- return reject(new Error('Init failed, invalid response from the server'));
37
+ return reject(new providers_1.ProviderTransferError('Init failed, invalid response from the server'));
37
38
  }
38
39
  resolve(res.transferID);
39
40
  })
@@ -42,12 +43,20 @@ class RemoteStrapiDestinationProvider {
42
43
  }
43
44
  async bootstrap() {
44
45
  const { url, auth } = this.options;
46
+ const validProtocols = ['https:', 'http:'];
45
47
  let ws;
46
- if (!['https:', 'http:'].includes(url.protocol)) {
47
- throw new Error(`Invalid protocol "${url.protocol}"`);
48
+ if (!validProtocols.includes(url.protocol)) {
49
+ throw new providers_1.ProviderValidationError(`Invalid protocol "${url.protocol}"`, {
50
+ check: 'url',
51
+ details: {
52
+ protocol: url.protocol,
53
+ validProtocols,
54
+ },
55
+ });
48
56
  }
49
57
  const wsProtocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
50
58
  const wsUrl = `${wsProtocol}//${url.host}${url.pathname}${constants_1.TRANSFER_PATH}`;
59
+ const validAuthMethods = ['token'];
51
60
  // No auth defined, trying public access for transfer
52
61
  if (!auth) {
53
62
  ws = new ws_1.WebSocket(wsUrl);
@@ -59,7 +68,13 @@ class RemoteStrapiDestinationProvider {
59
68
  }
60
69
  // Invalid auth method provided
61
70
  else {
62
- throw new Error('Auth method not implemented');
71
+ throw new providers_1.ProviderValidationError('Auth method not implemented', {
72
+ check: 'auth.type',
73
+ details: {
74
+ auth: auth.type,
75
+ validAuthMethods,
76
+ },
77
+ });
63
78
  }
64
79
  this.ws = ws;
65
80
  this.dispatcher = (0, utils_1.createDispatcher)(this.ws);
@@ -158,9 +173,9 @@ _RemoteStrapiDestinationProvider_instances = new WeakSet(), _RemoteStrapiDestina
158
173
  return e;
159
174
  }
160
175
  if (typeof e === 'string') {
161
- return new Error(e);
176
+ return new providers_1.ProviderTransferError(e);
162
177
  }
163
- return new Error('Unexpected error');
178
+ return new providers_1.ProviderTransferError('Unexpected error');
164
179
  }
165
180
  return null;
166
181
  };
@@ -1,5 +1,5 @@
1
1
  import { ILink } from '../../../types';
2
- export declare const createLinkQuery: (strapi: Strapi.Strapi) => () => {
2
+ export declare const createLinkQuery: (strapi: Strapi.Strapi, trx?: any) => () => {
3
3
  generateAll: (uid: string) => AsyncGenerator<ILink>;
4
4
  generateAllForAttribute: (uid: string, fieldName: string) => AsyncGenerator<ILink>;
5
5
  insert: (link: ILink) => Promise<void>;
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createLinkQuery = void 0;
4
4
  const fp_1 = require("lodash/fp");
5
5
  // TODO: Remove any types when we'll have types for DB metadata
6
- const createLinkQuery = (strapi) => {
6
+ const createLinkQuery = (strapi, trx) => {
7
7
  const query = () => {
8
8
  const { connection } = strapi.db;
9
9
  async function* generateAllForAttribute(uid, fieldName) {
@@ -23,6 +23,9 @@ const createLinkQuery = (strapi) => {
23
23
  if (attribute.joinColumn) {
24
24
  const joinColumnName = attribute.joinColumn.name;
25
25
  const qb = connection.queryBuilder().select('id', joinColumnName).from(metadata.tableName);
26
+ if (trx) {
27
+ qb.transacting(trx);
28
+ }
26
29
  // TODO: stream the query to improve performances
27
30
  const entries = await qb;
28
31
  for (const entry of entries) {
@@ -77,6 +80,9 @@ const createLinkQuery = (strapi) => {
77
80
  columns.right.order,
78
81
  ].filter((column) => !(0, fp_1.isNil)(column));
79
82
  qb.select(validColumns);
83
+ if (trx) {
84
+ qb.transacting(trx);
85
+ }
80
86
  // TODO: stream the query to improve performances
81
87
  const entries = await qb;
82
88
  for (const entry of entries) {
@@ -127,9 +133,13 @@ const createLinkQuery = (strapi) => {
127
133
  const payload = {};
128
134
  if (attribute.joinColumn) {
129
135
  const joinColumnName = attribute.joinColumn.name;
130
- await connection(metadata.tableName)
136
+ const qb = connection(metadata.tableName)
131
137
  .where('id', left.ref)
132
138
  .update({ [joinColumnName]: right.ref });
139
+ if (trx) {
140
+ qb.transacting(trx);
141
+ }
142
+ await qb;
133
143
  }
134
144
  if (attribute.joinTable) {
135
145
  const { name, joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName, morphColumn, } = attribute.joinTable;
@@ -168,7 +178,11 @@ const createLinkQuery = (strapi) => {
168
178
  assignMorphColumns();
169
179
  }
170
180
  assignOrderColumns();
171
- await connection.insert(payload).into(name);
181
+ const qb = connection.insert(payload).into(name);
182
+ if (trx) {
183
+ qb.transacting(trx);
184
+ }
185
+ await qb;
172
186
  }
173
187
  };
174
188
  return { generateAll, generateAllForAttribute, insert };
@@ -1 +1,2 @@
1
1
  export declare const TRANSFER_PATH = "/transfer";
2
+ export declare const TRANSFER_METHODS: string[];
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TRANSFER_PATH = void 0;
3
+ exports.TRANSFER_METHODS = exports.TRANSFER_PATH = void 0;
4
4
  exports.TRANSFER_PATH = '/transfer';
5
+ exports.TRANSFER_METHODS = ['push', 'pull'];
5
6
  //# sourceMappingURL=constants.js.map
@@ -7,6 +7,8 @@ exports.createTransferHandler = void 0;
7
7
  const crypto_1 = require("crypto");
8
8
  const ws_1 = require("ws");
9
9
  const push_1 = __importDefault(require("./controllers/push"));
10
+ const providers_1 = require("../../errors/providers");
11
+ const constants_1 = require("./constants");
10
12
  const createTransferHandler = (options = {}) => async (ctx) => {
11
13
  const upgradeHeader = (ctx.request.headers.upgrade || '')
12
14
  .split(',')
@@ -54,10 +56,12 @@ const createTransferHandler = (options = {}) => async (ctx) => {
54
56
  callback(e);
55
57
  }
56
58
  else if (typeof e === 'string') {
57
- callback(new Error(e));
59
+ callback(new providers_1.ProviderTransferError(e));
58
60
  }
59
61
  else {
60
- callback(new Error('Unexpected error'));
62
+ callback(new providers_1.ProviderTransferError('Unexpected error', {
63
+ error: e,
64
+ }));
61
65
  }
62
66
  }
63
67
  };
@@ -67,8 +71,9 @@ const createTransferHandler = (options = {}) => async (ctx) => {
67
71
  return { ok: true };
68
72
  };
69
73
  const init = (msg) => {
74
+ // TODO: this only checks for this instance of node: we should consider a database lock
70
75
  if (state.controller) {
71
- throw new Error('Transfer already in progres');
76
+ throw new providers_1.ProviderInitializationError('Transfer already in progres');
72
77
  }
73
78
  const { transfer } = msg.params;
74
79
  // Push transfer
@@ -82,7 +87,10 @@ const createTransferHandler = (options = {}) => async (ctx) => {
82
87
  }
83
88
  // Pull or any other string
84
89
  else {
85
- throw new Error(`Transfer not implemented: "${transfer}"`);
90
+ throw new providers_1.ProviderTransferError(`Transfer type not implemented: "${transfer}"`, {
91
+ transfer,
92
+ validTransfers: constants_1.TRANSFER_METHODS,
93
+ });
86
94
  }
87
95
  state.transfer = { id: (0, crypto_1.randomUUID)(), kind: transfer };
88
96
  return { transferID: state.transfer.id };
@@ -99,29 +107,35 @@ const createTransferHandler = (options = {}) => async (ctx) => {
99
107
  await answer(teardown);
100
108
  }
101
109
  if (command === 'status') {
102
- await callback(new Error('Command not implemented: "status"'));
110
+ await callback(new providers_1.ProviderTransferError('Command not implemented: "status"', {
111
+ command,
112
+ validCommands: ['init', 'end', 'status'],
113
+ }));
103
114
  }
104
115
  };
105
116
  const onTransferCommand = async (msg) => {
106
117
  const { transferID, kind } = msg;
107
118
  const { controller } = state;
108
119
  // TODO: (re)move this check
109
- // It shouldn't be possible to strart a pull transfer for now, so reaching
120
+ // It shouldn't be possible to start a pull transfer for now, so reaching
110
121
  // this code should be impossible too, but this has been added by security
111
122
  if (state.transfer?.kind === 'pull') {
112
- return callback(new Error('Pull transfer not implemented'));
123
+ return callback(new providers_1.ProviderTransferError('Pull transfer not implemented'));
113
124
  }
114
125
  if (!controller) {
115
- return callback(new Error("The transfer hasn't been initialized"));
126
+ return callback(new providers_1.ProviderTransferError("The transfer hasn't been initialized"));
116
127
  }
117
128
  if (!transferID) {
118
- return callback(new Error('Missing transfer ID'));
129
+ return callback(new providers_1.ProviderTransferError('Missing transfer ID'));
119
130
  }
120
131
  // Action
121
132
  if (kind === 'action') {
122
133
  const { action } = msg;
123
134
  if (!(action in controller.actions)) {
124
- return callback(new Error(`Invalid action provided: "${action}"`));
135
+ return callback(new providers_1.ProviderTransferError(`Invalid action provided: "${action}"`, {
136
+ action,
137
+ validActions: Object.keys(controller.actions),
138
+ }));
125
139
  }
126
140
  await answer(() => controller.actions[action]());
127
141
  }
@@ -148,12 +162,14 @@ const createTransferHandler = (options = {}) => async (ctx) => {
148
162
  });
149
163
  ws.on('error', (e) => {
150
164
  teardown();
165
+ // TODO: is logging a console error to the running instance of Strapi ok to do? Should we check for an existing strapi.logger to use?
151
166
  console.error(e);
152
167
  });
153
168
  ws.on('message', async (raw) => {
154
169
  const msg = JSON.parse(raw.toString());
155
170
  if (!msg.uuid) {
156
- throw new Error('Missing uuid in message');
171
+ await callback(new providers_1.ProviderTransferError('Missing uuid in message'));
172
+ return;
157
173
  }
158
174
  uuid = msg.uuid;
159
175
  // Regular command message (init, end, status)
@@ -166,7 +182,7 @@ const createTransferHandler = (options = {}) => async (ctx) => {
166
182
  }
167
183
  // Invalid messages
168
184
  else {
169
- await callback(new Error('Bad request'));
185
+ await callback(new providers_1.ProviderTransferError('Bad request'));
170
186
  }
171
187
  });
172
188
  });
@@ -2,3 +2,4 @@ export * as encryption from './encryption';
2
2
  export * as stream from './stream';
3
3
  export * as json from './json';
4
4
  export * as schema from './schema';
5
+ export * as transaction from './transaction';
@@ -23,9 +23,10 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.schema = exports.json = exports.stream = exports.encryption = void 0;
26
+ exports.transaction = exports.schema = exports.json = exports.stream = exports.encryption = void 0;
27
27
  exports.encryption = __importStar(require("./encryption"));
28
28
  exports.stream = __importStar(require("./stream"));
29
29
  exports.json = __importStar(require("./json"));
30
30
  exports.schema = __importStar(require("./schema"));
31
+ exports.transaction = __importStar(require("./transaction"));
31
32
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,2 @@
1
+ export declare type ValidStrapiAssertion = (strapi: unknown, msg?: string) => asserts strapi is Strapi.Strapi;
2
+ export declare const assertValidStrapi: ValidStrapiAssertion;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assertValidStrapi = void 0;
4
+ const providers_1 = require("../errors/providers");
5
+ const assertValidStrapi = (strapi, msg = '') => {
6
+ if (!strapi) {
7
+ throw new providers_1.ProviderInitializationError(`${msg}. Strapi instance not found.`);
8
+ }
9
+ };
10
+ exports.assertValidStrapi = assertValidStrapi;
11
+ //# sourceMappingURL=providers.js.map
@@ -0,0 +1,3 @@
1
+ import { Strapi } from '@strapi/strapi';
2
+ import { Transaction } from '../../types/utils';
3
+ export declare const createTransaction: (strapi: Strapi) => Transaction;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createTransaction = void 0;
4
+ const events_1 = require("events");
5
+ const crypto_1 = require("crypto");
6
+ const createTransaction = (strapi) => {
7
+ const fns = [];
8
+ let done = false;
9
+ let resume = null;
10
+ const e = new events_1.EventEmitter();
11
+ e.on('spawn', (uuid, cb) => {
12
+ fns.push({ fn: cb, uuid });
13
+ resume?.();
14
+ });
15
+ e.on('close', () => {
16
+ done = true;
17
+ resume?.();
18
+ });
19
+ strapi.db.transaction(async ({ trx, rollback }) => {
20
+ e.on('rollback', async () => {
21
+ await rollback();
22
+ });
23
+ while (!done) {
24
+ while (fns.length) {
25
+ const item = fns.shift();
26
+ if (item) {
27
+ const { fn, uuid } = item;
28
+ try {
29
+ const res = await fn(trx);
30
+ e.emit(uuid, { data: res });
31
+ }
32
+ catch (error) {
33
+ e.emit(uuid, { error });
34
+ }
35
+ }
36
+ }
37
+ if (!done && !fns.length) {
38
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
39
+ await new Promise((resolve) => {
40
+ resume = resolve;
41
+ });
42
+ }
43
+ }
44
+ });
45
+ return {
46
+ async attach(callback) {
47
+ const uuid = (0, crypto_1.randomUUID)();
48
+ e.emit('spawn', uuid, callback);
49
+ return new Promise((resolve, reject) => {
50
+ e.on(uuid, ({ data, error }) => {
51
+ if (data) {
52
+ resolve(data);
53
+ }
54
+ if (error) {
55
+ reject(error);
56
+ }
57
+ resolve(undefined);
58
+ });
59
+ });
60
+ },
61
+ end() {
62
+ return e.emit('close');
63
+ },
64
+ rollback() {
65
+ return e.emit('rollback');
66
+ },
67
+ };
68
+ };
69
+ exports.createTransaction = createTransaction;
70
+ //# sourceMappingURL=transaction.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/data-transfer",
3
- "version": "4.6.0-beta.2",
3
+ "version": "4.6.0",
4
4
  "description": "Data transfer capabilities for Strapi",
5
5
  "keywords": [
6
6
  "strapi",
@@ -30,7 +30,7 @@
30
30
  "build": "yarn build:ts",
31
31
  "build:ts": "tsc -p tsconfig.json",
32
32
  "build:clean": "yarn clean && yarn build",
33
- "clean": "rimraf ./dist",
33
+ "clean": "rimraf ./lib",
34
34
  "prepublishOnly": "yarn build:clean",
35
35
  "test:unit": "jest --verbose",
36
36
  "watch": "yarn build:ts -w --preserveWatchOutput"
@@ -39,8 +39,8 @@
39
39
  "lib": "./lib"
40
40
  },
41
41
  "dependencies": {
42
- "@strapi/logger": "4.6.0-beta.2",
43
- "@strapi/strapi": "4.6.0-beta.2",
42
+ "@strapi/logger": "4.6.0",
43
+ "@strapi/strapi": "4.6.0",
44
44
  "chalk": "4.1.2",
45
45
  "fs-extra": "10.0.0",
46
46
  "lodash": "4.17.21",
@@ -64,6 +64,7 @@
64
64
  "@types/tar": "6.1.3",
65
65
  "@types/tar-stream": "2.2.2",
66
66
  "@types/uuid": "9.0.0",
67
+ "knex": "2.4.0",
67
68
  "koa": "2.13.4",
68
69
  "rimraf": "3.0.2",
69
70
  "typescript": "4.6.2"
@@ -72,5 +73,5 @@
72
73
  "node": ">=14.19.1 <=18.x.x",
73
74
  "npm": ">=6.0.0"
74
75
  },
75
- "gitHead": "b852090f931cd21868c4016f24db2f9fdfc7a7ab"
76
+ "gitHead": "a9e55435c489f3379d88565bf3f729deb29bfb45"
76
77
  }