npm.io
0.9.0 • Published 4h ago

@nakanoaas/notion-valibot-schema

Licence
MIT
Version
0.9.0
Deps
0
Size
734 kB
Vulns
0
Weekly
833

Notion Valibot Schema

npm version JSR version License: MIT

Turn Notion's nested API responses into clean, typed JavaScript values.

This library provides a collection of Valibot schemas specifically designed to handle Notion API objects. It doesn't just validate; it transforms deeply nested Notion properties into simple, usable primitives like string, number, Date, and boolean.

The Problem

When you fetch a page from Notion, properties are deeply nested. To access them type-safely, you end up writing verbose type guards for every single property.

// 😫 The "Native" Way (Boilerplate Hell)

// 1. Get the property
const statusProp = page.properties["Status"];

// 2. Check if it exists and has the correct type
if (statusProp?.type === "status" && statusProp.status) {
  // 3. Finally access the value
  console.log(statusProp.status.name); // "In Progress"
}

// Repeat this for every property... 
const tagsProp = page.properties["Tags"];
if (tagsProp?.type === "multi_select") {
  console.log(tagsProp.multi_select.map(t => t.name)); 
}

The Solution

With @nakanoaas/notion-valibot-schema, you get this:

// After parsing
{
  Status: "In Progress",
  Tags: ["Urgent", "Work"],
  DueDate: new Date("2023-12-25"),
  Assignee: ["user-id-1", "user-id-2"]
}

No more checking for property.type === 'date', handling null, or digging through 3 layers of objects just to get a string.

Features

  • Composable: Works seamlessly with standard Valibot schemas (v.object, v.array, etc.).
  • Transformative: Automatically extracts values (e.g., RichText[] -> string).
  • Type-Safe: Full TypeScript support with inferred types.
  • Well Tested: Backed by a comprehensive test suite covering edge cases.
  • Comprehensive: Supports complex properties like Rollups, Formulas, and Relations.

Installation

Node.js (npm / pnpm / yarn / bun)
npm install @nakanoaas/notion-valibot-schema valibot
pnpm add @nakanoaas/notion-valibot-schema valibot
Deno / JSR
deno add @nakanoaas/notion-valibot-schema @valibot/valibot

Usage

Basic Example

Here is how to validate and transform a Notion page retrieved from the API.

import * as v from "valibot";
import {
  TitleSchema,
  RichTextSchema,
  StatusSchema,
  MultiSelectSchema,
  NullableSingleDateSchema,
  CheckboxSchema,
  PeopleIdSchema,
} from "@nakanoaas/notion-valibot-schema";

// 1. Define your schema based on your Data Source properties
const TaskPageSchema = v.object({
  id: v.string(),
  properties: v.object({
    // Map "Name" property -> string
    Name: TitleSchema,
    
    // Map "Description" property -> string
    Description: RichTextSchema,
    
    // Map "Status" property -> "ToDo" | "Doing" | "Done"
    Status: StatusSchema(v.picklist(["ToDo", "Doing", "Done"])),
    
    // Map "Tags" -> string[]
    Tags: MultiSelectSchema(v.string()),
    
    // Map "Due Date" -> Date | null
    DueDate: NullableSingleDateSchema,
    
    // Map "IsUrgent" -> boolean
    IsUrgent: CheckboxSchema,

    // Map "Assignee" -> string[] (User IDs)
    Assignee: PeopleIdSchema,
  }),
});

// 2. Fetch data from Notion
const page = await notion.pages.retrieve({ page_id: "..." });

// 3. Parse and transform
const task = v.parse(TaskPageSchema, page);

// 4. Use your clean data
console.log(task.properties.Name);       // "Buy Milk" (string)
console.log(task.properties.DueDate);    // Date object or null
console.log(task.properties.Tags);       // ["Personal", "Shopping"] (string[])
console.log(task.properties.Assignee);   // ["user-id-1", "user-id-2"] (string[])
Handling Lists (Query Results)

To parse the results of a data source query:

const TaskListSchema = v.array(TaskPageSchema);

const { results } = await notion.dataSources.query({ data_source_id: "..." });
const tasks = v.parse(TaskListSchema, results);

Schema Reference

For complete API documentation, including all available schemas and types, please visit the JSR Documentation.

