@moxn/kb-migrate 0.4.26 → 0.4.28

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/index.js CHANGED
@@ -487,6 +487,13 @@ program
487
487
  console.error(` Error pre-creating database "${dbImport.schema.name}": ${error instanceof Error ? error.message : error}`);
488
488
  }
489
489
  }
490
+ // Also add any other existing mappings (e.g. linked DB → source mappings
491
+ // from prior imports) so they're available during extraction
492
+ for (const [notionId, kbId] of existingMappings) {
493
+ if (!databaseIdMap.has(notionId)) {
494
+ databaseIdMap.set(notionId, kbId);
495
+ }
496
+ }
490
497
  // Make the map available during extraction
491
498
  source.setDatabaseIdMap(databaseIdMap);
492
499
  console.log(` ${databaseIdMap.size} database(s) ready for embed resolution.\n`);
@@ -509,6 +516,7 @@ program
509
516
  // Non-fatal — will create new databases
510
517
  }
511
518
  let inlineCreated = 0;
519
+ let linkedResolved = 0;
512
520
  for (const [notionDbId, title] of unresolvedDbs) {
513
521
  // Skip if already mapped (could happen if previously imported)
514
522
  if (databaseIdMap.has(notionDbId) || currentMappings.has(notionDbId)) {
@@ -552,12 +560,44 @@ program
552
560
  console.log(` Created inline database: ${schema.name || title} (${kbDb.id}) — ${entries.length} entries`);
553
561
  }
554
562
  catch (error) {
555
- console.warn(` Warning: Failed to create inline database "${title}" (${notionDbId}): ${error instanceof Error ? error.message : error}`);
563
+ // Check if this is a linked database — resolve to its source
564
+ const errMsg = error instanceof Error ? error.message : String(error);
565
+ if (errMsg.includes('linked database')) {
566
+ const sourceDbId = await notionApi.resolveLinkedDatabaseSource(notionDbId);
567
+ if (sourceDbId) {
568
+ // Look up the source database in our mappings
569
+ const kbDbId = databaseIdMap.get(sourceDbId) || currentMappings.get(sourceDbId);
570
+ if (kbDbId) {
571
+ databaseIdMap.set(notionDbId, kbDbId);
572
+ // Store mapping so future imports resolve during pre-create
573
+ try {
574
+ await inlineClient.createNotionDatabaseMapping({
575
+ kbDatabaseId: kbDbId,
576
+ notionDatabaseId: notionDbId,
577
+ notionDatabaseTitle: title,
578
+ });
579
+ }
580
+ catch {
581
+ // Non-fatal
582
+ }
583
+ linkedResolved++;
584
+ console.log(` Resolved linked database "${title}" (${notionDbId}) → source ${sourceDbId} → KB ${kbDbId}`);
585
+ continue;
586
+ }
587
+ }
588
+ console.warn(` Warning: Linked database "${title}" (${notionDbId}) — could not resolve source`);
589
+ }
590
+ else {
591
+ console.warn(` Warning: Failed to create inline database "${title}" (${notionDbId}): ${errMsg}`);
592
+ }
556
593
  }
557
594
  }
558
595
  if (inlineCreated > 0) {
559
596
  console.log(` Created ${inlineCreated} inline database(s). Re-run with --on-conflict update to resolve embeds.`);
560
597
  }
598
+ if (linkedResolved > 0) {
599
+ console.log(` Resolved ${linkedResolved} linked database(s) to existing source databases.`);
600
+ }
561
601
  }
562
602
  }
563
603
  // Step 4: Resolve cross-references (between page migration and database finalization)
