From 07cee98ff9a36558e78ac34c349a2ff8b553eb84 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 21 Mar 2024 17:24:24 +0800 Subject: [PATCH 01/35] allow label and highlight events to trigger rules --- packages/api/src/entity/rule.ts | 3 +++ packages/api/src/jobs/trigger_rule.ts | 12 +++++++++++- packages/api/src/pubsub.ts | 14 ++++++-------- packages/api/src/services/highlights.ts | 13 +++++++------ packages/api/src/services/labels.ts | 10 +++++----- 5 files changed, 32 insertions(+), 20 deletions(-) diff --git a/packages/api/src/entity/rule.ts b/packages/api/src/entity/rule.ts index 51c63b972..18538971c 100644 --- a/packages/api/src/entity/rule.ts +++ b/packages/api/src/entity/rule.ts @@ -15,11 +15,14 @@ export enum RuleActionType { Delete = 'DELETE', MarkAsRead = 'MARK_AS_READ', SendNotification = 'SEND_NOTIFICATION', + Webhook = 'WEBHOOK', } export enum RuleEventType { PageCreated = 'PAGE_CREATED', PageUpdated = 'PAGE_UPDATED', + LabelCreated = 'PAGE_CREATED', + HighlightCreated = 'HIGHLIGHT_CREATED', } export interface RuleAction { diff --git a/packages/api/src/jobs/trigger_rule.ts b/packages/api/src/jobs/trigger_rule.ts index 596760295..3a72dac11 100644 --- a/packages/api/src/jobs/trigger_rule.ts +++ b/packages/api/src/jobs/trigger_rule.ts @@ -86,7 +86,9 @@ const sendNotification = async (obj: RuleActionObj) => { return sendPushNotifications(obj.userId, message, 'rule', data) } -const getRuleAction = (actionType: RuleActionType): RuleActionFunc => { +const getRuleAction = ( + actionType: RuleActionType +): RuleActionFunc | undefined => { switch (actionType) { case RuleActionType.AddLabel: return addLabels @@ -98,6 +100,9 @@ const getRuleAction = (actionType: RuleActionType): RuleActionFunc => { return markPageAsRead case RuleActionType.SendNotification: return sendNotification + default: + logger.error('Unknown rule action type', actionType) + return undefined } } @@ -150,6 +155,11 @@ const triggerActions = async ( for (const action of rule.actions) { const actionFunc = getRuleAction(action.type) + if (!actionFunc) { + logger.error('No action function found for action', action.type) + continue + } + const actionObj: RuleActionObj = { libraryItemId, userId, diff --git a/packages/api/src/pubsub.ts b/packages/api/src/pubsub.ts index 4401cfad9..d8c6bba4d 100644 --- a/packages/api/src/pubsub.ts +++ b/packages/api/src/pubsub.ts @@ -53,14 +53,12 @@ export const createPubSubClient = (): PubsubClient => { libraryItemId: string ): Promise => { // queue trigger rule job - if (type === EntityType.PAGE) { - await enqueueTriggerRuleJob({ - userId, - ruleEventType: RuleEventType.PageCreated, - libraryItemId, - data, - }) - } + await enqueueTriggerRuleJob({ + userId, + ruleEventType: `${type.toUpperCase()}_CREATED` as RuleEventType, + libraryItemId, + data, + }) // queue export item job await enqueueExportItem({ userId, diff --git a/packages/api/src/services/highlights.ts b/packages/api/src/services/highlights.ts index babf4d4a4..91249626b 100644 --- a/packages/api/src/services/highlights.ts +++ b/packages/api/src/services/highlights.ts @@ -9,6 +9,7 @@ import { createPubSubClient, EntityType } from '../pubsub' import { authTrx } from '../repository' import { highlightRepository } from '../repository/highlight' import { enqueueUpdateHighlight } from '../utils/createTask' +import { UpdateItemEvent } from './library_item' type HighlightEvent = { id: string; pageId: string } type CreateHighlightEvent = DeepPartial & HighlightEvent @@ -56,9 +57,9 @@ export const createHighlight = async ( userId ) - await pubsub.entityCreated( + await pubsub.entityCreated( EntityType.HIGHLIGHT, - { ...newHighlight, pageId: libraryItemId }, + { id: libraryItemId, highlights: [newHighlight] }, userId, libraryItemId ) @@ -104,9 +105,9 @@ export const mergeHighlights = async ( }) }) - await pubsub.entityCreated( + await pubsub.entityCreated( EntityType.HIGHLIGHT, - { ...newHighlight, pageId: libraryItemId }, + { id: libraryItemId, highlights: [newHighlight] }, userId, libraryItemId ) @@ -139,9 +140,9 @@ export const updateHighlight = async ( }) const libraryItemId = updatedHighlight.libraryItem.id - await pubsub.entityUpdated( + await pubsub.entityUpdated( EntityType.HIGHLIGHT, - { ...highlight, id: highlightId, pageId: libraryItemId }, + { id: libraryItemId, highlights: [highlight] }, userId, libraryItemId ) diff --git a/packages/api/src/services/labels.ts b/packages/api/src/services/labels.ts index f4bae13b7..ca0e02d64 100644 --- a/packages/api/src/services/labels.ts +++ b/packages/api/src/services/labels.ts @@ -8,7 +8,7 @@ import { CreateLabelInput, labelRepository } from '../repository/label' import { bulkEnqueueUpdateLabels } from '../utils/createTask' import { logger } from '../utils/logger' import { findHighlightById } from './highlights' -import { findLibraryItemIdsByLabelId } from './library_item' +import { findLibraryItemIdsByLabelId, UpdateItemEvent } from './library_item' type AddLabelsToLibraryItemEvent = { pageId: string @@ -144,9 +144,9 @@ export const saveLabelsInLibraryItem = async ( if (source === 'user') { // create pubsub event - await pubsub.entityCreated( + await pubsub.entityCreated( EntityType.LABEL, - { pageId: libraryItemId, labels, source }, + { id: libraryItemId, labels }, userId, libraryItemId ) @@ -215,9 +215,9 @@ export const saveLabelsInHighlight = async ( const libraryItemId = highlight.libraryItemId // create pubsub event - await pubsub.entityCreated( + await pubsub.entityCreated( EntityType.LABEL, - { highlightId, labels }, + { id: libraryItemId, highlights: [{ id: highlightId, labels }] }, userId, libraryItemId ) From c551d9fe6bdd9dad026b370ebfcff017ac2747b0 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 21 Mar 2024 18:13:42 +0800 Subject: [PATCH 02/35] allow send data to webhook with rules --- packages/api/src/entity/rule.ts | 2 +- packages/api/src/generated/graphql.ts | 5 ++- packages/api/src/generated/schema.graphql | 3 ++ packages/api/src/jobs/trigger_rule.ts | 33 +++++++++++++++- packages/api/src/pubsub.ts | 31 +++++++-------- packages/api/src/schema.ts | 3 ++ packages/api/src/services/highlights.ts | 6 +-- packages/api/src/services/labels.ts | 4 +- packages/api/src/services/library_item.ts | 46 +++++++++-------------- 9 files changed, 80 insertions(+), 53 deletions(-) diff --git a/packages/api/src/entity/rule.ts b/packages/api/src/entity/rule.ts index 18538971c..9ef6d797e 100644 --- a/packages/api/src/entity/rule.ts +++ b/packages/api/src/entity/rule.ts @@ -21,7 +21,7 @@ export enum RuleActionType { export enum RuleEventType { PageCreated = 'PAGE_CREATED', PageUpdated = 'PAGE_UPDATED', - LabelCreated = 'PAGE_CREATED', + LabelCreated = 'LABEL_CREATED', HighlightCreated = 'HIGHLIGHT_CREATED', } diff --git a/packages/api/src/generated/graphql.ts b/packages/api/src/generated/graphql.ts index ae5bcdbd8..3e3119db6 100644 --- a/packages/api/src/generated/graphql.ts +++ b/packages/api/src/generated/graphql.ts @@ -2502,10 +2502,13 @@ export enum RuleActionType { Archive = 'ARCHIVE', Delete = 'DELETE', MarkAsRead = 'MARK_AS_READ', - SendNotification = 'SEND_NOTIFICATION' + SendNotification = 'SEND_NOTIFICATION', + Webhook = 'WEBHOOK' } export enum RuleEventType { + HighlightCreated = 'HIGHLIGHT_CREATED', + LabelCreated = 'LABEL_CREATED', PageCreated = 'PAGE_CREATED', PageUpdated = 'PAGE_UPDATED' } diff --git a/packages/api/src/generated/schema.graphql b/packages/api/src/generated/schema.graphql index 94695e350..1782328b8 100644 --- a/packages/api/src/generated/schema.graphql +++ b/packages/api/src/generated/schema.graphql @@ -1879,9 +1879,12 @@ enum RuleActionType { DELETE MARK_AS_READ SEND_NOTIFICATION + WEBHOOK } enum RuleEventType { + HIGHLIGHT_CREATED + LABEL_CREATED PAGE_CREATED PAGE_UPDATED } diff --git a/packages/api/src/jobs/trigger_rule.ts b/packages/api/src/jobs/trigger_rule.ts index 3a72dac11..132218ebb 100644 --- a/packages/api/src/jobs/trigger_rule.ts +++ b/packages/api/src/jobs/trigger_rule.ts @@ -1,4 +1,5 @@ import { LiqeQuery } from '@omnivore/liqe' +import axios, { Method } from 'axios' import { ReadingProgressDataSource } from '../datasources/reading_progress_data_source' import { LibraryItem, LibraryItemState } from '../entity/library_item' import { Rule, RuleAction, RuleActionType, RuleEventType } from '../entity/rule' @@ -28,6 +29,7 @@ interface RuleActionObj { userId: string action: RuleAction data: ItemEvent | LibraryItem + ruleEventType: RuleEventType } type RuleActionFunc = (obj: RuleActionObj) => Promise @@ -86,6 +88,29 @@ const sendNotification = async (obj: RuleActionObj) => { return sendPushNotifications(obj.userId, message, 'rule', data) } +const sendToWebhook = async (obj: RuleActionObj) => { + const [url, method, contentType] = obj.action.params + const [type, action] = obj.ruleEventType.split('_') + + const body = { + action, + userId: obj.userId, + [type]: obj.data, + } + + logger.info('triggering webhook', { url, method }) + + return axios.request({ + url, + method: method as Method, + headers: { + 'Content-Type': contentType, + }, + data: body, + timeout: 5000, // 5s + }) +} + const getRuleAction = ( actionType: RuleActionType ): RuleActionFunc | undefined => { @@ -100,6 +125,8 @@ const getRuleAction = ( return markPageAsRead case RuleActionType.SendNotification: return sendNotification + case RuleActionType.Webhook: + return sendToWebhook default: logger.error('Unknown rule action type', actionType) return undefined @@ -110,7 +137,8 @@ const triggerActions = async ( libraryItemId: string, userId: string, rules: Rule[], - data: ItemEvent + data: ItemEvent, + ruleEventType: RuleEventType ) => { const actionPromises: Promise[] = [] @@ -165,6 +193,7 @@ const triggerActions = async ( userId, action, data: results[0], + ruleEventType, } actionPromises.push(actionFunc(actionObj)) @@ -188,7 +217,7 @@ export const triggerRule = async (jobData: TriggerRuleJobData) => { return false } - await triggerActions(libraryItemId, userId, rules, data) + await triggerActions(libraryItemId, userId, rules, data, ruleEventType) return true } diff --git a/packages/api/src/pubsub.ts b/packages/api/src/pubsub.ts index d8c6bba4d..d340f520b 100644 --- a/packages/api/src/pubsub.ts +++ b/packages/api/src/pubsub.ts @@ -12,6 +12,8 @@ import { import { buildLogger } from './utils/logger' import { isYouTubeVideoURL } from './utils/youtube' +export type BaseEntityEvent = { id: string; userId: string } + const logger = buildLogger('pubsub') const client = new PubSub() @@ -46,7 +48,7 @@ export const createPubSubClient = (): PubsubClient => { Buffer.from(JSON.stringify({ userId, email, name, username })) ) }, - entityCreated: async >( + entityCreated: async ( type: EntityType, data: T, userId: string, @@ -54,10 +56,10 @@ export const createPubSubClient = (): PubsubClient => { ): Promise => { // queue trigger rule job await enqueueTriggerRuleJob({ - userId, ruleEventType: `${type.toUpperCase()}_CREATED` as RuleEventType, - libraryItemId, data, + userId, + libraryItemId, }) // queue export item job await enqueueExportItem({ @@ -92,21 +94,20 @@ export const createPubSubClient = (): PubsubClient => { } } }, - entityUpdated: async >( + entityUpdated: async ( type: EntityType, data: T, userId: string, libraryItemId: string ): Promise => { // queue trigger rule job - if (type === EntityType.PAGE) { - await enqueueTriggerRuleJob({ - userId, - ruleEventType: RuleEventType.PageUpdated, - libraryItemId, - data, - }) - } + await enqueueTriggerRuleJob({ + userId, + ruleEventType: RuleEventType.PageUpdated, + libraryItemId, + data, + }) + // queue export item job await enqueueExportItem({ userId, @@ -145,7 +146,7 @@ export const createPubSubClient = (): PubsubClient => { } export enum EntityType { - PAGE = 'page', + ITEM = 'page', HIGHLIGHT = 'highlight', LABEL = 'label', RSS_FEED = 'feed', @@ -158,13 +159,13 @@ export interface PubsubClient { name: string, username: string ) => Promise - entityCreated: >( + entityCreated: ( type: EntityType, data: T, userId: string, libraryItemId: string ) => Promise - entityUpdated: >( + entityUpdated: ( type: EntityType, data: T, userId: string, diff --git a/packages/api/src/schema.ts b/packages/api/src/schema.ts index e106514e4..d2279407e 100755 --- a/packages/api/src/schema.ts +++ b/packages/api/src/schema.ts @@ -2167,6 +2167,7 @@ const schema = gql` DELETE MARK_AS_READ SEND_NOTIFICATION + WEBHOOK } type RulesError { @@ -2181,6 +2182,8 @@ const schema = gql` enum RuleEventType { PAGE_CREATED PAGE_UPDATED + LABEL_CREATED + HIGHLIGHT_CREATED } input SetRuleInput { diff --git a/packages/api/src/services/highlights.ts b/packages/api/src/services/highlights.ts index 91249626b..8cc10ad0b 100644 --- a/packages/api/src/services/highlights.ts +++ b/packages/api/src/services/highlights.ts @@ -59,7 +59,7 @@ export const createHighlight = async ( await pubsub.entityCreated( EntityType.HIGHLIGHT, - { id: libraryItemId, highlights: [newHighlight] }, + { id: libraryItemId, highlights: [newHighlight], userId }, userId, libraryItemId ) @@ -107,7 +107,7 @@ export const mergeHighlights = async ( await pubsub.entityCreated( EntityType.HIGHLIGHT, - { id: libraryItemId, highlights: [newHighlight] }, + { id: libraryItemId, highlights: [newHighlight], userId }, userId, libraryItemId ) @@ -142,7 +142,7 @@ export const updateHighlight = async ( const libraryItemId = updatedHighlight.libraryItem.id await pubsub.entityUpdated( EntityType.HIGHLIGHT, - { id: libraryItemId, highlights: [highlight] }, + { id: libraryItemId, highlights: [highlight], userId }, userId, libraryItemId ) diff --git a/packages/api/src/services/labels.ts b/packages/api/src/services/labels.ts index ca0e02d64..1f05d14c1 100644 --- a/packages/api/src/services/labels.ts +++ b/packages/api/src/services/labels.ts @@ -146,7 +146,7 @@ export const saveLabelsInLibraryItem = async ( // create pubsub event await pubsub.entityCreated( EntityType.LABEL, - { id: libraryItemId, labels }, + { id: libraryItemId, labels, userId }, userId, libraryItemId ) @@ -217,7 +217,7 @@ export const saveLabelsInHighlight = async ( // create pubsub event await pubsub.entityCreated( EntityType.LABEL, - { id: libraryItemId, highlights: [{ id: highlightId, labels }] }, + { id: libraryItemId, highlights: [{ id: highlightId, labels }], userId }, userId, libraryItemId ) diff --git a/packages/api/src/services/library_item.ts b/packages/api/src/services/library_item.ts index 83473104c..4ac000900 100644 --- a/packages/api/src/services/library_item.ts +++ b/packages/api/src/services/library_item.ts @@ -15,7 +15,7 @@ import { Highlight } from '../entity/highlight' import { Label } from '../entity/label' import { LibraryItem, LibraryItemState } from '../entity/library_item' import { BulkActionType, InputMaybe, SortParams } from '../generated/graphql' -import { createPubSubClient, EntityType } from '../pubsub' +import { BaseEntityEvent, createPubSubClient, EntityType } from '../pubsub' import { redisDataSource } from '../redis_data_source' import { authTrx, @@ -37,10 +37,13 @@ type IgnoredFields = | 'links' | 'textContentHash' export type ItemEvent = CreateItemEvent | UpdateItemEvent -export type CreateItemEvent = Omit, IgnoredFields> -export type UpdateItemEvent = Omit< - QueryDeepPartialEntity, - IgnoredFields +export type CreateItemEvent = Merge< + Omit, IgnoredFields>, + BaseEntityEvent +> +export type UpdateItemEvent = Merge< + Omit, IgnoredFields>, + BaseEntityEvent > export class RequiresSearchQueryError extends Error { @@ -841,7 +844,7 @@ export const softDeleteLibraryItem = async ( userId ) - await pubsub.entityDeleted(EntityType.PAGE, id, userId) + await pubsub.entityDeleted(EntityType.ITEM, id, userId) return deletedLibraryItem } @@ -883,13 +886,8 @@ export const updateLibraryItem = async ( if (libraryItem.state === LibraryItemState.Succeeded) { // send create event if the item was created await pubsub.entityCreated( - EntityType.PAGE, - { - ...updatedLibraryItem, - originalContent: undefined, - readableContent: undefined, - feedContent: undefined, - }, + EntityType.ITEM, + { ...updatedLibraryItem, userId }, userId, id ) @@ -898,13 +896,8 @@ export const updateLibraryItem = async ( } await pubsub.entityUpdated( - EntityType.PAGE, - { - ...libraryItem, - originalContent: undefined, - readableContent: undefined, - feedContent: undefined, - }, + EntityType.ITEM, + { ...libraryItem, id, userId }, userId, id ) @@ -966,8 +959,8 @@ export const updateLibraryItemReadingProgress = async ( const updatedItem = result[0][0] await pubsub.entityUpdated( - EntityType.PAGE, - updatedItem, + EntityType.ITEM, + { ...updatedItem, id, userId }, userId, id ) @@ -1067,13 +1060,8 @@ export const createOrUpdateLibraryItem = async ( } await pubsub.entityCreated( - EntityType.PAGE, - { - ...newLibraryItem, - originalContent: undefined, - readableContent: undefined, - feedContent: undefined, - }, + EntityType.ITEM, + { ...newLibraryItem, userId }, userId, newLibraryItem.id ) From 88d6455222963e8819eef303702f2dc575a8b522 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 21 Mar 2024 19:51:31 +0800 Subject: [PATCH 03/35] default filter is in:all --- packages/api/src/jobs/trigger_rule.ts | 21 +++++------- packages/api/src/pubsub.ts | 17 +--------- packages/api/src/services/highlights.ts | 17 +++++----- packages/api/src/services/labels.ts | 16 +++------ packages/api/src/services/library_item.ts | 34 +++++++++---------- .../networking/queries/useGetRulesQuery.tsx | 3 ++ packages/web/pages/settings/rules.tsx | 34 +++++++++++++------ 7 files changed, 66 insertions(+), 76 deletions(-) diff --git a/packages/api/src/jobs/trigger_rule.ts b/packages/api/src/jobs/trigger_rule.ts index 132218ebb..88cc65144 100644 --- a/packages/api/src/jobs/trigger_rule.ts +++ b/packages/api/src/jobs/trigger_rule.ts @@ -1,5 +1,5 @@ import { LiqeQuery } from '@omnivore/liqe' -import axios, { Method } from 'axios' +import axios from 'axios' import { ReadingProgressDataSource } from '../datasources/reading_progress_data_source' import { LibraryItem, LibraryItemState } from '../entity/library_item' import { Rule, RuleAction, RuleActionType, RuleEventType } from '../entity/rule' @@ -89,24 +89,19 @@ const sendNotification = async (obj: RuleActionObj) => { } const sendToWebhook = async (obj: RuleActionObj) => { - const [url, method, contentType] = obj.action.params - const [type, action] = obj.ruleEventType.split('_') + const [url] = obj.action.params - const body = { - action, - userId: obj.userId, - [type]: obj.data, + const data = { + event: obj.ruleEventType, + data: obj.data, } - logger.info('triggering webhook', { url, method }) + logger.info(`triggering webhook: ${url}`) - return axios.request({ - url, - method: method as Method, + return axios.post(url, data, { headers: { - 'Content-Type': contentType, + 'Content-Type': 'application/json', }, - data: body, timeout: 5000, // 5s }) } diff --git a/packages/api/src/pubsub.ts b/packages/api/src/pubsub.ts index d340f520b..55da4e7c9 100644 --- a/packages/api/src/pubsub.ts +++ b/packages/api/src/pubsub.ts @@ -7,7 +7,6 @@ import { enqueueExportItem, enqueueProcessYouTubeVideo, enqueueTriggerRuleJob, - enqueueWebhookJob, } from './utils/createTask' import { buildLogger } from './utils/logger' import { isYouTubeVideoURL } from './utils/youtube' @@ -67,14 +66,7 @@ export const createPubSubClient = (): PubsubClient => { libraryItemIds: [libraryItemId], }) - await enqueueWebhookJob({ - userId, - type, - action: 'created', - data, - }) - - if (type === EntityType.PAGE) { + if (type === EntityType.ITEM) { // if (await findGrantedFeatureByName(FeatureName.AISummaries, userId)) { // await enqueueAISummarizeJob({ // userId, @@ -113,13 +105,6 @@ export const createPubSubClient = (): PubsubClient => { userId, libraryItemIds: [libraryItemId], }) - - await enqueueWebhookJob({ - userId, - type, - action: 'updated', - data, - }) }, entityDeleted: async ( type: EntityType, diff --git a/packages/api/src/services/highlights.ts b/packages/api/src/services/highlights.ts index 8cc10ad0b..624c5b2da 100644 --- a/packages/api/src/services/highlights.ts +++ b/packages/api/src/services/highlights.ts @@ -9,11 +9,12 @@ import { createPubSubClient, EntityType } from '../pubsub' import { authTrx } from '../repository' import { highlightRepository } from '../repository/highlight' import { enqueueUpdateHighlight } from '../utils/createTask' -import { UpdateItemEvent } from './library_item' +import { ItemEvent } from './library_item' -type HighlightEvent = { id: string; pageId: string } -type CreateHighlightEvent = DeepPartial & HighlightEvent -type UpdateHighlightEvent = QueryDeepPartialEntity & HighlightEvent +export type HighlightEvent = Omit< + DeepPartial, + 'user' | 'userId' | 'sharedAt' +> export const getHighlightLocation = (patch: string): number | undefined => { const dmp = new diff_match_patch() @@ -57,7 +58,7 @@ export const createHighlight = async ( userId ) - await pubsub.entityCreated( + await pubsub.entityCreated( EntityType.HIGHLIGHT, { id: libraryItemId, highlights: [newHighlight], userId }, userId, @@ -105,7 +106,7 @@ export const mergeHighlights = async ( }) }) - await pubsub.entityCreated( + await pubsub.entityCreated( EntityType.HIGHLIGHT, { id: libraryItemId, highlights: [newHighlight], userId }, userId, @@ -140,9 +141,9 @@ export const updateHighlight = async ( }) const libraryItemId = updatedHighlight.libraryItem.id - await pubsub.entityUpdated( + await pubsub.entityUpdated( EntityType.HIGHLIGHT, - { id: libraryItemId, highlights: [highlight], userId }, + { id: libraryItemId, highlights: [highlight], userId } as ItemEvent, userId, libraryItemId ) diff --git a/packages/api/src/services/labels.ts b/packages/api/src/services/labels.ts index 1f05d14c1..4a23f30c0 100644 --- a/packages/api/src/services/labels.ts +++ b/packages/api/src/services/labels.ts @@ -8,17 +8,9 @@ import { CreateLabelInput, labelRepository } from '../repository/label' import { bulkEnqueueUpdateLabels } from '../utils/createTask' import { logger } from '../utils/logger' import { findHighlightById } from './highlights' -import { findLibraryItemIdsByLabelId, UpdateItemEvent } from './library_item' +import { findLibraryItemIdsByLabelId, ItemEvent } from './library_item' -type AddLabelsToLibraryItemEvent = { - pageId: string - labels: DeepPartial