Skip to content

Commit 0ea5aee

Browse files
feat: improve configuration of databases with union type
1 parent 20fe79b commit 0ea5aee

8 files changed

+144
-71
lines changed

src/ChildDatabaseRenderer.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Database } from "./Database";
77
import { DatabaseViewRenderer } from "./DatabaseViewRenderer";
88
import { DeferredRenderer } from "./DeferredRenderer";
99
import { RenderDatabasePageTask } from "./RenderDatabasePageTask";
10-
import { DatabaseConfig } from "./SyncConfig";
10+
import { DatabaseConfig, DatabaseConfigRenderPages, DatabaseConfigRenderTable } from "./SyncConfig";
1111
import { DatabaseTableRenderer } from "./DatabaseTableRenderer";
1212

1313
export class ChildDatabaseRenderer {
@@ -26,11 +26,12 @@ export class ChildDatabaseRenderer {
2626
const allPages = await this.fetchPages(databaseId, dbConfig);
2727

2828
const isCmsDb = this.config.cmsDatabaseId === databaseId;
29-
const renderPages = isCmsDb || dbConfig.views;
29+
const renderPages = isCmsDb || dbConfig.renderAs === "pages+views"
3030

3131
if (renderPages) {
32-
const entries = await this.queuePageRendering(allPages, dbConfig);
33-
const markdown = await this.viewRenderer.renderViews(entries, dbConfig);
32+
const pageConfig = dbConfig as DatabaseConfigRenderPages;
33+
const entries = await this.queuePageRendering(allPages, pageConfig);
34+
const markdown = await this.viewRenderer.renderViews(entries, dbConfig as DatabaseConfigRenderPages);
3435

3536
return {
3637
config: dbConfig,
@@ -51,7 +52,7 @@ export class ChildDatabaseRenderer {
5152

5253
private async queueEntryRendering(
5354
allPages: Page[],
54-
dbConfig: DatabaseConfig
55+
dbConfig: DatabaseConfigRenderTable
5556
) {
5657
const prepareRenderEntryTasks = allPages.map((x) =>
5758
this.deferredRenderer.renderEntry(x, dbConfig)
@@ -63,7 +64,7 @@ export class ChildDatabaseRenderer {
6364

6465
private async queuePageRendering(
6566
allPages: Page[],
66-
dbConfig: DatabaseConfig
67+
dbConfig: DatabaseConfigRenderPages
6768
): Promise<RenderDatabasePageTask[]> {
6869
const prepareRenderPageTasks = allPages.map((x) =>
6970
this.deferredRenderer.renderPage(x, dbConfig)

src/DatabasePageRenderer.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import * as fsc from 'fs';
1+
import * as fsc from "fs";
22

3-
import { Page } from '@notionhq/client/build/src/api-types';
3+
import { Page } from "@notionhq/client/build/src/api-types";
44

5-
import { AssetWriter } from './AssetWriter';
6-
import { FrontmatterRenderer } from './FrontmatterRenderer';
7-
import { logger } from './logger';
8-
import { PropertiesParser } from './PropertiesParser';
9-
import { RecursiveBodyRenderer } from './RecursiveBodyRenderer';
5+
import { AssetWriter } from "./AssetWriter";
6+
import { FrontmatterRenderer } from "./FrontmatterRenderer";
7+
import { logger } from "./logger";
8+
import { PropertiesParser } from "./PropertiesParser";
9+
import { RecursiveBodyRenderer } from "./RecursiveBodyRenderer";
1010
import { RenderDatabasePageTask as RenderDatabasePageTask } from "./RenderDatabasePageTask";
11-
import { slugify } from './slugify';
12-
import { DatabaseConfig } from './SyncConfig';
11+
import { slugify } from "./slugify";
12+
import { DatabaseConfigRenderPages } from "./SyncConfig";
1313

1414
const fs = fsc.promises;
1515

@@ -20,8 +20,11 @@ export class DatabasePageRenderer {
2020
readonly bodyRenderer: RecursiveBodyRenderer
2121
) {}
2222

23-
async renderPage(page: Page, config: DatabaseConfig): Promise<RenderDatabasePageTask> {
24-
if (page.archived){
23+
async renderPage(
24+
page: Page,
25+
config: DatabaseConfigRenderPages
26+
): Promise<RenderDatabasePageTask> {
27+
if (page.archived) {
2528
logger.warn(`rendering archived page ${page.url}`);
2629
}
2730

src/DatabaseViewRenderer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { LinkRenderer } from "./LinkRenderer";
22
import * as markdownTable from "./markdown-table";
33
import { PropertiesParser } from "./PropertiesParser";
44
import { RenderDatabasePageTask } from "./RenderDatabasePageTask";
5-
import { DatabaseConfig, DatabaseView } from "./SyncConfig";
5+
import { DatabaseConfigRenderPages, DatabaseView } from "./SyncConfig";
66
import { DatabaseTableRenderer } from "./DatabaseTableRenderer";
77

88
const debug = require("debug")("database");
@@ -11,7 +11,7 @@ const debug = require("debug")("database");
1111
export class DatabaseViewRenderer {
1212
constructor(private readonly linkRenderer: LinkRenderer) {}
1313

14-
public renderViews(entries: RenderDatabasePageTask[], config: DatabaseConfig): string {
14+
public renderViews(entries: RenderDatabasePageTask[], config: DatabaseConfigRenderPages): string {
1515
const views = config.views?.map((view) => {
1616
const propKeys = entries[0].properties.keys;
1717
const propKey = propKeys.get(view.properties.groupBy);

src/DeferredRenderer.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import { RenderedDatabaseEntry } from "./RenderedDatabaseEntry";
77
import { RenderedDatabasePage } from "./RenderedDatabasePage";
88
import { RenderDatabasePageTask as RenderDatabasePageTask } from "./RenderDatabasePageTask";
99
import { RenderDatabaseEntryTask } from "./RenderDatabaseEntryTask";
10-
import { DatabaseConfig } from "./SyncConfig";
10+
import {
11+
DatabaseConfigRenderPages,
12+
DatabaseConfigRenderTable,
13+
} from "./SyncConfig";
1114
import { Database } from "./Database";
1215
import { DatabaseEntryRenderer } from "./DatabaseEntryRenderer";
1316

@@ -39,7 +42,7 @@ export class DeferredRenderer {
3942

4043
public async renderPage(
4144
page: Page,
42-
config: DatabaseConfig
45+
config: DatabaseConfigRenderPages
4346
): Promise<RenderDatabasePageTask> {
4447
// cache to avoid rendering the same page twice, e.g. when it is linked multiple times
4548
const cached = this.renderedPages.get(page.id);
@@ -58,7 +61,7 @@ export class DeferredRenderer {
5861

5962
public async renderEntry(
6063
page: Page,
61-
config: DatabaseConfig
64+
config: DatabaseConfigRenderTable
6265
): Promise<RenderDatabaseEntryTask> {
6366
const task = await this.entryRenderer.renderEntry(page, config);
6467

src/MentionedPageRenderer.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ export class MentionedPageRenderer {
2525

2626
const dbConfig = lookupDatabaseConfig(this.config, databaseId);
2727

28+
if (dbConfig.renderAs !== "pages+views") {
29+
throw new Error(
30+
`Encountered page mention for page ${pageId}, but the mentioned page is not part of a database configured to render as 'pages+views'`
31+
);
32+
}
33+
2834
return this.deferredRenderer.renderPage(page, dbConfig);
2935
}
3036

src/PropertiesParser.ts

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { Page, PropertyValue } from '@notionhq/client/build/src/api-types';
1+
import { Page, PropertyValue } from "@notionhq/client/build/src/api-types";
22

3-
import { logger } from './logger';
4-
import { PageProperties } from './PageProperties';
5-
import { RichTextRenderer } from './RichTextRenderer';
6-
import { slugify } from './slugify';
7-
import { DatabaseConfig } from './SyncConfig';
3+
import { logger } from "./logger";
4+
import { PageProperties } from "./PageProperties";
5+
import { RichTextRenderer } from "./RichTextRenderer";
6+
import { slugify } from "./slugify";
7+
import { DatabaseConfig, DatabaseConfigRenderPages } from "./SyncConfig";
88

99
const debug = require("debug")("properties");
1010

@@ -21,17 +21,25 @@ export class PropertiesParser {
2121

2222
public async parsePageProperties(
2323
page: Page,
24-
config: DatabaseConfig,
24+
config: DatabaseConfigRenderPages
2525
): Promise<PageProperties> {
26-
const { title, category, order, properties, keys } = await this
27-
.parseProperties(page, config);
26+
const {
27+
title,
28+
category,
29+
order,
30+
properties,
31+
keys,
32+
} = await this.parseProperties(page, config);
2833

2934
if (!title) {
3035
throw this.errorMissingRequiredProperty("of type 'title'", page);
3136
}
3237

3338
if (!category) {
34-
throw this.errorMissingRequiredProperty(config.properties.category, page);
39+
throw this.errorMissingRequiredProperty(
40+
config.pages.frontmatter.category.property,
41+
page
42+
);
3543
}
3644

3745
return {
@@ -41,26 +49,35 @@ export class PropertiesParser {
4149
title: title, // notion API always calls it name
4250
category: category,
4351
order: order,
44-
...config.additionalPageFrontmatter,
52+
...config.pages.frontmatter.extra,
4553
},
4654
values: properties,
4755
keys: keys,
4856
};
4957
}
5058

51-
public async parseProperties(page: Page, config: DatabaseConfig) {
59+
public async parseProperties(page: Page, config: DatabaseConfig) {
60+
/**
61+
* Design: we always lookup the properties opn the page object itself.
62+
* This way we only parse properties once and avoid any problems coming from
63+
* e.g. category properties being filtered via include filters.
64+
*/
5265
const properties: Record<string, any> = {};
5366
const keys = new Map<string, string>();
5467

5568
let title: string | null = null;
5669
let category: string | null = null;
5770
let order: number | undefined = undefined;
5871

72+
const categoryProperty =
73+
config.renderAs === "pages+views" &&
74+
config.pages.frontmatter.category.property;
75+
5976
for (const [name, value] of Object.entries(page.properties)) {
6077
const parsedValue = await this.parsePropertyValue(value);
6178

6279
if (
63-
!config.properties.include ||
80+
!config.properties?.include ||
6481
config.properties.include.indexOf(name) >= 0
6582
) {
6683
const slug = slugify(name);
@@ -72,7 +89,7 @@ export class PropertiesParser {
7289
title = parsedValue;
7390
}
7491

75-
if (name === config.properties.category) {
92+
if (categoryProperty && name === categoryProperty) {
7693
category = parsedValue;
7794
}
7895

@@ -86,8 +103,8 @@ export class PropertiesParser {
86103
order,
87104
properties,
88105
keys: PropertiesParser.filterIncludedKeys(
89-
config.properties.include,
90-
keys,
106+
config.properties?.include,
107+
keys
91108
),
92109
};
93110
}
@@ -137,7 +154,7 @@ export class PropertiesParser {
137154

138155
public static filterIncludedKeys(
139156
includes: string[] | undefined,
140-
keys: Map<string, string>,
157+
keys: Map<string, string>
141158
): Map<string, string> {
142159
if (!includes) {
143160
return keys;

src/SyncConfig.ts

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Sort } from '@notionhq/client/build/src/api-types';
1+
import { Sort } from "@notionhq/client/build/src/api-types";
22

33
export interface SyncConfig {
44
/**
@@ -33,7 +33,11 @@ export interface SyncConfig {
3333
databases: Record<string, DatabaseConfig>;
3434
}
3535

36-
export interface DatabaseConfig {
36+
export type DatabaseConfig =
37+
| DatabaseConfigRenderPages
38+
| DatabaseConfigRenderTable;
39+
40+
export interface DatabaseConfigBase {
3741
/**
3842
* The output directory where the sync will place pages of this database.
3943
*
@@ -46,49 +50,71 @@ export interface DatabaseConfig {
4650
*/
4751
sorts?: Sort[];
4852

49-
/**
50-
* Add custom data to the page frontmatter
51-
*/
52-
additionalPageFrontmatter?: Record<string, any>;
53-
5453
/**
5554
* Configuration options for Notion API page properties
5655
*/
57-
properties: {
58-
/**
59-
* The Notion API page property that provides an optional sub-category value to use for the markdown page category.
60-
*
61-
* Example: "Cluster"
62-
*/
63-
category: string;
64-
56+
properties?: {
6557
/**
6658
* A whitelist of Notion API page property names to include in the markdown page properties.
6759
* Use this to select properties for export and control their ordering in rendered tables.
6860
*/
6961
include?: string[];
7062
};
7163

64+
renderAs: "table" | "pages+views";
65+
}
66+
67+
export interface DatabaseConfigRenderTable extends DatabaseConfigBase {
68+
renderAs: "table";
69+
70+
entries: {
71+
/**
72+
* Controls whether to emit database entries to the index (see SyncConfig.indexPath)
73+
*/
74+
emitToIndex: boolean;
75+
};
76+
77+
78+
}
79+
80+
export interface DatabaseConfigRenderPages extends DatabaseConfigBase {
81+
renderAs: "pages+views";
7282
/**
73-
* Configure "views" to render on the page where the child_database is encountered.
83+
* Add custom data to the page frontmatter
84+
*/
85+
pages: {
86+
frontmatter: {
87+
category: {
88+
/**
89+
* The Notion API page property that provides an optional sub-category value to use for the markdown page category.
90+
*
91+
* Example: "Cluster"
92+
*/
93+
property: string;
94+
};
95+
extra?: Record<string, any>;
96+
};
97+
};
98+
99+
/**
100+
* Configure "views" to render on the page where the child_database is encountered.
74101
* This allows to render different subsets of columns, groupings etc. to break down large tables.
75102
* Most useful as a "limited" replacement of more advanced database views, which are not exposed by Notion API
76103
* and not supported by markdown.
77-
*
78-
* Note:
79-
* - When views are defined, child database pages are rendered as individual pages.
104+
*
105+
* Note:
106+
* - When views are defined, child database pages are rendered as individual pages.
80107
* The first column of the view will link to the individual pages.
81-
* - When no views are defined, child database pages are rendered as rows in a plain markdown table with all included
108+
* - When no views are defined, child database pages are rendered as rows in a plain markdown table with all included
82109
* properties (see DatabaseConfig.properties.include).
83-
*
110+
*
84111
*/
85-
views?: DatabaseView[]
112+
views: DatabaseView[];
86113
}
87-
88114
export interface DatabaseView {
89115
title: string;
90116
properties: {
91117
groupBy: string;
92118
include?: string[];
93-
}
119+
};
94120
}

0 commit comments

Comments
 (0)