@strapi/data-transfer 5.10.4 → 5.11.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/engine/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/engine/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var stream$1 = require('stream');
4
+ var promises = require('stream/promises');
4
5
  var path = require('path');
5
6
  var os = require('os');
6
7
  var streamChain = require('stream-chain');
@@ -654,8 +655,7 @@ const TRANSFER_STAGES = Object.freeze([
654
655
  };
655
656
  const DEFAULT_VERSION_STRATEGY = 'ignore';
656
657
  const DEFAULT_SCHEMA_STRATEGY = 'strict';
657
- var _metadata$1 = /*#__PURE__*/ _class_private_field_loose_key$6("_metadata"), _schema = /*#__PURE__*/ _class_private_field_loose_key$6("_schema"), _handlers = /*#__PURE__*/ _class_private_field_loose_key$6("_handlers"), // Save the currently open stream so that we can access it at any time
658
- _currentStream = /*#__PURE__*/ _class_private_field_loose_key$6("_currentStream"), /**
658
+ var _metadata$1 = /*#__PURE__*/ _class_private_field_loose_key$6("_metadata"), _schema = /*#__PURE__*/ _class_private_field_loose_key$6("_schema"), _handlers = /*#__PURE__*/ _class_private_field_loose_key$6("_handlers"), _currentStreamController = /*#__PURE__*/ _class_private_field_loose_key$6("_currentStreamController"), _aborted = /*#__PURE__*/ _class_private_field_loose_key$6("_aborted"), /**
659
659
  * Create and return a transform stream based on the given stage and options.
660
660
  *
661
661
  * Allowed transformations includes 'filter' and 'map'.
@@ -770,11 +770,9 @@ class TransferEngine {
770
770
  }
771
771
  // Cause an ongoing transfer to abort gracefully
772
772
  async abortTransfer() {
773
- const err = new TransferEngineError('fatal', 'Transfer aborted.');
774
- if (!_class_private_field_loose_base$6(this, _currentStream)[_currentStream]) {
775
- throw err;
776
- }
777
- _class_private_field_loose_base$6(this, _currentStream)[_currentStream].destroy(err);
773
+ _class_private_field_loose_base$6(this, _aborted)[_aborted] = true;
774
+ _class_private_field_loose_base$6(this, _currentStreamController)[_currentStreamController]?.abort();
775
+ throw new TransferEngineError('fatal', 'Transfer aborted.');
778
776
  }
779
777
  async init() {
780
778
  // Resolve providers' resource and store
@@ -1084,7 +1082,11 @@ class TransferEngine {
1084
1082
  writable: true,
1085
1083
  value: void 0
1086
1084
  });
1087
- Object.defineProperty(this, _currentStream, {
1085
+ Object.defineProperty(this, _currentStreamController, {
1086
+ writable: true,
1087
+ value: void 0
1088
+ });
1089
+ Object.defineProperty(this, _aborted, {
1088
1090
  writable: true,
1089
1091
  value: void 0
1090
1092
  });
@@ -1094,6 +1096,7 @@ class TransferEngine {
1094
1096
  schemaDiff: [],
1095
1097
  errors: {}
1096
1098
  };
1099
+ _class_private_field_loose_base$6(this, _aborted)[_aborted] = false;
1097
1100
  this.diagnostics = createDiagnosticReporter();
1098
1101
  validateProvider('source', sourceProvider);
1099
1102
  validateProvider('destination', destinationProvider);
@@ -1292,6 +1295,9 @@ function assertSchemasMatching(sourceSchemas, destinationSchemas) {
1292
1295
  }
1293
1296
  }
1294
1297
  async function transferStage(options) {
1298
+ if (_class_private_field_loose_base$6(this, _aborted)[_aborted]) {
1299
+ throw new TransferEngineError('fatal', 'Transfer aborted.');
1300
+ }
1295
1301
  const { stage, source, destination, transform, tracker } = options;
1296
1302
  const updateEndTime = ()=>{
1297
1303
  const stageData = this.progress.data[stage];
@@ -1323,27 +1329,42 @@ async function transferStage(options) {
1323
1329
  return;
1324
1330
  }
1325
1331
  _class_private_field_loose_base$6(this, _emitStageUpdate)[_emitStageUpdate]('start', stage);
1326
- await new Promise((resolve, reject)=>{
1327
- let stream = source;
1332
+ try {
1333
+ const streams = [
1334
+ source
1335
+ ];
1328
1336
  if (transform) {
1329
- stream = stream.pipe(transform);
1337
+ streams.push(transform);
1330
1338
  }
1331
1339
  if (tracker) {
1332
- stream = stream.pipe(tracker);
1333
- }
1334
- _class_private_field_loose_base$6(this, _currentStream)[_currentStream] = stream.pipe(destination).on('error', (e)=>{
1335
- updateEndTime();
1336
- _class_private_field_loose_base$6(this, _emitStageUpdate)[_emitStageUpdate]('error', stage);
1337
- this.reportError(e, 'error');
1340
+ streams.push(tracker);
1341
+ }
1342
+ streams.push(destination);
1343
+ // NOTE: to debug/confirm backpressure issues from misbehaving stream, uncomment the following lines
1344
+ // source.on('pause', () => console.log(`[${stage}] Source paused due to backpressure`));
1345
+ // source.on('resume', () => console.log(`[${stage}] Source resumed`));
1346
+ // destination.on('drain', () =>
1347
+ // console.log(`[${stage}] Destination drained, resuming data flow`)
1348
+ // );
1349
+ // destination.on('error', (err) => console.error(`[${stage}] Destination error:`, err));
1350
+ const controller = new AbortController();
1351
+ const { signal } = controller;
1352
+ // Store the controller so you can cancel later
1353
+ _class_private_field_loose_base$6(this, _currentStreamController)[_currentStreamController] = controller;
1354
+ await promises.pipeline(streams, {
1355
+ signal
1356
+ });
1357
+ _class_private_field_loose_base$6(this, _emitStageUpdate)[_emitStageUpdate]('finish', stage);
1358
+ } catch (e) {
1359
+ updateEndTime();
1360
+ _class_private_field_loose_base$6(this, _emitStageUpdate)[_emitStageUpdate]('error', stage);
1361
+ this.reportError(e, 'error');
1362
+ if (!destination.destroyed) {
1338
1363
  destination.destroy(e);
1339
- reject(e);
1340
- }).on('close', ()=>{
1341
- _class_private_field_loose_base$6(this, _currentStream)[_currentStream] = undefined;
1342
- updateEndTime();
1343
- resolve();
1344
- });
1345
- });
1346
- _class_private_field_loose_base$6(this, _emitStageUpdate)[_emitStageUpdate]('finish', stage);
1364
+ }
1365
+ } finally{
1366
+ updateEndTime();
1367
+ }
1347
1368
  }
1348
1369
  async function resolveProviderResource() {
1349
1370
  const sourceMetadata = await this.sourceProvider.getMetadata();
@@ -1915,9 +1936,7 @@ const createLinkQuery = (strapi, trx)=>{
1915
1936
  assignOrderColumns();
1916
1937
  const qb = connection.insert(payload).into(addSchema(joinTable.name));
1917
1938
  if (trx) {
1918
- await trx.transaction(async (nestedTrx)=>{
1919
- await qb.transacting(nestedTrx);
1920
- });
1939
+ await qb.transacting(trx);
1921
1940
  }
1922
1941
  }
1923
1942
  if ('morphColumn' in attribute && attribute.morphColumn) {
@@ -2370,7 +2389,7 @@ class LocalStrapiDestinationProvider {
2370
2389
  const provider = strapi.config.get('plugin::upload').provider;
2371
2390
  const fileId = fileEntitiesMapper?.[uploadData.id];
2372
2391
  if (!fileId) {
2373
- callback(new Error(`File ID not found for ID: ${uploadData.id}`));
2392
+ return callback(new Error(`File ID not found for ID: ${uploadData.id}`));
2374
2393
  }
2375
2394
  try {
2376
2395
  await strapi.plugin('upload').provider.uploadStream(uploadData);
@@ -2421,9 +2440,9 @@ class LocalStrapiDestinationProvider {
2421
2440
  provider
2422
2441
  }
2423
2442
  });
2424
- callback();
2443
+ return callback();
2425
2444
  } catch (error) {
2426
- callback(new Error(`Error while uploading asset ${chunk.filename} ${error}`));
2445
+ return callback(new Error(`Error while uploading asset ${chunk.filename} ${error}`));
2427
2446
  }
2428
2447
  });
2429
2448
  }
@@ -2960,7 +2979,7 @@ function reportError(message, error) {
2960
2979
  }
2961
2980
  function handleStreamError(streamType, err) {
2962
2981
  const { message, stack } = err;
2963
- const errorMessage = `Error in ${streamType} read stream: ${message}`;
2982
+ const errorMessage = `[Data transfer] Error in ${streamType} read stream: ${message}`;
2964
2983
  const formattedError = {
2965
2984
  message: errorMessage,
2966
2985
  stack,
@@ -3132,17 +3151,6 @@ const connectToWebsocket = (address, options, diagnostics)=>{
3132
3151
  const trimTrailingSlash = (input)=>{
3133
3152
  return input.replace(/\/$/, '');
3134
3153
  };
3135
- const wait = (ms)=>{
3136
- return new Promise((resolve)=>{
3137
- setTimeout(resolve, ms);
3138
- });
3139
- };
3140
- const waitUntil = async (test, interval)=>{
3141
- while(!test()){
3142
- await wait(interval);
3143
- }
3144
- return Promise.resolve();
3145
- };
3146
3154
 
3147
3155
  const TRANSFER_PATH = '/transfer/runner';
3148
3156
  const TRANSFER_METHODS = [
@@ -3563,6 +3571,17 @@ class RemoteStrapiSourceProvider {
3563
3571
  });
3564
3572
  // Init the asset map
3565
3573
  const assets = {};
3574
+ // Watch for stalled assets; if we don't receive a chunk within timeout, abort transfer
3575
+ const resetTimeout = (assetID)=>{
3576
+ if (assets[assetID].timeout) {
3577
+ clearTimeout(assets[assetID].timeout);
3578
+ }
3579
+ assets[assetID].timeout = setTimeout(()=>{
3580
+ _class_private_field_loose_base$2(this, _reportInfo$2)[_reportInfo$2](`Asset ${assetID} transfer stalled, aborting.`);
3581
+ assets[assetID].status = 'errored';
3582
+ assets[assetID].stream.destroy(new Error(`Asset ${assetID} transfer timed out`));
3583
+ }, this.options.streamTimeout);
3584
+ };
3566
3585
  stream/**
3567
3586
  * Process a payload of many transfer assets and performs the following tasks:
3568
3587
  * - Start: creates a stream for new assets.
@@ -3573,56 +3592,46 @@ class RemoteStrapiSourceProvider {
3573
3592
  const { action, assetID } = item;
3574
3593
  // Creates the stream to send the incoming asset through
3575
3594
  if (action === 'start') {
3576
- // Ignore the item if a transfer has already been started for the same asset ID
3595
+ // if a transfer has already been started for the same asset ID, something is wrong
3577
3596
  if (assets[assetID]) {
3578
- continue;
3597
+ throw new Error(`Asset ${assetID} already started`);
3579
3598
  }
3599
+ _class_private_field_loose_base$2(this, _reportInfo$2)[_reportInfo$2](`Asset ${assetID} starting`);
3580
3600
  // Register the asset
3581
3601
  assets[assetID] = {
3582
3602
  ...item.data,
3583
3603
  stream: new stream$1.PassThrough(),
3584
- status: 'idle',
3604
+ status: 'ok',
3585
3605
  queue: []
3586
3606
  };
3607
+ resetTimeout(assetID);
3587
3608
  // Connect the individual asset stream to the main asset stage stream
3588
3609
  // Note: nothing is transferred until data chunks are fed to the asset stream
3589
3610
  await this.writeAsync(pass, assets[assetID]);
3590
- } else if (action === 'stream') {
3591
- // If the asset hasn't been registered, or if it's been closed already, then ignore the message
3611
+ } else if (action === 'stream' || action === 'end') {
3612
+ // If the asset hasn't been registered, or if it's been closed already, something is wrong
3592
3613
  if (!assets[assetID]) {
3593
- continue;
3614
+ throw new Error(`No id matching ${assetID} for stream action`);
3594
3615
  }
3595
- switch(assets[assetID].status){
3596
- // The asset is ready to accept a new chunk, write it now
3597
- case 'idle':
3598
- await writeAssetChunk(assetID, item.data);
3599
- break;
3600
- // The resource is busy, queue the current chunk so that it gets transferred as soon as possible
3601
- case 'busy':
3602
- assets[assetID].queue.push(item);
3603
- break;
3616
+ // On every action, reset the timeout timer
3617
+ if (action === 'stream') {
3618
+ resetTimeout(assetID);
3619
+ } else {
3620
+ clearTimeout(assets[assetID].timeout);
3604
3621
  }
3605
- } else if (action === 'end') {
3606
- // If the asset has already been closed, or if it was never registered, ignore the command
3607
- if (!assets[assetID]) {
3608
- continue;
3622
+ if (assets[assetID].status === 'closed') {
3623
+ throw new Error(`Asset ${assetID} is closed`);
3609
3624
  }
3610
- switch(assets[assetID].status){
3611
- // There's no ongoing activity, the asset is ready to be closed
3612
- case 'idle':
3613
- case 'errored':
3614
- await closeAssetStream(assetID);
3615
- break;
3616
- // The resource is busy, wait for a different state and close the stream.
3617
- case 'busy':
3618
- await Promise.race([
3619
- // Either: wait for the asset to be ready to be closed
3620
- waitUntil(()=>assets[assetID].status !== 'busy', 100),
3621
- // Or: if the last chunks are still not processed after ten seconds
3622
- wait(10000)
3623
- ]);
3624
- await closeAssetStream(assetID);
3625
- break;
3625
+ assets[assetID].queue.push(item);
3626
+ }
3627
+ }
3628
+ // each new payload will start new processQueue calls, which may cause some extra calls
3629
+ // it's essentially saying "start processing this asset again, I added more data to the queue"
3630
+ for(const assetID in assets){
3631
+ if (Object.prototype.hasOwnProperty.call(assets, assetID)) {
3632
+ const asset = assets[assetID];
3633
+ if (asset.queue?.length > 0) {
3634
+ await processQueue(assetID);
3626
3635
  }
3627
3636
  }
3628
3637
  }
@@ -3630,38 +3639,48 @@ class RemoteStrapiSourceProvider {
3630
3639
  pass.end();
3631
3640
  });
3632
3641
  /**
3633
- * Writes a chunk of data for the specified asset with the given id.
3634
- */ const writeAssetChunk = async (id, data)=>{
3642
+ * Start processing the queue for a given assetID
3643
+ *
3644
+ * Even though this is a loop that attempts to process the entire queue, it is safe to call this more than once
3645
+ * for the same asset id because the queue is shared globally, the items are shifted off, and immediately written
3646
+ */ const processQueue = async (id)=>{
3635
3647
  if (!assets[id]) {
3636
3648
  throw new Error(`Failed to write asset chunk for "${id}". Asset not found.`);
3637
3649
  }
3638
- const { status: currentStatus } = assets[id];
3639
- if (currentStatus !== 'idle') {
3650
+ const asset = assets[id];
3651
+ const { status: currentStatus } = asset;
3652
+ if ([
3653
+ 'closed',
3654
+ 'errored'
3655
+ ].includes(currentStatus)) {
3640
3656
  throw new Error(`Failed to write asset chunk for "${id}". The asset is currently "${currentStatus}"`);
3641
3657
  }
3642
- const nextItemInQueue = ()=>assets[id].queue.shift();
3643
- try {
3644
- // Lock the asset
3645
- assets[id].status = 'busy';
3646
- // Save the current chunk
3647
- await unsafe_writeAssetChunk(id, data);
3648
- // Empty the queue if needed
3649
- let item = nextItemInQueue();
3650
- while(item){
3651
- await unsafe_writeAssetChunk(id, item.data);
3652
- item = nextItemInQueue();
3658
+ while(asset.queue.length > 0){
3659
+ const data = asset.queue.shift();
3660
+ if (!data) {
3661
+ throw new Error(`Invalid chunk found for ${id}`);
3662
+ }
3663
+ try {
3664
+ // if this is an end chunk, close the asset stream
3665
+ if (data.action === 'end') {
3666
+ _class_private_field_loose_base$2(this, _reportInfo$2)[_reportInfo$2](`Ending asset stream for ${id}`);
3667
+ await closeAssetStream(id);
3668
+ break; // Exit the loop after closing the stream
3669
+ }
3670
+ // Save the current chunk
3671
+ await writeChunkToStream(id, data);
3672
+ } catch {
3673
+ if (!assets[id]) {
3674
+ throw new Error(`No id matching ${id} for writeAssetChunk`);
3675
+ }
3653
3676
  }
3654
- // Unlock the asset
3655
- assets[id].status = 'idle';
3656
- } catch {
3657
- assets[id].status = 'errored';
3658
3677
  }
3659
3678
  };
3660
3679
  /**
3661
3680
  * Writes a chunk of data to the asset's stream.
3662
3681
  *
3663
3682
  * Only check if the targeted asset exists, no other validation is done.
3664
- */ const unsafe_writeAssetChunk = async (id, data)=>{
3683
+ */ const writeChunkToStream = async (id, data)=>{
3665
3684
  const asset = assets[id];
3666
3685
  if (!asset) {
3667
3686
  throw new Error(`Failed to write asset chunk for "${id}". Asset not found.`);
@@ -3682,9 +3701,11 @@ class RemoteStrapiSourceProvider {
3682
3701
  await new Promise((resolve, reject)=>{
3683
3702
  const { stream } = assets[id];
3684
3703
  stream.on('close', ()=>{
3685
- delete assets[id];
3686
3704
  resolve();
3687
- }).on('error', reject).end();
3705
+ }).on('error', (e)=>{
3706
+ assets[id].status = 'errored';
3707
+ reject(new Error(`Failed to close asset "${id}". Asset stream error: ${e.toString()}`));
3708
+ }).end();
3688
3709
  });
3689
3710
  };
3690
3711
  return pass;
@@ -3799,6 +3820,9 @@ class RemoteStrapiSourceProvider {
3799
3820
  });
3800
3821
  this.name = 'source::remote-strapi';
3801
3822
  this.type = 'source';
3823
+ this.defaultOptions = {
3824
+ streamTimeout: 15000
3825
+ };
3802
3826
  this.writeAsync = (stream, data)=>{
3803
3827
  return new Promise((resolve, reject)=>{
3804
3828
  stream.write(data, (error)=>{
@@ -3809,7 +3833,10 @@ class RemoteStrapiSourceProvider {
3809
3833
  });
3810
3834
  });
3811
3835
  };
3812
- this.options = options;
3836
+ this.options = {
3837
+ ...this.defaultOptions,
3838
+ ...options
3839
+ };
3813
3840
  this.ws = null;
3814
3841
  this.dispatcher = null;
3815
3842
  }
@@ -4742,6 +4769,18 @@ const createPullController = handlerControllerFactory((proto)=>({
4742
4769
  kind: 'warning'
4743
4770
  });
4744
4771
  },
4772
+ onError (error) {
4773
+ this.diagnostics?.report({
4774
+ details: {
4775
+ message: error.message,
4776
+ error,
4777
+ createdAt: new Date(),
4778
+ name: error.name,
4779
+ severity: 'fatal'
4780
+ },
4781
+ kind: 'error'
4782
+ });
4783
+ },
4745
4784
  assertValidTransferAction (action) {
4746
4785
  // Abstract the constant to string[] to allow looser check on the given action
4747
4786
  const validActions = VALID_TRANSFER_ACTIONS;
@@ -4815,6 +4854,15 @@ const createPullController = handlerControllerFactory((proto)=>({
4815
4854
  let batch = [];
4816
4855
  const stream = this.streams?.[stage];
4817
4856
  const batchLength = ()=>Buffer.byteLength(JSON.stringify(batch));
4857
+ const maybeConfirm = async (data)=>{
4858
+ try {
4859
+ await this.confirm(data);
4860
+ } catch (error) {
4861
+ // Handle the error, log it, or take other appropriate actions
4862
+ strapi?.log.error(`[Data transfer] Message confirmation failed: ${error?.message}`);
4863
+ this.onError(error);
4864
+ }
4865
+ };
4818
4866
  const sendBatch = async ()=>{
4819
4867
  await this.confirm({
4820
4868
  type: 'transfer',
@@ -4823,6 +4871,7 @@ const createPullController = handlerControllerFactory((proto)=>({
4823
4871
  error: null,
4824
4872
  id
4825
4873
  });
4874
+ batch = [];
4826
4875
  };
4827
4876
  if (!stream) {
4828
4877
  throw new ProviderTransferError(`No available stream found for ${stage}`);
@@ -4833,7 +4882,6 @@ const createPullController = handlerControllerFactory((proto)=>({
4833
4882
  batch.push(chunk);
4834
4883
  if (batchLength() >= batchSize) {
4835
4884
  await sendBatch();
4836
- batch = [];
4837
4885
  }
4838
4886
  } else {
4839
4887
  await this.confirm({
@@ -4849,7 +4897,6 @@ const createPullController = handlerControllerFactory((proto)=>({
4849
4897
  }
4850
4898
  if (batch.length > 0 && stage !== 'assets') {
4851
4899
  await sendBatch();
4852
- batch = [];
4853
4900
  }
4854
4901
  await this.confirm({
4855
4902
  type: 'transfer',
@@ -4859,7 +4906,8 @@ const createPullController = handlerControllerFactory((proto)=>({
4859
4906
  id
4860
4907
  });
4861
4908
  } catch (e) {
4862
- await this.confirm({
4909
+ // TODO: if this confirm fails, can we abort the whole transfer?
4910
+ await maybeConfirm({
4863
4911
  type: 'transfer',
4864
4912
  data: null,
4865
4913
  ended: true,
@@ -4908,7 +4956,7 @@ const createPullController = handlerControllerFactory((proto)=>({
4908
4956
  };
4909
4957
  const BATCH_MAX_SIZE = 1024 * 1024; // 1MB
4910
4958
  if (!assets) {
4911
- throw new Error('bad');
4959
+ throw new Error('Assets read stream could not be created');
4912
4960
  }
4913
4961
  /**
4914
4962
  * Generates batches of 1MB of data from the assets stream to avoid