Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify and clean up #3

Merged
merged 11 commits into from
Feb 17, 2022
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"sourceType": "module"
},
"rules": {
"prettier/prettier": "error"
"prettier/prettier": "error",
"@typescript-eslint/no-explicit-any": 0
},
"plugins": ["prettier", "@typescript-eslint"]
}
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@
"AUTHORS"
],
"lint-staged": {
"*.js": "eslint --cache --fix"
"*.ts": [
"prettier -w",
"eslint --cache"
],
"*.md": "prettier -w",
"*.{json,jsonc}": "prettier -w"
},
"dependencies": {
"@notionhq/client": "0.4.11",
Expand Down
46 changes: 46 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import request from './request'
import type { Databases, Pages } from 'notion-api-types/endpoints'

export function queryDatabase(
args: {
token: string
database_id: string
} & Databases.Query.Request['params']
): Promise<Databases.Query.Response> {
const { token, database_id, ...params } = args
return request<Databases.Query.Request>({
token,
endpoint: `databases/${database_id}/query`,
method: 'POST',
params,
})
}

export function createPage(
args: {
token: string
} & Pages.Create.Request['params']
): Promise<Pages.Create.Response> {
const { token, ...params } = args
return request<Pages.Create.Request>({
token,
endpoint: 'pages',
method: 'POST',
params,
})
}

export function updatePage(
args: {
token: string
page_id: string
} & Pages.Update.Request['params']
): Promise<Pages.Update.Response> {
const { token, page_id, ...params } = args
return request<Pages.Update.Request>({
token,
endpoint: `pages/${page_id}`,
method: 'PATCH',
params: params,
})
}
64 changes: 64 additions & 0 deletions src/api/request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import fetch, { RequestInit } from 'node-fetch'
import { URL } from 'url'
import type { RequestTemplate } from 'notion-api-types/endpoints/global'

export default request
async function request<T extends RequestTemplate>(
args: Omit<T, 'headers'> & { token: string }
): Promise<any>
async function request(args: DirectRequest & { token: string }): Promise<any>
async function request<T extends RequestTemplate | DirectRequest>(
args: T extends RequestTemplate
? Omit<T, 'headers'> & { params?: any; token: string }
: T & { token: string }
) {
const init: RequestInit = {
method: args.method,
headers: {
'Notion-Version': '2021-08-16',
Authorization: `Bearer ${args.token}`,
},
}

let urlStr: string
if ('url' in args) urlStr = args.url
else {
const url = new URL('https://api.notion.com/v1/' + args.endpoint)
if ('params' in args)
if (args.method == 'GET')
for (const key in args.params)
url.searchParams.set(key, args.params[key].toString())
else init.body = JSON.stringify(args.params)
urlStr = url.href
}

const res = await fetch(urlStr, init)

if (res.ok) return await res.json()
else throw (await res.json()) as NotionError
}

interface DirectRequest {
method: 'GET' | 'POST' | 'PATCH'
url: string
}

export interface NotionError {
object: 'error'
status: number
code:
| 'invalid_json'
| 'invalid_request_url'
| 'invalid_request'
| 'validation_error'
| 'missing_version'
| 'unauthorized'
| 'restricted_resource'
| 'object_not_found'
| 'conflict_error'
| 'rate_limited'
| 'internal_server_error'
| 'service_unavailable'
| 'database_connection_unavailable'
message: string
}
39 changes: 39 additions & 0 deletions src/database.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { queryDatabase, createPage } from './api'
import { getEntireList } from './list'
import type { Item } from '.'
import type { CustomProps, Page, Properties } from './item'
import type { Filter } from 'notion-api-types/endpoints/databases/query'

export default class Database<P extends CustomProps> {
constructor(
private readonly item: ItemClass<P>,
private readonly token: string,
private readonly id: string,
private readonly filter?: Filter
) {}

readonly items = getEntireList(queryDatabase, {
token: this.token,
database_id: this.id,
filter: this.filter,
page_size: 100,
}).then(pages =>
pages.map(page => new this.item(this.token, page as Page<P>))
)

async newItem(properties: Partial<Properties<P>>) {
const page = await createPage({
token: this.token,
parent: { database_id: this.id },
properties: properties as Properties<P>,
})
const item = new this.item(this.token, page as Page<P>)
;(await this.items).push(item)
return item
}
}

type ItemClass<P extends CustomProps> = new (
token: string,
value: Page<P>
) => Item<P>
43 changes: 4 additions & 39 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,4 @@
import { getEntireList } from './list'
import NotionClient from './request'
export { NotionClient }
import type { Item, CustomProps, ItemParams, Page, Properties } from './item'
export { Item } from './item'
export type { CustomProps, Page, Properties }
export type { Property, PropertyType } from './item'
import type { Filter } from 'notion-api-types/endpoints/databases/query'

export class Database<P extends CustomProps, T extends Item<P>> {
constructor(
private readonly item: new (...args: ItemParams<P>) => T,
private readonly client: NotionClient,
private readonly id: string,
private readonly filter?: Filter
) {}

readonly items: T[] = []

readonly getItemsPromise = getEntireList({
method: this.client.database.query,
database_id: this.id,
page_size: 100,
filter: this.filter
}).then((list) => {
this.items.push(
...list.map(
item => new this.item(item as Page<P>, this.client.pages.update)
)
)
})

newItem(properties: Partial<Properties<P>>) {
return this.client.pages.create({
parent: { database_id: this.id },
properties: properties as Properties<P>,
})
}
}
import Database from './database'
import Item from './item'
export { Database, Item }
export type { Page, Property, PropertyType } from './item'
43 changes: 20 additions & 23 deletions src/item.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,37 @@
import NotionClient from './request'
import { updatePage } from './api'
import type { NotionRequest, NotionResponse } from 'notion-api-types'

export class Item<P extends CustomProps> {
readonly properties: Page<P>['properties']
export default class Item<P extends CustomProps> {
#props: Page<P>['properties']
protected get properties() {
return this.#props
}
private set properties(value: Page<P>['properties']) {
this.#props = value
}
private readonly id: Page<P>['id']
protected readonly updatePage: NotionClient['pages']['update']

constructor(...[value, updatePage]: ItemParams<P>) {
this.properties = value.properties
constructor(private readonly token: string, value: Page<P>) {
this.#props = value.properties
this.id = value.id
this.updatePage = updatePage
}

// getProp<K extends keyof P>(name: K) {
// const value = this.properties[name]
// return value[value.type as P[K]]
// }

update(props: Partial<Properties<P>>) {
return this.updatePage({
async update(props: Partial<Properties<P>>) {
const page = await updatePage({
token: this.token,
page_id: this.id,
properties: props as Properties<P>
properties: props as Properties<P>,
})
this.properties = page.properties as Page<P>['properties']
return page
}
}

export type ItemParams<P extends CustomProps> = [
data: Page<P>,
updatePage: Item<P>['updatePage']
]

export type PropertyType = NonNullable<NotionRequest.PageProperty['type']>

export type CustomProps = Record<string, PropertyType>

export type Property<T extends PropertyType> = T extends T
export type Property<T extends PropertyType = PropertyType> = T extends T
? Extract<NotionRequest.PageProperty, Record<T, any>>
: never

Expand All @@ -45,7 +42,7 @@ export type Properties<T extends CustomProps> = {
export interface Page<P extends CustomProps> extends NotionResponse.Page {
properties: {
[K in keyof P]: P[K] extends P[K]
? Extract<NotionResponse.PageProperty, Record<P[K], any>>
: never
? Extract<NotionResponse.PageProperty, Record<P[K], any>>
: never
}
}
31 changes: 12 additions & 19 deletions src/list.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
import type { List } from 'notion-api-types/endpoints/global'

export function getEntireList<
A extends {},
R extends List<any>,
T extends R['results'][number]
>(
args: {
method: (args: A) => Promise<R>
} & A
export async function getEntireList<A extends { start_cursor?: string }, T>(
method: (args: A) => Promise<List<T>>,
args: A
): Promise<T[]> {
return loop()
async function loop(next_cursor?: string | null) {
const { method, ...params } = args
// @ts-ignore
params.next_cursor = next_cursor
const response = await method(params as unknown as A)
const list = response.results as T[]
if (next_cursor && response.has_more)
list.push(...(await loop(response.next_cursor)))
return list
const list: T[] = []
async function requestPage(thisCursor?: string) {
args.start_cursor = thisCursor
const { results, next_cursor } = await method(args)
list.push(...results)
if (next_cursor) await requestPage(next_cursor)
}
}
await requestPage()
return list
}
Loading