@stoneforge/quarry 1.13.0 → 1.14.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/dist/api/quarry-api.d.ts +9 -1
- package/dist/api/quarry-api.d.ts.map +1 -1
- package/dist/api/quarry-api.js +21 -2
- package/dist/api/quarry-api.js.map +1 -1
- package/dist/api/types.d.ts +8 -1
- package/dist/api/types.d.ts.map +1 -1
- package/dist/api/types.js.map +1 -1
- package/dist/cli/commands/auto-link-helper.d.ts.map +1 -1
- package/dist/cli/commands/auto-link-helper.js +1 -0
- package/dist/cli/commands/auto-link-helper.js.map +1 -1
- package/dist/cli/commands/crud.d.ts +2 -0
- package/dist/cli/commands/crud.d.ts.map +1 -1
- package/dist/cli/commands/crud.js +100 -10
- package/dist/cli/commands/crud.js.map +1 -1
- package/dist/cli/commands/docs.js +2 -2
- package/dist/cli/commands/docs.js.map +1 -1
- package/dist/cli/commands/document.js +1 -1
- package/dist/cli/commands/document.js.map +1 -1
- package/dist/cli/commands/entity.js +1 -1
- package/dist/cli/commands/entity.js.map +1 -1
- package/dist/cli/commands/external-sync.d.ts +6 -5
- package/dist/cli/commands/external-sync.d.ts.map +1 -1
- package/dist/cli/commands/external-sync.js +1032 -180
- package/dist/cli/commands/external-sync.js.map +1 -1
- package/dist/cli/commands/library.js +1 -1
- package/dist/cli/commands/library.js.map +1 -1
- package/dist/cli/commands/message.js +2 -2
- package/dist/cli/commands/message.js.map +1 -1
- package/dist/cli/commands/serve.d.ts.map +1 -1
- package/dist/cli/commands/serve.js +2 -0
- package/dist/cli/commands/serve.js.map +1 -1
- package/dist/cli/commands/task.d.ts.map +1 -1
- package/dist/cli/commands/task.js +7 -4
- package/dist/cli/commands/task.js.map +1 -1
- package/dist/cli/commands/team.js +1 -1
- package/dist/cli/commands/team.js.map +1 -1
- package/dist/cli/commands/workflow.js +1 -1
- package/dist/cli/commands/workflow.js.map +1 -1
- package/dist/cli/utils/progress.d.ts +30 -0
- package/dist/cli/utils/progress.d.ts.map +1 -0
- package/dist/cli/utils/progress.js +47 -0
- package/dist/cli/utils/progress.js.map +1 -0
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +6 -0
- package/dist/config/config.js.map +1 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +1 -0
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/file.d.ts.map +1 -1
- package/dist/config/file.js +10 -0
- package/dist/config/file.js.map +1 -1
- package/dist/config/merge.d.ts.map +1 -1
- package/dist/config/merge.js +7 -1
- package/dist/config/merge.js.map +1 -1
- package/dist/config/types.d.ts +7 -2
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +3 -0
- package/dist/config/types.js.map +1 -1
- package/dist/config/validation.d.ts.map +1 -1
- package/dist/config/validation.js +13 -0
- package/dist/config/validation.js.map +1 -1
- package/dist/external-sync/adapters/document-sync-adapter.d.ts +150 -0
- package/dist/external-sync/adapters/document-sync-adapter.d.ts.map +1 -0
- package/dist/external-sync/adapters/document-sync-adapter.js +325 -0
- package/dist/external-sync/adapters/document-sync-adapter.js.map +1 -0
- package/dist/external-sync/index.d.ts +3 -0
- package/dist/external-sync/index.d.ts.map +1 -1
- package/dist/external-sync/index.js +4 -0
- package/dist/external-sync/index.js.map +1 -1
- package/dist/external-sync/provider-registry.d.ts +7 -3
- package/dist/external-sync/provider-registry.d.ts.map +1 -1
- package/dist/external-sync/provider-registry.js +20 -3
- package/dist/external-sync/provider-registry.js.map +1 -1
- package/dist/external-sync/providers/folder/folder-document-adapter.d.ts +97 -0
- package/dist/external-sync/providers/folder/folder-document-adapter.d.ts.map +1 -0
- package/dist/external-sync/providers/folder/folder-document-adapter.js +261 -0
- package/dist/external-sync/providers/folder/folder-document-adapter.js.map +1 -0
- package/dist/external-sync/providers/folder/folder-fs.d.ts +146 -0
- package/dist/external-sync/providers/folder/folder-fs.d.ts.map +1 -0
- package/dist/external-sync/providers/folder/folder-fs.js +300 -0
- package/dist/external-sync/providers/folder/folder-fs.js.map +1 -0
- package/dist/external-sync/providers/folder/folder-provider.d.ts +28 -0
- package/dist/external-sync/providers/folder/folder-provider.d.ts.map +1 -0
- package/dist/external-sync/providers/folder/folder-provider.js +87 -0
- package/dist/external-sync/providers/folder/folder-provider.js.map +1 -0
- package/dist/external-sync/providers/folder/index.d.ts +11 -0
- package/dist/external-sync/providers/folder/index.d.ts.map +1 -0
- package/dist/external-sync/providers/folder/index.js +13 -0
- package/dist/external-sync/providers/folder/index.js.map +1 -0
- package/dist/external-sync/providers/index.d.ts +4 -0
- package/dist/external-sync/providers/index.d.ts.map +1 -1
- package/dist/external-sync/providers/index.js +5 -0
- package/dist/external-sync/providers/index.js.map +1 -1
- package/dist/external-sync/providers/notion/index.d.ts +19 -0
- package/dist/external-sync/providers/notion/index.d.ts.map +1 -0
- package/dist/external-sync/providers/notion/index.js +20 -0
- package/dist/external-sync/providers/notion/index.js.map +1 -0
- package/dist/external-sync/providers/notion/notion-api.d.ts +253 -0
- package/dist/external-sync/providers/notion/notion-api.d.ts.map +1 -0
- package/dist/external-sync/providers/notion/notion-api.js +492 -0
- package/dist/external-sync/providers/notion/notion-api.js.map +1 -0
- package/dist/external-sync/providers/notion/notion-blocks.d.ts +93 -0
- package/dist/external-sync/providers/notion/notion-blocks.d.ts.map +1 -0
- package/dist/external-sync/providers/notion/notion-blocks.js +773 -0
- package/dist/external-sync/providers/notion/notion-blocks.js.map +1 -0
- package/dist/external-sync/providers/notion/notion-document-adapter.d.ts +176 -0
- package/dist/external-sync/providers/notion/notion-document-adapter.d.ts.map +1 -0
- package/dist/external-sync/providers/notion/notion-document-adapter.js +413 -0
- package/dist/external-sync/providers/notion/notion-document-adapter.js.map +1 -0
- package/dist/external-sync/providers/notion/notion-provider.d.ts +57 -0
- package/dist/external-sync/providers/notion/notion-provider.d.ts.map +1 -0
- package/dist/external-sync/providers/notion/notion-provider.js +159 -0
- package/dist/external-sync/providers/notion/notion-provider.js.map +1 -0
- package/dist/external-sync/providers/notion/notion-types.d.ts +388 -0
- package/dist/external-sync/providers/notion/notion-types.d.ts.map +1 -0
- package/dist/external-sync/providers/notion/notion-types.js +47 -0
- package/dist/external-sync/providers/notion/notion-types.js.map +1 -0
- package/dist/external-sync/sync-engine.d.ts +70 -4
- package/dist/external-sync/sync-engine.d.ts.map +1 -1
- package/dist/external-sync/sync-engine.js +436 -67
- package/dist/external-sync/sync-engine.js.map +1 -1
- package/dist/server/index.js +8 -8
- package/dist/server/index.js.map +1 -1
- package/package.json +4 -12
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notion REST API Client
|
|
3
|
+
*
|
|
4
|
+
* Pure fetch-based client for Notion page and block operations.
|
|
5
|
+
* Supports Bearer token authentication, rate limit handling with Retry-After,
|
|
6
|
+
* automatic retry on transient server errors (502/503/504) with exponential backoff,
|
|
7
|
+
* cursor-based pagination, and typed error responses.
|
|
8
|
+
*
|
|
9
|
+
* No external dependencies — uses only the standard fetch API.
|
|
10
|
+
*
|
|
11
|
+
* @see https://developers.notion.com/reference
|
|
12
|
+
*/
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Error Types
|
|
15
|
+
// ============================================================================
|
|
16
|
+
/**
|
|
17
|
+
* Typed error for Notion API failures.
|
|
18
|
+
* Wraps fetch errors with status code, Notion error code, and message.
|
|
19
|
+
*/
|
|
20
|
+
export class NotionApiError extends Error {
|
|
21
|
+
/** HTTP status code from Notion's response */
|
|
22
|
+
status;
|
|
23
|
+
/** Notion-specific error code (e.g., "validation_error", "object_not_found") */
|
|
24
|
+
code;
|
|
25
|
+
/** Parsed error body from Notion (if available) */
|
|
26
|
+
responseBody;
|
|
27
|
+
constructor(message, status, code = 'unknown', responseBody = null, cause) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.name = 'NotionApiError';
|
|
30
|
+
this.status = status;
|
|
31
|
+
this.code = code;
|
|
32
|
+
this.responseBody = responseBody;
|
|
33
|
+
this.cause = cause;
|
|
34
|
+
if (Error.captureStackTrace) {
|
|
35
|
+
Error.captureStackTrace(this, NotionApiError);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Whether this error is due to rate limiting (429 Too Many Requests).
|
|
40
|
+
*/
|
|
41
|
+
get isRateLimited() {
|
|
42
|
+
return this.status === 429;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Whether this error is transient and can be retried.
|
|
46
|
+
* Includes rate limiting (429) and server errors (502, 503, 504).
|
|
47
|
+
*/
|
|
48
|
+
get isRetryable() {
|
|
49
|
+
return this.status === 429 || this.status === 502 || this.status === 503 || this.status === 504;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Whether this error is due to authentication failure.
|
|
53
|
+
*/
|
|
54
|
+
get isAuthError() {
|
|
55
|
+
return this.status === 401;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Whether this error is a not-found response.
|
|
59
|
+
*/
|
|
60
|
+
get isNotFound() {
|
|
61
|
+
return this.status === 404 || this.code === 'object_not_found';
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Whether this is a validation error.
|
|
65
|
+
*/
|
|
66
|
+
get isValidationError() {
|
|
67
|
+
return this.status === 400 || this.code === 'validation_error';
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Returns a JSON-serializable representation of the error.
|
|
71
|
+
*/
|
|
72
|
+
toJSON() {
|
|
73
|
+
return {
|
|
74
|
+
name: this.name,
|
|
75
|
+
message: this.message,
|
|
76
|
+
status: this.status,
|
|
77
|
+
code: this.code,
|
|
78
|
+
responseBody: this.responseBody,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Type guard for NotionApiError.
|
|
84
|
+
*/
|
|
85
|
+
export function isNotionApiError(error) {
|
|
86
|
+
return error instanceof NotionApiError;
|
|
87
|
+
}
|
|
88
|
+
// ============================================================================
|
|
89
|
+
// Constants
|
|
90
|
+
// ============================================================================
|
|
91
|
+
/** Notion API base URL */
|
|
92
|
+
const NOTION_API_BASE = 'https://api.notion.com/v1';
|
|
93
|
+
/** Default Notion API version */
|
|
94
|
+
const DEFAULT_NOTION_VERSION = '2022-06-28';
|
|
95
|
+
/** Default maximum retry count for retryable responses (429, 502, 503, 504) */
|
|
96
|
+
const DEFAULT_MAX_RETRIES = 3;
|
|
97
|
+
/** Default Retry-After fallback if the header is missing (in seconds) */
|
|
98
|
+
const DEFAULT_RETRY_AFTER_SECONDS = 1;
|
|
99
|
+
/** Maximum number of blocks per Notion API request (POST /pages or PATCH /blocks/{id}/children) */
|
|
100
|
+
const NOTION_BLOCK_BATCH_SIZE = 100;
|
|
101
|
+
/** Number of concurrent block delete requests (kept under Notion's 3 req/s rate limit with headroom) */
|
|
102
|
+
const CONCURRENT_BLOCK_DELETES = 8;
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// Client Implementation
|
|
105
|
+
// ============================================================================
|
|
106
|
+
/**
|
|
107
|
+
* Fetch-based Notion REST API client for page and block operations.
|
|
108
|
+
*
|
|
109
|
+
* Features:
|
|
110
|
+
* - Bearer token authentication (internal integrations or OAuth)
|
|
111
|
+
* - Notion-Version header for API versioning
|
|
112
|
+
* - Automatic retry on 429 (Too Many Requests) with Retry-After
|
|
113
|
+
* - Automatic retry on 502/503/504 (server errors) with exponential backoff
|
|
114
|
+
* - Cursor-based pagination for list endpoints
|
|
115
|
+
* - Typed errors with Notion error codes
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```typescript
|
|
119
|
+
* const client = new NotionApiClient({ token: 'ntn_...' });
|
|
120
|
+
* const page = await client.getPage('page-uuid');
|
|
121
|
+
* const blocks = await client.getBlocks('page-uuid');
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
export class NotionApiClient {
|
|
125
|
+
token;
|
|
126
|
+
notionVersion;
|
|
127
|
+
maxRetries;
|
|
128
|
+
warnOnRateLimit;
|
|
129
|
+
/** Rate limit state tracked from 429 responses */
|
|
130
|
+
rateLimitState = {
|
|
131
|
+
wasRateLimited: false,
|
|
132
|
+
lastRetryAfterSeconds: null,
|
|
133
|
+
totalRateLimitHits: 0,
|
|
134
|
+
};
|
|
135
|
+
constructor(options) {
|
|
136
|
+
if (!options.token) {
|
|
137
|
+
throw new Error('Notion API token is required');
|
|
138
|
+
}
|
|
139
|
+
this.token = options.token;
|
|
140
|
+
this.notionVersion = options.notionVersion ?? DEFAULT_NOTION_VERSION;
|
|
141
|
+
this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
142
|
+
this.warnOnRateLimit = options.warnOnRateLimit ?? true;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Returns the current rate limit state.
|
|
146
|
+
*/
|
|
147
|
+
getRateLimitState() {
|
|
148
|
+
return { ...this.rateLimitState };
|
|
149
|
+
}
|
|
150
|
+
// --------------------------------------------------------------------------
|
|
151
|
+
// Public API Methods
|
|
152
|
+
// --------------------------------------------------------------------------
|
|
153
|
+
/**
|
|
154
|
+
* Retrieve a page by ID.
|
|
155
|
+
*
|
|
156
|
+
* GET /pages/{page_id}
|
|
157
|
+
*
|
|
158
|
+
* @see https://developers.notion.com/reference/retrieve-a-page
|
|
159
|
+
*/
|
|
160
|
+
async getPage(pageId) {
|
|
161
|
+
return this.request('GET', `/pages/${pageId}`);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Retrieve a database by ID.
|
|
165
|
+
*
|
|
166
|
+
* GET /databases/{database_id}
|
|
167
|
+
*
|
|
168
|
+
* Returns the database schema including all property definitions.
|
|
169
|
+
* Used for discovering the title property name and checking which
|
|
170
|
+
* properties (Category, Tags) exist before creating pages.
|
|
171
|
+
*
|
|
172
|
+
* @see https://developers.notion.com/reference/retrieve-a-database
|
|
173
|
+
*/
|
|
174
|
+
async getDatabase(databaseId) {
|
|
175
|
+
return this.request('GET', `/databases/${databaseId}`);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Update a database schema (add or modify properties).
|
|
179
|
+
*
|
|
180
|
+
* PATCH /databases/{database_id}
|
|
181
|
+
*
|
|
182
|
+
* Used to auto-create missing properties (e.g., Category as select,
|
|
183
|
+
* Tags as multi_select) when they don't exist in the database schema.
|
|
184
|
+
*
|
|
185
|
+
* @see https://developers.notion.com/reference/update-a-database
|
|
186
|
+
*/
|
|
187
|
+
async updateDatabase(databaseId, updates) {
|
|
188
|
+
return this.request('PATCH', `/databases/${databaseId}`, updates);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Retrieve all block children for a given block (or page).
|
|
192
|
+
* Automatically paginates through all results.
|
|
193
|
+
*
|
|
194
|
+
* GET /blocks/{block_id}/children
|
|
195
|
+
*
|
|
196
|
+
* @see https://developers.notion.com/reference/get-block-children
|
|
197
|
+
*/
|
|
198
|
+
async getBlocks(blockId) {
|
|
199
|
+
const allBlocks = [];
|
|
200
|
+
let cursor;
|
|
201
|
+
// eslint-disable-next-line no-constant-condition
|
|
202
|
+
while (true) {
|
|
203
|
+
const params = new URLSearchParams({ page_size: '100' });
|
|
204
|
+
if (cursor) {
|
|
205
|
+
params.set('start_cursor', cursor);
|
|
206
|
+
}
|
|
207
|
+
const response = await this.request('GET', `/blocks/${blockId}/children?${params.toString()}`);
|
|
208
|
+
allBlocks.push(...response.results);
|
|
209
|
+
if (!response.has_more || !response.next_cursor) {
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
cursor = response.next_cursor;
|
|
213
|
+
}
|
|
214
|
+
return allBlocks;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Create a new page in a database or as a child of another page.
|
|
218
|
+
*
|
|
219
|
+
* POST /pages
|
|
220
|
+
*
|
|
221
|
+
* @see https://developers.notion.com/reference/post-page
|
|
222
|
+
*/
|
|
223
|
+
async createPage(databaseId, properties, children) {
|
|
224
|
+
const body = {
|
|
225
|
+
parent: { database_id: databaseId },
|
|
226
|
+
properties,
|
|
227
|
+
...(children && children.length > 0 ? { children } : {}),
|
|
228
|
+
};
|
|
229
|
+
return this.request('POST', '/pages', body);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Update page properties.
|
|
233
|
+
*
|
|
234
|
+
* PATCH /pages/{page_id}
|
|
235
|
+
*
|
|
236
|
+
* @see https://developers.notion.com/reference/patch-page
|
|
237
|
+
*/
|
|
238
|
+
async updatePage(pageId, properties) {
|
|
239
|
+
const body = { properties };
|
|
240
|
+
return this.request('PATCH', `/pages/${pageId}`, body);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Replace all content blocks of a page.
|
|
244
|
+
*
|
|
245
|
+
* This is a two-step operation:
|
|
246
|
+
* 1. Delete all existing top-level block children
|
|
247
|
+
* 2. Append new block children in batches of 100 (Notion's per-request limit)
|
|
248
|
+
*
|
|
249
|
+
* CAUTION: This is not atomic. If step 2 fails, the page may have partial content.
|
|
250
|
+
*
|
|
251
|
+
* @see https://developers.notion.com/reference/delete-a-block
|
|
252
|
+
* @see https://developers.notion.com/reference/patch-block-children
|
|
253
|
+
*/
|
|
254
|
+
async updatePageContent(pageId, blocks) {
|
|
255
|
+
// Step 1: Get existing blocks and delete them concurrently in batches
|
|
256
|
+
// Filter out archived blocks — Notion rejects DELETE on archived blocks with:
|
|
257
|
+
// "Can't edit block that is archived. You must unarchive the block before editing."
|
|
258
|
+
const existingBlocks = await this.getBlocks(pageId);
|
|
259
|
+
const deletableBlocks = existingBlocks.filter(block => !block.archived);
|
|
260
|
+
for (let i = 0; i < deletableBlocks.length; i += CONCURRENT_BLOCK_DELETES) {
|
|
261
|
+
const batch = deletableBlocks.slice(i, i + CONCURRENT_BLOCK_DELETES);
|
|
262
|
+
await Promise.all(batch.map(block => this.request('DELETE', `/blocks/${block.id}`)));
|
|
263
|
+
}
|
|
264
|
+
// Step 2: Append new blocks in batches of 100
|
|
265
|
+
if (blocks.length === 0) {
|
|
266
|
+
return [];
|
|
267
|
+
}
|
|
268
|
+
const allResults = [];
|
|
269
|
+
for (let i = 0; i < blocks.length; i += NOTION_BLOCK_BATCH_SIZE) {
|
|
270
|
+
const batch = blocks.slice(i, i + NOTION_BLOCK_BATCH_SIZE);
|
|
271
|
+
const response = await this.request('PATCH', `/blocks/${pageId}/children`, { children: batch });
|
|
272
|
+
allResults.push(...response.results);
|
|
273
|
+
}
|
|
274
|
+
return allResults;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Append block children to a page or block.
|
|
278
|
+
*
|
|
279
|
+
* Respects Notion's 100-block-per-request limit by batching automatically.
|
|
280
|
+
* Block order is preserved across batches.
|
|
281
|
+
*
|
|
282
|
+
* PATCH /blocks/{block_id}/children
|
|
283
|
+
*
|
|
284
|
+
* @param blockId - The page or block ID to append children to
|
|
285
|
+
* @param blocks - The blocks to append
|
|
286
|
+
* @returns All appended blocks
|
|
287
|
+
*
|
|
288
|
+
* @see https://developers.notion.com/reference/patch-block-children
|
|
289
|
+
*/
|
|
290
|
+
async appendBlocks(blockId, blocks) {
|
|
291
|
+
if (blocks.length === 0) {
|
|
292
|
+
return [];
|
|
293
|
+
}
|
|
294
|
+
const allResults = [];
|
|
295
|
+
for (let i = 0; i < blocks.length; i += NOTION_BLOCK_BATCH_SIZE) {
|
|
296
|
+
const batch = blocks.slice(i, i + NOTION_BLOCK_BATCH_SIZE);
|
|
297
|
+
const response = await this.request('PATCH', `/blocks/${blockId}/children`, { children: batch });
|
|
298
|
+
allResults.push(...response.results);
|
|
299
|
+
}
|
|
300
|
+
return allResults;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Query a database with optional filter and cursor-based pagination.
|
|
304
|
+
*
|
|
305
|
+
* POST /databases/{database_id}/query
|
|
306
|
+
*
|
|
307
|
+
* When no cursor is provided and the result has more pages, this method
|
|
308
|
+
* returns only the first page. Callers can use `next_cursor` to fetch
|
|
309
|
+
* additional pages, or use `queryDatabaseAll()` to auto-paginate.
|
|
310
|
+
*
|
|
311
|
+
* @see https://developers.notion.com/reference/post-database-query
|
|
312
|
+
*/
|
|
313
|
+
async queryDatabase(databaseId, filter, cursor) {
|
|
314
|
+
const body = {
|
|
315
|
+
page_size: 100,
|
|
316
|
+
};
|
|
317
|
+
if (filter) {
|
|
318
|
+
body.filter = filter;
|
|
319
|
+
}
|
|
320
|
+
if (cursor) {
|
|
321
|
+
body.start_cursor = cursor;
|
|
322
|
+
}
|
|
323
|
+
return this.request('POST', `/databases/${databaseId}/query`, body);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Query a database and automatically paginate through all results.
|
|
327
|
+
*
|
|
328
|
+
* This is a convenience wrapper around `queryDatabase()` that follows
|
|
329
|
+
* `next_cursor` until all pages have been fetched.
|
|
330
|
+
*
|
|
331
|
+
* @param databaseId - The database to query.
|
|
332
|
+
* @param filter - Optional Notion filter object.
|
|
333
|
+
* @returns All pages matching the query.
|
|
334
|
+
*/
|
|
335
|
+
async queryDatabaseAll(databaseId, filter) {
|
|
336
|
+
const allPages = [];
|
|
337
|
+
let cursor;
|
|
338
|
+
// eslint-disable-next-line no-constant-condition
|
|
339
|
+
while (true) {
|
|
340
|
+
const response = await this.queryDatabase(databaseId, filter, cursor);
|
|
341
|
+
allPages.push(...response.results);
|
|
342
|
+
if (!response.has_more || !response.next_cursor) {
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
cursor = response.next_cursor;
|
|
346
|
+
}
|
|
347
|
+
return allPages;
|
|
348
|
+
}
|
|
349
|
+
// --------------------------------------------------------------------------
|
|
350
|
+
// Internal: HTTP Request Handling
|
|
351
|
+
// --------------------------------------------------------------------------
|
|
352
|
+
/**
|
|
353
|
+
* Performs an HTTP request to the Notion API with automatic retry on retryable errors.
|
|
354
|
+
*
|
|
355
|
+
* Retry strategy:
|
|
356
|
+
* - 429 (rate limited): Uses Retry-After header value (existing behavior)
|
|
357
|
+
* - 502/503/504 (server errors): Uses exponential backoff starting at 1s (1s, 2s, 4s, ...)
|
|
358
|
+
*/
|
|
359
|
+
async request(method, path, body) {
|
|
360
|
+
let lastError;
|
|
361
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
362
|
+
try {
|
|
363
|
+
return await this.executeRequest(method, path, body);
|
|
364
|
+
}
|
|
365
|
+
catch (err) {
|
|
366
|
+
if (err instanceof NotionApiError && err.isRetryable && attempt < this.maxRetries) {
|
|
367
|
+
let retryDelay;
|
|
368
|
+
if (err.isRateLimited) {
|
|
369
|
+
// 429: Use Retry-After header (existing behavior)
|
|
370
|
+
retryDelay = (this.rateLimitState.lastRetryAfterSeconds ?? DEFAULT_RETRY_AFTER_SECONDS) * 1000;
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
// 502/503/504: Use exponential backoff starting at 1s
|
|
374
|
+
retryDelay = DEFAULT_RETRY_AFTER_SECONDS * 1000 * Math.pow(2, attempt);
|
|
375
|
+
}
|
|
376
|
+
if (this.warnOnRateLimit) {
|
|
377
|
+
const reason = err.isRateLimited
|
|
378
|
+
? 'Rate limited'
|
|
379
|
+
: `Server error (${err.status})`;
|
|
380
|
+
console.warn(`[NotionApiClient] ${reason} on ${method} ${path}. ` +
|
|
381
|
+
`Retrying in ${retryDelay / 1000}s (attempt ${attempt + 1}/${this.maxRetries}).`);
|
|
382
|
+
}
|
|
383
|
+
await sleep(retryDelay);
|
|
384
|
+
lastError = err;
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
throw err;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
// Should not reach here, but just in case
|
|
391
|
+
throw lastError ?? new NotionApiError('Max retries exceeded', 429, 'rate_limited');
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Executes a single HTTP request to the Notion API (no retry logic).
|
|
395
|
+
*/
|
|
396
|
+
async executeRequest(method, path, body) {
|
|
397
|
+
const url = `${NOTION_API_BASE}${path}`;
|
|
398
|
+
const headers = {
|
|
399
|
+
Authorization: `Bearer ${this.token}`,
|
|
400
|
+
'Notion-Version': this.notionVersion,
|
|
401
|
+
};
|
|
402
|
+
if (body !== undefined) {
|
|
403
|
+
headers['Content-Type'] = 'application/json';
|
|
404
|
+
}
|
|
405
|
+
let response;
|
|
406
|
+
try {
|
|
407
|
+
response = await fetch(url, {
|
|
408
|
+
method,
|
|
409
|
+
headers,
|
|
410
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
catch (err) {
|
|
414
|
+
throw new NotionApiError(`Network error requesting ${method} ${path}: ${err instanceof Error ? err.message : String(err)}`, 0, 'network_error', null, err instanceof Error ? err : undefined);
|
|
415
|
+
}
|
|
416
|
+
if (!response.ok) {
|
|
417
|
+
await this.handleErrorResponse(response, method, path);
|
|
418
|
+
}
|
|
419
|
+
// DELETE responses may have no body (204 No Content)
|
|
420
|
+
if (response.status === 204 || response.headers.get('content-length') === '0') {
|
|
421
|
+
return undefined;
|
|
422
|
+
}
|
|
423
|
+
return (await response.json());
|
|
424
|
+
}
|
|
425
|
+
// --------------------------------------------------------------------------
|
|
426
|
+
// Internal: Error Handling
|
|
427
|
+
// --------------------------------------------------------------------------
|
|
428
|
+
/**
|
|
429
|
+
* Handles non-OK responses by throwing a NotionApiError.
|
|
430
|
+
*/
|
|
431
|
+
async handleErrorResponse(response, method, path) {
|
|
432
|
+
let responseBody = null;
|
|
433
|
+
let errorMessage;
|
|
434
|
+
let errorCode = 'unknown';
|
|
435
|
+
try {
|
|
436
|
+
responseBody = (await response.json());
|
|
437
|
+
errorMessage = responseBody.message ?? `Notion API error: ${response.status}`;
|
|
438
|
+
errorCode = responseBody.code ?? 'unknown';
|
|
439
|
+
}
|
|
440
|
+
catch {
|
|
441
|
+
errorMessage = `Notion API error: ${response.status} ${response.statusText}`;
|
|
442
|
+
}
|
|
443
|
+
// Enrich rate limit error with Retry-After info
|
|
444
|
+
if (response.status === 429) {
|
|
445
|
+
const retryAfter = response.headers.get('Retry-After');
|
|
446
|
+
if (retryAfter) {
|
|
447
|
+
errorMessage = `Rate limited. Retry after ${retryAfter}s. ${errorMessage}`;
|
|
448
|
+
}
|
|
449
|
+
// Track the rate limit hit
|
|
450
|
+
const retryAfterSeconds = retryAfter
|
|
451
|
+
? parseInt(retryAfter, 10)
|
|
452
|
+
: DEFAULT_RETRY_AFTER_SECONDS;
|
|
453
|
+
this.rateLimitState = {
|
|
454
|
+
wasRateLimited: true,
|
|
455
|
+
lastRetryAfterSeconds: isNaN(retryAfterSeconds)
|
|
456
|
+
? DEFAULT_RETRY_AFTER_SECONDS
|
|
457
|
+
: retryAfterSeconds,
|
|
458
|
+
totalRateLimitHits: this.rateLimitState.totalRateLimitHits + 1,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
throw new NotionApiError(`${method} ${path} failed: ${errorMessage}`, response.status, errorCode, responseBody);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// ============================================================================
|
|
465
|
+
// Utility Functions
|
|
466
|
+
// ============================================================================
|
|
467
|
+
/**
|
|
468
|
+
* Parses the Retry-After value from a NotionApiError's response body or falls back to default.
|
|
469
|
+
*/
|
|
470
|
+
function parseRetryAfterFromError(err) {
|
|
471
|
+
// The Retry-After header is already parsed into the error message;
|
|
472
|
+
// try to extract it. This is a best-effort parse.
|
|
473
|
+
const match = err.message.match(/Retry after (\d+)s/);
|
|
474
|
+
if (match) {
|
|
475
|
+
const seconds = parseInt(match[1], 10);
|
|
476
|
+
if (!isNaN(seconds) && seconds > 0) {
|
|
477
|
+
return seconds;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return err.responseBody?.status === 429
|
|
481
|
+
? DEFAULT_RETRY_AFTER_SECONDS
|
|
482
|
+
: DEFAULT_RETRY_AFTER_SECONDS;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Promise-based sleep utility.
|
|
486
|
+
*/
|
|
487
|
+
function sleep(ms) {
|
|
488
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
489
|
+
}
|
|
490
|
+
// Export utility functions and constants for testing
|
|
491
|
+
export { sleep, parseRetryAfterFromError, NOTION_BLOCK_BATCH_SIZE, CONCURRENT_BLOCK_DELETES };
|
|
492
|
+
//# sourceMappingURL=notion-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notion-api.js","sourceRoot":"","sources":["../../../../src/external-sync/providers/notion/notion-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notion Blocks ↔ Markdown Converter
|
|
3
|
+
*
|
|
4
|
+
* Bidirectional converter between markdown text and Notion block format.
|
|
5
|
+
* Internal module with no external dependencies.
|
|
6
|
+
*
|
|
7
|
+
* Supported conversions:
|
|
8
|
+
* - Paragraphs ↔ paragraph blocks
|
|
9
|
+
* - Headings (# ## ###) ↔ heading_1, heading_2, heading_3 blocks
|
|
10
|
+
* - Bulleted lists (- or *) ↔ bulleted_list_item blocks
|
|
11
|
+
* - Numbered lists (1. 2. 3.) ↔ numbered_list_item blocks
|
|
12
|
+
* - Code blocks (```) ↔ code blocks (with language annotation)
|
|
13
|
+
* - Blockquotes (>) ↔ quote blocks
|
|
14
|
+
* - Checkboxes (- [ ] / - [x]) ↔ to_do blocks
|
|
15
|
+
* - Rich text: **bold**, *italic*, `inline code`, [links](url)
|
|
16
|
+
*/
|
|
17
|
+
import type { NotionBlock, NotionRichText } from './notion-types.js';
|
|
18
|
+
/**
|
|
19
|
+
* Maximum length for a single rich_text element's text.content in the Notion API.
|
|
20
|
+
* @see https://developers.notion.com/reference/block
|
|
21
|
+
*/
|
|
22
|
+
export declare const NOTION_MAX_TEXT_LENGTH = 2000;
|
|
23
|
+
/**
|
|
24
|
+
* Maximum number of elements in a single rich_text array in the Notion API.
|
|
25
|
+
* When a block's rich_text array exceeds this limit, the block must be split
|
|
26
|
+
* into multiple blocks of the same type.
|
|
27
|
+
* @see https://developers.notion.com/reference/block
|
|
28
|
+
*/
|
|
29
|
+
export declare const NOTION_MAX_RICH_TEXT_ARRAY_LENGTH = 100;
|
|
30
|
+
/**
|
|
31
|
+
* Split a plain text string into multiple NotionRichText elements,
|
|
32
|
+
* each with text.content at most `maxLength` characters.
|
|
33
|
+
* Splits at word boundaries when possible.
|
|
34
|
+
*/
|
|
35
|
+
export declare function chunkRichText(text: string, maxLength?: number): NotionRichText[];
|
|
36
|
+
/**
|
|
37
|
+
* Convert markdown text to an array of Notion blocks.
|
|
38
|
+
*
|
|
39
|
+
* Parses markdown line-by-line, recognizing headings, lists, code blocks,
|
|
40
|
+
* blockquotes, checkboxes, and paragraphs. Rich text formatting (bold,
|
|
41
|
+
* italic, inline code, links) is preserved within each block.
|
|
42
|
+
*/
|
|
43
|
+
export declare function markdownToNotionBlocks(markdown: string): NotionBlock[];
|
|
44
|
+
/**
|
|
45
|
+
* Convert Notion blocks back to a markdown string.
|
|
46
|
+
*
|
|
47
|
+
* Handles all supported block types. Unsupported block types produce
|
|
48
|
+
* a fallback `[Unsupported: {type}]` text.
|
|
49
|
+
*/
|
|
50
|
+
export declare function notionBlocksToMarkdown(blocks: readonly NotionBlock[]): string;
|
|
51
|
+
/**
|
|
52
|
+
* Check whether a URL string is valid for use in a Notion link block.
|
|
53
|
+
* Notion only accepts absolute http: or https: URLs. Relative paths,
|
|
54
|
+
* fragment-only references (#section), workspace element IDs (el-xxxx),
|
|
55
|
+
* empty strings, and malformed URLs are all rejected.
|
|
56
|
+
*/
|
|
57
|
+
export declare function isValidNotionUrl(url: string): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Parse inline markdown formatting into an array of NotionRichText objects.
|
|
60
|
+
*
|
|
61
|
+
* Supports:
|
|
62
|
+
* - **bold** or __bold__
|
|
63
|
+
* - *italic* or _italic_
|
|
64
|
+
* - `inline code`
|
|
65
|
+
* - [link text](url)
|
|
66
|
+
* - Combinations thereof
|
|
67
|
+
*/
|
|
68
|
+
export declare function parseInlineMarkdown(text: string): NotionRichText[];
|
|
69
|
+
/**
|
|
70
|
+
* Convert an array of Notion rich text objects back to inline markdown.
|
|
71
|
+
*/
|
|
72
|
+
export declare function richTextToMarkdown(richTexts: readonly NotionRichText[]): string;
|
|
73
|
+
/**
|
|
74
|
+
* The complete set of language identifiers accepted by the Notion API
|
|
75
|
+
* for code blocks. Any language not in this set will be rejected.
|
|
76
|
+
* @see https://developers.notion.com/reference/block#code
|
|
77
|
+
*/
|
|
78
|
+
export declare const NOTION_LANGUAGES: Set<string>;
|
|
79
|
+
/**
|
|
80
|
+
* Map common code fence language aliases to their Notion-accepted equivalents.
|
|
81
|
+
* These cover short names, file extensions, and alternate spellings that
|
|
82
|
+
* markdown authors commonly use but Notion does not recognize.
|
|
83
|
+
*/
|
|
84
|
+
export declare const LANGUAGE_ALIASES: Record<string, string>;
|
|
85
|
+
/**
|
|
86
|
+
* Map a code fence language identifier to a Notion-accepted language value.
|
|
87
|
+
*
|
|
88
|
+
* 1. If the lowercased input is already in NOTION_LANGUAGES, return it.
|
|
89
|
+
* 2. If it matches a known alias, return the mapped value.
|
|
90
|
+
* 3. Otherwise, fall back to 'plain text' (always accepted by Notion).
|
|
91
|
+
*/
|
|
92
|
+
export declare function mapLanguageToNotion(lang: string): string;
|
|
93
|
+
//# sourceMappingURL=notion-blocks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notion-blocks.d.ts","sourceRoot":"","sources":["../../../../src/external-sync/providers/notion/notion-blocks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EACV,WAAW,EACX,cAAc,EAEf,MAAM,mBAAmB,CAAC;AAO3B;;;GAGG;AACH,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAE3C;;;;;GAKG;AACH,eAAO,MAAM,iCAAiC,MAAM,CAAC;AAiCrD;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,SAAS,SAAyB,GACjC,cAAc,EAAE,CAQlB;AA8GD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,EAAE,CAuHtE;AAMD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,CA2B7E;AAoPD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOrD;AAMD;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,EAAE,CA2DlE;AAMD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,CAM/E;AA4GD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,aAa3B,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CA6BnD,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKxD"}
|