1
0

chore: initial commit

Signed-off-by: Alan Brault <alan.brault@visus.io>
This commit is contained in:
2025-10-14 10:40:06 -04:00
commit 716c73afc3
21 changed files with 6152 additions and 0 deletions

View File

10
src/lib/schemas/index.ts Normal file
View File

@@ -0,0 +1,10 @@
export * from './notion/block.schema';
export * from './notion/colors';
export * from './notion/emoji.schema';
export * from './notion/file.schema';
export * from './notion/fileUpload.schema';
export * from './notion/page.schema';
export * from './notion/pageProperties.schema';
export * from './notion/parent.schema';
export * from './notion/richText.schema';
export * from './notion/user.schema';

View File

@@ -0,0 +1,129 @@
import { z } from 'zod';
import { emojiSchema, fileSchema, NOTION_COLORS, parentSchema, richTextSchema, userSchema } from '@/lib/schemas';
const headingsObject = z.object({
rich_text: z.array(richTextSchema),
color: z.enum(NOTION_COLORS),
is_toggleable: z.boolean(),
});
/**
* Schema representing a Notion block object.
*/
export const blockSchema = z.object({
object: z.literal('block'),
id: z.uuid(),
parent: parentSchema,
type: z.enum([
'audio',
'bookmark',
'breadcrumb',
'bulleted_list_item',
'callout',
'child_database',
'child_page',
'column',
'column_list',
'divider',
'embed',
'equation',
'file',
'heading_1',
'heading_2',
'heading_3',
'image',
'link_preview',
'numbered_list_item',
'paragraph',
'pdf',
'quote',
'synced_block',
'table',
'table_of_contents',
'table_row',
'template',
'to_do',
'toggle',
'unsupported',
'video',
]),
created_time: z.iso.datetime(),
created_by: userSchema,
last_edited_time: z.iso.datetime(),
last_edited_by: userSchema,
archived: z.boolean(),
in_trash: z.boolean(),
has_children: z.boolean(),
// Optional properties for each type
audio: fileSchema.optional(),
bookmark: z
.object({
caption: z.array(richTextSchema),
url: z.url(),
})
.optional(),
breadcrumb: z.object({}).optional(),
bulleted_list_item: z
.object({
rich_text: z.array(richTextSchema),
color: z.enum(NOTION_COLORS),
})
.optional(),
callout: z
.object({
rich_text: z.array(richTextSchema),
icon: z.union([emojiSchema, fileSchema]),
color: z.enum(NOTION_COLORS),
})
.optional(),
child_database: z
.object({
title: z.string(),
})
.optional(),
child_page: z
.object({
title: z.string(),
})
.optional(),
code: z
.object({
caption: z.array(richTextSchema),
rich_text: z.array(richTextSchema),
language: z.enum([]), // TODO: Fill in with actual languages
})
.optional(),
column_list: z.object({}).optional(),
column: z
.object({
width_ratio: z.number().min(0).max(1).optional(),
})
.optional(),
divider: z.object({}).optional(),
embed: z
.object({
url: z.url(),
})
.optional(),
equation: z.object({ expression: z.string() }).optional(),
file: z
.object({
caption: z.array(richTextSchema),
type: z.enum(['file', 'file_upload', 'external']),
file: fileSchema,
external: fileSchema,
file_upload: fileSchema,
name: z.string(),
})
.optional(),
heading_1: headingsObject.optional(),
heading_2: headingsObject.optional(),
heading_3: headingsObject.optional(),
image: fileSchema.optional(),
link_preview: z.object({ url: z.url() }).optional(),
// TODO: Continue with mention and others
});
export type NotionBlock = z.infer<typeof blockSchema>;

View File

@@ -0,0 +1,22 @@
/** Notion color options */
export const NOTION_COLORS = [
'blue',
'blue_background',
'brown',
'brown_background',
'default',
'gray',
'gray_background',
'green',
'green_background',
'orange',
'orange_background',
'pink',
'pink_background',
'purple',
'purple_background',
'red',
'red_background',
'yellow',
'yellow_background',
];

View File

@@ -0,0 +1,8 @@
import { z } from 'zod';
export const emojiSchema = z.object({
type: z.literal('emoji'),
emoji: z.string(),
});
export type NotionEmoji = z.infer<typeof emojiSchema>;

View File

@@ -0,0 +1,33 @@
import { z } from 'zod';
/**
* Schema representing a Notion file object.
*
* This schema can represent three types of files:
* 1. 'file': A file hosted by Notion, with a URL and an expiry time.
* 2. 'file_upload': A file uploaded to Notion, identified by a unique ID.
* 3. 'external': A file hosted externally, represented by a URL.
*
* Each type has its own structure, and only one type will be present in a valid object.
*/
export const fileSchema = z.object({
type: z.enum(['file', 'file_upload', 'external']),
file: z
.object({
url: z.url(),
expiry_time: z.iso.datetime(),
})
.optional(),
file_upload: z
.object({
id: z.uuid(),
})
.optional(),
external: z
.object({
url: z.url(),
})
.optional(),
});
export type NotionFile = z.infer<typeof fileSchema>;

View File

@@ -0,0 +1,22 @@
import { z } from 'zod';
/**
* Schema representing a Notion file upload object.
*
* Includes details about the file upload status, URLs, and metadata.
*/
export const fileUploadSchema = z.object({
object: z.literal('file_upload'),
id: z.uuid(),
created_time: z.iso.datetime(),
expiry_time: z.iso.datetime().nullable(),
status: z.enum(['pending', 'uploaded', 'expired', 'failed']),
filename: z.string(),
content_type: z.string().nullable(),
content_length: z.number().nullable(),
upload_url: z.string(),
complete_url: z.string(),
file_import_result: z.string(),
});
export type NotionFileUpload = z.infer<typeof fileUploadSchema>;

