UNPKG

notion-helper

Version:

A library of functions for working more easily with the Notion API

1,109 lines (1,020 loc) 36.6 kB
import { setIcon } from "./emoji-and-files.mjs"; import { enforceRichText } from "./rich-text.mjs"; import { isValidURL, isValidUUID, validateDate, validateStringLength, validateArrayLength } from "./utils.mjs"; /** * Object with methods for constructing Notion page metadata, including parent, page, block, property, cover, and icon. * * Parent creates a parent object. Page, block, and property create ID objects. Cover creates an external image object, while icon can create an external image object or an emoji object. * * @namespace */ export const page_meta = { /** * Metadata definition for a parent entity. * * @namespace * @property {string} type - The data type the property accepts. */ parent: { type: "string", /** * Creates a parent object with a data_source_id, page_id, or database_id. * @function * @param {Object} params - Parameters for creating parent metadata. * @param {string} params.id - The ID of the parent. * @param {string} params.type - The type of the parent ("data_source_id", "page_id", or "database_id" (database_id is deprecated)). * @returns {Object} A parent metadata object. */ createMeta: ({ id, type }) => { if (type === "database_id") { console.warn("Creating a page with a parent database_id is deprecated, and will not work in databases with more than one data source. Use parentDataSource() with a data_source_id instead."); } // Handle common mistakes in the 'type' parameter if (typeof type === "string") { const normalized = type.toLowerCase().replace(/[-_]/g, ""); if (["page", "pageid"].includes(normalized)) { type = "page_id"; } else if (["database", "databaseid"].includes(normalized)) { type = "database_id"; } else if (["datasource", "datasourceid", "datasource_id", "data_source", "data_sourceid", "data_source_id"].includes(normalized)) { type = "data_source_id"; } } return { type: type, [type]: id, }; }, }, /** * Metadata definition for a page ID property. * * @namespace * @property {string} type - The data type the property accepts. */ page: { type: "string", /** * Creates a page_id object. * @function * @param {string} page_id - The ID of the page. * @returns {string} A string-validated page ID. */ createMeta: (page_id) => validateValue(page_id, "UUID"), }, /** * Metadata definition for a block ID property. * * @namespace * @property {string} type - The data type the property accepts. */ block: { type: "string", /** * Creates a block_id object. * @function * @param {string} block_id - The ID of the block. * @returns {string} A string-validated block ID. */ createMeta: (block_id) => validateValue(block_id, "UUID"), }, /** * Metadata definition for a property ID property. * * @namespace * @property {string} type - The data type the property accepts. */ property: { type: "string", /** * Creates a property_id object. * @function * @param {string} property_id - The ID of the property. * @returns {string} A string-validated property ID. */ createMeta: (property_id) => validateValue(property_id, "string"), }, /** * Metadata definition for an icon. * * @namespace * @property {string} type - The data type the property accepts. */ icon: { type: "string", /** * Creates an icon object. * @function * @param {string} value - The icon value (URL for "external" or emoji character). * @returns {Object} An icon metadata object. */ createMeta: (value) => setIcon(value), }, /** * Metadata definition for a page cover. * * @namespace * @property {string} type - The data type the property accepts. */ cover: { type: "string", /** * Creates a page cover object. * @function * @param {string} value - The URL of the cover image. * @returns {Object} A cover metadata object. */ createMeta: (value) => setIcon(value), }, /** * Metadata definition for a data source template. * * @namespace * @property {string} type - The data type the property accepts. */ template: { type: "string", /** * Creates a data source template object. * @function * @param {(Object|string)} templateChoice - The template to use for the page. Can be: * - A fully-formed template object, e.g.: * { * type: "template_id", * template_id: "your-template-id" * } * - A string value: * - "none": Do not use a template. * - "default": Use the default template, if available. * - A valid template page ID (a valid UUID string). * @returns {Object} A data source template metadata object. */ createMeta: (templateChoice) => { if (templateChoice === undefined || templateChoice === null || typeof templateChoice !== "string" && typeof templateChoice !== "object") { console.warn("template() method called in builder without a valid template choice. Ignoring this method call."); return null; } if (typeof templateChoice === "string") { if (templateChoice.toLowerCase() === "none") { return null; } else if (templateChoice.toLowerCase() === "default") { return { type: "default" }; } else if (isValidUUID(templateChoice)) { return { type: "template_id", template_id: templateChoice }; } else { console.warn(`Invalid template choice: ${templateChoice} – returning null.`) return null; } } else if (typeof templateChoice === "object") { // Check that the object has a type property if (!templateChoice.hasOwnProperty("type")) { console.warn(`Template object does not have a "type" property. Returning null.`); return null; } if (templateChoice.type === "template_id" && templateChoice.hasOwnProperty("template_id") && isValidUUID(templateChoice.template_id)) { return templateChoice; } else if (templateChoice.type === "default" || templateChoice.type === "none") { return templateChoice; } else { console.warn(`Invalid template choice: ${templateChoice} – returning null.`) return null; } } } }, }; /* * Quality-of-life functions for page meta: */ /** * Page shorthand methods - these allow you to call the createMeta() method for the properties of the page_meta object more quickly. Import them directly into a file, or call them on NotionHelper. * @namespace PageShorthand */ /** * Creates a parent database object. Deprecated in September 2025. Will not work in databases with more than one data source. * @memberof PageShorthand * @param {string} database_id - The ID of the parent database. * @returns {Object} A parent database object. */ export function parentDatabase(database_id) { return page_meta.parent.createMeta({ id: database_id, type: "database_id", }); } /** * Alias for parentDatabase(). Creates a parent database object. Deprecated in September 2025. Will not work in databases with more than one data source. * @memberof PageShorthand * @param {string} database_id - The ID of the parent database. * @returns {Object} A parent database object. */ export function parentDb(database_id) { return parentDatabase(database_id); } /** * Creates a parent data source object. * @memberof PageShorthand * @param {string} data_source_id - The ID of the parent data source. * @returns {Object} A parent data source object. */ export function parentDataSource(data_source_id) { return page_meta.parent.createMeta({ id: data_source_id, type: "data_source_id" }); } /** * Alias for parentDataSource(). Creates a parent data source object. * @memberof PageShorthand * @param {string} data_source_id - The ID of the parent data source. * @returns {Object} A parent data source object. */ export function parentDs(data_source_id) { return parentDataSource(data_source_id); } /** * Creates a parent page object. * @memberof PageShorthand * @param {string} page_id - The ID of the parent page. * @returns {Object} A parent page object. */ export function parentPage(page_id) { return page_meta.parent.createMeta({ id: page_id, type: "page_id" }); } /** * Creates a page_id object. Used for retrieving pages and page properties, updating page properties, and trashing pages. * @memberof PageShorthand * @param {string} page_id - The ID of the page to be read/updated/archived. * @returns {Object} A page_id object. */ export function pageId(page_id) { return page_meta.page.createMeta(page_id); } /** * Creates a block_id object. Used for all block endpoints. * @memberof PageShorthand * @param {string} block_id * @returns {Object} A block_id object. */ export function blockId(block_id) { return page_meta.block.createMeta(block_id); } /** * Creates a property_id object. Used for retrieving a page property item. * @memberof PageShorthand * @param {string} property_id * @returns {Object} A property_id object. */ export function propertyId(property_id) { return page_meta.property.createMeta(property_id); } /** * Creates a cover object. * @memberof PageShorthand * @param {string} url - The URL of the cover image. * @returns {Object} A cover object. */ export function cover(url) { return page_meta.cover.createMeta(url); } /** * Creates an icon object. * @memberof PageShorthand * @param {string} url - The URL of the icon image or an emoji character. * @returns {Object} An icon object. */ export function icon(url) { return page_meta.icon.createMeta(url); } /** * Object with methods for constructing each of the possible property types within a Notion database page. * * Property types include title, rich_text, checkbox, date, email, files, multi_select, number, people, phone_number, relation, select, status, and url. * * @namespace */ export const page_props = { /** * Methods for title properties. * * @namespace * @property {string} type - The data type the property accepts. */ title: { type: "string[]", /** * Sets a title property's value. * @function * @param {Object[]} value - The array of Rich Text Objects for the title content. * @returns {Object} A title property object. * * Notion API will throw an error if title doesn't contain an array of Rich Text object(s) (RTOs). * setProp() will convert a string, or array of strings, to an array of Rich Text object(s). * On other invalid input, it will throw an error. */ setProp: (value) => ({ title: validateValue(value, "rich_text"), }), }, /** * Methods for rich text properties. * * @namespace * @property {string} type - The data type the property accepts. */ rich_text: { type: "string[]", returns: "rich_text", /** * Sets a rich text property's value. * @function * @param {Object[]} value - The array of Rich Text Objects for the rich text content. * @returns {Object} A rich text property object. */ setProp: (value) => ({ rich_text: validateValue(value, "rich_text"), }), }, /** * Methods for checkbox properties. * * @namespace * @property {string} type - The data type the property accepts. */ checkbox: { type: "boolean", returns: "boolean", /** * Sets a checkbox property's value. * @function * @param {boolean} value - The boolean value for the checkbox state. * @returns {Object} A checkbox property object. */ setProp: (value) => ({ checkbox: validateValue(value, "boolean"), }), }, /** * Methods for date properties. * * @namespace * @property {string} type - The data type the property accepts. */ date: { type: "string", returns: "date", /** * Sets a date property's value. * @function * @param {string} start - The start date. * @param {string} [end=null] - The optional end date. * @returns {Object} A date property object. */ setProp: (start, end = null) => { const date = { date: { start: validateValue(start, "date"), end: end ? validateValue(end, "date") : null, }, }; if (!date || !date.date || date.date.start == null) { return { date: null, }; } return date; }, }, /** * Methods for email properties. * * @namespace * @property {string} type - The data type the property accepts. */ email: { type: "string", returns: "email", /** * Sets an email property's value. * @function * @param {string} value - The email address. * @returns {Object} An email property object. */ setProp: (value) => ({ email: validateValue(value, "email"), }), }, /** * Methods for files properties. * * @namespace * @property {string} type - The data type the property accepts. */ files: { type: "string[]", returns: "array", /** * Sets a files property's value. * @function * @param {(string|Array<string|Array<string>|Object>|Object)} files - A url/file ID string, an array of url/file ID strings, an array of [url/file ID, name] arrays, an array of file objects, or a single file object. File objects can be simple - ({ name, url }) or ({ name, id }) - or fully constructed ({ name, external: { url }}) or ({ name, file_upload: { id }}) * @param {string} [fileName] - a name for a singular file. Used if a string value is passed for the files parameter. If not provided, the file's URL/ID will be used for the name. * @returns {Object} A files property object. */ setProp: (files, fileName) => { const processFile = (file) => { if (typeof file === "string") { const isFileUpload = validateValue(file, "UUID"); const isExternal = validateValue(file, "url"); const isValidFile = isFileUpload || isExternal; const needsName = isExternal || (isFileUpload && fileName); if (!isValidFile) { return null; } else { return { ...(needsName && { name: fileName && fileName !== "" ? validateValue(fileName, "string") : validateValue(file, "string") }), [isExternal ? "external" : "file_upload"]: { [isExternal ? "url" : "id"]: file, }, }; } } else if (Array.isArray(file) && file.length === 2) { const [urlOrId, name] = file; const isFileUpload = validateValue(urlOrId, "UUID"); const isExternal = validateValue(urlOrId, "url"); const isValidFile = isFileUpload || isExternal; const needsName = isExternal || (isFileUpload && name); if (!isValidFile) { return null; } else { return { ...(needsName && { name: validateValue(name, "string") ? validateValue(name, "string") : validateValue(urlOrId, "string") }), [isExternal ? "external" : "file_upload"]: { [isExternal ? "url" : "id"]: urlOrId, }, }; } } else if (typeof file === "object") { if (file.external && file.external.url) { if (!validateValue(file.external.url, "url")) { return null; } else { return { name: validateValue(file.name, "string") ? validateValue(file.name, "string") : validateValue( file.external.url, "string" ), external: { url: validateValue( file.external.url, "url" ), }, }; } } else if (file.file_upload && file.file_upload.id) { if (!validateValue(file.file_upload.id, "UUID")) { return null; } else { return { ...(file.name && validateValue(file.name, "string") !== "" && { name: validateValue(file.name, "string") }), file_upload: { id: file.file_upload.id }, }; } } else if (file.url) { if (!validateValue(file.url, "url")) { return null; } else { return { name: validateValue(file.name, "string") ? validateValue(file.name, "string") : validateValue(file.url, "string"), external: { url: validateValue(file.url, "url"), }, }; } } else if (file.id) { if (!validateValue(file.id, "UUID")) { return null; } else { return { ...(file.name && validateValue(file.name, "string") !== "" && { name: validateValue(file.name, "string") }), file_upload: { id: validateValue(file.id, "UUID") }, }; } } } return null; }; let fileObjects; if (typeof files === "string") { fileObjects = [processFile(files)]; } else if (Array.isArray(files)) { fileObjects = files.map(processFile).filter(Boolean); } else if (typeof files === "object") { fileObjects = [processFile(files)]; } else { return { files: null, }; } return fileObjects.length > 0 ? { files: fileObjects, } : { files: null, }; }, }, /** * Methods for multi_select properties. * * @namespace * @property {string} type - The data type the property accepts. */ multi_select: { type: "string[]", returns: "array", /** * Sets a multi_select property's value. * @function * @param {(string|string[])} values - A single string value or an array of string values. * @returns {Object} A multi-select property object. */ setProp: (values) => { if (typeof values === "string") { // single string case const validatedValue = validateValue(values, "string"); return { multi_select: validatedValue ? [{ name: validatedValue }] : null, }; } else if (Array.isArray(values)) { // array case validateArrayLength({ array: values, type: "multi_select" }); const validValues = values .map((value) => { const validatedValue = validateValue(value, "string"); return validatedValue ? { name: validatedValue } : null; }) .filter(Boolean); return { multi_select: validValues.length > 0 ? validValues : null, }; } else { // invalid input return { multi_select: null, }; } }, }, /** * Methods for number properties. * * @namespace * @property {string} type - The data type the property accepts. */ number: { type: "number", returns: "number", /** * Sets a number property's value. * @function * @param {(number|string)} value - The numeric value. A string may also be passed, and setProp() will attempt to convert it to a number. * @returns {Object} A number property object. */ setProp: (value) => ({ number: validateValue(value, "number"), }), }, /** * Methods for people properties. * * @namespace * @property {string} type - The data type the property accepts. */ people: { type: "string[]", returns: "array", /** * Sets a people property's value. * @function * @param {(string|Array<string|Object>)} values - A single person ID, an array of person IDs, an array of user objects, or a single user object. * @returns {Object} A people property object. */ setProp: (values) => { const processUser = (value) => { if (typeof value === "string") { const person = validateValue(value, "string"); return person ? { object: "user", id: person } : null; } else if (typeof value === "object" && value !== null) { if (value.id && value.id !== "") { return { object: "user", id: value, }; } } return null; }; let people; if (typeof values === "string") { people = [processUser(values)]; } else if (Array.isArray(values)) { validateArrayLength({ array: values, type: "people" }); people = values.map(processUser).filter(Boolean); } else if (typeof values === "object" && values !== null) { people = [processUser(values)]; } else { return { people: null, }; } return { people: people.length > 0 ? people : null, }; }, }, /** * Methods for phone_number properties. * * @namespace * @property {string} type - The data type the property accepts. */ phone_number: { type: "string", returns: "string", /** * Sets a phone number property's value. * @function * @param {string} value - The phone number. * @returns {Object} A phone number property object. */ setProp: (value) => ({ phone_number: validateValue(value, "phone_number"), }), }, /** * Methods for relation properties. * * @namespace * @property {string} type - The data type the property accepts. */ relation: { type: "string[]", returns: "array", /** * Sets a relation property's value. * @function * @param {(string|Array<string|Object>)} values - A single page ID, an array of page IDs, an array of page objects, or a single page object. * @returns {Object} A relation property object. */ setProp: (values) => { const processRelation = (value) => { if (typeof value === "string") { const page = validateValue(value, "string"); return page ? { id: page } : null; } else if (typeof value === "object") { if (value.id) { return { id: validateValue(value.id, "string") }; } } return null; }; let relations; if (typeof values === "string") { // Single string case relations = [processRelation(values)]; } else if (Array.isArray(values)) { // Array case validateArrayLength({ array: values, type: "relation" }); relations = values.map(processRelation).filter(Boolean); } else if (typeof values === "object") { // Single object case relations = [processRelation(values)]; } else { // Invalid input return { relation: null }; } return { relation: relations.length > 0 ? relations : null, }; }, }, /** * Methods for select properties. * * @namespace * @property {string} type - The data type the property accepts. */ select: { type: "string", returns: "string", /** * Sets a select property's value. * @function * @param {string} value - The selected value. * @returns {Object} A select property object. */ setProp: (value) => ({ select: { name: validateValue(value, "string"), }, }), }, /** * Methods for status properties. * * @namespace * @property {string} type - The data type the property accepts. */ status: { type: "string", returns: "string", /** * Sets a status property's value. * @function * @param {string} value - The status value. * @returns {Object} A status property object. */ setProp: (value) => ({ status: { name: validateValue(value, "string"), }, }), }, /** * Methods for URL properties. * * @namespace * @property {string} type - The data type the property accepts. */ url: { type: "string", returns: "string", /** * Sets a URL property's value. * @function * @param {string} value - The URL. * @returns {Object} A URL property object. */ setProp: (value) => ({ url: validateValue(value, "url"), }), }, }; /* * Quality-of-life functions for page props: */ /** * Property shorthand methods - these allow you to call the setProp() method for the properties of the page_props object more quickly. Import them directly into a file, or call them on NotionHelper. * @namespace PropertyShorthand */ /** * Creates a title property object. * @memberof PropertyShorthand * @param {string|string[]} value - The title content, either a string or an array of strings. * @returns {Object} A title property object. */ export function title(value) { return page_props.title.setProp(value); } /** * Creates a rich text property object. * @memberof PropertyShorthand * @param {string|string[]} value - The rich text content, either a string or an array of strings. * @returns {Object} A rich text property object. */ export function richText(value) { return page_props.rich_text.setProp(value); } /** * Creates a checkbox property object. * @memberof PropertyShorthand * @param {boolean} value - The boolean value for the checkbox state. * @returns {Object} A checkbox property object. */ export function checkbox(value) { return page_props.checkbox.setProp(value); } /** * Creates a date property object. * @memberof PropertyShorthand * @param {string} start - The start date in ISO 8601 format. * @param {string} [end] - The optional end date in ISO 8601 format. * @returns {Object} A date property object. */ export function date(start, end) { return page_props.date.setProp(start, end); } /** * Creates an email property object. * @memberof PropertyShorthand * @param {string} value - The email address. * @returns {Object} An email property object. */ export function email(value) { return page_props.email.setProp(value); } /** * Creates a files property object. * @memberof PropertyShorthand * @param {(string|Array<string>|Array<{name: string, url: string})>} files - A url string, or an array of url strings, or an array of file objects. * @returns {Object} A files property object. */ export function files(files) { return page_props.files.setProp(files); } /** * Creates a multi-select property object. * @memberof PropertyShorthand * @param {(string|string[])} values - A single string value or an array of string values. * @returns {Object} A multi-select property object. */ export function multiSelect(values) { return page_props.multi_select.setProp(values); } /** * Creates a number property object. * @memberof PropertyShorthand * @param {number|string} value - The numeric value. A string may also be passed, and it will be converted to a number if possible. * @returns {Object} A number property object. */ export function number(value) { return page_props.number.setProp(value); } /** * Creates a people property object. * @memberof PropertyShorthand * @param {(string|string[])} people - A single person ID or an array of person IDs. * @returns {Object} A people property object. */ export function people(people) { return page_props.people.setProp(people); } /** * Creates a phone number property object. * @memberof PropertyShorthand * @param {string} value - The phone number. * @returns {Object} A phone number property object. */ export function phoneNumber(value) { return page_props.phone_number.setProp(value); } /** * Creates a relation property object. * @memberof PropertyShorthand * @param {(string|string[])} values - A single page ID or an array of page IDs. * @returns {Object} A relation property object. */ export function relation(values) { return page_props.relation.setProp(values); } /** * Creates a select property object. * @memberof PropertyShorthand * @param {string} value - The selected value. * @returns {Object} A select property object. */ export function select(value) { return page_props.select.setProp(value); } /** * Creates a status property object. * @memberof PropertyShorthand * @param {string} value - The status value. * @returns {Object} A status property object. */ export function status(value) { return page_props.status.setProp(value); } /** * Creates a URL property object. * @memberof PropertyShorthand * @param {string} value - The URL. * @returns {Object} A URL property object. */ export function url(value) { return page_props.url.setProp(value); } /** * Validates values passed to the setProp() methods above. Performs some transformation in certain cases. * * @param {*} value - the value being passed to setProp(), which invokes this function * @param {string} type - the type of value expected by this Notion API property * @returns */ function validateValue(value, type) { if ( value === undefined || value === null || !type || typeof type !== "string" ) { console.error( `Invalid value or type variable provided to validateValue(). Passed value: ${value}` ); throw new Error( `Invalid value or type variable provided to validateValue().` ); } if (type === "rich_text") { return enforceRichText(value); } if (type === "number") { if (typeof value === "string") { console.warn( `String data passed to a number property. Attempting to convert to a number. Passed value: ${value}` ); const num = Number(value); if (!isNaN(num)) { return num; } else { return null; } } if (typeof value !== "number") { console.warn( `Invalid data type passed to a number property. Returning null. Passed value: ${value}` ); return null; } return value; } if (type === "boolean") { if (typeof value !== "boolean") { console.warn( `Invalid data type passed to a boolean property. Returning null. Passed value: ${value}` ); return null; } return value; } if (type === "date") { return validateDate(value); } if (type === "string") { if (typeof value !== "string") { console.warn( `Invalid data type passed to a string property. Returning null. Passed value: ${value}` ); return null; } return value; } if (type === "url") { if (typeof value !== "string") { console.warn( `Invalid data type passed to a url property. Returning null. Passed value: ${value}` ); return null; } if (isValidURL(value)) { return value; } else { console.warn(`Invalid URL. Returning null. Passed value: ${value}`); return null; } } if (type === "email") { if (typeof value !== "string") { console.warn( `Invalid data type passed to a email property. Returning null. Passed value: ${value}` ); return null; } validateStringLength({ string: value, type: "email" }); return value; } if (type === "phone_number") { if (typeof value !== "string") { console.warn( `Invalid data type passed to a phone number property. Returning null. Passed value: ${value}` ); return null; } validateStringLength({ string: value, type: "phone_number" }); return value; } if (type === "UUID") { if (isValidUUID(value)) { return value; } else { console.warn(`Invalid UUID. Returning null. Passed value: ${value}`); return null; } } console.warn( `Type specified to validateValue is not a valid type. Returning the input...` ); return value; }