Notion Property Schema Transformed Output (Type)
Text / Title TitleSchema / RichTextSchema / NullableTitleSchema / NullableRichTextSchema string / string | null
Number NumberSchema / NullableNumberSchema number / number | null
Checkbox CheckboxSchema boolean
Select SelectSchema(schema) Inferred<schema>
Multi-Select MultiSelectSchema(schema) Inferred<schema>[]
Status StatusSchema(schema) Inferred<schema>
Date (Single) SingleDateSchema / NullableSingleDateSchema Date / Date | null
Date (Range) RangeDateSchema / NullableRangeDateSchema { start: Date; end: Date; time_zone: string | null } / { start: Date; end: Date; time_zone: string | null } | null
Date (Full) FullDateSchema / NullableFullDateSchema { start: Date; end: Date | null; time_zone: string | null } / { start: Date; end: Date | null; time_zone: string | null } | null
Relation RelationSchema string[] (Page IDs)
Relation (Single) SingleRelationSchema string (Page ID)
Rollup (Simple) RollupSimpleSchema(schema) Inferred<schema>
Rollup (Array) RollupArraySchema(schema) Inferred<schema>[]
Rollup (Single) SingleRollupArraySchema(schema) Inferred<schema>
Rollup (Single, Nullable) NullableSingleRollupArraySchema(schema) Inferred<schema> | null
Formula FormulaSchema(schema) Inferred<schema>
URL UrlSchema string
Email EmailSchema string
Phone PhoneNumberSchema string
Files FileSchema string[] (URLs)
Files (Single) SingleFileSchema / NullableSingleFileSchema string (URL) / string | null
People (building blocks) UserOrGroupIdSchema / UserOrGroupSchema / UserSchema / PersonSchema / BotSchema Building-block object types
People (generic) PeopleSchema(schema) / SinglePeopleSchema(schema) / NullableSinglePeopleSchema(schema) Inferred<schema>[] / Inferred<schema> / Inferred<schema> | null
People (convenience) PeopleIdSchema / SinglePeopleIdSchema / NullableSinglePeopleIdSchema string[] / string / string | null
Created/Edited By (generic) CreatedBySchema(schema) / LastEditedBySchema(schema) Inferred<schema>
Created/Edited By (convenience) CreatedByIdSchema / NullableCreatedByNameSchema / LastEditedByIdSchema / NullableLastEditedByNameSchema string / string | null
Created/Edited Time CreatedTimeSchema / LastEditedTimeSchema Date
Place PlaceSchema / NullablePlaceSchema { lat: number; lon: number; name?: string | null; address?: string | null } / { lat: number; lon: number; name?: string | null; address?: string | null } | null
Unique ID UniqueIdNumberSchema / PrefixedUniqueIdStringSchema / NullableUniqueIdSchema number / string (e.g. "PREFIX-123") / { prefix: string | null; number: number | null }
Verification VerificationSchema / NullableVerificationSchema { state: "unverified" | "verified" | "expired"; date: DateObject | null; verified_by: { id: string; object: "user"; name: string | null; avatar_url: string | null } | null } / same | null
Advanced Schemas
Formulas

Formulas in Notion can return different types (string, number, boolean, date). Use FormulaSchema with the matching inner schema for each formula property.

import * as v from "valibot";
import {
  BooleanFormulaSchema,
  FormulaSchema,
  NumberSchema,
  SingleDateSchema,
  StringFormulaSchema,
} from "@nakanoaas/notion-valibot-schema";

const PageSchema = v.object({
  id: v.string(),
  properties: v.object({
    FormulaText: FormulaSchema(StringFormulaSchema),
    FormulaNumber: FormulaSchema(NumberSchema),
    FormulaBoolean: FormulaSchema(BooleanFormulaSchema),
    FormulaDate: FormulaSchema(SingleDateSchema),
  }),
});

const page = await notion.pages.retrieve({ page_id: "..." });
const parsed = v.parse(PageSchema, page);
// parsed.properties.FormulaText: string
// parsed.properties.FormulaNumber: number
// parsed.properties.FormulaBoolean: boolean
// parsed.properties.FormulaDate: Date
Rollups

Rollups are powerful but complex. We provide helpers for common rollup types.

import { 
  RollupSimpleSchema,
  RollupArraySchema,
  SingleRollupArraySchema,
  NullableSingleRollupArraySchema,
  NumberSchema,
  SingleDateSchema,
} from "@nakanoaas/notion-valibot-schema";

const MySchema = v.object({
  // Sum/Average rollup (returns number)
  TotalCost: RollupSimpleSchema(NumberSchema),
  
  // Date rollup (returns Date)
  LatestMeeting: RollupSimpleSchema(SingleDateSchema),
  
  // Array rollup (e.g., pulling tags from related items)
  AllTags: RollupArraySchema(v.string()),
  
  // Single-element array rollup (returns the one item, not an array)
  PrimaryTag: SingleRollupArraySchema(v.string()),
  
  // Single-element or empty array rollup (returns item or null)
  OptionalPrimaryTag: NullableSingleRollupArraySchema(v.string()),
});
People & Created/Edited By

People-related schemas are organized into building blocks, generic factories, and convenience schemas:

  • Building blocks (UserOrGroupIdSchema, UserOrGroupSchema, UserSchema, PersonSchema, BotSchema) validate individual user/group objects.
  • Generic factories (PeopleSchema, SinglePeopleSchema, NullableSinglePeopleSchema, CreatedBySchema, LastEditedBySchema) accept a building-block schema and extract the property value.
  • Convenience schemas (PeopleIdSchema, CreatedByIdSchema, etc.) require no generic parameter and extract common primitives like IDs or names.
import * as v from "valibot";
import {
  CreatedByIdSchema,
  CreatedBySchema,
  PeopleIdSchema,
  PeopleSchema,
  PersonSchema,
  UserOrGroupSchema,
  UserSchema,
} from "@nakanoaas/notion-valibot-schema";

const PageSchema = v.object({
  id: v.string(),
  properties: v.object({
    // Full person details
    People: PeopleSchema(PersonSchema),

    // IDs only (no generic needed)
    AssigneeIds: PeopleIdSchema,

    // created_by ID extraction
    CreatedById: CreatedByIdSchema,

    // Custom user schema
    CreatedBy: CreatedBySchema(UserSchema),
  }),
});
Migration Guide (Breaking Changes)

The following schemas changed from constants to generic factories:

Before After
PeopleSchema PeopleSchema(UserOrGroupSchema) or PeopleSchema(PersonSchema)
CreatedBySchema CreatedBySchema(UserOrGroupSchema)
LastEditedBySchema LastEditedBySchema(UserOrGroupSchema)

For ID-only extraction, use the convenience schemas instead: PeopleIdSchema, CreatedByIdSchema, LastEditedByIdSchema.

License

MIT Nakano as a Service

Keywords