@moxn/kb-migrate 0.4.20 → 0.4.21

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 CHANGED
@@ -72,6 +72,7 @@ export declare class MoxnClient {
72
72
  path: string;
73
73
  description?: string;
74
74
  color?: string;
75
+ displayName?: string;
75
76
  }): Promise<{
76
77
  id: string;
77
78
  path: string;
@@ -81,6 +82,20 @@ export declare class MoxnClient {
81
82
  * Assign a tag to a document.
82
83
  */
83
84
  assignTag(documentId: string, tagId: string, branchId: string): Promise<void>;
85
+ /**
86
+ * Batch-create references from a document's sections to other documents.
87
+ * Returns { created, skipped, errors }.
88
+ */
89
+ createReferences(documentId: string, references: Array<{
90
+ sourceSectionId: string;
91
+ branchId: string;
92
+ targetDocumentId: string;
93
+ displayTitle?: string | null;
94
+ }>): Promise<{
95
+ created: number;
96
+ skipped: number;
97
+ errors: number;
98
+ }>;
84
99
  /**
85
100
  * List all KB databases for the tenant.
86
101
  */
package/dist/client.js CHANGED
@@ -51,6 +51,9 @@ export class MoxnClient {
51
51
  documentId: createResult.id,
52
52
  branchId: createResult.branchId,
53
53
  sectionsCount: createResult.sections.length,
54
+ sectionIds: createResult.sections.map((s) => s.id),
55
+ references: doc.references,
56
+ sourcePageId: doc.metadata?.notionPageId,
54
57
  duration: Date.now() - startTime,
55
58
  };
56
59
  }
@@ -82,6 +85,9 @@ export class MoxnClient {
82
85
  documentId: updateResult.id,
83
86
  branchId: updateResult.branchId,
84
87
  sectionsCount: updateResult.sections.length,
88
+ sectionIds: updateResult.sections.map((s) => s.id),
89
+ references: doc.references,
90
+ sourcePageId: doc.metadata?.notionPageId,
85
91
  duration: Date.now() - startTime,
86
92
  };
87
93
  }
@@ -414,6 +420,25 @@ export class MoxnClient {
414
420
  throw new Error(body.error || `Failed to assign tag: ${response.status}`);
415
421
  }
416
422
  }
423
+ /**
424
+ * Batch-create references from a document's sections to other documents.
425
+ * Returns { created, skipped, errors }.
426
+ */
427
+ async createReferences(documentId, references) {
428
+ const response = await fetch(`${this.apiUrl}/api/v1/kb/documents/${documentId}/references`, {
429
+ method: 'POST',
430
+ headers: {
431
+ 'Content-Type': 'application/json',
432
+ 'x-api-key': this.apiKey,
433
+ },
434
+ body: JSON.stringify({ references }),
435
+ });
436
+ if (!response.ok) {
437
+ const body = await response.json().catch(() => ({}));
438
+ throw new Error(body.error || `Failed to create references: ${response.status}`);
439
+ }
440
+ return response.json();
441
+ }
417
442
  /**
418
443
  * List all KB databases for the tenant.
419
444
  */
package/dist/index.js CHANGED
@@ -140,6 +140,85 @@ function printNotionExportSummary(log) {
140
140
  console.log('\n(Dry run - no changes made)');
141
141
  }
142
142
  }