View File

@@ -0,0 +1,26 @@
import { z } from 'zod';
import { fileSchema, pagePropertiesSchema, parentSchema, userSchema } from '@/lib/schemas';
/**
* Schema representing a Notion page object.
*
* Includes metadata about the page, its properties, and its parent.
*/
export const pageSchema = z.object({
object: z.literal('page'),
id: z.uuid(),
created_time: z.iso.datetime(),
created_by: userSchema,
last_edited_time: z.iso.datetime(),
last_edited_by: userSchema,
archived: z.boolean(),
in_trash: z.boolean(),
icon: z.nullable(fileSchema),
cover: z.nullable(fileSchema),
properties: z.record(z.string(), pagePropertiesSchema),
parent: parentSchema,
url: z.url(),
public_url: z.url().nullable(),
});
export type NotionPage = z.infer<typeof pageSchema>;

View File

@@ -0,0 +1,92 @@
import { z } from 'zod';
import { NOTION_COLORS, richTextSchema, userSchema } from '@/lib/schemas';
/**
* Schema representing the properties of a Notion page.
*
* Includes various property types such as text, number, select, multi-select, date, people, files, and more.
*/
export const pagePropertiesSchema = z.object({
id: z.string(),
type: z.enum([
'checkbox',
'created_by',
'created_time',
'date',
'email',
'files',
'formula',
'last_edited_by',
'last_edited_time',
'multi_select',
'number',
'people',
'phone_number',
'relation',
'rich_text',
'rollup',
'select',
'status',
'title',
'url',
'unique_id',
'verification',
]),
// Optional properties for each type
checkbox: z.boolean().optional(),
created_by: userSchema.optional(),
created_time: z.iso.datetime().optional(),
date: z
.object({
start: z.iso.datetime(),
end: z.iso.datetime().optional(),
})
.optional(),
email: z.email().optional(),
files: z.array(z.any()).optional(),
formula: z.any().optional(),
last_edited_by: userSchema.optional(),
last_edited_time: z.iso.datetime().optional(),
multi_select: z
.array(
z.object({
id: z.string(),
name: z.string(),
color: z.enum(NOTION_COLORS),
})
)
.optional(),
number: z.number().optional(),
people: z.array(userSchema).optional(),
phone_number: z.string().optional(),
relation: z.array(z.object({ id: z.string() })).optional(),
rich_text: richTextSchema.optional(),
rollup: z.any().optional(),
select: z
.object({
id: z.string(),
name: z.string(),
color: z.enum(NOTION_COLORS),
})
.optional(),
status: z
.object({
id: z.string(),
name: z.string(),
color: z.enum(NOTION_COLORS),
})
.optional(),
title: richTextSchema.optional(),
url: z.url().optional(),
unique_id: z.string().optional(),
verification: z
.object({
state: z.string(),
verified_by: userSchema.nullable(),
date: z.iso.datetime(),
})
.optional(),
});
export type NotionPageProperties = z.infer<typeof pagePropertiesSchema>;

View File

@@ -0,0 +1,17 @@
import { z } from 'zod';
/**
* Schema representing the parent of a Notion page.
*
* Includes various types of parents such as database, data source, page, workspace, or block.
*/
export const parentSchema = z.object({
type: z.enum(['database_id', 'data_source_id', 'page_id', 'workspace', 'block_id']),
database_id: z.uuid().optional(),
data_source_id: z.uuid().optional(),
page_id: z.uuid().optional(),
workspace: z.boolean().optional(),
block_id: z.uuid().optional(),
});
export type NotionParent = z.infer<typeof parentSchema>;

View File

@@ -0,0 +1,68 @@
import { z } from 'zod';
import { NOTION_COLORS, userSchema } from '@/lib/schemas';
/**
* Schema representing a Notion rich text object.
*
* Includes text content, annotations, mentions, and equations.
*/
export const richTextSchema = z.array(
z.object({
type: z.literal('text'),
text: z.object({
content: z.string(),
link: z.url().nullable(),
}),
mention: z
.object({
type: z.enum(['database', 'date', 'link_preview', 'page', 'template_mention', 'user']),
database: z
.object({
id: z.uuid(),
})
.optional(),
date: z
.object({
start: z.iso.datetime(),
end: z.iso.datetime().optional(),
})
.optional(),
link_preview: z
.object({
url: z.url(),
})
.optional(),
page: z
.object({
id: z.uuid(),
})
.optional(),
template_mention: z
.object({
type: z.enum(['template_mention_date', 'template_mention_user']),
template_mention_date: z.enum(['today', 'now']).optional(),
template_mention_user: z.literal('me').optional(),
})
.optional(),
user: z.object(userSchema).optional(),
})
.optional(),
equation: z
.object({
expression: z.string(),
})
.optional(),
annotations: z.object({
bold: z.boolean(),
italic: z.boolean(),
strikethrough: z.boolean(),
underline: z.boolean(),
code: z.boolean(),
color: z.enum(NOTION_COLORS),
}),
plain_text: z.string(),
href: z.url().optional(),
})
);
export type NotionRichText = z.infer<typeof richTextSchema>;

View File

@@ -0,0 +1,16 @@
import { z } from 'zod';
/**
* Schema representing a Notion user object.
*
* Includes both person and bot user types.
*/
export const userSchema = z.object({
object: z.literal('user'),
id: z.uuid(),
type: z.enum(['person', 'bot']).optional(),
name: z.string().optional(),
avatar_url: z.url().optional(),
});
export type NotionUser = z.infer<typeof userSchema>;