@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 +15 -0
- package/dist/client.js +25 -0
- package/dist/index.js +91 -1
- package/dist/targets/notion-database-export.js +4 -1
- package/dist/types.d.ts +6 -0
- package/package.json +1 -1
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:
|
|
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 &&
|
|
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