143
+ /**
144
+ * Resolve cross-references after migration.
145
+ *
146
+ * Builds a notionId → { documentId, branchId, sectionIds } mapping from results,
147
+ * then creates KB Reference objects for each extracted cross-reference.
148
+ */
149
+ async function resolveReferences(client, results) {
150
+ // Build notionId → document info mapping
151
+ const notionIdMap = new Map();
152
+ for (const result of results) {
153
+ if (result.sourcePageId &&
154
+ result.documentId &&
155
+ result.branchId &&
156
+ result.sectionIds &&
157
+ (result.status === 'created' || result.status === 'updated')) {
158
+ notionIdMap.set(result.sourcePageId, {
159
+ documentId: result.documentId,
160
+ branchId: result.branchId,
161
+ sectionIds: result.sectionIds,
162
+ });
163
+ }
164
+ }
165
+ let totalCreated = 0;
166
+ let totalSkipped = 0;
167
+ let totalErrors = 0;
168
+ let unresolvedTargets = 0;
169
+ let invalidSectionIndexes = 0;
170
+ for (const result of results) {
171
+ if (!result.references?.length ||
172
+ !result.documentId ||
173
+ !result.branchId ||
174
+ !result.sectionIds) {
175
+ continue;
176
+ }
177
+ if (result.status !== 'created' && result.status !== 'updated') {
178
+ continue;
179
+ }
180
+ const refs = [];
181
+ for (const ref of result.references) {
182
+ // Resolve target Notion ID → Moxn document ID
183
+ const target = notionIdMap.get(ref.targetNotionId);
184
+ if (!target) {
185
+ unresolvedTargets++;
186
+ continue;
187
+ }
188
+ // Map sectionIndex → sourceSectionId
189
+ const sourceSectionId = result.sectionIds[ref.sectionIndex];
190
+ if (!sourceSectionId) {
191
+ invalidSectionIndexes++;
192
+ continue;
193
+ }
194
+ refs.push({
195
+ sourceSectionId,
196
+ branchId: result.branchId,
197
+ targetDocumentId: target.documentId,
198
+ displayTitle: ref.displayText || null,
199
+ });
200
+ }
201
+ if (refs.length === 0)
202
+ continue;
203
+ try {
204
+ const { created, skipped, errors } = await client.createReferences(result.documentId, refs);
205
+ totalCreated += created;
206
+ totalSkipped += skipped;
207
+ totalErrors += errors;
208
+ }
209
+ catch (error) {
210
+ console.error(` Failed to create references for ${result.documentPath}: ${error instanceof Error ? error.message : error}`);
211
+ totalErrors += refs.length;
212
+ }
213
+ }
214
+ if (unresolvedTargets > 0) {
215
+ console.log(` ${unresolvedTargets} reference(s) skipped: target page not in import scope`);
216
+ }
217
+ if (invalidSectionIndexes > 0) {
218
+ console.log(` ${invalidSectionIndexes} reference(s) skipped: section index out of bounds`);
219
+ }
220
+ return { created: totalCreated, skipped: totalSkipped, errors: totalErrors };
221
+ }
143
222
  const program = new Command();
144
223
  program
145
224
  .name('moxn-kb-migrate')
@@ -354,7 +433,17 @@ program
354
433
  }
355
434
  // Step 3: Run page migration (validate is idempotent, extract uses databaseIdMap)
356
435
  const log = await runMigration(source, migrationOptions);
357
- // Step 4: Finalize databases — create columns, link entries, assign tags.
436
+ // Step 4: Resolve cross-references (between page migration and database finalization)
437
+ if (!opts.dryRun) {
438
+ const refsWithData = log.results.filter((r) => r.references?.length && r.sectionIds?.length);
439
+ if (refsWithData.length > 0) {
440
+ console.log(`\nResolving cross-references from ${refsWithData.length} document(s)...`);
441
+ const refClient = new MoxnClient(migrationOptions);
442
+ const refResult = await resolveReferences(refClient, log.results);
443
+ console.log(` Created ${refResult.created} reference(s) (${refResult.skipped} skipped, ${refResult.errors} errors)`);
444
+ }
445
+ }
446
+ // Step 5: Finalize databases — create columns, link entries, assign tags.
358
447
  // Database records already exist from pre-create; this step adds schema + data.
359
448
  if (!opts.dryRun && dbImports.length > 0) {
360
449
  console.log(`\nFinalizing ${dbImports.length} database(s)...`);
@@ -537,6 +626,7 @@ async function importNotionDatabase(client, dbImport, log, options, preCreatedDb
537
626
  const tag = await client.createTag({
538
627
  path: tagPath,
539
628
  color: notionColorToHex(option.color),
629
+ displayName: option.name,
540
630
  });
541
631
  optionTagMap.set(option.name, tag.id);
542
632
  tagIds.push(tag.id);
@@ -292,7 +292,10 @@ async function createDatabaseEntries(ctx, dataSourceId, resolved) {
292
292
  }
293
293
  else {
294
294
  // Scalar columns (number, date, text, url, email)
295
- if (propValue.textValue == null && propValue.numberValue == null && propValue.dateValue == null && propValue.booleanValue == null)
295
+ if (propValue.textValue == null &&
296
+ propValue.numberValue == null &&
297
+ propValue.dateValue == null &&
298
+ propValue.booleanValue == null)
296
299
  continue;
297
300
  switch (propValue.columnType) {
298
301
  case 'number':
package/dist/types.d.ts CHANGED
@@ -139,6 +139,12 @@ export interface MigrationResult {
139
139
  documentId?: string;
140
140
  branchId?: string;
141
141
  sectionsCount?: number;
142
+ /** Ordered section IDs from create/update response (for cross-reference resolution) */
143
+ sectionIds?: string[];
144
+ /** Cross-references extracted from source (for post-migration resolution) */
145
+ references?: ExtractedReference[];
146
+ /** Source page ID (e.g. Notion page ID) for building cross-ref mappings */
147
+ sourcePageId?: string;
142
148
  error?: string;
143
149
  duration?: number;
144
150
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moxn/kb-migrate",
3
- "version": "0.4.20",
3
+ "version": "0.4.21",
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",