@im-nobsidian/core 0.1.11 → 0.2.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/README.md +46 -0
- package/dist/index.d.ts +676 -72
- package/dist/index.js +2649 -921
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -129,19 +129,49 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
129
129
|
}>;
|
|
130
130
|
advanced: z.ZodObject<{
|
|
131
131
|
concurrency: z.ZodDefault<z.ZodNumber>;
|
|
132
|
-
maxRetries: z.ZodDefault<z.ZodNumber>;
|
|
133
132
|
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
133
|
+
pageSize: z.ZodDefault<z.ZodNumber>;
|
|
134
134
|
batchSize: z.ZodDefault<z.ZodNumber>;
|
|
135
|
+
rateLimitIntervalMs: z.ZodDefault<z.ZodNumber>;
|
|
136
|
+
maxRetries: z.ZodDefault<z.ZodNumber>;
|
|
137
|
+
retryBaseDelayMs: z.ZodDefault<z.ZodNumber>;
|
|
138
|
+
retryBackoffFactor: z.ZodDefault<z.ZodNumber>;
|
|
139
|
+
retryWaitMs: z.ZodDefault<z.ZodNumber>;
|
|
140
|
+
mediaConcurrency: z.ZodDefault<z.ZodNumber>;
|
|
141
|
+
mediaMaxRetries: z.ZodDefault<z.ZodNumber>;
|
|
142
|
+
mediaRetryBaseMs: z.ZodDefault<z.ZodNumber>;
|
|
143
|
+
fileConcurrency: z.ZodDefault<z.ZodNumber>;
|
|
144
|
+
maxFileSizeBytes: z.ZodDefault<z.ZodNumber>;
|
|
135
145
|
}, "strip", z.ZodTypeAny, {
|
|
136
146
|
concurrency: number;
|
|
137
|
-
maxRetries: number;
|
|
138
147
|
timeoutMs: number;
|
|
148
|
+
pageSize: number;
|
|
139
149
|
batchSize: number;
|
|
150
|
+
rateLimitIntervalMs: number;
|
|
151
|
+
maxRetries: number;
|
|
152
|
+
retryBaseDelayMs: number;
|
|
153
|
+
retryBackoffFactor: number;
|
|
154
|
+
retryWaitMs: number;
|
|
155
|
+
mediaConcurrency: number;
|
|
156
|
+
mediaMaxRetries: number;
|
|
157
|
+
mediaRetryBaseMs: number;
|
|
158
|
+
fileConcurrency: number;
|
|
159
|
+
maxFileSizeBytes: number;
|
|
140
160
|
}, {
|
|
141
161
|
concurrency?: number | undefined;
|
|
142
|
-
maxRetries?: number | undefined;
|
|
143
162
|
timeoutMs?: number | undefined;
|
|
163
|
+
pageSize?: number | undefined;
|
|
144
164
|
batchSize?: number | undefined;
|
|
165
|
+
rateLimitIntervalMs?: number | undefined;
|
|
166
|
+
maxRetries?: number | undefined;
|
|
167
|
+
retryBaseDelayMs?: number | undefined;
|
|
168
|
+
retryBackoffFactor?: number | undefined;
|
|
169
|
+
retryWaitMs?: number | undefined;
|
|
170
|
+
mediaConcurrency?: number | undefined;
|
|
171
|
+
mediaMaxRetries?: number | undefined;
|
|
172
|
+
mediaRetryBaseMs?: number | undefined;
|
|
173
|
+
fileConcurrency?: number | undefined;
|
|
174
|
+
maxFileSizeBytes?: number | undefined;
|
|
145
175
|
}>;
|
|
146
176
|
}, "strip", z.ZodTypeAny, {
|
|
147
177
|
version: 1;
|
|
@@ -180,9 +210,19 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
180
210
|
};
|
|
181
211
|
advanced: {
|
|
182
212
|
concurrency: number;
|
|
183
|
-
maxRetries: number;
|
|
184
213
|
timeoutMs: number;
|
|
214
|
+
pageSize: number;
|
|
185
215
|
batchSize: number;
|
|
216
|
+
rateLimitIntervalMs: number;
|
|
217
|
+
maxRetries: number;
|
|
218
|
+
retryBaseDelayMs: number;
|
|
219
|
+
retryBackoffFactor: number;
|
|
220
|
+
retryWaitMs: number;
|
|
221
|
+
mediaConcurrency: number;
|
|
222
|
+
mediaMaxRetries: number;
|
|
223
|
+
mediaRetryBaseMs: number;
|
|
224
|
+
fileConcurrency: number;
|
|
225
|
+
maxFileSizeBytes: number;
|
|
186
226
|
};
|
|
187
227
|
}, {
|
|
188
228
|
version: 1;
|
|
@@ -221,9 +261,19 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
221
261
|
};
|
|
222
262
|
advanced: {
|
|
223
263
|
concurrency?: number | undefined;
|
|
224
|
-
maxRetries?: number | undefined;
|
|
225
264
|
timeoutMs?: number | undefined;
|
|
265
|
+
pageSize?: number | undefined;
|
|
226
266
|
batchSize?: number | undefined;
|
|
267
|
+
rateLimitIntervalMs?: number | undefined;
|
|
268
|
+
maxRetries?: number | undefined;
|
|
269
|
+
retryBaseDelayMs?: number | undefined;
|
|
270
|
+
retryBackoffFactor?: number | undefined;
|
|
271
|
+
retryWaitMs?: number | undefined;
|
|
272
|
+
mediaConcurrency?: number | undefined;
|
|
273
|
+
mediaMaxRetries?: number | undefined;
|
|
274
|
+
mediaRetryBaseMs?: number | undefined;
|
|
275
|
+
fileConcurrency?: number | undefined;
|
|
276
|
+
maxFileSizeBytes?: number | undefined;
|
|
227
277
|
};
|
|
228
278
|
}>;
|
|
229
279
|
type Config = z.infer<typeof ConfigSchema>;
|
|
@@ -279,28 +329,19 @@ interface ProgressItem {
|
|
|
279
329
|
readonly operation: "create" | "update" | "delete";
|
|
280
330
|
}
|
|
281
331
|
type ProgressCallback = (current: number, total: number, item: ProgressItem) => void;
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
readonly excludePaths?: string[];
|
|
285
|
-
readonly force?: boolean;
|
|
286
|
-
readonly dryRun?: boolean;
|
|
287
|
-
readonly onProgress?: ProgressCallback;
|
|
288
|
-
readonly signal?: AbortSignal;
|
|
289
|
-
}
|
|
290
|
-
interface PullOptions {
|
|
332
|
+
/** push/pull/sync 가 공통으로 받는 동기화 옵션. */
|
|
333
|
+
interface BaseSyncOptions {
|
|
291
334
|
readonly paths?: string[];
|
|
292
335
|
readonly force?: boolean;
|
|
293
336
|
readonly dryRun?: boolean;
|
|
294
337
|
readonly onProgress?: ProgressCallback;
|
|
295
338
|
readonly signal?: AbortSignal;
|
|
296
339
|
}
|
|
297
|
-
interface
|
|
298
|
-
readonly
|
|
299
|
-
readonly force?: boolean;
|
|
300
|
-
readonly dryRun?: boolean;
|
|
301
|
-
readonly onProgress?: ProgressCallback;
|
|
302
|
-
readonly signal?: AbortSignal;
|
|
340
|
+
interface PushOptions extends BaseSyncOptions {
|
|
341
|
+
readonly excludePaths?: string[];
|
|
303
342
|
}
|
|
343
|
+
type PullOptions = BaseSyncOptions;
|
|
344
|
+
type SyncOptions = BaseSyncOptions;
|
|
304
345
|
interface PushResult {
|
|
305
346
|
readonly created: number;
|
|
306
347
|
readonly updated: number;
|
|
@@ -478,10 +519,14 @@ interface IStateDB {
|
|
|
478
519
|
setNotionLastEdited(id: string, lastEdited: string): void;
|
|
479
520
|
updateStatCache(id: string, mtime: string, fileSize: number): void;
|
|
480
521
|
setNotionParentId(id: string, parentId: string): void;
|
|
522
|
+
/** 레코드의 로컬 경로를 갱신한다 (파일 rename/move 추적용). */
|
|
523
|
+
updatePath(id: string, newPath: string): void;
|
|
481
524
|
delete(id: string): void;
|
|
482
525
|
resolveWikilink(text: string): WikilinkEntry | null;
|
|
483
526
|
resolvePageId(pageId: string): WikilinkEntry | null;
|
|
484
527
|
upsertWikilink(entry: WikilinkEntry): void;
|
|
528
|
+
/** 레코드 삭제 시 해당 경로의 wikilink 항목을 제거한다 (stale 링크 방지). */
|
|
529
|
+
deleteWikilink(obsidianPath: string): void;
|
|
485
530
|
getMeta(key: string): string | null;
|
|
486
531
|
setMeta(key: string, value: string): void;
|
|
487
532
|
isFileRegistered(localPath: string): boolean;
|
|
@@ -489,6 +534,12 @@ interface IStateDB {
|
|
|
489
534
|
getFilesByPageId(notionPageId: string): FileRegistryEntry[];
|
|
490
535
|
registerFile(entry: RegisterFileInput): void;
|
|
491
536
|
deleteFileRegistry(localPath: string): void;
|
|
537
|
+
recordPendingOperation(input: RecordPendingInput): string;
|
|
538
|
+
getIncompletePendingOperations(): PendingOperation[];
|
|
539
|
+
getIncompleteOpByState(syncStateId: string, operation: PendingOperation["operation"]): PendingOperation | null;
|
|
540
|
+
markPendingCompleted(id: string): void;
|
|
541
|
+
markPendingFailed(id: string, errorMessage: string): void;
|
|
542
|
+
clearCompletedOperations(): void;
|
|
492
543
|
storePreserveMarkers(path: string, markers: PreserveMarker[]): void;
|
|
493
544
|
getPreserveMarkers(path: string): PreserveMarker[];
|
|
494
545
|
transaction<T>(fn: () => T): T;
|
|
@@ -511,10 +562,12 @@ declare class StateDB implements IStateDB {
|
|
|
511
562
|
setNotionLastEdited(id: string, lastEdited: string): void;
|
|
512
563
|
updateStatCache(id: string, mtime: string, fileSize: number): void;
|
|
513
564
|
setNotionParentId(id: string, parentId: string): void;
|
|
565
|
+
updatePath(id: string, newPath: string): void;
|
|
514
566
|
delete(id: string): void;
|
|
515
567
|
resolveWikilink(text: string): WikilinkEntry | null;
|
|
516
568
|
resolvePageId(pageId: string): WikilinkEntry | null;
|
|
517
569
|
upsertWikilink(entry: WikilinkEntry): void;
|
|
570
|
+
deleteWikilink(obsidianPath: string): void;
|
|
518
571
|
getMeta(key: string): string | null;
|
|
519
572
|
setMeta(key: string, value: string): void;
|
|
520
573
|
isFileRegistered(localPath: string): boolean;
|
|
@@ -523,6 +576,13 @@ declare class StateDB implements IStateDB {
|
|
|
523
576
|
registerFile(entry: RegisterFileInput): void;
|
|
524
577
|
deleteFileRegistry(localPath: string): void;
|
|
525
578
|
private mapFileRegistryRow;
|
|
579
|
+
recordPendingOperation(input: RecordPendingInput): string;
|
|
580
|
+
getIncompletePendingOperations(): PendingOperation[];
|
|
581
|
+
getIncompleteOpByState(syncStateId: string, operation: PendingOperation["operation"]): PendingOperation | null;
|
|
582
|
+
markPendingCompleted(id: string): void;
|
|
583
|
+
markPendingFailed(id: string, errorMessage: string): void;
|
|
584
|
+
clearCompletedOperations(): void;
|
|
585
|
+
private mapPendingRow;
|
|
526
586
|
storePreserveMarkers(path: string, markers: PreserveMarker[]): void;
|
|
527
587
|
getPreserveMarkers(path: string): PreserveMarker[];
|
|
528
588
|
transaction<T>(fn: () => T): T;
|
|
@@ -560,20 +620,71 @@ interface RegisterFileInput {
|
|
|
560
620
|
readonly fileHash: string;
|
|
561
621
|
readonly fileSize: number;
|
|
562
622
|
}
|
|
623
|
+
/** I12 크래시 복구 WAL 항목. push/pull 적용 전 기록되고, 적용 완료 시 completed 로 마감된다. */
|
|
624
|
+
interface PendingOperation {
|
|
625
|
+
readonly id: string;
|
|
626
|
+
readonly syncStateId: string;
|
|
627
|
+
readonly operation: "create" | "update" | "delete" | "move";
|
|
628
|
+
readonly direction: "push" | "pull";
|
|
629
|
+
readonly payload: string | null;
|
|
630
|
+
readonly retryCount: number;
|
|
631
|
+
readonly errorMessage: string | null;
|
|
632
|
+
readonly status: "pending" | "processing" | "completed" | "failed";
|
|
633
|
+
readonly createdAt: string;
|
|
634
|
+
readonly completedAt: string | null;
|
|
635
|
+
}
|
|
636
|
+
interface RecordPendingInput {
|
|
637
|
+
readonly syncStateId: string;
|
|
638
|
+
readonly operation: PendingOperation["operation"];
|
|
639
|
+
readonly direction: PendingOperation["direction"];
|
|
640
|
+
readonly payload?: string | null;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
type NotionPropertyValue = Record<string, unknown>;
|
|
644
|
+
interface WikilinkResolver$1 {
|
|
645
|
+
resolve(title: string): string | null;
|
|
646
|
+
resolvePageId(pageId: string): string | null;
|
|
647
|
+
}
|
|
648
|
+
declare class PropertyMapper {
|
|
649
|
+
private schema;
|
|
650
|
+
private wikilinkResolver;
|
|
651
|
+
setWikilinkResolver(resolver: WikilinkResolver$1): void;
|
|
652
|
+
loadSchema(properties: Record<string, {
|
|
653
|
+
id: string;
|
|
654
|
+
type: string;
|
|
655
|
+
}>): void;
|
|
656
|
+
toNotionProperties(frontmatter: Record<string, unknown>, title: string): Record<string, NotionPropertyValue>;
|
|
657
|
+
fromNotionProperties(notionProps: Record<string, unknown>): Record<string, unknown>;
|
|
658
|
+
private convertBySchema;
|
|
659
|
+
private inferAndConvert;
|
|
660
|
+
private extractValue;
|
|
661
|
+
}
|
|
563
662
|
|
|
564
663
|
interface NotionClientOptions {
|
|
565
664
|
readonly token: string;
|
|
566
665
|
readonly concurrency?: number;
|
|
567
666
|
readonly timeoutMs?: number;
|
|
667
|
+
readonly rateLimitIntervalMs?: number;
|
|
668
|
+
readonly maxRetries?: number;
|
|
669
|
+
readonly retryBaseDelayMs?: number;
|
|
670
|
+
readonly retryBackoffFactor?: number;
|
|
671
|
+
readonly pageSize?: number;
|
|
672
|
+
readonly batchSize?: number;
|
|
568
673
|
readonly fetch?: typeof globalThis.fetch;
|
|
569
674
|
}
|
|
570
675
|
declare class NotionClient {
|
|
571
676
|
private readonly client;
|
|
572
677
|
private readonly sema;
|
|
573
|
-
private readonly token;
|
|
574
678
|
private readonly propertyMapper;
|
|
575
|
-
private readonly
|
|
679
|
+
private readonly minRequestInterval;
|
|
680
|
+
private readonly maxRetries;
|
|
681
|
+
private readonly retryBaseDelayMs;
|
|
682
|
+
private readonly retryBackoffFactor;
|
|
683
|
+
private readonly defaultPageSize;
|
|
684
|
+
private readonly batchSize;
|
|
576
685
|
constructor(options: NotionClientOptions);
|
|
686
|
+
/** config.advanced 의 운영 튜닝값으로 클라이언트를 생성한다 (매직넘버 단일 진실원). */
|
|
687
|
+
static fromConfig(config: Config, fetch?: typeof globalThis.fetch): NotionClient;
|
|
577
688
|
getInternalClient(): Client;
|
|
578
689
|
getPage(pageId: string): Promise<PageObjectResponse>;
|
|
579
690
|
createPage(params: {
|
|
@@ -599,8 +710,29 @@ declare class NotionClient {
|
|
|
599
710
|
replaceAll?: boolean;
|
|
600
711
|
}>): Promise<PageMarkdownResponse>;
|
|
601
712
|
movePage(pageId: string, newParentId: string, newParentType: "page" | "database"): Promise<PageObjectResponse>;
|
|
602
|
-
|
|
713
|
+
/**
|
|
714
|
+
* 데이터베이스 1개를 신 모델(2025-09-03 data source 분리)로 조회한다.
|
|
715
|
+
* - title: database 객체에 그대로 존재한다.
|
|
716
|
+
* - properties(스키마): 신 모델에선 data source 에 있으므로 1차 data source 를 조회해 채운다.
|
|
717
|
+
* data source 접근 불가(링크드 DB 등)면 database 객체의 properties 로 폴백한다(제목은 보존).
|
|
718
|
+
*
|
|
719
|
+
* 폐기한 레거시 raw fetch(`GET /v1/databases/{id}`, Notion-Version 2022-06-28)는 신 모델로
|
|
720
|
+
* 업그레이드된 다수 DB에 400 을 반환해 자동 발견 DB 가 통째 드롭(내용 손실)되던 원인이었다.
|
|
721
|
+
* SDK(`databases.retrieve`/`dataSources.retrieve`)는 동일 토큰으로 정상 동작한다.
|
|
722
|
+
*/
|
|
723
|
+
private fetchDatabaseModern;
|
|
603
724
|
getDatabaseTitle(databaseId: string): Promise<string>;
|
|
725
|
+
/**
|
|
726
|
+
* 발견된 DB 가 **동기화 가능한지**(접근 가능한 data source 가 있는지) 1회 호출로 판별한다.
|
|
727
|
+
* 신 모델(2025-09-03)에서 링크드 DB·미공유 데이터 소스·삭제 DB 는 `data_sources` 가
|
|
728
|
+
* 비어, 행 조회(`dataSources.query`)가 404 로 실패하고 그 전에 생성된 빈 폴더/`.base` 만
|
|
729
|
+
* 남긴다. 발견 단계에서 미리 걸러 **빈 폴더/.base 오염 + 매 pull 의 404 노이즈**를 차단한다.
|
|
730
|
+
* 제목은 함께 반환해 호출처가 추가 조회 없이 폴더명을 잡게 한다.
|
|
731
|
+
*/
|
|
732
|
+
getDatabaseSyncability(databaseId: string): Promise<{
|
|
733
|
+
title: string;
|
|
734
|
+
queryable: boolean;
|
|
735
|
+
}>;
|
|
604
736
|
getDatabaseSchema(databaseId: string): Promise<Record<string, {
|
|
605
737
|
id: string;
|
|
606
738
|
type: string;
|
|
@@ -618,6 +750,12 @@ declare class NotionClient {
|
|
|
618
750
|
optionIds?: string[];
|
|
619
751
|
}>;
|
|
620
752
|
}>>;
|
|
753
|
+
/** 특정 data source 1개를 직접 쿼리한다(저수준). 다중 data source 순회의 빌딩블록. */
|
|
754
|
+
private queryDataSource;
|
|
755
|
+
/**
|
|
756
|
+
* database 의 1차 data source 1페이지를 쿼리한다(증분 detect 등 단일 소스 경로용).
|
|
757
|
+
* 전 행을 빠짐없이 받으려면 {@link queryAllDatabasePages} 를 쓴다(전 data source 순회).
|
|
758
|
+
*/
|
|
621
759
|
queryDatabase(databaseId: string, options?: {
|
|
622
760
|
startCursor?: string;
|
|
623
761
|
pageSize?: number;
|
|
@@ -626,8 +764,24 @@ declare class NotionClient {
|
|
|
626
764
|
results: PageObjectResponse[];
|
|
627
765
|
nextCursor: string | null;
|
|
628
766
|
}>;
|
|
767
|
+
/**
|
|
768
|
+
* database 의 **모든 data source** 행을 빠짐없이 조회한다(2025-09-03 신 모델).
|
|
769
|
+
* 한 database 가 2개 이상의 data source(각자 행 집합)를 가질 수 있어, 1차 data source 만
|
|
770
|
+
* 페이지네이션하면 2번째+ 의 행이 통째 침묵 유실된다. 전 data source 를 순회·페이지네이션
|
|
771
|
+
* 하고 page_id 로 디듀프(소스 간 동일 페이지 방어)한 뒤 합친다. 다중 소스면 1회 경고해
|
|
772
|
+
* "소스별 탭 구분이 한 폴더로 병합"되는 점을 비침묵으로 알린다.
|
|
773
|
+
*/
|
|
629
774
|
queryAllDatabasePages(databaseId: string, filter?: unknown): Promise<PageObjectResponse[]>;
|
|
775
|
+
/** database 의 1차 data source id(없으면 databaseId 폴백). 단일 소스 경로 호환용. */
|
|
630
776
|
getDataSourceId(databaseId: string): Promise<string>;
|
|
777
|
+
/**
|
|
778
|
+
* database 의 전 data source 메타(id + 이름)를 반환한다. data source 가 없으면(레거시/단일)
|
|
779
|
+
* databaseId 자체를 단일 소스로 폴백해 호출처가 항상 ≥1 개를 받게 한다.
|
|
780
|
+
*/
|
|
781
|
+
getDataSourceMetas(databaseId: string): Promise<Array<{
|
|
782
|
+
id: string;
|
|
783
|
+
name: string;
|
|
784
|
+
}>>;
|
|
631
785
|
archivePage(pageId: string): Promise<void>;
|
|
632
786
|
getFileBlockUrl(blockId: string): Promise<string | null>;
|
|
633
787
|
getBlock(blockId: string): Promise<BlockObjectResponse>;
|
|
@@ -661,17 +815,53 @@ declare class NotionClient {
|
|
|
661
815
|
last_edited_time: string;
|
|
662
816
|
}>>;
|
|
663
817
|
getChildPages(parentId: string): Promise<PageObjectResponse[]>;
|
|
818
|
+
/** 페이지가 휴지통(in_trash)이거나 보관(archived) 상태인지 판정한다. */
|
|
819
|
+
private static isTrashedOrArchived;
|
|
664
820
|
getChildDatabaseIds(parentId: string): Promise<string[]>;
|
|
665
|
-
|
|
821
|
+
/**
|
|
822
|
+
* root 서브트리를 직접 BFS 순회해 하위 페이지를 모두 수집한다(중첩 깊이 무관·무손실).
|
|
823
|
+
* 비용은 서브트리 전체 블록 수에 비례한다.
|
|
824
|
+
*
|
|
825
|
+
* @param opts.deadlineMs `Date.now()` 기준 마감 시각. 각 페이지 처리 전 초과를 검사해
|
|
826
|
+
* 초과 시 {@link DiscoveryTooLargeError}를 던진다(대규모 서브트리 → search 폴백 유도).
|
|
827
|
+
* 미지정 시 무제한(기존 동작 유지 — 테스트 mock 경로는 영향 없음).
|
|
828
|
+
*/
|
|
829
|
+
getChildPagesRecursive(parentId: string, opts?: {
|
|
830
|
+
deadlineMs?: number;
|
|
831
|
+
}): Promise<PageObjectResponse[]>;
|
|
832
|
+
/** 워크스페이스에서 통합에 공유된 모든 페이지를 bulk search 로 열거한다(100/요청). */
|
|
833
|
+
searchAllPages(): Promise<PageObjectResponse[]>;
|
|
834
|
+
/**
|
|
835
|
+
* root 하위 페이지를 **search 기반**으로 디스커버리한다(대규모 서브트리용 폴백).
|
|
836
|
+
*
|
|
837
|
+
* 전략: 워크스페이스 전체 페이지를 1회 bulk search 로 열거(비용 ∝ 워크스페이스 페이지 수,
|
|
838
|
+
* 예측가능·유한)한 뒤, 각 페이지의 부모 체인을 따라 root 도달 여부를 판정한다. page_id
|
|
839
|
+
* 부모는 메모리 맵으로 추가 호출 없이 즉시 해석하고, block_id 부모(컬럼·토글 등 레이아웃에
|
|
840
|
+
* 중첩된 페이지)만 getBlock 으로 소유 페이지를 해석(memo)한다. search 는 중첩 깊이와 무관하게
|
|
841
|
+
* 모든 페이지를 반환하므로 직접 순회와 동일하게 **무손실**이다.
|
|
842
|
+
*
|
|
843
|
+
* getChildPagesRecursive 와 동일 집합을 보장하기 위해:
|
|
844
|
+
* - DB 행(parent=data_source_id/database_id)은 child_page 로 도달 불가 → 제외
|
|
845
|
+
* - 휴지통/보관(in_trash/archived) 페이지 제외
|
|
846
|
+
* - root 자신은 제외(직접 순회도 root 의 *자식*부터 수집)
|
|
847
|
+
*/
|
|
848
|
+
getPagesUnderRootViaSearch(rootId: string): Promise<PageObjectResponse[]>;
|
|
666
849
|
listDatabaseViews(databaseId: string): Promise<ViewConfig[]>;
|
|
667
850
|
getView(viewId: string): Promise<ViewConfig | null>;
|
|
668
851
|
getDatabaseViewsConfig(databaseId: string): Promise<DatabaseViewsConfig>;
|
|
669
852
|
extractCover(page: PageObjectResponse): PageCover | null;
|
|
670
853
|
extractIcon(page: PageObjectResponse): PageIcon | null;
|
|
671
854
|
extractTitle(page: PageObjectResponse): string;
|
|
855
|
+
/**
|
|
856
|
+
* 페이지 모드 pull(extractProperties)의 relation/people 속성을 raw Notion UUID 가
|
|
857
|
+
* 아니라 [[제목]] 위키링크로 해소하도록 resolver 를 주입한다. 주입하지 않으면 매
|
|
858
|
+
* pull 마다 relation 이 raw UUID 로 재생성돼, 후처리(resolveNotionLinks)에만 의존하는
|
|
859
|
+
* 2-write churn 이 남는다(M1). DB 모드는 orchestrator/DatabaseSyncer 의 자체 mapper 가
|
|
860
|
+
* 이미 resolver 를 주입받아 면역이다.
|
|
861
|
+
*/
|
|
862
|
+
setWikilinkResolver(resolver: WikilinkResolver$1): void;
|
|
672
863
|
extractProperties(page: PageObjectResponse): Record<string, unknown>;
|
|
673
864
|
private lastRequestTime;
|
|
674
|
-
private readonly minRequestInterval;
|
|
675
865
|
private withRateLimit;
|
|
676
866
|
private executeWithRetry;
|
|
677
867
|
}
|
|
@@ -780,26 +970,6 @@ declare class NotionBlockBuilder {
|
|
|
780
970
|
static table(rows: string[][], hasColumnHeader?: boolean, hasRowHeader?: boolean): NotionBlock;
|
|
781
971
|
}
|
|
782
972
|
|
|
783
|
-
type NotionPropertyValue = Record<string, unknown>;
|
|
784
|
-
interface WikilinkResolver$1 {
|
|
785
|
-
resolve(title: string): string | null;
|
|
786
|
-
resolvePageId(pageId: string): string | null;
|
|
787
|
-
}
|
|
788
|
-
declare class PropertyMapper {
|
|
789
|
-
private schema;
|
|
790
|
-
private wikilinkResolver;
|
|
791
|
-
setWikilinkResolver(resolver: WikilinkResolver$1): void;
|
|
792
|
-
loadSchema(properties: Record<string, {
|
|
793
|
-
id: string;
|
|
794
|
-
type: string;
|
|
795
|
-
}>): void;
|
|
796
|
-
toNotionProperties(frontmatter: Record<string, unknown>, title: string): Record<string, NotionPropertyValue>;
|
|
797
|
-
fromNotionProperties(notionProps: Record<string, unknown>): Record<string, unknown>;
|
|
798
|
-
private convertBySchema;
|
|
799
|
-
private inferAndConvert;
|
|
800
|
-
private extractValue;
|
|
801
|
-
}
|
|
802
|
-
|
|
803
973
|
interface FileInfo {
|
|
804
974
|
readonly path: string;
|
|
805
975
|
readonly content: string;
|
|
@@ -838,6 +1008,27 @@ interface VaultFS {
|
|
|
838
1008
|
getFileStat(path: string): Promise<FileStatInfo | null>;
|
|
839
1009
|
}
|
|
840
1010
|
|
|
1011
|
+
type ResolutionChoice = "local" | "remote" | "merge" | "duplicate";
|
|
1012
|
+
interface ResolutionResult {
|
|
1013
|
+
readonly path: string;
|
|
1014
|
+
readonly choice: ResolutionChoice;
|
|
1015
|
+
readonly success: boolean;
|
|
1016
|
+
readonly mergeHadConflicts?: boolean;
|
|
1017
|
+
}
|
|
1018
|
+
declare class ConflictResolver {
|
|
1019
|
+
private readonly stateDb;
|
|
1020
|
+
private readonly vaultFs;
|
|
1021
|
+
constructor(stateDb: IStateDB, vaultFs: VaultFS);
|
|
1022
|
+
resolve(conflict: Conflict, choice: ResolutionChoice): Promise<ResolutionResult>;
|
|
1023
|
+
resolveByStrategy(conflict: Conflict, strategy: ConflictStrategy): Promise<ResolutionResult>;
|
|
1024
|
+
resolveAll(conflicts: Conflict[], strategy: ConflictStrategy): Promise<ResolutionResult[]>;
|
|
1025
|
+
private resolveLocal;
|
|
1026
|
+
private resolveRemote;
|
|
1027
|
+
private resolveMerge;
|
|
1028
|
+
private resolveDuplicate;
|
|
1029
|
+
generateDiff(conflict: Conflict): string;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
841
1032
|
declare class SyncOrchestrator {
|
|
842
1033
|
private readonly config;
|
|
843
1034
|
private readonly stateDb;
|
|
@@ -850,9 +1041,13 @@ declare class SyncOrchestrator {
|
|
|
850
1041
|
private readonly fileHandler;
|
|
851
1042
|
private readonly databaseSyncer;
|
|
852
1043
|
private readonly propertyMapper;
|
|
1044
|
+
private readonly conflictResolver;
|
|
853
1045
|
private dbSchemaLoaded;
|
|
854
1046
|
private _pullImageCount;
|
|
855
1047
|
private _pullFileCount;
|
|
1048
|
+
private _inlineDbRefs;
|
|
1049
|
+
private _childParentIds;
|
|
1050
|
+
private static readonly DISCOVERY_RECURSIVE_BUDGET_MS;
|
|
856
1051
|
constructor(config: Config, stateDb: IStateDB, notionClient: NotionClient, vaultFs: VaultFS, customFetch?: typeof globalThis.fetch);
|
|
857
1052
|
push(options?: PushOptions): Promise<PushResult>;
|
|
858
1053
|
pull(options?: PullOptions): Promise<PullResult>;
|
|
@@ -868,17 +1063,65 @@ declare class SyncOrchestrator {
|
|
|
868
1063
|
private buildConflictsFromRecords;
|
|
869
1064
|
private get isDatabaseMode();
|
|
870
1065
|
private discoverChildDatabases;
|
|
1066
|
+
/**
|
|
1067
|
+
* 자동 발견된 child_database 1개를 DatabaseSyncer 설정으로 변환한다.
|
|
1068
|
+
* 블록 스캔/markdown 양쪽 발견 경로에서 공통으로 사용한다.
|
|
1069
|
+
* - localFolder: 부모 페이지의 obsidianPath 기준으로 폴더를 잡아 원본 중첩 구조를 보존
|
|
1070
|
+
* (예: 부모 "AI Engineer (1).md" + DB "Minirecord Project" → "AI Engineer (1)/Minirecord-Project")
|
|
1071
|
+
* - 제목 취득 실패 시 부모 폴더명 기반 안전 이름으로 폴백
|
|
1072
|
+
*/
|
|
1073
|
+
private buildDiscoveredDbConfig;
|
|
1074
|
+
/** 접근 불가 DB denylist 를 상태 메타에서 로드한다(하이픈 정규화). */
|
|
1075
|
+
private loadInaccessibleDbIds;
|
|
871
1076
|
private pullDiscoveredDatabases;
|
|
872
1077
|
private resolveNotionLinks;
|
|
873
1078
|
private ensureDbSchema;
|
|
874
1079
|
private pushCreate;
|
|
875
1080
|
private pushUpdate;
|
|
1081
|
+
/** 단일 충돌을 사용자가 고른 선택지(local/remote/merge/duplicate)로 해소 + Notion 전파. */
|
|
1082
|
+
resolveConflict(conflict: Conflict, choice: ResolutionChoice): Promise<ResolutionResult>;
|
|
1083
|
+
/** 단일 충돌을 전략(manual/local-first/remote-first/duplicate)으로 해소 + Notion 전파. */
|
|
1084
|
+
resolveConflictByStrategy(conflict: Conflict, strategy: ConflictStrategy): Promise<ResolutionResult>;
|
|
1085
|
+
/** 여러 충돌을 동일 전략으로 일괄 해소 + Notion 전파. */
|
|
1086
|
+
resolveAllConflicts(conflicts: Conflict[], strategy: ConflictStrategy): Promise<ResolutionResult[]>;
|
|
1087
|
+
/** 충돌 미리보기용 통합 diff(원본 vs 로컬 vs 원격). 해소 없이 표시 전용. */
|
|
1088
|
+
generateConflictDiff(conflict: Conflict): string;
|
|
1089
|
+
/**
|
|
1090
|
+
* 해소 결과를 Notion 으로 전파해 로컬↔원격 일관성을 봉합한다.
|
|
1091
|
+
* - remote 선택: 로컬이 원격으로 갱신됐을 뿐이므로 push 불필요. notionLastEdited 만
|
|
1092
|
+
* 원격 변경의 lastEdited 로 재조정 → 다음 pull 이 같은 변경을 재충돌로 보지 않음.
|
|
1093
|
+
* - merge 실패(충돌 마커 잔존): 사용자가 직접 풀어야 하므로 conflict 상태 유지·push 안 함.
|
|
1094
|
+
* - local / merge(성공) / duplicate: 해소된 로컬 내용을 Notion 에 재push(pushUpdate 가
|
|
1095
|
+
* 변환·이미지·속성·해시·notionLastEdited 를 한 트랜잭션으로 재조정) → 무손실 수렴.
|
|
1096
|
+
*/
|
|
1097
|
+
private propagateResolution;
|
|
876
1098
|
private pushDelete;
|
|
877
1099
|
private pushMove;
|
|
878
1100
|
private detectRemoteChanges;
|
|
1101
|
+
/**
|
|
1102
|
+
* 증분 원격 변경 감지 — `since` 이후 수정된 페이지만 search 로 받아 created/modified 만
|
|
1103
|
+
* 만든다. **삭제는 의도적으로 감지하지 않는다**: search API 는 in_trash/archived 페이지를
|
|
1104
|
+
* 반환하지 않아(=사라진 것을 증분만으로는 구분 불가) 삭제 판정에는 전체 enumeration 이
|
|
1105
|
+
* 필수다. 따라서 호출부는 `deleteSync` 가 켜진 경우 이 fast-path 를 쓰지 않고
|
|
1106
|
+
* `detectRemoteChanges`(전체 스캔, 삭제 diff 포함)로 우회한다. (I10)
|
|
1107
|
+
*/
|
|
879
1108
|
private detectRemoteChangesIncremental;
|
|
880
1109
|
private isTrackedParent;
|
|
881
1110
|
private pullCreate;
|
|
1111
|
+
/**
|
|
1112
|
+
* 페이지가 폴더(자식 페이지·자식 DB 보유)인지 신뢰성 있게 판정한다.
|
|
1113
|
+
*
|
|
1114
|
+
* 결함(폴더노트 본문분리)의 근본 원인은 얕은 판정이었다 — 최상위 블록 첫 페이지에서
|
|
1115
|
+
* child_page 만 검사하면 callout·column·toggle 안에 중첩된 자식 페이지나 child_database 를
|
|
1116
|
+
* 놓쳐 폴더노트가 file 로 오분류되고, 본문이 폴더 밖 최상위로 밀려 resolveUniqueFilePath
|
|
1117
|
+
* 충돌(' (1).md')로 쪼개졌다.
|
|
1118
|
+
*
|
|
1119
|
+
* 1차: 발견 단계(서브트리 순회/증분)에서 전 페이지의 부모를 해소하며 만든
|
|
1120
|
+
* _childParentIds 집합으로 O(1) 판정(추가 API 호출 0, 처리 순서 무관) — 전체 pull 경로.
|
|
1121
|
+
* 폴백: 집합에 없을 때만(증분 pull 의 신규 폴더 등) fetchAllChildrenDeep 로 컨테이너를
|
|
1122
|
+
* 재귀 탐색해 child_page·child_database 를 직접 확인한다.
|
|
1123
|
+
*/
|
|
1124
|
+
private pageHasChildContainers;
|
|
882
1125
|
private pullUpdate;
|
|
883
1126
|
private pullDelete;
|
|
884
1127
|
private ensureFolderPage;
|
|
@@ -887,11 +1130,32 @@ declare class SyncOrchestrator {
|
|
|
887
1130
|
private isFolderNote;
|
|
888
1131
|
private resolveParentPath;
|
|
889
1132
|
private pushCreatePage;
|
|
1133
|
+
private pageHasLiveChildren;
|
|
890
1134
|
private pushUpdatePage;
|
|
891
1135
|
private fetchPageMarkdown;
|
|
1136
|
+
private collectInlineDbRefs;
|
|
1137
|
+
private resolveNotionIdWikilinks;
|
|
892
1138
|
private extractParentId;
|
|
893
1139
|
private resolveBlockToPageId;
|
|
894
1140
|
private cleanupInterruptedSync;
|
|
1141
|
+
/**
|
|
1142
|
+
* I12 — 중단된 push create 작업 재개.
|
|
1143
|
+
*
|
|
1144
|
+
* pending_operations 에 미완료(create·push) 항목이 있으면:
|
|
1145
|
+
* - state 에 notion_page_id 가 이미 있으면 → 생성·매핑까지는 끝났고 markCompleted 직전에
|
|
1146
|
+
* 중단된 것 → 완료 처리(잔여 본문/이미지는 다음 변경감지가 pushUpdate 로 마무리).
|
|
1147
|
+
* - notion_page_id 가 비어 있으면(Window A: 생성 적용됐으나 매핑 기록 전 중단) → 부모에서
|
|
1148
|
+
* 제목으로 child_page 를 검색해 고아 페이지를 입양(중복 생성 차단). 없으면 자리표시
|
|
1149
|
+
* 레코드를 제거(FK CASCADE 로 op 도 삭제)해 다음 push 가 새로 생성하게 한다.
|
|
1150
|
+
*
|
|
1151
|
+
* 한계(문서화): Notion 은 idempotency key 가 없어 "생성 요청 적용 직후 같은 호출 내 재시도"
|
|
1152
|
+
* 로 인한 중복은 완전히 차단하지 못한다. 429 는 적용 전 거절이라 안전하고, 프로세스 재시작
|
|
1153
|
+
* 후 재개는 본 검색-입양으로 중복을 막는다. DB 모드(부모가 database)는 child_page 검색이
|
|
1154
|
+
* 불가하므로 자리표시 제거 후 재생성으로 폴백한다.
|
|
1155
|
+
*/
|
|
1156
|
+
private recoverInterruptedPushOps;
|
|
1157
|
+
/** 부모 페이지의 직속 자식 중 제목이 일치하는(보관/휴지통 제외) child_page id 를 찾는다. */
|
|
1158
|
+
private findChildPageByTitle;
|
|
895
1159
|
private resolveUniqueFilePath;
|
|
896
1160
|
}
|
|
897
1161
|
|
|
@@ -924,6 +1188,13 @@ declare class NodeVaultFS implements VaultFS {
|
|
|
924
1188
|
private walkDir;
|
|
925
1189
|
}
|
|
926
1190
|
|
|
1191
|
+
/** 미디어(이미지/파일) 다운로드 운영 튜닝값. 미지정 시 기존 동작과 동일한 기본값 사용. */
|
|
1192
|
+
interface MediaOptions {
|
|
1193
|
+
readonly concurrency?: number;
|
|
1194
|
+
readonly maxRetries?: number;
|
|
1195
|
+
readonly retryBaseMs?: number;
|
|
1196
|
+
readonly maxFileSizeBytes?: number;
|
|
1197
|
+
}
|
|
927
1198
|
interface ImageDownloadResult {
|
|
928
1199
|
readonly originalUrl: string;
|
|
929
1200
|
readonly localPath: string;
|
|
@@ -933,13 +1204,21 @@ interface ImageDownloadResult {
|
|
|
933
1204
|
interface ImageUploadResult {
|
|
934
1205
|
readonly localPath: string;
|
|
935
1206
|
readonly fileUploadId: string;
|
|
1207
|
+
/** 업로드한 이미지의 전체 sha256 — file_registry 비교 키(FileHandler 와 동일 산식). */
|
|
1208
|
+
readonly fileHash: string;
|
|
1209
|
+
readonly fileSize: number;
|
|
936
1210
|
}
|
|
937
1211
|
declare class ImageHandler {
|
|
938
1212
|
private readonly vaultFs;
|
|
939
1213
|
private readonly attachmentFolder;
|
|
940
1214
|
private readonly notionClient?;
|
|
1215
|
+
private readonly stateDb?;
|
|
941
1216
|
private readonly customFetch?;
|
|
942
|
-
|
|
1217
|
+
private readonly concurrency;
|
|
1218
|
+
private readonly maxRetries;
|
|
1219
|
+
private readonly retryBaseMs;
|
|
1220
|
+
private readonly maxFileSizeBytes;
|
|
1221
|
+
constructor(vaultFs: VaultFS, attachmentFolder?: string, notionClient?: NotionClient | undefined, customFetch?: typeof globalThis.fetch, options?: MediaOptions, stateDb?: IStateDB | undefined);
|
|
943
1222
|
private get fetchFn();
|
|
944
1223
|
downloadImage(url: string, pageTitle: string): Promise<ImageDownloadResult>;
|
|
945
1224
|
downloadAllImages(markdown: string, pageTitle: string): Promise<{
|
|
@@ -979,7 +1258,7 @@ declare class FileHandler {
|
|
|
979
1258
|
private readonly notionClient;
|
|
980
1259
|
private readonly stateDb;
|
|
981
1260
|
private readonly sema;
|
|
982
|
-
constructor(vaultFs: VaultFS, notionClient: NotionClient, stateDb: IStateDB);
|
|
1261
|
+
constructor(vaultFs: VaultFS, notionClient: NotionClient, stateDb: IStateDB, concurrency?: number);
|
|
983
1262
|
pushFilesForFolder(folderPageId: string, folderPath: string): Promise<FileUploadResult[]>;
|
|
984
1263
|
pushAllFiles(): Promise<FileUploadResult[]>;
|
|
985
1264
|
downloadFileBlock(url: string, filename: string, targetFolder: string): Promise<FileDownloadResult>;
|
|
@@ -1006,7 +1285,16 @@ declare class ConversionPipeline {
|
|
|
1006
1285
|
interface DatabaseSyncResult {
|
|
1007
1286
|
created: number;
|
|
1008
1287
|
updated: number;
|
|
1288
|
+
/** Pull 시 로컬·리모트 동시 수정으로 발생한 충돌 (push 는 항상 빈 배열). */
|
|
1289
|
+
conflicts: Conflict[];
|
|
1009
1290
|
failed: FailedOperation[];
|
|
1291
|
+
/**
|
|
1292
|
+
* 이번 pull 에서 실제로 디스크에 기록한 db-row 파일 경로(SSOT).
|
|
1293
|
+
* 오케스트레이터의 링크 해소(resolveNotionLinks) 대상은 반드시 이 목록으로 정한다 —
|
|
1294
|
+
* 과거의 `getByStatus("synced").slice(-N)` 휴리스틱은 ORDER BY 가 없어 증분 pull 에서
|
|
1295
|
+
* 엉뚱한 행을 골라 정작 바뀐 행의 relation UUID 를 영영 해소하지 못했다(M3). push 경로는 항상 빈 배열.
|
|
1296
|
+
*/
|
|
1297
|
+
writtenPaths: string[];
|
|
1010
1298
|
}
|
|
1011
1299
|
declare class DatabaseSyncer {
|
|
1012
1300
|
private readonly config;
|
|
@@ -1017,14 +1305,33 @@ declare class DatabaseSyncer {
|
|
|
1017
1305
|
private readonly imageHandler;
|
|
1018
1306
|
private readonly propertyMapper;
|
|
1019
1307
|
private readonly baseFileGenerator;
|
|
1308
|
+
private readonly sidecarGenerator;
|
|
1020
1309
|
constructor(config: Config, stateDb: IStateDB, notionClient: NotionClient, vaultFs: VaultFS, pipeline: ConversionPipeline, imageHandler: ImageHandler);
|
|
1021
1310
|
pullAll(): Promise<DatabaseSyncResult>;
|
|
1022
1311
|
pushAll(): Promise<DatabaseSyncResult>;
|
|
1023
1312
|
pullDatabase(dbConfig: DatabaseSyncConfig): Promise<DatabaseSyncResult>;
|
|
1024
1313
|
private pullDatabaseViews;
|
|
1025
1314
|
private generateBaseFile;
|
|
1315
|
+
/**
|
|
1316
|
+
* DB 제목이 바뀌면 `.base`/`.notion.json` 파일명(safeName)이 바뀌어 옛 이름의 산출물이
|
|
1317
|
+
* 같은 폴더에 고아로 잔존한다(drift·부활 원인). DB 폴더 **직속**의 산출물 중 현재 이름이
|
|
1318
|
+
* 아닌 것을 삭제해 멱등을 보장한다. 하위 폴더(중첩 DB)의 산출물은 보호한다.
|
|
1319
|
+
*/
|
|
1320
|
+
private cleanupStaleDbArtifacts;
|
|
1321
|
+
/**
|
|
1322
|
+
* `.base` 가 표현하지 못하는 Notion DB 메타데이터(미지원 뷰·필터식·커버크기 등)를
|
|
1323
|
+
* 사이드카 `<db>.notion.json` 으로 무손실 보존하고, degrade 된 항목을 정직하게 로그한다.
|
|
1324
|
+
* 결정적 직렬화라 동일 DB 상태 → 동일 바이트 → 멱등(drift/churn 0).
|
|
1325
|
+
*/
|
|
1326
|
+
private generateSidecar;
|
|
1026
1327
|
private pullDatabasePage;
|
|
1027
1328
|
private pushDatabase;
|
|
1329
|
+
/**
|
|
1330
|
+
* rename 감지를 위한 고아 레코드 인덱스(내용 해시 → 레코드)를 만든다.
|
|
1331
|
+
* 고아 = 해당 DB 소속 db-row 레코드 중 로컬 파일이 더 이상 존재하지 않는(livePaths 에 없는) 것.
|
|
1332
|
+
* 같은 해시가 여러 고아에 걸리면 첫 항목을 유지한다(희박한 케이스).
|
|
1333
|
+
*/
|
|
1334
|
+
private buildOrphanHashIndex;
|
|
1028
1335
|
}
|
|
1029
1336
|
|
|
1030
1337
|
declare class BlockConverter {
|
|
@@ -1032,6 +1339,19 @@ declare class BlockConverter {
|
|
|
1032
1339
|
private client;
|
|
1033
1340
|
initNotionToMd(client: Client): void;
|
|
1034
1341
|
private registerCustomTransformers;
|
|
1342
|
+
/**
|
|
1343
|
+
* `numbered_list_item` 을 `1.` 마크다운으로 내보내 **번호 목록 타입을 보존**한다(I2 블록 라운드트립).
|
|
1344
|
+
*
|
|
1345
|
+
* notion-to-md 기본 렌더는 `md.bullet(text, block.numbered_list_item.number)` 를 쓰는데
|
|
1346
|
+
* Notion API 는 list item 에 `.number` 를 채워주지 않아 `undefined` → 불릿(`- `)으로 **강등**된다.
|
|
1347
|
+
* 그러면 블록 폴백 경로(notionBlocksToMarkdown)로 pull 한 뒤 다시 push 하면 numbered_list_item 이
|
|
1348
|
+
* bulleted_list_item 으로 **타입 유실**된다(기본 markdown-api 경로는 영향 없음).
|
|
1349
|
+
*
|
|
1350
|
+
* 항상 `1.` 로 내보내면 마크다운이 시각적으로 자동 재번호하므로 표시는 동일하고, martian 이 다시
|
|
1351
|
+
* numbered_list_item 으로 파싱해 타입이 보존된다. 커스텀 트랜스포머 등록은 notion-to-md 의 기본
|
|
1352
|
+
* 자식 처리를 끄므로(라이브러리 설계), 자식은 toggle/callout 과 동일하게 직접 fetch·들여쓰기한다.
|
|
1353
|
+
*/
|
|
1354
|
+
private registerNumberedListTransformer;
|
|
1035
1355
|
private registerToggleTransformer;
|
|
1036
1356
|
private registerToggleHeadingTransformers;
|
|
1037
1357
|
private registerColumnTransformer;
|
|
@@ -1055,6 +1375,14 @@ declare class BlockConverter {
|
|
|
1055
1375
|
private toggleCounter;
|
|
1056
1376
|
private columnCounter;
|
|
1057
1377
|
private preProcessMarkdown;
|
|
1378
|
+
/**
|
|
1379
|
+
* compact underline/color 보존마커를 평문으로 강등 — block(martian) 폴백 경로 전용.
|
|
1380
|
+
*
|
|
1381
|
+
* 무손실 underline/color 복원은 기본 경로인 Markdown API({@link obsidianToNotionEnhanced})가
|
|
1382
|
+
* 전담한다. martian 은 이 서식을 표현할 수 없으므로, Markdown API 가 실패해 이 폴백을 탈 때는
|
|
1383
|
+
* 마커 텍스트가 본문에 리터럴로 새지 않도록 텍스트만 남기고 마커를 제거한다(서식 degrade).
|
|
1384
|
+
*/
|
|
1385
|
+
private stripInlineAnnotationMarkers;
|
|
1058
1386
|
private replaceDividers;
|
|
1059
1387
|
private extractToggleBlocks;
|
|
1060
1388
|
private extractColumnBlocks;
|
|
@@ -1062,9 +1390,41 @@ declare class BlockConverter {
|
|
|
1062
1390
|
private convertTogglePlaceholder;
|
|
1063
1391
|
private convertColumnPlaceholder;
|
|
1064
1392
|
private convertDividerPlaceholder;
|
|
1393
|
+
/**
|
|
1394
|
+
* 단일 라인 보존 마커(목차·breadcrumb)를 원래 블록으로 복원한다.
|
|
1395
|
+
* pull 시 마크다운 표현이 없는 블록을 `%%im-nobsidian:toc%%` /
|
|
1396
|
+
* `%%im-nobsidian:breadcrumb%%` 마커 문단으로 남겨두므로, push 시 이 문단을
|
|
1397
|
+
* 감지해 table_of_contents / breadcrumb 블록으로 되돌린다. 그러지 않으면
|
|
1398
|
+
* 마커 텍스트가 일반 문단으로 Notion 에 기록되는 잠재 손실이 된다.
|
|
1399
|
+
*/
|
|
1400
|
+
private convertSingleLineMarker;
|
|
1065
1401
|
private convertImageToVideoOrEmbed;
|
|
1402
|
+
/**
|
|
1403
|
+
* `@tryfabric/martian` 의 blockquote 표현을 **평탄화**한다(I2/I3 무손실 push).
|
|
1404
|
+
*
|
|
1405
|
+
* martian 의 parseBlockquote 는 `notion.blockquote([], children)` 로 본문을 quote 의
|
|
1406
|
+
* **자식 paragraph** 로 내려보내고 quote.rich_text 를 비운다(라이브러리 설계). 그 결과
|
|
1407
|
+
* `> 인용문` 을 push 하면 Notion 에 "빈 인용 줄 + 들여쓴 자식 문단"이 만들어지고, 다시
|
|
1408
|
+
* pull 하면 `> `(빈 줄) 아래에 자식 문단이 매달려 **원문과 구조가 갈라진다**(라운드트립 손실).
|
|
1409
|
+
* 또한 quote.rich_text[0] 이 항상 비어 callout 이모지 감지({@link convertQuoteToCallout})도
|
|
1410
|
+
* 무력화됐다.
|
|
1411
|
+
*
|
|
1412
|
+
* 선두의 연속된 paragraph 자식을 quote.rich_text 로 끌어올리고(문단 사이는 줄바꿈 rich_text
|
|
1413
|
+
* 로 연결 — Notion 네이티브 멀티라인 인용 표현), paragraph 가 아닌 자식(중첩 리스트 등)은
|
|
1414
|
+
* children 으로 남겨 구조를 보존한다. 이미 rich_text 에 본문이 있거나 끌어올릴 paragraph 가
|
|
1415
|
+
* 없으면 원본을 그대로 반환한다(안전 no-op).
|
|
1416
|
+
*/
|
|
1417
|
+
private flattenMartianBlockquote;
|
|
1066
1418
|
private convertQuoteToCallout;
|
|
1067
1419
|
notionBlocksToMarkdown(pageId: string): Promise<string>;
|
|
1420
|
+
/**
|
|
1421
|
+
* **in-memory** 블록 배열을 마크다운으로 변환한다(페이지 fetch 없음). notion-to-md 의
|
|
1422
|
+
* blocksToMarkdown 을 직접 호출하므로, `has_children:false` 인 평면 블록은 Notion 클라이언트를
|
|
1423
|
+
* 전혀 건드리지 않는다 → 고정 픽스처로 **오프라인 결정론** 블록 라운드트립(I2)을 잠글 수 있는
|
|
1424
|
+
* 정식 테스트 시임. children 이 있는 블록(table 행·callout 자식 등)은 클라이언트를 호출하므로
|
|
1425
|
+
* 호출 측이 평면 블록만 넣어야 오프라인이 보장된다.
|
|
1426
|
+
*/
|
|
1427
|
+
notionBlockArrayToMarkdown(blocks: Array<Record<string, unknown>>): Promise<string>;
|
|
1068
1428
|
}
|
|
1069
1429
|
|
|
1070
1430
|
declare class FrontmatterExtractor implements Processor {
|
|
@@ -1124,8 +1484,26 @@ declare class PropertiesTableInjector implements Processor {
|
|
|
1124
1484
|
process(input: ProcessorInput): ProcessorOutput;
|
|
1125
1485
|
}
|
|
1126
1486
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1487
|
+
/**
|
|
1488
|
+
* 인라인 annotation(underline·color) 무손실 보존기 — push 전처리(order 5).
|
|
1489
|
+
*
|
|
1490
|
+
* 과거에는 underline/color 표기를 **제거**(strip)했으나, 그러면 Notion 으로 push 할 때
|
|
1491
|
+
* 서식이 영구 소실됐다(I3 손실점). Notion 의 Enhanced Markdown 은 underline/color 를
|
|
1492
|
+
* `<span underline="true">` / `<span color="X">` 로 **무손실 표현**하므로, 여기서는 제거 대신
|
|
1493
|
+
* 프로젝트 정본인 **compact 보존마커**(`%%im-nobsidian:underline%%…%%/underline%%`,
|
|
1494
|
+
* `%%im-nobsidian:color:C%%…%%/color%%`)로 **승격**만 한다. 실제 Notion span 복원은
|
|
1495
|
+
* push 직전 단일 SSOT 인 {@link obsidianToNotionEnhanced} 가 담당한다.
|
|
1496
|
+
*
|
|
1497
|
+
* 입력 표기는 pull 경로별로 다르다.
|
|
1498
|
+
* - Markdown API 경로: 이미 compact 마커 → 무변경 통과(여기 정규식에 걸리지 않음).
|
|
1499
|
+
* - notion-to-md(block) 경로: `<u>` + 공백형 색상 마커 → compact 마커로 정규화.
|
|
1500
|
+
* - 사용자가 직접 작성한 `<u>` / 레거시 `<span class="notion-*">` → compact 마커로 승격.
|
|
1501
|
+
*
|
|
1502
|
+
* compact 마커는 공백이 없어 PreserveMarkerCollector(공백형만 수집)·기타 전처리기를
|
|
1503
|
+
* 무손상 통과한다(기존 underline/color 왕복이 이를 실증).
|
|
1504
|
+
*/
|
|
1505
|
+
declare class InlineAnnotationPreserver implements Processor {
|
|
1506
|
+
readonly name = "InlineAnnotationPreserver";
|
|
1129
1507
|
readonly order = 5;
|
|
1130
1508
|
process(input: ProcessorInput): ProcessorOutput;
|
|
1131
1509
|
}
|
|
@@ -1184,29 +1562,26 @@ interface MergeConflictRegion {
|
|
|
1184
1562
|
readonly localLines: string[];
|
|
1185
1563
|
readonly remoteLines: string[];
|
|
1186
1564
|
}
|
|
1565
|
+
/**
|
|
1566
|
+
* 3-way 병합 (diff3). base 를 공통 조상으로 보고 local/remote 의 변경을 합친다.
|
|
1567
|
+
*
|
|
1568
|
+
* 표준 diff3 알고리즘:
|
|
1569
|
+
* 1. base↔local, base↔remote 각각의 LCS 로 "변경 조각(hunk)"을 추출한다.
|
|
1570
|
+
* (hunk = base 의 어떤 구간 [baseStart, baseEnd) 을 새 내용으로 치환·삭제·삽입)
|
|
1571
|
+
* 2. 모든 hunk 를 base 위치순으로 정렬하고, **base 구간이 겹치는** hunk 들을
|
|
1572
|
+
* 하나의 region 으로 묶는다. 겹치지 않는 hunk 사이의 base 줄은 양쪽 공통이므로 그대로 출력.
|
|
1573
|
+
* 3. 각 region 에서 local/remote 가 만들어낸 내용을 비교해 판정한다.
|
|
1574
|
+
* - local 결과 == base → local 미변경 → remote 채택
|
|
1575
|
+
* - remote 결과 == base → remote 미변경 → local 채택
|
|
1576
|
+
* - local 결과 == remote 결과 → 동일 변경 → 한쪽 채택
|
|
1577
|
+
* - 그 외 → 충돌 (양쪽이 같은 구간을 다르게 변경)
|
|
1578
|
+
*
|
|
1579
|
+
* 핵심은 hunk 의 base 구간이 **실제로 겹칠 때만** 충돌로 본다는 점이다. 서로 다른(인접
|
|
1580
|
+
* 포함) 줄을 고친 삽입·삭제·수정은 충돌 없이 합쳐진다. (이전 단순 라인-치환 구현은
|
|
1581
|
+
* 삽입/삭제를 오정렬해 거짓 충돌을 만들거나 무한 루프에 빠질 수 있었다.)
|
|
1582
|
+
*/
|
|
1187
1583
|
declare function threeWayMerge(base: string, local: string, remote: string): MergeResult;
|
|
1188
1584
|
|
|
1189
|
-
type ResolutionChoice = "local" | "remote" | "merge" | "duplicate";
|
|
1190
|
-
interface ResolutionResult {
|
|
1191
|
-
readonly path: string;
|
|
1192
|
-
readonly choice: ResolutionChoice;
|
|
1193
|
-
readonly success: boolean;
|
|
1194
|
-
readonly mergeHadConflicts?: boolean;
|
|
1195
|
-
}
|
|
1196
|
-
declare class ConflictResolver {
|
|
1197
|
-
private readonly stateDb;
|
|
1198
|
-
private readonly vaultFs;
|
|
1199
|
-
constructor(stateDb: IStateDB, vaultFs: VaultFS);
|
|
1200
|
-
resolve(conflict: Conflict, choice: ResolutionChoice): Promise<ResolutionResult>;
|
|
1201
|
-
resolveByStrategy(conflict: Conflict, strategy: ConflictStrategy): Promise<ResolutionResult>;
|
|
1202
|
-
resolveAll(conflicts: Conflict[], strategy: ConflictStrategy): Promise<ResolutionResult[]>;
|
|
1203
|
-
private resolveLocal;
|
|
1204
|
-
private resolveRemote;
|
|
1205
|
-
private resolveMerge;
|
|
1206
|
-
private resolveDuplicate;
|
|
1207
|
-
generateDiff(conflict: Conflict): string;
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
1585
|
interface NotionPageNode {
|
|
1211
1586
|
readonly id: string;
|
|
1212
1587
|
readonly title: string;
|
|
@@ -1260,12 +1635,12 @@ declare class FileWatcher {
|
|
|
1260
1635
|
private watcher;
|
|
1261
1636
|
constructor(rootPath: string, callback: WatchCallback);
|
|
1262
1637
|
start(): void;
|
|
1638
|
+
private toVaultPath;
|
|
1263
1639
|
stop(): Promise<void>;
|
|
1264
1640
|
}
|
|
1265
1641
|
|
|
1266
1642
|
interface WatchSyncOptions {
|
|
1267
1643
|
readonly debounceMs?: number;
|
|
1268
|
-
readonly syncDirection?: "push" | "pull" | "both";
|
|
1269
1644
|
readonly onSyncStart?: () => void;
|
|
1270
1645
|
readonly onSyncComplete?: (result: SyncResult) => void;
|
|
1271
1646
|
readonly onSyncError?: (error: Error) => void;
|
|
@@ -1376,6 +1751,18 @@ interface BaseFileOptions {
|
|
|
1376
1751
|
readonly viewsConfig: DatabaseViewsConfig;
|
|
1377
1752
|
readonly folderPath: string;
|
|
1378
1753
|
}
|
|
1754
|
+
type BasesViewType = "table" | "cards" | "list";
|
|
1755
|
+
/**
|
|
1756
|
+
* Notion 뷰 타입 → Obsidian Bases 뷰 타입 매핑(SSOT).
|
|
1757
|
+
*
|
|
1758
|
+
* `null` = Bases 에 대응 뷰가 없어 `.base` 로 표현 불가. 이 경우 해당 뷰는 `.base` 에서
|
|
1759
|
+
* 누락되므로, 사이드카(`<db>.notion.json`)가 원본 뷰 설정을 무손실 보존한다
|
|
1760
|
+
* (Notion API 는 뷰 생성/수정을 지원하지 않아 뷰는 pull-authoritative — push 로 되돌릴 수
|
|
1761
|
+
* 없으므로 "조용한 유실 금지"는 곧 "보존 + 정직한 degrade 리포트"를 의미한다).
|
|
1762
|
+
*/
|
|
1763
|
+
declare const NOTION_TO_BASES_VIEW: Record<string, BasesViewType | null>;
|
|
1764
|
+
/** Notion 뷰 타입의 Bases 대응 타입을 돌려준다. 미지원/미상 타입은 `null`. */
|
|
1765
|
+
declare function basesViewTypeOf(notionType: string): BasesViewType | null;
|
|
1379
1766
|
declare class BaseFileGenerator {
|
|
1380
1767
|
generate(options: BaseFileOptions): string;
|
|
1381
1768
|
private writeFilters;
|
|
@@ -1383,13 +1770,104 @@ declare class BaseFileGenerator {
|
|
|
1383
1770
|
private writeFormulas;
|
|
1384
1771
|
private writePropertyDisplayNames;
|
|
1385
1772
|
private writeViews;
|
|
1773
|
+
/**
|
|
1774
|
+
* 뷰 이름을 Bases 요구사항대로 유일화한다(in-place). 동명 충돌 시 ' 2',' 3'… 를 붙이되
|
|
1775
|
+
* 이미 존재하는 이름과 다시 충돌하지 않을 때까지 증가시킨다.
|
|
1776
|
+
*/
|
|
1777
|
+
private dedupeViewNames;
|
|
1386
1778
|
private convertView;
|
|
1387
1779
|
private toNoteRef;
|
|
1780
|
+
/**
|
|
1781
|
+
* 속성 참조(order/sort/groupBy/image)를 YAML로 안전하게 출력한다.
|
|
1782
|
+
* `file.name`·`formula.coverImage`·`note.cover` 같은 접두사 식별자와 공백 포함 일반 속성명은
|
|
1783
|
+
* 평문 스칼라로 안전하므로 그대로 두고, 콜론·해시·따옴표 등 YAML 메타문자가 있을 때만 인용한다.
|
|
1784
|
+
*/
|
|
1785
|
+
private propRef;
|
|
1388
1786
|
private resolvePropertyName;
|
|
1787
|
+
private decodeId;
|
|
1389
1788
|
private yamlKey;
|
|
1390
1789
|
private yamlValue;
|
|
1391
1790
|
}
|
|
1392
1791
|
|
|
1792
|
+
/**
|
|
1793
|
+
* DB 사이드카(`<db>.notion.json`) — `.base` 로 표현할 수 없는 Notion DB 메타데이터를
|
|
1794
|
+
* 무손실 보존하는 단일 파일.
|
|
1795
|
+
*
|
|
1796
|
+
* ## 왜 필요한가 (I4·I7 "조용한 유실 금지")
|
|
1797
|
+
*
|
|
1798
|
+
* Obsidian Bases(`.base`)는 Notion DB 뷰의 일부만 표현한다:
|
|
1799
|
+
* · 미지원 뷰(calendar/timeline/form/chart/map/dashboard)는 `.base` 에서 통째 누락.
|
|
1800
|
+
* · 표현되는 뷰도 Notion **필터식·커버 크기/비율·컬럼 width/wrap·빈그룹 숨김** 등은
|
|
1801
|
+
* Bases 문법에 대응이 없어 손실된다.
|
|
1802
|
+
*
|
|
1803
|
+
* Notion API 는 뷰 생성/수정을 지원하지 않으므로(뷰는 pull-authoritative) 이 메타데이터는
|
|
1804
|
+
* push 로 Notion 에 되돌릴 수 없다. 따라서 "무손실"의 의미는 **로컬에 완전·정직하게
|
|
1805
|
+
* 보존하고, 무엇이 degrade 됐는지 명시 리포트**하는 것이다. 사이드카는 이를 `.base` 와
|
|
1806
|
+
* 같은 폴더에 co-locate 해 사람이 발견 가능하고 도구가 재사용 가능하게 만든다.
|
|
1807
|
+
*
|
|
1808
|
+
* ## 멱등성
|
|
1809
|
+
*
|
|
1810
|
+
* 매 pull 마다 재생성되므로 **결정적**이어야 한다(타임스탬프·실행시각 금지, 키 정렬).
|
|
1811
|
+
* 동일 DB 상태 → 동일 바이트 → drift 0 / churn 0.
|
|
1812
|
+
*/
|
|
1813
|
+
interface NotionSidecar {
|
|
1814
|
+
readonly generator: "im-nobsidian";
|
|
1815
|
+
readonly schemaVersion: 1;
|
|
1816
|
+
readonly databaseId: string;
|
|
1817
|
+
readonly databaseName: string;
|
|
1818
|
+
/** 속성 스키마 — 이름 정렬. `.base` displayName 만으로는 잃는 타입/옵션을 보존. */
|
|
1819
|
+
readonly properties: SidecarProperty[];
|
|
1820
|
+
/** 모든 뷰(표현/미표현 공통) — Notion 순서 유지. */
|
|
1821
|
+
readonly views: SidecarView[];
|
|
1822
|
+
/** 정직한 손실 리포트 — 무엇이 `.base` 에서 누락/degrade 됐는가. */
|
|
1823
|
+
readonly degraded: DegradeNote[];
|
|
1824
|
+
}
|
|
1825
|
+
interface SidecarProperty {
|
|
1826
|
+
readonly name: string;
|
|
1827
|
+
readonly id: string;
|
|
1828
|
+
readonly type: string;
|
|
1829
|
+
/** select/multi_select/status 옵션명(있으면). */
|
|
1830
|
+
readonly options?: string[];
|
|
1831
|
+
}
|
|
1832
|
+
interface SidecarView {
|
|
1833
|
+
readonly id: string;
|
|
1834
|
+
readonly name: string;
|
|
1835
|
+
readonly notionType: string;
|
|
1836
|
+
/** Bases 대응 타입. `null` = `.base` 미표현(사이드카가 유일 보존처). */
|
|
1837
|
+
readonly basesType: BasesViewType | null;
|
|
1838
|
+
/** 원본 뷰 설정 중 의미 있는 항목(키 정렬). 빈 값은 생략. */
|
|
1839
|
+
readonly config: Record<string, unknown>;
|
|
1840
|
+
}
|
|
1841
|
+
interface DegradeNote {
|
|
1842
|
+
readonly view: string;
|
|
1843
|
+
readonly kind: "view-unrepresentable" | "setting-dropped";
|
|
1844
|
+
readonly detail: string;
|
|
1845
|
+
}
|
|
1846
|
+
declare class SidecarGenerator {
|
|
1847
|
+
/**
|
|
1848
|
+
* 사이드카 객체를 빌드한다(순수 — I/O 없음). 직렬화는 {@link serialize} 가 담당.
|
|
1849
|
+
*/
|
|
1850
|
+
build(options: {
|
|
1851
|
+
databaseId: string;
|
|
1852
|
+
databaseName: string;
|
|
1853
|
+
schema: Record<string, BasePropertySchema>;
|
|
1854
|
+
viewsConfig: DatabaseViewsConfig;
|
|
1855
|
+
}): NotionSidecar;
|
|
1856
|
+
/**
|
|
1857
|
+
* 사이드카를 결정적 JSON 문자열로 직렬화한다(키 재귀 정렬 + 말미 개행).
|
|
1858
|
+
* 동일 입력 → 동일 바이트 → 멱등(drift/churn 0).
|
|
1859
|
+
*/
|
|
1860
|
+
serialize(sidecar: NotionSidecar): string;
|
|
1861
|
+
private buildProperties;
|
|
1862
|
+
private buildView;
|
|
1863
|
+
/**
|
|
1864
|
+
* 뷰 설정 중 의미 있는 항목을 추출한다. `.base` 가 표현 못하는 항목(필터·커버크기 등)은
|
|
1865
|
+
* config 에 보존하면서 동시에 degrade 리포트에 1줄씩 기록한다(표현되는 뷰에 한해 —
|
|
1866
|
+
* 미표현 뷰는 이미 view-unrepresentable 로 통째 보고됨).
|
|
1867
|
+
*/
|
|
1868
|
+
private extractConfig;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1393
1871
|
declare function sortEntries(entries: DBEntry[], sorts: NonNullable<ViewConfig["sorts"]>): DBEntry[];
|
|
1394
1872
|
declare function groupEntries(entries: DBEntry[], groupByProperty: string, options?: {
|
|
1395
1873
|
sort?: "ascending" | "descending";
|
|
@@ -1414,12 +1892,95 @@ declare function getNotionColor(color: NotionColor | string): string;
|
|
|
1414
1892
|
declare function getNotionBgColor(color: NotionColor | string): string;
|
|
1415
1893
|
declare function generateColorCSS(): string;
|
|
1416
1894
|
|
|
1895
|
+
/**
|
|
1896
|
+
* 본문 링크 충실도(fidelity) 분류기 — 회귀 상시 잠금용 측정 인프라.
|
|
1897
|
+
*
|
|
1898
|
+
* pull 결과물(.md 본문)에 남은 notion 링크/멘션을 네 부류로 분류한다:
|
|
1899
|
+
*
|
|
1900
|
+
* 1. preservedMarkers — `[text](url)%%im-nobsidian:...%%` 형태의 의도된 무손실
|
|
1901
|
+
* 보존 마커(copy_indicator 등). push 라운드트립 복원용으로 *일부러* 남긴 것이라
|
|
1902
|
+
* 결함이 아니다.
|
|
1903
|
+
* 2. externalLinks — 동기화 코퍼스에 없는 외부 notion.so 링크. 위키링크로 환원할
|
|
1904
|
+
* 대상이 없으므로 맨 링크로 두는 게 정당하다.
|
|
1905
|
+
* 3. selfReferences — 자기 자신 페이지를 가리키는 링크/멘션(블록 앵커 등). 정당.
|
|
1906
|
+
* 4. defects — 동기화된 *다른* 페이지로의 미해소 링크/멘션. 이것만이 진짜
|
|
1907
|
+
* 충실도 결함이다(M2/M3/M4/M6 가 봉합한 대상).
|
|
1908
|
+
*
|
|
1909
|
+
* 변환 파이프라인이 완전하면 `defects.length === 0` 이어야 한다. 이 분류기를 픽스처로
|
|
1910
|
+
* 잠가(unit test) 두면 변환기 회귀가 즉시 드러나고, 실제 vault 에 대고 돌리면(아래
|
|
1911
|
+
* scripts/audit-vault.mjs) 코퍼스 전체의 충실도를 정량 측정할 수 있다.
|
|
1912
|
+
*
|
|
1913
|
+
* 마커/ID 판별은 SSOT 를 재사용한다 — 마커 브랜드는 {@link MARKER_BRAND},
|
|
1914
|
+
* 정규 id 는 {@link compactNotionId}. 별도 헬퍼를 만들지 않는다.
|
|
1915
|
+
*/
|
|
1916
|
+
/** 미해소로 남은 링크/멘션의 형태. */
|
|
1917
|
+
type FidelityDefectForm = "md-link" | "mention-page";
|
|
1918
|
+
/** 진짜 충실도 결함 — 동기화된 다른 페이지로의 미해소 링크/멘션. */
|
|
1919
|
+
interface FidelityDefect {
|
|
1920
|
+
/** 미해소 형태(맨 마크다운 링크 / 미변환 mention-page 태그). */
|
|
1921
|
+
form: FidelityDefectForm;
|
|
1922
|
+
/** 대상 페이지의 정규(compact) id. */
|
|
1923
|
+
targetPageId: string;
|
|
1924
|
+
/** 대상 페이지 제목(맵에 있으면). */
|
|
1925
|
+
targetTitle: string | null;
|
|
1926
|
+
/** 결함 원문 조각(진단용). */
|
|
1927
|
+
raw: string;
|
|
1928
|
+
}
|
|
1929
|
+
/** {@link classifyBodyFidelity} 분류 결과. */
|
|
1930
|
+
interface FidelityClassification {
|
|
1931
|
+
/** `[text](url)%%im-nobsidian:...%%` — 의도된 무손실 보존 마커(결함 아님). */
|
|
1932
|
+
preservedMarkers: number;
|
|
1933
|
+
/** 코퍼스에 없는 외부 notion.so 링크(정당). */
|
|
1934
|
+
externalLinks: number;
|
|
1935
|
+
/** 자기 자신 페이지를 가리키는 링크/멘션(정당). */
|
|
1936
|
+
selfReferences: number;
|
|
1937
|
+
/** 동기화된 다른 페이지로의 미해소 링크/멘션 — 진짜 결함. */
|
|
1938
|
+
defects: FidelityDefect[];
|
|
1939
|
+
}
|
|
1940
|
+
/** 분류에 필요한 코퍼스 컨텍스트. */
|
|
1941
|
+
interface FidelityContext {
|
|
1942
|
+
/** 동기화된 전체 페이지의 정규(compact) id 집합. */
|
|
1943
|
+
knownPageIds: ReadonlySet<string>;
|
|
1944
|
+
/** 정규 id → 제목. 선택(결함 진단용). */
|
|
1945
|
+
titleById?: ReadonlyMap<string, string>;
|
|
1946
|
+
/** 이 본문이 속한 페이지의 정규 id. 자기참조 판별용. 선택. */
|
|
1947
|
+
ownPageId?: string;
|
|
1948
|
+
}
|
|
1949
|
+
/**
|
|
1950
|
+
* 본문 문자열의 notion 링크/멘션 충실도를 분류한다. 순수 함수 — I/O 없음.
|
|
1951
|
+
*
|
|
1952
|
+
* @param body 분석할 마크다운 본문(프론트매터 포함 가능; 보존 마커·위키링크는 무시됨).
|
|
1953
|
+
* @param ctx 코퍼스 컨텍스트(알려진 페이지 id 집합 등).
|
|
1954
|
+
*/
|
|
1955
|
+
declare function classifyBodyFidelity(body: string, ctx: FidelityContext): FidelityClassification;
|
|
1956
|
+
/** 여러 본문의 분류 결과를 합산한 코퍼스 단위 요약. */
|
|
1957
|
+
interface FidelitySummary {
|
|
1958
|
+
files: number;
|
|
1959
|
+
preservedMarkers: number;
|
|
1960
|
+
externalLinks: number;
|
|
1961
|
+
selfReferences: number;
|
|
1962
|
+
defects: number;
|
|
1963
|
+
/** 결함이 1건 이상인 파일 경로 → 결함 목록. */
|
|
1964
|
+
defectsByFile: Record<string, FidelityDefect[]>;
|
|
1965
|
+
}
|
|
1966
|
+
/** 파일별 분류 결과(경로 + 분류)를 코퍼스 요약으로 합산한다. */
|
|
1967
|
+
declare function summarizeFidelity(perFile: ReadonlyArray<{
|
|
1968
|
+
path: string;
|
|
1969
|
+
classification: FidelityClassification;
|
|
1970
|
+
}>): FidelitySummary;
|
|
1971
|
+
|
|
1417
1972
|
declare function computeHash(content: string): string;
|
|
1418
1973
|
declare function computeBufferHash(buffer: Buffer): string;
|
|
1419
1974
|
|
|
1420
1975
|
declare function generateId(): string;
|
|
1421
1976
|
declare function normalizeNotionId(id: string): string;
|
|
1422
1977
|
declare function notionIdsEqual(a: string, b: string): boolean;
|
|
1978
|
+
/**
|
|
1979
|
+
* 멘션 위키링크용 정규 id — 하이픈 제거 + 소문자. 오케스트레이터의 멘션 해소 정규식
|
|
1980
|
+
* (`[[notion:<32hex>]]`)이 인식하는 유일한 형태다. 블록 폴백 경로(formatMention)와
|
|
1981
|
+
* markdown-api 경로(convertPageMentions)가 동일 형태로 수렴하도록 한다(rank20/I3).
|
|
1982
|
+
*/
|
|
1983
|
+
declare function compactNotionId(id: string): string;
|
|
1423
1984
|
|
|
1424
1985
|
declare function sanitizeFileName(name: string): string;
|
|
1425
1986
|
|
|
@@ -1437,4 +1998,47 @@ interface PipelineOptions {
|
|
|
1437
1998
|
}
|
|
1438
1999
|
declare function createDefaultPipeline(options?: PipelineOptions): ConversionPipeline;
|
|
1439
2000
|
|
|
1440
|
-
|
|
2001
|
+
/**
|
|
2002
|
+
* 내부 메타데이터 경로 단일 진실원(SSOT).
|
|
2003
|
+
*
|
|
2004
|
+
* `.im-nobsidian` 디렉터리·하위 파일명·ignore 파일·gitignore 항목을 한곳에서 정의한다.
|
|
2005
|
+
* 디렉터리/파일명을 바꾸거나 리브랜딩할 때 이 파일만 수정하면 전 패키지에 반영된다.
|
|
2006
|
+
*/
|
|
2007
|
+
/** 내부 메타데이터 디렉터리명 (vault 루트 기준). */
|
|
2008
|
+
declare const INTERNAL_DIR = ".im-nobsidian";
|
|
2009
|
+
/** 동기화 설정 파일명. */
|
|
2010
|
+
declare const CONFIG_FILE = "config.json";
|
|
2011
|
+
/** SQLite 상태 DB 파일명. */
|
|
2012
|
+
declare const STATE_DB_FILE = "sync.db";
|
|
2013
|
+
/** DB 뷰 메타데이터 파일명. */
|
|
2014
|
+
declare const DB_VIEWS_FILE = "db-views.json";
|
|
2015
|
+
/** 동기화 제외 패턴 파일명 (디렉터리 외부, vault 루트 직속). */
|
|
2016
|
+
declare const IGNORE_FILE = ".im-nobsidian-ignore";
|
|
2017
|
+
/** `.gitignore`에 추가하는 항목. */
|
|
2018
|
+
declare const GITIGNORE_ENTRY = ".im-nobsidian/";
|
|
2019
|
+
/** vault 상대 POSIX 경로 — DB 뷰 메타데이터. */
|
|
2020
|
+
declare const DB_VIEWS_PATH = ".im-nobsidian/db-views.json";
|
|
2021
|
+
/** vault 상대 POSIX 경로 — 상태 DB. */
|
|
2022
|
+
declare const STATE_DB_PATH = ".im-nobsidian/sync.db";
|
|
2023
|
+
/** 파일 감시에서 제외할 내부 디렉터리 glob. */
|
|
2024
|
+
declare const INTERNAL_DIR_GLOB = "**/.im-nobsidian/**";
|
|
2025
|
+
/**
|
|
2026
|
+
* 주어진 vault 상대 경로(POSIX)가 내부 메타데이터 디렉터리에 속하는지 판별한다.
|
|
2027
|
+
*/
|
|
2028
|
+
declare function isInternalPath(path: string): boolean;
|
|
2029
|
+
|
|
2030
|
+
/**
|
|
2031
|
+
* 보존 마커(preserve marker) 단일 진실원(SSOT).
|
|
2032
|
+
*
|
|
2033
|
+
* 변환 불가 요소를 라운드트립 보존하기 위해 본문에 삽입하는 마커가 공유하는
|
|
2034
|
+
* 브랜드 토큰을 한곳에서 정의한다. 리브랜딩 시 {@link MARKER_BRAND}만 바꾸면
|
|
2035
|
+
* 모든 빌더·정규식에 반영된다.
|
|
2036
|
+
*
|
|
2037
|
+
* 주의: 마커 포맷(공백 유무)은 기존 vault·Notion에 이미 기록된 데이터와의 호환을
|
|
2038
|
+
* 위해 변경하지 않는다. 두 포맷이 공존한다 — 공백형(`%% brand:... %%`)과
|
|
2039
|
+
* 압축형(`%%brand:...%%`).
|
|
2040
|
+
*/
|
|
2041
|
+
/** 모든 보존 마커가 공유하는 브랜드 토큰. */
|
|
2042
|
+
declare const MARKER_BRAND = "im-nobsidian";
|
|
2043
|
+
|
|
2044
|
+
export { BaseFileGenerator, type BaseFileOptions, type BasePropertySchema, type BaseSyncOptions, type BasesViewType, BlockConverter, CONFIG_FILE, type CalendarEntry, CalloutRestorer, CalloutTransformer, ChangeDetector, ColorAnnotator, type Config, ConfigManager, ConfigSchema, type Conflict, ConflictResolver, type ConflictStrategy, type ConversionContext, type ConversionPath, ConversionPipeline, type ConversionResult, type CoverConfig, type DBEntry, DB_VIEWS_FILE, DB_VIEWS_PATH, DEFAULT_CONFIG, type DatabaseSyncConfig, type DatabaseSyncResult, DatabaseSyncer, type DatabaseViewsConfig, type DegradeNote, EmbedResolver, EntryEditor, type FailedOperation, type FidelityClassification, type FidelityContext, type FidelityDefect, type FidelityDefectForm, type FidelitySummary, type FileDownloadResult, FileHandler, type FileInfo, type FileRegistryEntry, type FileStatInfo, type FileSystemEntry, type FileType, type FileUploadResult, FileWatcher, type FilterCondition, FrontmatterExtractor, FrontmatterGenerator, GITIGNORE_ENTRY, type GroupByConfig, type GroupedEntries, IGNORE_FILE, INTERNAL_DIR, INTERNAL_DIR_GLOB, type IStateDB, type ImageDownloadResult, ImageHandler, type ImageReference, InlineAnnotationPreserver, InlineDBParser, type LocalChange, LocalImageRestorer, type Logger, MARKER_BRAND, type MappingResult, type MappingType, MathNormalizer, MentionToWikilink, type MergeConflictRegion, type MergeResult, NOTION_TO_BASES_VIEW, NodeVaultFS, type NonMdFileInfo, type NotionBlock, NotionBlockBuilder, NotionClient, type NotionClientOptions, type NotionColor, type NotionPageNode, type NotionRichText, type RichTextItem as NotionRichTextItem, type NotionSidecar, type OperationType, type PageCover, type PageCreationPlan, type PageIcon, type PathFilterConfig, type PendingOperation, type PreserveMarker, PreserveMarkerCollector, PreserveMarkerInjector, type Processor, type ProcessorInput, type ProcessorMetadata, type ProcessorOutput, type ProgressCallback, type ProgressItem, PropertiesTableInjector, PropertiesTableRestorer, PropertyMapper, type PropertyOption, type PropertySchema, type PropertyValue, type PullOptions, type PullResult, type PushOptions, type PushResult, type RecordPendingInput, type RegisterFileInput, type RemoteChange, type ResolutionChoice, type ResolutionResult, type RichTextSegment, STATE_DB_FILE, STATE_DB_PATH, SidecarGenerator, type SidecarProperty, type SidecarView, StateDB, type StatusResult, type SyncDirection, type SyncOptions, SyncOrchestrator, type SyncRecord, type SyncResult, type SyncStatus, TreeMapper, UnsupportedBlockStripper, type UpsertSyncRecord, type VaultFS, type ViewConfig, ViewDataProvider, type ViewPropertyConfig, type ViewRenderData, type ViewType, type WatchSyncOptions, WatchSyncService, type WikilinkEntry, WikilinkResolver, basesViewTypeOf, classifyBodyFidelity, compactNotionId, computeBufferHash, computeHash, createDefaultPipeline, extractCalendarEntries, filterByMonth, filterEntries, generateColorCSS, generateId, getLogger, getNotionBgColor, getNotionColor, getVisibleProperties, groupEntries, isInternalPath, normalizeNotionId, notionIdsEqual, sanitizeFileName, setLogger, sortEntries, summarizeFidelity, threeWayMerge };
|