@@ -225,6 +225,12 @@ export declare class NotionApiClient {
225
225
  searchDatabases(): Promise<NotionDatabase[]>;
226
226
  /** Get a database schema by ID. */
227
227
  getDatabase(databaseId: string): Promise<NotionDatabase>;
228
+ /**
229
+ * For a linked database, resolve the source database ID by querying
230
+ * one entry and reading its parent.database_id.
231
+ * Returns null if the database is empty or not queryable.
232
+ */
233
+ resolveLinkedDatabaseSource(linkedDbId: string): Promise<string | null>;
228
234
  /**
229
235
  * Query all entries (pages) in a database.
230
236
  */
@@ -101,6 +101,26 @@ export class NotionApiClient {
101
101
  async getDatabase(databaseId) {
102
102
  return this.request('GET', `/databases/${databaseId}`);
103
103
  }
104
+ /**
105
+ * For a linked database, resolve the source database ID by querying
106
+ * one entry and reading its parent.database_id.
107
+ * Returns null if the database is empty or not queryable.
108
+ */
109
+ async resolveLinkedDatabaseSource(linkedDbId) {
110
+ try {
111
+ const response = await this.request('POST', `/databases/${linkedDbId}/query`, { page_size: 1 });
112
+ if (response.results.length > 0) {
113
+ const entry = response.results[0];
114
+ if (entry.parent?.type === 'database_id' && entry.parent.database_id) {
115
+ return entry.parent.database_id.replace(/-/g, '');
116
+ }
117
+ }
118
+ return null;
119
+ }
120
+ catch {
121
+ return null;
122
+ }
123
+ }
104
124
  /**
105
125
  * Query all entries (pages) in a database.
106
126
  */
@@ -112,7 +112,7 @@ async function convertBlock(block, client, pagePathMap, visitedSyncedBlocks, dat
112
112
  // Just note it in the content.
113
113
  break;
114
114
  case 'child_database':
115
- results.push(...convertChildDatabase(block, databaseIdMap, unresolvedChildDatabases));
115
+ results.push(...(await convertChildDatabase(block, client, databaseIdMap, unresolvedChildDatabases)));
116
116
  break;
117
117
  case 'synced_block':
118
118
  results.push(...(await convertSyncedBlock(block, client, pagePathMap, visitedSyncedBlocks, databaseIdMap, unsupportedBlocks, unresolvedChildDatabases)));
@@ -278,7 +278,7 @@ async function convertToggle(block, client, pagePathMap, visitedSyncedBlocks, da
278
278
  }
279
279
  return results;
280
280
  }
281
- function convertChildDatabase(block, databaseIdMap, unresolvedChildDatabases) {
281
+ async function convertChildDatabase(block, client, databaseIdMap, unresolvedChildDatabases) {
282
282
  const cd = block;
283
283
  const title = cd.child_database?.title || 'Untitled Database';
284
284
  const notionDbId = normalizeId(block.id);
@@ -287,6 +287,19 @@ function convertChildDatabase(block, databaseIdMap, unresolvedChildDatabases) {
287
287
  if (kbDatabaseId) {
288
288
  return [{ blockType: 'database_embed', databaseId: kbDatabaseId }];
289
289
  }
290
+ // Try to resolve as a linked database: query one entry to find the source DB
291
+ if (databaseIdMap) {
292
+ const sourceDbId = await client.resolveLinkedDatabaseSource(notionDbId);
293
+ if (sourceDbId) {
294
+ const sourceKbDbId = databaseIdMap.get(sourceDbId);
295
+ if (sourceKbDbId) {
296
+ // Cache for future blocks in this extraction
297
+ databaseIdMap.set(notionDbId, sourceKbDbId);
298
+ console.log(` Resolved linked database "${title}" → source KB ${sourceKbDbId}`);
299
+ return [{ blockType: 'database_embed', databaseId: sourceKbDbId }];
300
+ }
301
+ }
302
+ }
290
303
  // No mapping available — collect for later creation and emit text placeholder
291
304
  if (unresolvedChildDatabases) {
292
305
  unresolvedChildDatabases.set(notionDbId, title);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moxn/kb-migrate",
3
- "version": "0.4.26",
3
+ "version": "0.4.28",
4
4
  "description": "Migration tool for importing documents into Moxn Knowledge Base from local files, Notion, Google Docs, and more",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",