@sanity/export 3.38.1 → 3.39.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/export",
3
- "version": "3.38.1",
3
+ "version": "3.39.0",
4
4
  "description": "Export Sanity documents and assets",
5
5
  "keywords": [
6
6
  "sanity",
@@ -34,7 +34,7 @@
34
34
  "@sanity/util": "3.37.2",
35
35
  "archiver": "^7.0.0",
36
36
  "debug": "^4.3.4",
37
- "get-it": "^8.4.21",
37
+ "get-it": "^8.6.2",
38
38
  "lodash": "^4.17.21",
39
39
  "mississippi": "^4.0.0",
40
40
  "p-queue": "^2.3.0",
package/src/constants.js CHANGED
@@ -20,3 +20,16 @@ exports.ASSET_DOWNLOAD_MAX_RETRIES = 10
20
20
  * @internal
21
21
  */
22
22
  exports.ASSET_DOWNLOAD_CONCURRENCY = 8
23
+
24
+ /**
25
+ * How frequently we will `debug` log while streaming the documents.
26
+ * @internal
27
+ */
28
+ exports.DOCUMENT_STREAM_DEBUG_INTERVAL = 10000
29
+
30
+ /**
31
+ * How long to wait before timing out the read of a request due to inactivity.
32
+ * User overridable as `options.readTimeout`.
33
+ * @internal
34
+ */
35
+ exports.REQUEST_READ_TIMEOUT = 3 * 60 * 1000 // 3 minutes
package/src/export.js CHANGED
@@ -17,6 +17,7 @@ const stringifyStream = require('./stringifyStream')
17
17
  const tryParseJson = require('./tryParseJson')
18
18
  const rimraf = require('./util/rimraf')
19
19
  const validateOptions = require('./validateOptions')
20
+ const {DOCUMENT_STREAM_DEBUG_INTERVAL} = require('./constants')
20
21
 
21
22
  const noop = () => null
22
23
 
@@ -93,11 +94,14 @@ async function exportDataset(opts) {
93
94
  onProgress({step: 'Exporting documents...'})
94
95
 
95
96
  let documentCount = 0
97
+ let lastDocumentID = null
96
98
  let lastReported = Date.now()
97
- const reportDocumentCount = (chunk, enc, cb) => {
99
+ const reportDocumentCount = (doc, enc, cb) => {
98
100
  ++documentCount
99
101
 
100
102
  const now = Date.now()
103
+ // We report to the `onProgress` handler every 50 ms.
104
+ // It's up to the caller to not do too much expensive work.
101
105
  if (now - lastReported > 50) {
102
106
  onProgress({
103
107
  step: 'Exporting documents...',
@@ -109,13 +113,30 @@ async function exportDataset(opts) {
109
113
  lastReported = now
110
114
  }
111
115
 
112
- cb(null, chunk)
116
+ lastDocumentID = doc._id
117
+
118
+ cb(null, doc)
113
119
  }
114
120
 
115
121
  const inputStream = await getDocumentsStream(options)
116
122
  debug('Got HTTP %d', inputStream.statusCode)
117
123
  debug('Response headers: %o', inputStream.headers)
118
124
 
125
+ let debugTimer = null
126
+ function scheduleDebugTimer() {
127
+ debugTimer = setTimeout(() => {
128
+ debug('Still streaming documents', {
129
+ documentCount,
130
+ lastDocumentID,
131
+ })
132
+
133
+ // Schedule another tick:
134
+ scheduleDebugTimer()
135
+ }, DOCUMENT_STREAM_DEBUG_INTERVAL)
136
+ }
137
+
138
+ scheduleDebugTimer()
139
+
119
140
  const jsonStream = miss.pipeline(
120
141
  inputStream,
121
142
  logFirstChunk(),
@@ -125,13 +146,15 @@ async function exportDataset(opts) {
125
146
  assetStreamHandler,
126
147
  filterDocumentTypes(options.types),
127
148
  options.drafts ? miss.through.obj() : filterDrafts(),
149
+ miss.through.obj(reportDocumentCount),
128
150
  stringifyStream(),
129
- miss.through(reportDocumentCount),
130
151
  )
131
152
 
132
153
  miss.pipe(jsonStream, fs.createWriteStream(dataPath), async (err) => {
154
+ if (debugTimer !== null) clearTimeout(debugTimer)
155
+
133
156
  if (err) {
134
- debug('Export stream error: ', err)
157
+ debug(`Export stream error @ ${lastDocumentID}/${documentCount}: `, err)
135
158
  reject(err)
136
159
  return
137
160
  }
@@ -11,5 +11,10 @@ module.exports = (options) => {
11
11
  ...(token ? {Authorization: `Bearer ${token}`} : {}),
12
12
  }
13
13
 
14
- return requestStream({url, headers, maxRetries: options.maxRetries})
14
+ return requestStream({
15
+ url,
16
+ headers,
17
+ maxRetries: options.maxRetries,
18
+ readTimeout: options.readTimeout,
19
+ })
15
20
  }
@@ -2,18 +2,26 @@ const miss = require('mississippi')
2
2
 
3
3
  module.exports = () =>
4
4
  miss.through.obj((doc, enc, callback) => {
5
- if (doc.error && doc.statusCode) {
6
- callback(
7
- new Error(
8
- ['Export', `HTTP ${doc.statusCode}`, doc.error, doc.message]
9
- .filter((part) => typeof part === 'string')
10
- .join(': '),
11
- ),
12
- )
5
+ // check if the document passed contains a document attribtue first, and return early.
6
+ if (doc._id) {
7
+ callback(null, doc)
13
8
  return
14
9
  }
15
10
 
16
- if (!doc._id && doc.error) {
11
+ if (doc.error) {
12
+ // if we got a statusCode we can decorate the error with it
13
+ if (doc.statusCode) {
14
+ callback(
15
+ new Error(
16
+ ['Export', `HTTP ${doc.statusCode}`, doc.error, doc.message]
17
+ .filter((part) => typeof part === 'string')
18
+ .join(': '),
19
+ ),
20
+ )
21
+ return
22
+ }
23
+
24
+ // no statusCode, just serialize and return the error
17
25
  callback(new Error(doc.error.description || doc.error.message || JSON.stringify(doc)))
18
26
  return
19
27
  }
@@ -2,13 +2,11 @@ const {getIt} = require('get-it')
2
2
  const {keepAlive, promise} = require('get-it/middleware')
3
3
  const debug = require('./debug')
4
4
  const {extractFirstError} = require('./util/extractFirstError')
5
- const {DOCUMENT_STREAM_MAX_RETRIES} = require('./constants')
5
+ const {DOCUMENT_STREAM_MAX_RETRIES, REQUEST_READ_TIMEOUT} = require('./constants')
6
6
 
7
7
  const request = getIt([keepAlive(), promise({onlyBody: true})])
8
- const socketsWithTimeout = new WeakSet()
9
8
 
10
9
  const CONNECTION_TIMEOUT = 15 * 1000 // 15 seconds
11
- const READ_TIMEOUT = 3 * 60 * 1000 // 3 minutes
12
10
  const RETRY_DELAY_MS = 1500 // 1.5 seconds
13
11
 
14
12
  function delay(ms) {
@@ -20,30 +18,18 @@ module.exports = async (options) => {
20
18
  const maxRetries =
21
19
  typeof options.maxRetries === 'number' ? options.maxRetries : DOCUMENT_STREAM_MAX_RETRIES
22
20
 
21
+ const readTimeout =
22
+ typeof options.readTimeout === 'number' ? options.readTimeout : REQUEST_READ_TIMEOUT
23
+
23
24
  let error
24
25
  for (let i = 0; i < maxRetries; i++) {
25
26
  try {
26
- const response = await request({
27
+ return await request({
27
28
  ...options,
28
29
  stream: true,
29
30
  maxRedirects: 0,
30
- timeout: {connect: CONNECTION_TIMEOUT, socket: READ_TIMEOUT},
31
+ timeout: {connect: CONNECTION_TIMEOUT, socket: readTimeout},
31
32
  })
32
-
33
- if (
34
- response.connection &&
35
- typeof response.connection.setTimeout === 'function' &&
36
- !socketsWithTimeout.has(response.connection)
37
- ) {
38
- socketsWithTimeout.add(response.connection)
39
- response.connection.setTimeout(READ_TIMEOUT, () => {
40
- response.destroy(
41
- new Error(`Export: Read timeout: No data received on socket for ${READ_TIMEOUT} ms`),
42
- )
43
- })
44
- }
45
-
46
- return response
47
33
  } catch (err) {
48
34
  error = extractFirstError(err)
49
35
 
@@ -1,9 +1,13 @@
1
1
  const defaults = require('lodash/defaults')
2
- const {DOCUMENT_STREAM_MAX_RETRIES, ASSET_DOWNLOAD_MAX_RETRIES} = require('./constants')
2
+ const {
3
+ DOCUMENT_STREAM_MAX_RETRIES,
4
+ ASSET_DOWNLOAD_MAX_RETRIES,
5
+ REQUEST_READ_TIMEOUT,
6
+ } = require('./constants')
3
7
 
4
8
  const clientMethods = ['getUrl', 'config']
5
9
  const booleanFlags = ['assets', 'raw', 'compress', 'drafts']
6
- const numberFlags = ['maxAssetRetries', 'maxRetries', 'assetConcurrency']
10
+ const numberFlags = ['maxAssetRetries', 'maxRetries', 'assetConcurrency', 'readTimeout']
7
11
  const exportDefaults = {
8
12
  compress: true,
9
13
  drafts: true,
@@ -11,6 +15,7 @@ const exportDefaults = {
11
15
  raw: false,
12
16
  maxRetries: DOCUMENT_STREAM_MAX_RETRIES,
13
17
  maxAssetRetries: ASSET_DOWNLOAD_MAX_RETRIES,
18
+ readTimeout: REQUEST_READ_TIMEOUT,
14
19
  }
15
20
 
16
21
  function validateOptions(opts) {