@jahands/notion-client
Version:
A simple and easy to use client for the Notion API
408 lines • 14 kB
JavaScript
import { appendBlockChildren, createComment, createDatabase, createPage, deleteBlock, getBlock, getDatabase, getPage, getPageProperty, getSelf, getUser, listBlockChildren, listComments, listDatabases, listUsers, oauthToken, queryDatabase, search, updateBlock, updateDatabase, updatePage, } from './api-endpoints';
import { buildRequestError, isHTTPResponseError, isNotionClientError } from './errors';
import { LogLevel, logLevelSeverity, makeConsoleLogger } from './logging';
import { PACKAGE_NAME, PACKAGE_VERSION } from './package';
import { pick } from './utils';
export default class Client {
#auth;
#logLevel;
#logger;
#prefixUrl;
#notionVersion;
#userAgent;
static defaultNotionVersion = '2022-06-28';
constructor(options) {
this.#auth = options?.auth;
this.#logLevel = options?.logLevel ?? LogLevel.WARN;
this.#logger = options?.logger ?? makeConsoleLogger(PACKAGE_NAME);
this.#prefixUrl = (options?.baseUrl ?? 'https://api.notion.com') + '/v1/';
this.#notionVersion = options?.notionVersion ?? Client.defaultNotionVersion;
this.#userAgent = `notionhq-client/${PACKAGE_VERSION}`;
}
/**
* Sends a request.
*
* @param path
* @param method
* @param query
* @param body
* @returns
*/
async request({ path, method, query, body, auth, }) {
this.log(LogLevel.INFO, 'request start', { method, path });
// If the body is empty, don't send the body in the HTTP request
const bodyAsJsonString = !body || Object.entries(body).length === 0 ? undefined : JSON.stringify(body);
const url = new URL(`${this.#prefixUrl}${path}`);
if (query) {
for (const [key, value] of Object.entries(query)) {
if (value !== undefined) {
if (Array.isArray(value)) {
value.forEach((val) => url.searchParams.append(key, decodeURIComponent(val)));
}
else {
url.searchParams.append(key, String(value));
}
}
}
}
// Allow both client ID / client secret based auth as well as token based auth.
let authorizationHeader;
if (typeof auth === 'object') {
// Client ID and secret based auth is **ONLY** supported when using the
// `/oauth/token` endpoint. If this is the case, handle formatting the
// authorization header as required by `Basic` auth.
const unencodedCredential = `${auth.client_id}:${auth.client_secret}`;
const encodedCredential = Buffer.from(unencodedCredential).toString('base64');
authorizationHeader = { authorization: `Basic ${encodedCredential}` };
}
else {
// Otherwise format authorization header as `Bearer` token auth.
authorizationHeader = this.authAsHeaders(auth);
}
const headers = {
...authorizationHeader,
'Notion-Version': this.#notionVersion,
'user-agent': this.#userAgent,
};
if (bodyAsJsonString !== undefined) {
headers['content-type'] = 'application/json';
}
try {
const response = await fetch(url.toString(), {
method: method.toUpperCase(),
headers,
body: bodyAsJsonString,
signal: AbortSignal.timeout(60_000),
});
const responseText = await response.text();
if (!response.ok) {
throw buildRequestError(response, responseText);
}
const responseJson = JSON.parse(responseText);
this.log(LogLevel.INFO, `request success`, { method, path });
return responseJson;
}
catch (error) {
if (!isNotionClientError(error)) {
throw error;
}
// Log the error if it's one of our known error types
this.log(LogLevel.WARN, `request fail`, {
code: error.code,
message: error.message,
});
if (isHTTPResponseError(error)) {
// The response body may contain sensitive information so it is logged separately at the DEBUG level
this.log(LogLevel.DEBUG, `failed response body`, {
body: error.body,
});
}
throw error;
}
}
/*
* Notion API endpoints
*/
blocks = {
/**
* Retrieve block
*/
retrieve: (args) => {
return this.request({
path: getBlock.path(args),
method: getBlock.method,
query: pick(args, getBlock.queryParams),
body: pick(args, getBlock.bodyParams),
auth: args?.auth,
});
},
/**
* Update block
*/
update: (args) => {
return this.request({
path: updateBlock.path(args),
method: updateBlock.method,
query: pick(args, updateBlock.queryParams),
body: pick(args, updateBlock.bodyParams),
auth: args?.auth,
});
},
/**
* Delete block
*/
delete: (args) => {
return this.request({
path: deleteBlock.path(args),
method: deleteBlock.method,
query: pick(args, deleteBlock.queryParams),
body: pick(args, deleteBlock.bodyParams),
auth: args?.auth,
});
},
children: {
/**
* Append block children
*/
append: (args) => {
return this.request({
path: appendBlockChildren.path(args),
method: appendBlockChildren.method,
query: pick(args, appendBlockChildren.queryParams),
body: pick(args, appendBlockChildren.bodyParams),
auth: args?.auth,
});
},
/**
* Retrieve block children
*/
list: (args) => {
return this.request({
path: listBlockChildren.path(args),
method: listBlockChildren.method,
query: pick(args, listBlockChildren.queryParams),
body: pick(args, listBlockChildren.bodyParams),
auth: args?.auth,
});
},
},
};
databases = {
/**
* List databases
*
* @deprecated Please use `search`
*/
list: (args) => {
return this.request({
path: listDatabases.path(),
method: listDatabases.method,
query: pick(args, listDatabases.queryParams),
body: pick(args, listDatabases.bodyParams),
auth: args?.auth,
});
},
/**
* Retrieve a database
*/
retrieve: (args) => {
return this.request({
path: getDatabase.path(args),
method: getDatabase.method,
query: pick(args, getDatabase.queryParams),
body: pick(args, getDatabase.bodyParams),
auth: args?.auth,
});
},
/**
* Query a database
*/
query: (args) => {
return this.request({
path: queryDatabase.path(args),
method: queryDatabase.method,
query: pick(args, queryDatabase.queryParams),
body: pick(args, queryDatabase.bodyParams),
auth: args?.auth,
});
},
/**
* Create a database
*/
create: (args) => {
return this.request({
path: createDatabase.path(),
method: createDatabase.method,
query: pick(args, createDatabase.queryParams),
body: pick(args, createDatabase.bodyParams),
auth: args?.auth,
});
},
/**
* Update a database
*/
update: (args) => {
return this.request({
path: updateDatabase.path(args),
method: updateDatabase.method,
query: pick(args, updateDatabase.queryParams),
body: pick(args, updateDatabase.bodyParams),
auth: args?.auth,
});
},
};
pages = {
/**
* Create a page
*/
create: (args) => {
return this.request({
path: createPage.path(),
method: createPage.method,
query: pick(args, createPage.queryParams),
body: pick(args, createPage.bodyParams),
auth: args?.auth,
});
},
/**
* Retrieve a page
*/
retrieve: (args) => {
return this.request({
path: getPage.path(args),
method: getPage.method,
query: pick(args, getPage.queryParams),
body: pick(args, getPage.bodyParams),
auth: args?.auth,
});
},
/**
* Update page properties
*/
update: (args) => {
return this.request({
path: updatePage.path(args),
method: updatePage.method,
query: pick(args, updatePage.queryParams),
body: pick(args, updatePage.bodyParams),
auth: args?.auth,
});
},
properties: {
/**
* Retrieve page property
*/
retrieve: (args) => {
return this.request({
path: getPageProperty.path(args),
method: getPageProperty.method,
query: pick(args, getPageProperty.queryParams),
body: pick(args, getPageProperty.bodyParams),
auth: args?.auth,
});
},
},
};
users = {
/**
* Retrieve a user
*/
retrieve: (args) => {
return this.request({
path: getUser.path(args),
method: getUser.method,
query: pick(args, getUser.queryParams),
body: pick(args, getUser.bodyParams),
auth: args?.auth,
});
},
/**
* List all users
*/
list: (args) => {
return this.request({
path: listUsers.path(),
method: listUsers.method,
query: pick(args, listUsers.queryParams),
body: pick(args, listUsers.bodyParams),
auth: args?.auth,
});
},
/**
* Get details about bot
*/
me: (args) => {
return this.request({
path: getSelf.path(),
method: getSelf.method,
query: pick(args, getSelf.queryParams),
body: pick(args, getSelf.bodyParams),
auth: args?.auth,
});
},
};
comments = {
/**
* Create a comment
*/
create: (args) => {
return this.request({
path: createComment.path(),
method: createComment.method,
query: pick(args, createComment.queryParams),
body: pick(args, createComment.bodyParams),
auth: args?.auth,
});
},
/**
* List comments
*/
list: (args) => {
return this.request({
path: listComments.path(),
method: listComments.method,
query: pick(args, listComments.queryParams),
body: pick(args, listComments.bodyParams),
auth: args?.auth,
});
},
};
/**
* Search
*/
search = (args) => {
return this.request({
path: search.path(),
method: search.method,
query: pick(args, search.queryParams),
body: pick(args, search.bodyParams),
auth: args?.auth,
});
};
oauth = {
/**
* Get token
*/
token: (args) => {
return this.request({
path: oauthToken.path(),
method: oauthToken.method,
query: pick(args, oauthToken.queryParams),
body: pick(args, oauthToken.bodyParams),
auth: {
client_id: args.client_id,
client_secret: args.client_secret,
},
});
},
};
/**
* Emits a log message to the console.
*
* @param level The level for this message
* @param args Arguments to send to the console
*/
log(level, message, extraInfo) {
if (logLevelSeverity(level) >= logLevelSeverity(this.#logLevel)) {
this.#logger(level, message, extraInfo);
}
}
/**
* Transforms an API key or access token into a headers object suitable for an HTTP request.
*
* This method uses the instance's value as the default when the input is undefined. If neither are defined, it returns
* an empty object
*
* @param auth API key or access token
* @returns headers key-value object
*/
authAsHeaders(auth) {
const headers = {};
const authHeaderValue = auth ?? this.#auth;
if (authHeaderValue !== undefined) {
headers['authorization'] = `Bearer ${authHeaderValue}`;
}
return headers;
}
}
//# sourceMappingURL=Client.js.map