@elumixor/notion-orm 0.1.1 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,267 +1,169 @@
1
- # Notion ORM
1
+ # notion-orm
2
2
 
3
- ⚠️ This package is Still in development 🏗️
4
-
5
- A library to simplify adding and querying [Notion](https://notion.so/product) databases/tables. Giving typeahead/intellisense support on columns and expected column values on user specified databases. Built on top of [Notion API](https://developers.notion.com/)
6
-
7
- Databases with the following column types are supported:
8
-
9
- - Multi-select
10
- - Select
11
- - Status
12
- - Date
13
- - Text
14
- - Url
15
- - Checkbox
16
- - Email
17
- - Phone Number
3
+ TypeScript ORM for Notion databases. Generates fully-typed clients from your Notion schemas.
18
4
 
19
5
  ## Installation
20
6
 
21
- The only requirement is a Notion Developer API key ([here](https://developers.notion.com/)) and database IDs you want. Be sure to connect your [integration](https://developers.notion.com/docs/working-with-databases#adding-pages-to-a-database) (🚧 *Permissions* section) with your tables
22
-
23
7
  ```bash
24
- npm install @haustle/notion-orm --save-dev
8
+ npm install @elumixor/notion-orm
25
9
  ```
26
10
 
27
- At the root of your project run the CLI to scaffold a config file (defaults to JavaScript unless a `tsconfig.json` is present):
11
+ ## Setup
12
+
13
+ **1. Initialize config**
28
14
 
29
15
  ```bash
30
- npx notion init
31
- # or
32
- bun notion init
16
+ notion init
33
17
  ```
34
18
 
35
- You can force a specific format with `--js` or `--ts`. You’ll need to pass your developer key and database IDs. How to get database IDs [here](https://developers.notion.com/docs/working-with-databases#adding-pages-to-a-database)
19
+ Creates `notion.config.ts` in your project root:
36
20
 
37
- ```tsx
38
- // notion.config.ts (generated with `notion init --ts`)
21
+ ```ts
22
+ import type { NotionConfigType } from "@elumixor/notion-orm";
39
23
 
40
- // Be sure to create a .env.local file and add your NOTION_KEY
41
-
42
- // If you don't have an API key, sign up for free
43
- // [here](https://developers.notion.com)
44
-
45
- const auth = process.env.NOTION_KEY || "your-notion-api-key-here";
46
- const NotionConfig = {
47
- auth,
48
- databaseIds: [
49
- // Add undashed database source IDs here (ex. "2a3c495da03c80bc99fe000bbf2be4bb")
50
- // or use the following command to automatically update
51
- // `notion add <database-source-id or URL>`
52
- // If you decide to manually add database IDs, be sure to run
53
- // `notion generate` to properly update the local database types
54
- ],
55
- };
56
-
57
- export default NotionConfig;
24
+ export default {
25
+ auth: process.env.NOTION_API_KEY ?? "",
26
+ databases: {
27
+ tasks: "your-notion-tasks-database-id",
28
+ people: "your-notion-people-database-id",
29
+ },
30
+ } satisfies NotionConfigType;
58
31
  ```
59
32
 
60
- Execute the following command from the root project directory.
33
+ **2. Generate types**
61
34
 
62
35
  ```bash
63
- npx notion generate
36
+ notion generate
64
37
  ```
65
38
 
66
- **Package Size**
67
- Unpackaged size is 70.6KB and the installation size is 5.12MB (5.03MB from `@notionhq/client` dependency)
39
+ Generates `generated/notion-orm/` with a typed client per database and an `index.ts` entry point.
68
40
 
69
- ## Implementation
41
+ **3. Use in your project**
70
42
 
71
- Databases can be imported via barrel file or from the individual database file. All database names will be camelCase 🐫.
43
+ ```ts
44
+ import { NotionORM } from "../generated/notion-orm";
72
45
 
73
- ```tsx
74
- // Barrel Import (access to all databases)
75
- import * as notion from "@haustle/notion-orm";
76
- notion.databaseName.add();
77
- notion.databaseName2.query();
78
- ```
46
+ const notion = new NotionORM(process.env.NOTION_API_KEY);
79
47
 
80
- ```jsx
81
- // Individual Database Import
82
- import {
83
- databaseName,
84
- DatabaseSchemaType,
85
- QuerySchemaType,
86
- } from "@haustle/notion-orm/build/db/databaseName";
87
-
88
- databaseName.add();
48
+ // or with a config object
49
+ const notion = new NotionORM({ auth: process.env.NOTION_API_KEY });
89
50
  ```
90
51
 
91
- - `DatabaseSchemaType`: Object type accepted in the database’s `add()` function
92
- - `QuerySchemaType`: Object type accepted in the database’s `query()` function
52
+ ## API
93
53
 
94
- The following examples for querying & adding are for server-side calls. If you’re looking to use this framework to execute client-side calls (ex. button click add/query X) visit the [Client (React)](https://www.notion.so/Notion-ORM-README-fdd30271bf944a3e85cb999ec8d5447d) section after reading
54
+ All methods are fully typed based on your Notion schema.
95
55
 
96
- **Adding**
56
+ ### Reading
97
57
 
98
- Only required column required is the title.
58
+ ```ts
59
+ // All records
60
+ const tasks = await notion.tasks.findMany();
99
61
 
100
- ```jsx
101
- notion.books.add({
102
- bookName: "Raphael, Painter in Rome: a Novel", // title
103
- author: "Stephanie Storey", // text
104
- status: "In progress", // status
105
- numberOfPages: 307, // number
106
- genre: ["Historical Fiction"], // multi-select
107
- rating: "⭐️⭐️⭐️⭐️", // select
108
- startDate: {
109
- // date
110
- start: "2023-01-01",
111
- },
112
- phone: "0000000000", // phone
113
- email: "tyrus@haustle.studio", // email
62
+ // With filter, sort, and limit
63
+ const tasks = await notion.tasks.findMany({
64
+ where: { status: { equals: "In Progress" } },
65
+ orderBy: { name: "asc" },
66
+ take: 10,
114
67
  });
115
- ```
116
68
 
117
- All column types in Notion databases are mapped to a typescript type.
118
-
119
- | Column Type | Object |
120
- | ------------ | -------------- |
121
- | Title | string |
122
- | Text | string |
123
- | Select | string |
124
- | Multi-select | Array (string) |
125
- | Status | string |
126
- | Number | number |
127
- | Date | Object |
128
- | Phone number | string |
129
- | Email | string |
69
+ // First match or null
70
+ const task = await notion.tasks.findFirst({
71
+ where: { name: { contains: "bug" } },
72
+ });
130
73
 
131
- **Querying**
74
+ // By page ID
75
+ const task = await notion.tasks.findUnique({ where: { id: "page-id" } });
132
76
 
133
- For each column type you’ll be presented with the available querying filter. Find all filter conditions [here](https://developers.notion.com/reference/post-database-query-filter)
77
+ // Page-by-page (UI pagination)
78
+ const page1 = await notion.tasks.paginate({ take: 20 });
79
+ const page2 = await notion.tasks.paginate({
80
+ take: 20,
81
+ after: page1.nextCursor,
82
+ });
83
+ // => { data, nextCursor, hasMore }
134
84
 
135
- While the querying functionality works, it’s **not complete and there is room for user error**. For instance, the `filter` object should contain one child. Either the column name (signifies single filter), or `and` or `or` (signify compound filters). However there is no typecheck in place to stop adding multiple children
85
+ // Streaming all results in batches (AsyncIterable)
86
+ for await (const task of notion.tasks.findMany({ stream: 50 })) {
87
+ console.log(task.name);
88
+ }
136
89
 
137
- Unlike `add()` , there is no transformation after the inputted object. So the querying object you’re creating is exactly what you’d normally use to query the Notion API. Learn more about them [here](https://developers.notion.com/reference/post-database-query-filter)
90
+ // Count
91
+ const total = await notion.tasks.count({
92
+ where: { status: { equals: "Done" } },
93
+ });
94
+ ```
138
95
 
139
- Example of a single filter
96
+ ### Select / omit
140
97
 
141
- ```tsx
142
- notion.books.query({
143
- filter: {
144
- genre: {
145
- contains: "Sci-Fi",
146
- },
147
- },
148
- sort: [
149
- {
150
- property: "name",
151
- direction: "ascending",
152
- },
153
- {
154
- property: "Author name",
155
- direction: "ascending",
156
- },
157
- ],
98
+ ```ts
99
+ // Return only specific fields
100
+ const tasks = await notion.tasks.findMany({
101
+ select: { name: true, status: true },
158
102
  });
159
- ```
160
103
 
161
- Example of compound filters, which is signified with `and` and `or`. You can nest these are far as you want (i.e `and` filters within `or` filter). Learn more [here](https://developers.notion.com/reference/post-database-query-filter#compound-filter-object)
162
-
163
- ```tsx
164
- await notion.books.query({
165
- filter: {
166
- or: [
167
- {
168
- genre: {
169
- contains: "Sci-Fi",
170
- },
171
- },
172
- {
173
- genre: {
174
- contains: "Biography",
175
- },
176
- },
177
- ],
178
- },
104
+ // Exclude specific fields
105
+ const tasks = await notion.tasks.findMany({
106
+ omit: { internalNotes: true },
179
107
  });
180
108
  ```
181
109
 
182
- Down below is what’s returned on a successful response. `results` being a simplified extracted version of the `rawResponse` (response from Notion API)
183
-
184
- ```tsx
185
- {
186
- rawResponse: { /* Whatever Notion API returns */},
187
- results: [
188
- {
189
- bookName: "How to Change Your Mind",
190
- genre: ["Non-fiction"],
191
- numberPages: 460,
192
- rating: "⭐️⭐️⭐️⭐️"
193
- },
194
- ]
195
- }
196
- ```
110
+ ### Writing
197
111
 
198
- ## Client-side (React)
112
+ ```ts
113
+ // Create
114
+ const task = await notion.tasks.create({
115
+ data: { name: "Fix bug", status: "Todo" },
116
+ });
199
117
 
200
- Notion API currently blocks calls from browser (per CORS)
118
+ // Create many
119
+ await notion.tasks.createMany({
120
+ data: [{ name: "Task A" }, { name: "Task B" }],
121
+ });
201
122
 
202
- You can get around this by creating API endpoints on stack of your choice. I’ve provided examples only for **Next.js**, but the high level implementation should work with any backend.
123
+ // Update by ID
124
+ await notion.tasks.update({
125
+ where: { id: "page-id" },
126
+ data: { status: "Done" },
127
+ });
203
128
 
204
- If you’re planning to only make server-side calls to your Notion database (from `GetStaticProps` or `GetServerSideProps`). These calls work totally fine, as these functions are executed server-side before page load. So you can ignore the proceeding steps
129
+ // Update all matching
130
+ await notion.tasks.updateMany({
131
+ where: { status: { equals: "Todo" } },
132
+ data: { status: "In Progress" },
133
+ });
205
134
 
206
- ```tsx
207
- export const getStaticProps: GetStaticProps = async () => {
208
- const response = await NotionClient.books.query({
209
- filter: {
210
- genre: {
211
- is_not_empty: true,
212
- },
213
- },
214
- });
215
- return {
216
- props: {
217
- apiResponse: response
218
- }
219
- }
220
- ```
135
+ // Upsert
136
+ await notion.tasks.upsert({
137
+ where: { name: { equals: "Fix bug" } },
138
+ create: { name: "Fix bug", status: "Todo" },
139
+ update: { status: "In Progress" },
140
+ });
221
141
 
222
- To execute calls client-side (ex. on button click) an API endpoint is needed to get around CORS. In this example we’re passing the databases `DatabaseSchemaType` as the body of the API call.
142
+ // Delete by ID
143
+ await notion.tasks.delete({ where: { id: "page-id" } });
223
144
 
224
- ```tsx
225
- import { DatabaseSchemaType } from "@haustle/notion-orm/build/db/books";
145
+ // Delete all matching
146
+ await notion.tasks.deleteMany({
147
+ where: { status: { equals: "Done" } },
148
+ });
149
+ ```
226
150
 
227
- async function addPageToNotionDatabase() {
228
- const example: DatabaseSchemaType = {
229
- bookName: "How to Change Your Mind",
230
- genre: ["Non-fiction"],
231
- };
151
+ ## CLI
232
152
 
233
- // make sure this route reflects your API path
234
- await fetch("/api/notion/books", {
235
- method: "POST",
236
- body: JSON.stringify(example),
237
- });
238
- }
239
153
  ```
240
-
241
- ```jsx
242
- <button onClick={ async() => await addPageToNotionDatabase()}>
154
+ notion init Create notion.config.ts
155
+ notion generate Generate types for all configured databases
156
+ notion add <name> <database-id-or-url> Add a database and generate its types
243
157
  ```
244
158
 
245
- Example API endpoint below, where we’re taking the body of type `DatabaseSchemaType` and passing it into the respected databases `add()` function to add a new page to the database. Learn more about _Next.js_ API’s and routing [here](https://nextjs.org/docs/api-routes/introduction).
246
-
247
- ```tsx
248
- // pages/api/notion/yourDatabaseName.ts
159
+ ## Config options
249
160
 
250
- import type { NextApiRequest, NextApiResponse } from "next";
251
- import {
252
- DatabaseSchemaType,
253
- yourDatabaseName,
254
- } from "@haustle/notion-orm/build/db/yourDatabaseName";
161
+ | Field | Type | Default | Description |
162
+ | ----------- | ------------------------ | ------------------------ | ------------------------------------------- |
163
+ | `auth` | `string` | — | Notion integration token |
164
+ | `databases` | `Record<string, string>` | — | Map of name → database ID |
165
+ | `outputDir` | `string` | `"generated/notion-orm"` | Output directory (relative to project root) |
255
166
 
256
- export default async function handler(
257
- req: NextApiRequest,
258
- res: NextApiResponse
259
- ) {
260
- const { method, body } = req;
167
+ ## Environment
261
168
 
262
- if (method === "POST") {
263
- const bodyJSON = JSON.parse(body) as DatabaseSchemaType;
264
- await yourDatabaseName.add(bodyJSON);
265
- }
266
- }
267
- ```
169
+ Set `NOTION_API_KEY` in your environment or `.env` file, or pass it directly via the `auth` field in `notion.config.ts`.
@@ -1,27 +1,26 @@
1
1
  /**
2
2
  * Internal constants for AST and db-client modules.
3
3
  */
4
- /** Generated DB files directory always relative to the consuming project */
5
- export declare const DATABASES_DIR: string;
4
+ /** Default output directory for generated files, relative to consuming project root */
5
+ export declare const DEFAULT_OUTPUT_DIR = "generated/notion-orm";
6
+ /** Resolve the absolute path to the databases output directory */
7
+ export declare function getDatabasesDir(outputDir?: string): string;
6
8
  /** File system paths for CLI output */
7
- export declare const AST_FS_PATHS: {
8
- /** metadata.json inside generated/ */
9
+ export declare function getFSPaths(databasesDirPath: string): {
10
+ /** metadata.json inside the output directory */
9
11
  readonly metadataFile: string;
10
- /** src/index.ts — the dynamic barrel rewritten on every generate */
11
- readonly sourceIndexTs: string;
12
- /** generated/index.ts — barrel exporting all DB clients */
13
- readonly generatedBarrelTs: string;
12
+ /** index.ts — the NotionORM class written to the output directory */
13
+ readonly notionORMFile: string;
14
14
  };
15
15
  export declare const AST_FS_FILENAMES: {
16
16
  readonly METADATA: "metadata.json";
17
17
  readonly INDEX_TS: "index.ts";
18
18
  };
19
- /** Import path strings used when generating TypeScript code */
20
- export declare const AST_IMPORT_PATHS: {
21
- readonly DATABASE_CLIENT: "../src/db-client/DatabaseClient" | "@elumixor/notion-orm";
22
- readonly QUERY_TYPES: "@elumixor/notion-orm" | "../src/db-client/queryTypes";
19
+ /** Import path strings used when generating TypeScript code inside a given output directory */
20
+ export declare function getImportPaths(databasesDirPath: string): {
21
+ readonly DATABASE_CLIENT: string;
22
+ readonly QUERY_TYPES: string;
23
23
  readonly ZOD: "zod";
24
- readonly databaseClass: (className: string) => string;
25
24
  };
26
25
  export declare const AST_RUNTIME_CONSTANTS: {
27
26
  readonly NOTION_API_VERSION: "2025-09-03";