@moxn/kb-migrate 0.3.0 → 0.4.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/client.d.ts +42 -0
- package/dist/client.js +115 -0
- package/dist/index.js +209 -6
- package/dist/sources/index.d.ts +1 -0
- package/dist/sources/index.js +1 -3
- package/dist/sources/notion-api.d.ts +240 -0
- package/dist/sources/notion-api.js +196 -0
- package/dist/sources/notion-blocks.d.ts +30 -0
- package/dist/sources/notion-blocks.js +505 -0
- package/dist/sources/notion-databases.d.ts +59 -0
- package/dist/sources/notion-databases.js +266 -0
- package/dist/sources/notion-media.d.ts +30 -0
- package/dist/sources/notion-media.js +133 -0
- package/dist/sources/notion.d.ts +66 -0
- package/dist/sources/notion.js +390 -0
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -37,5 +37,47 @@ export declare class MoxnClient {
|
|
|
37
37
|
private getUploadUrl;
|
|
38
38
|
private createDocument;
|
|
39
39
|
private updateDocument;
|
|
40
|
+
/**
|
|
41
|
+
* Create a KB database.
|
|
42
|
+
*/
|
|
43
|
+
createDatabase(input: {
|
|
44
|
+
name: string;
|
|
45
|
+
description?: string;
|
|
46
|
+
}): Promise<{
|
|
47
|
+
id: string;
|
|
48
|
+
name: string;
|
|
49
|
+
}>;
|
|
50
|
+
/**
|
|
51
|
+
* Add a column to a KB database.
|
|
52
|
+
*/
|
|
53
|
+
addDatabaseColumn(databaseId: string, input: {
|
|
54
|
+
name: string;
|
|
55
|
+
type: 'select' | 'multi_select';
|
|
56
|
+
optionTagIds?: string[];
|
|
57
|
+
newOptionParentPath?: string;
|
|
58
|
+
}): Promise<{
|
|
59
|
+
id: string;
|
|
60
|
+
name: string;
|
|
61
|
+
}>;
|
|
62
|
+
/**
|
|
63
|
+
* Add a document to a KB database.
|
|
64
|
+
*/
|
|
65
|
+
addDocumentToDatabase(databaseId: string, documentId: string): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Create a tag (with automatic ancestor creation).
|
|
68
|
+
*/
|
|
69
|
+
createTag(input: {
|
|
70
|
+
path: string;
|
|
71
|
+
description?: string;
|
|
72
|
+
color?: string;
|
|
73
|
+
}): Promise<{
|
|
74
|
+
id: string;
|
|
75
|
+
path: string;
|
|
76
|
+
name: string;
|
|
77
|
+
}>;
|
|
78
|
+
/**
|
|
79
|
+
* Assign a tag to a document.
|
|
80
|
+
*/
|
|
81
|
+
assignTag(documentId: string, tagId: string, branchId: string): Promise<void>;
|
|
40
82
|
private isConflictError;
|
|
41
83
|
}
|
package/dist/client.js
CHANGED
|
@@ -192,6 +192,19 @@ export class MoxnClient {
|
|
|
192
192
|
alt: block.alt,
|
|
193
193
|
};
|
|
194
194
|
}
|
|
195
|
+
// Handle PDF/document files - upload to storage
|
|
196
|
+
if (block.blockType === 'document' && block.type === 'file' && block.path) {
|
|
197
|
+
const data = await fs.readFile(block.path);
|
|
198
|
+
const filename = block.filename || block.path.split('/').pop();
|
|
199
|
+
const { key } = await this.uploadFile(data, block.mediaType || 'application/pdf', filename);
|
|
200
|
+
return {
|
|
201
|
+
blockType: block.blockType,
|
|
202
|
+
type: 'storage',
|
|
203
|
+
key,
|
|
204
|
+
mediaType: block.mediaType || 'application/pdf',
|
|
205
|
+
filename: block.filename,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
195
208
|
// Handle CSV files - upload to storage
|
|
196
209
|
if (block.blockType === 'csv' && block.type === 'file' && block.path) {
|
|
197
210
|
const data = await fs.readFile(block.path);
|
|
@@ -278,6 +291,108 @@ export class MoxnClient {
|
|
|
278
291
|
}
|
|
279
292
|
return response.json();
|
|
280
293
|
}
|
|
294
|
+
// ──────────────────────────────────────────────
|
|
295
|
+
// Database & tag methods (for Notion import)
|
|
296
|
+
// ──────────────────────────────────────────────
|
|
297
|
+
/**
|
|
298
|
+
* Create a KB database.
|
|
299
|
+
*/
|
|
300
|
+
async createDatabase(input) {
|
|
301
|
+
const response = await fetch(`${this.apiUrl}/api/v1/kb/databases`, {
|
|
302
|
+
method: 'POST',
|
|
303
|
+
headers: {
|
|
304
|
+
'Content-Type': 'application/json',
|
|
305
|
+
'x-api-key': this.apiKey,
|
|
306
|
+
},
|
|
307
|
+
body: JSON.stringify(input),
|
|
308
|
+
});
|
|
309
|
+
if (!response.ok) {
|
|
310
|
+
const body = await response.json().catch(() => ({}));
|
|
311
|
+
throw new Error(body.error || `Failed to create database: ${response.status}`);
|
|
312
|
+
}
|
|
313
|
+
return response.json();
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Add a column to a KB database.
|
|
317
|
+
*/
|
|
318
|
+
async addDatabaseColumn(databaseId, input) {
|
|
319
|
+
const response = await fetch(`${this.apiUrl}/api/v1/kb/databases/${databaseId}/columns`, {
|
|
320
|
+
method: 'POST',
|
|
321
|
+
headers: {
|
|
322
|
+
'Content-Type': 'application/json',
|
|
323
|
+
'x-api-key': this.apiKey,
|
|
324
|
+
},
|
|
325
|
+
body: JSON.stringify(input),
|
|
326
|
+
});
|
|
327
|
+
if (!response.ok) {
|
|
328
|
+
const body = await response.json().catch(() => ({}));
|
|
329
|
+
throw new Error(body.error || `Failed to add column: ${response.status}`);
|
|
330
|
+
}
|
|
331
|
+
return response.json();
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Add a document to a KB database.
|
|
335
|
+
*/
|
|
336
|
+
async addDocumentToDatabase(databaseId, documentId) {
|
|
337
|
+
const response = await fetch(`${this.apiUrl}/api/v1/kb/databases/${databaseId}/documents`, {
|
|
338
|
+
method: 'POST',
|
|
339
|
+
headers: {
|
|
340
|
+
'Content-Type': 'application/json',
|
|
341
|
+
'x-api-key': this.apiKey,
|
|
342
|
+
},
|
|
343
|
+
body: JSON.stringify({ documentId }),
|
|
344
|
+
});
|
|
345
|
+
if (!response.ok) {
|
|
346
|
+
const body = await response.json().catch(() => ({}));
|
|
347
|
+
// Ignore 409 — document already in database
|
|
348
|
+
if (response.status === 409)
|
|
349
|
+
return;
|
|
350
|
+
throw new Error(body.error || `Failed to add document to database: ${response.status}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Create a tag (with automatic ancestor creation).
|
|
355
|
+
*/
|
|
356
|
+
async createTag(input) {
|
|
357
|
+
const response = await fetch(`${this.apiUrl}/api/v1/kb/tags`, {
|
|
358
|
+
method: 'POST',
|
|
359
|
+
headers: {
|
|
360
|
+
'Content-Type': 'application/json',
|
|
361
|
+
'x-api-key': this.apiKey,
|
|
362
|
+
},
|
|
363
|
+
body: JSON.stringify(input),
|
|
364
|
+
});
|
|
365
|
+
if (!response.ok) {
|
|
366
|
+
const body = await response.json().catch(() => ({}));
|
|
367
|
+
// If tag already exists (409), try to find it
|
|
368
|
+
if (response.status === 409 && body.tagId) {
|
|
369
|
+
return {
|
|
370
|
+
id: body.tagId,
|
|
371
|
+
path: input.path,
|
|
372
|
+
name: input.path.split('/').pop() || '',
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
throw new Error(body.error || `Failed to create tag: ${response.status}`);
|
|
376
|
+
}
|
|
377
|
+
return response.json();
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Assign a tag to a document.
|
|
381
|
+
*/
|
|
382
|
+
async assignTag(documentId, tagId, branchId) {
|
|
383
|
+
const response = await fetch(`${this.apiUrl}/api/v1/kb/documents/${documentId}/tags`, {
|
|
384
|
+
method: 'POST',
|
|
385
|
+
headers: {
|
|
386
|
+
'Content-Type': 'application/json',
|
|
387
|
+
'x-api-key': this.apiKey,
|
|
388
|
+
},
|
|
389
|
+
body: JSON.stringify({ tagId, branchId }),
|
|
390
|
+
});
|
|
391
|
+
if (!response.ok) {
|
|
392
|
+
const body = await response.json().catch(() => ({}));
|
|
393
|
+
throw new Error(body.error || `Failed to assign tag: ${response.status}`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
281
396
|
isConflictError(error) {
|
|
282
397
|
return (error instanceof Error &&
|
|
283
398
|
'documentId' in error &&
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { Command } from 'commander';
|
|
16
16
|
import { LocalSource } from './sources/local.js';
|
|
17
|
+
import { NotionSource } from './sources/notion.js';
|
|
18
|
+
import { notionColorToHex } from './sources/notion-api.js';
|
|
17
19
|
import { MoxnClient } from './client.js';
|
|
18
20
|
import { runExport } from './export.js';
|
|
19
21
|
const DEFAULT_API_URL = 'https://moxn.dev';
|
|
@@ -216,10 +218,211 @@ program
|
|
|
216
218
|
process.exit(1);
|
|
217
219
|
}
|
|
218
220
|
});
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
221
|
+
program
|
|
222
|
+
.command('notion')
|
|
223
|
+
.description('Migrate documents from a Notion workspace')
|
|
224
|
+
.option('--token <token>', 'Notion integration token (or set NOTION_TOKEN env var)')
|
|
225
|
+
.option('--api-key <key>', 'Moxn API key (or set MOXN_API_KEY env var)')
|
|
226
|
+
.option('--api-url <url>', 'Moxn API base URL', DEFAULT_API_URL)
|
|
227
|
+
.option('--base-path <path>', 'Base path for imported documents', '/imported-from-notion')
|
|
228
|
+
.option('--root-page-id <id>', 'Import subtree starting from this Notion page ID')
|
|
229
|
+
.option('--max-depth <n>', 'Maximum nesting depth')
|
|
230
|
+
.option('--on-conflict <action>', 'Action on conflict: skip or update', 'skip')
|
|
231
|
+
.option('--default-permission <perm>', 'Default permission: edit, read, or none')
|
|
232
|
+
.option('--ai-access <perm>', 'AI access permission: edit, read, or none')
|
|
233
|
+
.option('--visibility <vis>', 'Convenience flag: team (read) or private (none)')
|
|
234
|
+
.option('--dry-run', 'Preview without making changes', false)
|
|
235
|
+
.option('--json', 'Output results as JSON', false)
|
|
236
|
+
.action(async (opts) => {
|
|
237
|
+
const token = opts.token || process.env.NOTION_TOKEN;
|
|
238
|
+
if (!token) {
|
|
239
|
+
console.error('Error: Notion token required. Use --token or set NOTION_TOKEN env var.');
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
const apiKey = opts.apiKey || process.env.MOXN_API_KEY;
|
|
243
|
+
if (!apiKey) {
|
|
244
|
+
console.error('Error: Moxn API key required. Use --api-key or set MOXN_API_KEY env var.');
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
const onConflict = opts.onConflict;
|
|
248
|
+
if (!['skip', 'update'].includes(onConflict)) {
|
|
249
|
+
console.error('Error: --on-conflict must be "skip" or "update"');
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
// Resolve visibility → defaultPermission (visibility is syntactic sugar)
|
|
253
|
+
let defaultPermission = opts.defaultPermission;
|
|
254
|
+
if (!defaultPermission && opts.visibility) {
|
|
255
|
+
defaultPermission = opts.visibility === 'private' ? 'none' : 'read';
|
|
256
|
+
}
|
|
257
|
+
const source = new NotionSource({
|
|
258
|
+
token,
|
|
259
|
+
rootPageId: opts.rootPageId,
|
|
260
|
+
maxDepth: opts.maxDepth ? parseInt(opts.maxDepth, 10) : undefined,
|
|
261
|
+
});
|
|
262
|
+
const migrationOptions = {
|
|
263
|
+
apiUrl: opts.apiUrl,
|
|
264
|
+
apiKey,
|
|
265
|
+
basePath: opts.basePath,
|
|
266
|
+
onConflict,
|
|
267
|
+
dryRun: opts.dryRun,
|
|
268
|
+
defaultPermission,
|
|
269
|
+
aiAccess: opts.aiAccess,
|
|
270
|
+
visibility: opts.visibility,
|
|
271
|
+
};
|
|
272
|
+
try {
|
|
273
|
+
// Run page migration
|
|
274
|
+
const log = await runMigration(source, migrationOptions);
|
|
275
|
+
// After page migration, import databases
|
|
276
|
+
if (!opts.dryRun) {
|
|
277
|
+
const dbImports = source.getDatabaseImports();
|
|
278
|
+
if (dbImports.length > 0) {
|
|
279
|
+
console.log(`\nImporting ${dbImports.length} database(s)...`);
|
|
280
|
+
const client = new MoxnClient(migrationOptions);
|
|
281
|
+
for (const dbImport of dbImports) {
|
|
282
|
+
try {
|
|
283
|
+
await importNotionDatabase(client, dbImport, log, migrationOptions);
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
console.error(` Error importing database "${dbImport.schema.name}": ${error instanceof Error ? error.message : error}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// Cleanup temp files
|
|
292
|
+
await source.cleanup();
|
|
293
|
+
if (opts.json) {
|
|
294
|
+
console.log(JSON.stringify(log, null, 2));
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
printSummary(log);
|
|
298
|
+
}
|
|
299
|
+
if (log.summary.failed > 0) {
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
await source.cleanup().catch(() => { });
|
|
305
|
+
console.error('Migration failed:', error instanceof Error ? error.message : error);
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
/**
|
|
310
|
+
* Import a Notion database into Moxn KB.
|
|
311
|
+
*
|
|
312
|
+
* Creates the database, columns with tags, links entries, and assigns tag values.
|
|
313
|
+
*/
|
|
314
|
+
async function importNotionDatabase(client, dbImport, log, options) {
|
|
315
|
+
const { schema, entries } = dbImport;
|
|
316
|
+
console.log(` Creating database: ${schema.name}`);
|
|
317
|
+
// 1. Create the database
|
|
318
|
+
const db = await client.createDatabase({
|
|
319
|
+
name: schema.name,
|
|
320
|
+
description: schema.description || undefined,
|
|
321
|
+
});
|
|
322
|
+
console.log(` Database created: ${db.id}`);
|
|
323
|
+
// 2. Create columns with tags
|
|
324
|
+
// Map: column name → { columnId, optionTagMap: option name → tagId }
|
|
325
|
+
const columnMap = new Map();
|
|
326
|
+
for (const col of schema.mappedColumns) {
|
|
327
|
+
const tagBasePath = `/imported/${slugifyTagPath(schema.name)}/${slugifyTagPath(col.notionPropertyName)}`;
|
|
328
|
+
// Create tags for each option
|
|
329
|
+
const optionTagMap = new Map();
|
|
330
|
+
const tagIds = [];
|
|
331
|
+
for (const option of col.options) {
|
|
332
|
+
const tagPath = `${tagBasePath}/${slugifyTagPath(option.name)}`;
|
|
333
|
+
try {
|
|
334
|
+
const tag = await client.createTag({
|
|
335
|
+
path: tagPath,
|
|
336
|
+
color: notionColorToHex(option.color),
|
|
337
|
+
});
|
|
338
|
+
optionTagMap.set(option.name, tag.id);
|
|
339
|
+
tagIds.push(tag.id);
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
console.warn(` Warning: Failed to create tag "${tagPath}": ${error instanceof Error ? error.message : error}`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
// Create the column
|
|
346
|
+
try {
|
|
347
|
+
const column = await client.addDatabaseColumn(db.id, {
|
|
348
|
+
name: col.notionPropertyName,
|
|
349
|
+
type: col.moxnType,
|
|
350
|
+
optionTagIds: tagIds,
|
|
351
|
+
newOptionParentPath: tagBasePath,
|
|
352
|
+
});
|
|
353
|
+
columnMap.set(col.notionPropertyName, {
|
|
354
|
+
columnId: column.id,
|
|
355
|
+
optionTagMap,
|
|
356
|
+
});
|
|
357
|
+
console.log(` Column "${col.notionPropertyName}" (${col.moxnType}) with ${tagIds.length} options`);
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
console.warn(` Warning: Failed to create column "${col.notionPropertyName}": ${error instanceof Error ? error.message : error}`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// 3. Link entries and assign tags
|
|
364
|
+
// Build a map of KB path → { documentId, branchId } from migration results
|
|
365
|
+
const docByPath = new Map();
|
|
366
|
+
for (const result of log.results) {
|
|
367
|
+
if (result.documentId && result.branchId) {
|
|
368
|
+
docByPath.set(result.documentPath, {
|
|
369
|
+
documentId: result.documentId,
|
|
370
|
+
branchId: result.branchId,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
let linkedCount = 0;
|
|
375
|
+
for (const entry of entries) {
|
|
376
|
+
const kbPath = '/' +
|
|
377
|
+
options.basePath.replace(/^\/+|\/+$/g, '') +
|
|
378
|
+
'/' +
|
|
379
|
+
entry.kbPath.replace(/^\/+/, '');
|
|
380
|
+
const docInfo = docByPath.get(kbPath);
|
|
381
|
+
if (!docInfo) {
|
|
382
|
+
// Document wasn't created (maybe skipped or failed)
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
// Add document to database
|
|
386
|
+
try {
|
|
387
|
+
await client.addDocumentToDatabase(db.id, docInfo.documentId);
|
|
388
|
+
linkedCount++;
|
|
389
|
+
// Parse and assign tag values
|
|
390
|
+
const { parseEntryValues } = await import('./sources/notion-databases.js');
|
|
391
|
+
const values = parseEntryValues(entry.page, schema);
|
|
392
|
+
for (const [colName, selectedOptions] of values.tagValues) {
|
|
393
|
+
const colInfo = columnMap.get(colName);
|
|
394
|
+
if (!colInfo)
|
|
395
|
+
continue;
|
|
396
|
+
for (const optionName of selectedOptions) {
|
|
397
|
+
const tagId = colInfo.optionTagMap.get(optionName);
|
|
398
|
+
if (tagId) {
|
|
399
|
+
try {
|
|
400
|
+
await client.assignTag(docInfo.documentId, tagId, docInfo.branchId);
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
console.warn(` Warning: Failed to assign tag "${optionName}" to ${docInfo.documentId}: ${error instanceof Error ? error.message : error}`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
console.warn(` Warning: Failed to link entry to database: ${error instanceof Error ? error.message : error}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
console.log(` Linked ${linkedCount}/${entries.length} entries`);
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Slugify a string for use as a tag path segment.
|
|
417
|
+
* Similar to page slug but for the tag hierarchy.
|
|
418
|
+
*/
|
|
419
|
+
function slugifyTagPath(s) {
|
|
420
|
+
return (s
|
|
421
|
+
.toLowerCase()
|
|
422
|
+
.trim()
|
|
423
|
+
.replace(/[^\w\s-]/g, '')
|
|
424
|
+
.replace(/[\s_]+/g, '-')
|
|
425
|
+
.replace(/-+/g, '-')
|
|
426
|
+
.replace(/^-|-$/g, '') || 'untitled');
|
|
427
|
+
}
|
|
225
428
|
program.parse();
|
package/dist/sources/index.d.ts
CHANGED
package/dist/sources/index.js
CHANGED
|
@@ -3,6 +3,4 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export { MigrationSource } from './base.js';
|
|
5
5
|
export { LocalSource } from './local.js';
|
|
6
|
-
|
|
7
|
-
// export { NotionSource, type NotionSourceConfig } from './notion.js';
|
|
8
|
-
// export { GoogleDocsSource, type GoogleDocsSourceConfig } from './google-docs.js';
|
|
6
|
+
export { NotionSource } from './notion.js';
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin HTTP client for Notion API with rate limiting.
|
|
3
|
+
*
|
|
4
|
+
* No @notionhq/client dependency — raw REST calls with retry on 429.
|
|
5
|
+
* Designed for the migration use case: read-only traversal of workspaces.
|
|
6
|
+
*/
|
|
7
|
+
export interface NotionUser {
|
|
8
|
+
id: string;
|
|
9
|
+
type: 'person' | 'bot';
|
|
10
|
+
name: string | null;
|
|
11
|
+
avatar_url: string | null;
|
|
12
|
+
person?: {
|
|
13
|
+
email?: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface NotionRichText {
|
|
17
|
+
type: 'text' | 'mention' | 'equation';
|
|
18
|
+
plain_text: string;
|
|
19
|
+
href: string | null;
|
|
20
|
+
annotations: {
|
|
21
|
+
bold: boolean;
|
|
22
|
+
italic: boolean;
|
|
23
|
+
strikethrough: boolean;
|
|
24
|
+
underline: boolean;
|
|
25
|
+
code: boolean;
|
|
26
|
+
color: string;
|
|
27
|
+
};
|
|
28
|
+
text?: {
|
|
29
|
+
content: string;
|
|
30
|
+
link: {
|
|
31
|
+
url: string;
|
|
32
|
+
} | null;
|
|
33
|
+
};
|
|
34
|
+
mention?: {
|
|
35
|
+
type: string;
|
|
36
|
+
user?: {
|
|
37
|
+
id: string;
|
|
38
|
+
name?: string;
|
|
39
|
+
};
|
|
40
|
+
page?: {
|
|
41
|
+
id: string;
|
|
42
|
+
};
|
|
43
|
+
database?: {
|
|
44
|
+
id: string;
|
|
45
|
+
};
|
|
46
|
+
date?: {
|
|
47
|
+
start: string;
|
|
48
|
+
end: string | null;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
equation?: {
|
|
52
|
+
expression: string;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export interface NotionBlock {
|
|
56
|
+
id: string;
|
|
57
|
+
type: string;
|
|
58
|
+
has_children: boolean;
|
|
59
|
+
created_by?: {
|
|
60
|
+
id: string;
|
|
61
|
+
};
|
|
62
|
+
last_edited_by?: {
|
|
63
|
+
id: string;
|
|
64
|
+
};
|
|
65
|
+
[key: string]: unknown;
|
|
66
|
+
}
|
|
67
|
+
export interface NotionPage {
|
|
68
|
+
id: string;
|
|
69
|
+
object: 'page';
|
|
70
|
+
parent: {
|
|
71
|
+
type: 'page_id' | 'database_id' | 'workspace' | 'block_id';
|
|
72
|
+
page_id?: string;
|
|
73
|
+
database_id?: string;
|
|
74
|
+
workspace?: boolean;
|
|
75
|
+
block_id?: string;
|
|
76
|
+
};
|
|
77
|
+
created_time: string;
|
|
78
|
+
last_edited_time: string;
|
|
79
|
+
created_by: {
|
|
80
|
+
id: string;
|
|
81
|
+
object: string;
|
|
82
|
+
};
|
|
83
|
+
last_edited_by: {
|
|
84
|
+
id: string;
|
|
85
|
+
object: string;
|
|
86
|
+
};
|
|
87
|
+
url: string;
|
|
88
|
+
properties: Record<string, NotionPropertyValue>;
|
|
89
|
+
}
|
|
90
|
+
export interface NotionDatabase {
|
|
91
|
+
id: string;
|
|
92
|
+
object: 'database';
|
|
93
|
+
title: NotionRichText[];
|
|
94
|
+
description: NotionRichText[];
|
|
95
|
+
parent: {
|
|
96
|
+
type: 'page_id' | 'workspace' | 'block_id';
|
|
97
|
+
page_id?: string;
|
|
98
|
+
workspace?: boolean;
|
|
99
|
+
block_id?: string;
|
|
100
|
+
};
|
|
101
|
+
created_time: string;
|
|
102
|
+
last_edited_time: string;
|
|
103
|
+
properties: Record<string, NotionPropertySchema>;
|
|
104
|
+
}
|
|
105
|
+
export interface NotionPropertySchema {
|
|
106
|
+
id: string;
|
|
107
|
+
name: string;
|
|
108
|
+
type: string;
|
|
109
|
+
select?: {
|
|
110
|
+
options: Array<{
|
|
111
|
+
id: string;
|
|
112
|
+
name: string;
|
|
113
|
+
color: string;
|
|
114
|
+
}>;
|
|
115
|
+
};
|
|
116
|
+
multi_select?: {
|
|
117
|
+
options: Array<{
|
|
118
|
+
id: string;
|
|
119
|
+
name: string;
|
|
120
|
+
color: string;
|
|
121
|
+
}>;
|
|
122
|
+
};
|
|
123
|
+
status?: {
|
|
124
|
+
options: Array<{
|
|
125
|
+
id: string;
|
|
126
|
+
name: string;
|
|
127
|
+
color: string;
|
|
128
|
+
}>;
|
|
129
|
+
groups: Array<{
|
|
130
|
+
id: string;
|
|
131
|
+
name: string;
|
|
132
|
+
option_ids: string[];
|
|
133
|
+
}>;
|
|
134
|
+
};
|
|
135
|
+
[key: string]: unknown;
|
|
136
|
+
}
|
|
137
|
+
export interface NotionPropertyValue {
|
|
138
|
+
id: string;
|
|
139
|
+
type: string;
|
|
140
|
+
title?: NotionRichText[];
|
|
141
|
+
rich_text?: NotionRichText[];
|
|
142
|
+
number?: number | null;
|
|
143
|
+
select?: {
|
|
144
|
+
id: string;
|
|
145
|
+
name: string;
|
|
146
|
+
color: string;
|
|
147
|
+
} | null;
|
|
148
|
+
multi_select?: Array<{
|
|
149
|
+
id: string;
|
|
150
|
+
name: string;
|
|
151
|
+
color: string;
|
|
152
|
+
}>;
|
|
153
|
+
status?: {
|
|
154
|
+
id: string;
|
|
155
|
+
name: string;
|
|
156
|
+
color: string;
|
|
157
|
+
} | null;
|
|
158
|
+
date?: {
|
|
159
|
+
start: string;
|
|
160
|
+
end: string | null;
|
|
161
|
+
time_zone: string | null;
|
|
162
|
+
} | null;
|
|
163
|
+
checkbox?: boolean;
|
|
164
|
+
url?: string | null;
|
|
165
|
+
email?: string | null;
|
|
166
|
+
phone_number?: string | null;
|
|
167
|
+
people?: Array<{
|
|
168
|
+
id: string;
|
|
169
|
+
name?: string;
|
|
170
|
+
}>;
|
|
171
|
+
files?: Array<{
|
|
172
|
+
name: string;
|
|
173
|
+
type: 'file' | 'external';
|
|
174
|
+
file?: {
|
|
175
|
+
url: string;
|
|
176
|
+
expiry_time: string;
|
|
177
|
+
};
|
|
178
|
+
external?: {
|
|
179
|
+
url: string;
|
|
180
|
+
};
|
|
181
|
+
}>;
|
|
182
|
+
relation?: Array<{
|
|
183
|
+
id: string;
|
|
184
|
+
}>;
|
|
185
|
+
formula?: {
|
|
186
|
+
type: string;
|
|
187
|
+
string?: string;
|
|
188
|
+
number?: number;
|
|
189
|
+
boolean?: boolean;
|
|
190
|
+
date?: unknown;
|
|
191
|
+
};
|
|
192
|
+
rollup?: {
|
|
193
|
+
type: string;
|
|
194
|
+
number?: number;
|
|
195
|
+
date?: unknown;
|
|
196
|
+
array?: unknown[];
|
|
197
|
+
};
|
|
198
|
+
[key: string]: unknown;
|
|
199
|
+
}
|
|
200
|
+
export interface PaginatedResponse<T> {
|
|
201
|
+
results: T[];
|
|
202
|
+
has_more: boolean;
|
|
203
|
+
next_cursor: string | null;
|
|
204
|
+
}
|
|
205
|
+
export declare function notionColorToHex(color: string): string;
|
|
206
|
+
export declare class NotionApiClient {
|
|
207
|
+
private token;
|
|
208
|
+
private lastRequestTime;
|
|
209
|
+
constructor(token: string);
|
|
210
|
+
/** Test that the token is valid. Throws if not. */
|
|
211
|
+
validateToken(): Promise<void>;
|
|
212
|
+
/** List all workspace users. */
|
|
213
|
+
listUsers(): Promise<NotionUser[]>;
|
|
214
|
+
/**
|
|
215
|
+
* Search for all pages in the workspace.
|
|
216
|
+
* Sorted by last_edited_time descending for incremental sync support.
|
|
217
|
+
*/
|
|
218
|
+
searchPages(options?: {
|
|
219
|
+
sortByLastEdited?: boolean;
|
|
220
|
+
}): Promise<NotionPage[]>;
|
|
221
|
+
/**
|
|
222
|
+
* Search for all databases in the workspace.
|
|
223
|
+
*/
|
|
224
|
+
searchDatabases(): Promise<NotionDatabase[]>;
|
|
225
|
+
/** Get a database schema by ID. */
|
|
226
|
+
getDatabase(databaseId: string): Promise<NotionDatabase>;
|
|
227
|
+
/**
|
|
228
|
+
* Query all entries (pages) in a database.
|
|
229
|
+
*/
|
|
230
|
+
queryDatabase(databaseId: string): Promise<NotionPage[]>;
|
|
231
|
+
/**
|
|
232
|
+
* Get all child blocks of a block (page or another block).
|
|
233
|
+
* Automatically paginates through all results.
|
|
234
|
+
*/
|
|
235
|
+
getBlockChildren(blockId: string): Promise<NotionBlock[]>;
|
|
236
|
+
/** Get a single page by ID. */
|
|
237
|
+
getPage(pageId: string): Promise<NotionPage>;
|
|
238
|
+
private request;
|
|
239
|
+
private rateLimit;
|
|
240
|
+
}
|