sources/updated-page/updated-page.mjs
import zlib from "zlib"; import notion from "../../notion.app.mjs"; import base from "../common/base.mjs"; import constants from "../common/constants.mjs"; import sampleEmit from "./test-event.mjs"; export default { ...base, key: "notion-updated-page", name: "New or Updated Page in Data Source (By Property)", description: "Emit new event when a page is created or updated in the selected data source. [See the documentation](https://developers.notion.com/reference/page)", version: "1.0.0", type: "source", dedupe: "unique", props: { ...base.props, dataSourceId: { propDefinition: [ notion, "dataSourceId", ], }, includeNewPages: { type: "boolean", label: "Include New Pages", description: "Set to `false` to emit events only for updates, not for new pages.", default: true, }, properties: { propDefinition: [ notion, "propertyTypes", (c) => ({ parentId: c.dataSourceId, parentType: "data_source", }), ], description: "Only emit events when one or more of the selected properties have changed", optional: true, }, alert: { type: "alert", alertType: "warning", content: "Source not saving? Your database might be too large. If deployment takes longer than one minute, an error will occur.", }, }, hooks: { async activate() { console.log("Activating: fetching pages and properties"); this._setLastUpdatedTimestamp(Date.now()); const propertyValues = {}; const propertiesToCheck = await this._getPropertiesToCheck(); const params = this.lastUpdatedSortParam(); const pagesStream = this.notion.getPages(this.dataSourceId, params); for await (const page of pagesStream) { for (const propertyName of propertiesToCheck) { const currentValue = this._maybeRemoveFileSubItems(page.properties[propertyName]); propertyValues[page.id] = { ...propertyValues[page.id], [propertyName]: currentValue, }; } } this._setPropertyValues(propertyValues); }, async deactivate() { console.log("Deactivating: clearing states"); this._setLastUpdatedTimestamp(null); }, }, methods: { ...base.methods, _getLastUpdatedTimestamp() { return this.db.get(constants.timestamps.LAST_EDITED_TIME); }, _setLastUpdatedTimestamp(ts) { this.db.set(constants.timestamps.LAST_EDITED_TIME, ts); }, _getPropertyValues() { const compressed = this.db.get("propertyValues"); const buffer = Buffer.from(compressed, "base64"); const decompressed = zlib.inflateSync(buffer).toString(); return JSON.parse(decompressed); }, _setPropertyValues(propertyValues) { const string = JSON.stringify(propertyValues); const compressed = zlib.deflateSync(string).toString("base64"); this.db.set("propertyValues", compressed); }, async _getPropertiesToCheck() { if (this.properties?.length) { return this.properties; } const { properties } = await this.notion.retrieveDataSource(this.dataSourceId); return Object.keys(properties); }, _maybeRemoveFileSubItems(property) { if (property.type === "files") { const modified = structuredClone(property); for (const file of modified.files) { if (file.type === "file") { delete file.file; } } return modified; } return property; }, _generateMeta(obj, summary) { const { id } = obj; const title = this.notion.extractPageTitle(obj); const ts = Date.now(); return { id: `${id}-${ts}`, summary: `${summary}: ${title}`, ts, }; }, _emitEvent(page, changes = [], isNewPage = true) { const meta = isNewPage ? this._generateMeta(page, constants.summaries.PAGE_ADDED) : this._generateMeta(page, constants.summaries.PAGE_UPDATED); const event = { page, changes, }; this.$emit(event, meta); }, }, async run() { const lastCheckedTimestamp = this._getLastUpdatedTimestamp(); const propertyValues = this._getPropertyValues(); if (!lastCheckedTimestamp) { console.log("Awaiting restart completion: skipping execution"); return; } const params = { ...this.lastUpdatedSortParam(), filter: { timestamp: "last_edited_time", last_edited_time: { on_or_after: new Date(lastCheckedTimestamp).toISOString(), }, }, }; let newLastUpdatedTimestamp = lastCheckedTimestamp; const propertiesToCheck = await this._getPropertiesToCheck(); const pagesStream = this.notion.getPages(this.dataSourceId, params); for await (const page of pagesStream) { const changes = []; let isNewPage = false; let propertyHasChanged = false; newLastUpdatedTimestamp = Math.max( newLastUpdatedTimestamp, Date.parse(page.last_edited_time), ); if (lastCheckedTimestamp > Date.parse(page.last_edited_time)) { break; } const pageExistsInDB = propertyValues[page.id] != null; isNewPage = !pageExistsInDB; for (const propertyName of propertiesToCheck) { const previousValue = structuredClone(propertyValues[page.id]?.[propertyName]); const currentValueToSave = this._maybeRemoveFileSubItems(page.properties[propertyName]); const currentValueToEmit = page.properties[propertyName]; const propertyChanged = JSON.stringify(previousValue) !== JSON.stringify(currentValueToSave); if (pageExistsInDB && propertyChanged) { propertyHasChanged = true; propertyValues[page.id] = { ...propertyValues[page.id], [propertyName]: currentValueToSave, }; changes.push({ property: propertyName, previousValue, currentValue: currentValueToEmit, }); } if (!pageExistsInDB) { if (!propertyValues[page.id]) { propertyValues[page.id] = {}; } propertyValues[page.id][propertyName] = currentValueToSave; if (this.includeNewPages) { propertyHasChanged = true; changes.push({ property: propertyName, previousValue, currentValue: currentValueToEmit, }); } } } if (propertyHasChanged && (!isNewPage || this.includeNewPages)) { this._emitEvent(page, changes, isNewPage); } } this._setLastUpdatedTimestamp(newLastUpdatedTimestamp); this._setPropertyValues(propertyValues); }, sampleEmit, };