@sillsdev/docu-notion 0.17.0-alpha.2 → 0.17.0-alpha.3

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.
@@ -18,8 +18,13 @@ function notionColumnListToMarkdown(notionToMarkdown, getBlockChildren, block) {
18
18
  if (!has_children)
19
19
  return "";
20
20
  const column_list_children = yield getBlockChildren(id);
21
- const column_list_promise = column_list_children.map((column) => __awaiter(this, void 0, void 0, function* () { return yield notionToMarkdown.blockToMarkdown(column); }));
22
- const columns = yield Promise.all(column_list_promise);
21
+ const columns = [];
22
+ for (const column of column_list_children) {
23
+ // Keep column rendering sequential. A column block can trigger more Notion
24
+ // reads downstream, so Promise.all() here would turn one page into a burst
25
+ // of concurrent API requests during stage 2.
26
+ columns.push(yield notionToMarkdown.blockToMarkdown(column));
27
+ }
23
28
  return `<div class='notion-row'>\n${columns.join("\n\n")}\n</div>`;
24
29
  });
25
30
  }
@@ -29,7 +29,13 @@ function notionColumnToMarkdown(notionToMarkdown, getBlockChildren, block) {
29
29
  if (!has_children)
30
30
  return "";
31
31
  const columnChildren = yield getBlockChildren(id);
32
- const childrenMdBlocksArray = yield Promise.all(columnChildren.map((child) => __awaiter(this, void 0, void 0, function* () { return yield notionToMarkdown.blocksToMarkdown([child]); })));
32
+ const childrenMdBlocksArray = [];
33
+ for (const child of columnChildren) {
34
+ // Intentionally serialize these subtree conversions. notion-to-md will fetch
35
+ // nested block children during blocksToMarkdown(), and parallelizing sibling
36
+ // columns creates bursts that can exceed Notion's per-integration rate limit.
37
+ childrenMdBlocksArray.push(yield notionToMarkdown.blocksToMarkdown([child]));
38
+ }
33
39
  const childrenMarkdown = childrenMdBlocksArray.map(mdBlockArray => notionToMarkdown.toMarkdownString(mdBlockArray).parent);
34
40
  const columnWidth = yield getColumnWidth(block);
35
41
  return (`<div class='notion-column' style={{width: '${columnWidth}'}}>\n\n${childrenMarkdown.join("\n")}\n</div>` +
package/dist/pull.js CHANGED
@@ -89,9 +89,7 @@ function notionPull(options) {
89
89
  (0, log_1.info)("Connecting to Notion...");
90
90
  // Do a quick test to see if we can connect to the root so that we can give a better error than just a generic "could not find page" one.
91
91
  try {
92
- yield executeWithRateLimitAndRetries("retrieving root page", () => __awaiter(this, void 0, void 0, function* () {
93
- yield notionClient.pages.retrieve({ page_id: options.rootPage });
94
- }));
92
+ yield notionClient.pages.retrieve({ page_id: options.rootPage });
95
93
  }
96
94
  catch (e) {
97
95
  (0, log_1.error)(`docu-notion could not retrieve the root page from Notion. \r\na) Check that the root page id really is "${options.rootPage}".\r\nb) Check that your Notion API token (the "Integration Secret") is correct. It starts with "${optionsForLogging.notionToken}".\r\nc) Check that your root page includes your "integration" in its "connections".\r\nThis internal error message may help:\r\n ${e.message}`);
@@ -223,13 +221,31 @@ const notionLimiter = new limiter_1.RateLimiter({
223
221
  let notionClient;
224
222
  function getPageMetadata(id) {
225
223
  return __awaiter(this, void 0, void 0, function* () {
226
- return yield executeWithRateLimitAndRetries(`pages.retrieve(${id})`, () => {
227
- return notionClient.pages.retrieve({
228
- page_id: id,
229
- });
224
+ return yield notionClient.pages.retrieve({
225
+ page_id: id,
230
226
  });
231
227
  });
232
228
  }
229
+ function isRetryableNotionError(error) {
230
+ const message = String((error === null || error === void 0 ? void 0 : error.message) || "");
231
+ return ((error === null || error === void 0 ? void 0 : error.code) === "notionhq_client_request_timeout" ||
232
+ (error === null || error === void 0 ? void 0 : error.code) === "notionhq_client_response_error" ||
233
+ (error === null || error === void 0 ? void 0 : error.code) === "service_unavailable" ||
234
+ (error === null || error === void 0 ? void 0 : error.code) === client_1.APIErrorCode.RateLimited ||
235
+ message.includes("timeout") ||
236
+ message.includes("Timeout") ||
237
+ message.includes("limit") ||
238
+ message.includes("Limit"));
239
+ }
240
+ function getRetryDelayMilliseconds(error, retryIndex) {
241
+ var _a, _b;
242
+ const retryAfterHeader = (_b = (_a = error === null || error === void 0 ? void 0 : error.headers) === null || _a === void 0 ? void 0 : _a.get) === null || _b === void 0 ? void 0 : _b.call(_a, "retry-after");
243
+ const retryAfterSeconds = Number.parseInt(retryAfterHeader || "", 10);
244
+ if (Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0) {
245
+ return retryAfterSeconds * 1000;
246
+ }
247
+ return (retryIndex + 1) * 1000;
248
+ }
233
249
  // While everything works fine locally, on Github Actions we are getting a lot of timeouts, so
234
250
  // we're trying this extra retry-able wrapper.
235
251
  function executeWithRateLimitAndRetries(label, asyncFunction) {
@@ -243,16 +259,10 @@ function executeWithRateLimitAndRetries(label, asyncFunction) {
243
259
  }
244
260
  catch (e) {
245
261
  lastException = e;
246
- if ((e === null || e === void 0 ? void 0 : e.code) === "notionhq_client_request_timeout" ||
247
- e.message.includes("timeout") ||
248
- e.message.includes("Timeout") ||
249
- e.message.includes("limit") ||
250
- e.message.includes("Limit") ||
251
- (e === null || e === void 0 ? void 0 : e.code) === "notionhq_client_response_error" ||
252
- (e === null || e === void 0 ? void 0 : e.code) === "service_unavailable") {
253
- const secondsToWait = i + 1;
254
- (0, log_1.warning)(`While doing "${label}", got error "${e.message}". Will retry after ${secondsToWait}s...`);
255
- yield new Promise(resolve => setTimeout(resolve, 1000 * secondsToWait));
262
+ if (isRetryableNotionError(e)) {
263
+ const millisecondsToWait = getRetryDelayMilliseconds(e, i);
264
+ (0, log_1.warning)(`While doing "${label}", got error "${e.message}". Will retry after ${millisecondsToWait / 1000}s...`);
265
+ yield new Promise(resolve => setTimeout(resolve, millisecondsToWait));
256
266
  }
257
267
  else {
258
268
  throw e;
@@ -282,11 +292,9 @@ function getBlockChildren(id) {
282
292
  // Note: there is a now a collectPaginatedAPI() in the notion client, so
283
293
  // we could switch to using that (I don't know if it does rate limiting?)
284
294
  do {
285
- const response = yield executeWithRateLimitAndRetries(`getBlockChildren(${id})`, () => {
286
- return notionClient.blocks.children.list({
287
- start_cursor: start_cursor,
288
- block_id: id,
289
- });
295
+ const response = yield notionClient.blocks.children.list({
296
+ start_cursor: start_cursor,
297
+ block_id: id,
290
298
  });
291
299
  if (!overallResult) {
292
300
  overallResult = response;
@@ -309,6 +317,10 @@ function initNotionClient(notionToken) {
309
317
  notionClient = new client_1.Client({
310
318
  auth: notionToken,
311
319
  });
320
+ const originalRequest = notionClient.request.bind(notionClient);
321
+ notionClient.request = (args) => __awaiter(this, void 0, void 0, function* () {
322
+ return yield executeWithRateLimitAndRetries(`${args.method.toUpperCase()} ${args.path}`, () => originalRequest(args));
323
+ });
312
324
  return notionClient;
313
325
  }
314
326
  function fromPageId(context, pageId, order, foundDirectlyInOutline) {
package/dist/transform.js CHANGED
@@ -16,7 +16,6 @@ exports.getMarkdownForPage = getMarkdownForPage;
16
16
  exports.getMarkdownFromNotionBlocks = getMarkdownFromNotionBlocks;
17
17
  const chalk_1 = __importDefault(require("chalk"));
18
18
  const log_1 = require("./log");
19
- const pull_1 = require("./pull");
20
19
  function getMarkdownForPage(config, context, page) {
21
20
  return __awaiter(this, void 0, void 0, function* () {
22
21
  (0, log_1.info)(`Reading & converting page ${page.layoutContext}/${page.nameOrTitle} (${chalk_1.default.blue(page.hasExplicitSlug
@@ -127,17 +126,10 @@ function doTransformsOnMarkdown(context, config, input) {
127
126
  }
128
127
  function doNotionToMarkdown(docunotionContext, blocks) {
129
128
  return __awaiter(this, void 0, void 0, function* () {
130
- let mdBlocks;
131
- yield (0, pull_1.executeWithRateLimitAndRetries)("notionToMarkdown.blocksToMarkdown", () => __awaiter(this, void 0, void 0, function* () {
132
- mdBlocks = yield docunotionContext.notionToMarkdown.blocksToMarkdown(
133
- // We need to provide a copy of blocks.
134
- // Calling blocksToMarkdown can modify the values in the blocks. If it does, and then
135
- // we have to retry, we end up retrying with the modified values, which
136
- // causes various issues (like using the transformed image url instead of the original one).
137
- // Note, currently, we don't do anything else with blocks after this.
138
- // If that changes, we'll need to figure out a more sophisticated approach.
139
- JSON.parse(JSON.stringify(blocks)));
140
- }));
129
+ const mdBlocks = yield docunotionContext.notionToMarkdown.blocksToMarkdown(
130
+ // We need to provide a copy of blocks.
131
+ // Calling blocksToMarkdown can modify the values in the blocks.
132
+ JSON.parse(JSON.stringify(blocks)));
141
133
  const markdown = docunotionContext.notionToMarkdown.toMarkdownString(mdBlocks).parent || "";
142
134
  return markdown;
143
135
  });
package/package.json CHANGED
@@ -92,5 +92,5 @@
92
92
  "volta": {
93
93
  "node": "22.21.0"
94
94
  },
95
- "version": "0.17.0-alpha.2"
95
+ "version": "0.17.0-alpha.3"
96
96
  }