@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.
@@ -1,10 +1,12 @@
1
1
  export type NotionConfigType = {
2
2
  auth: string;
3
3
  databases: Record<string, string>;
4
+ /** Output directory for generated files, relative to project root. Defaults to "generated/notion-orm" */
5
+ outputDir?: string;
4
6
  };
5
7
  export declare function validateConfig(): Promise<void>;
6
8
  export declare function findConfigFile(): {
7
9
  path: string;
8
10
  isTS: true;
9
11
  } | undefined;
10
- export declare function initializeNotionConfigFile(): Promise<void>;
12
+ export declare function initializeNotionConfigFile(): Promise<"created" | "exists">;
@@ -1,4 +1,4 @@
1
- import { SupportedNotionColumnType } from "./queryTypes";
1
+ import { SupportedNotionColumnType } from "./types";
2
2
  export declare function buildPropertyValueForAddPage(args: {
3
3
  type: SupportedNotionColumnType;
4
4
  value: string | number | boolean;
@@ -0,0 +1,169 @@
1
+ import type { CreatePageParameters, UpdatePageParameters } from "@notionhq/client/build/src/api-endpoints";
2
+ import type { ZodTypeAny } from "zod";
3
+ import type { FindManyArgs, PaginateArgs, QueryFilter, SupportedNotionColumnType } from "./types";
4
+ export type camelPropertyNameToNameAndTypeMapType = Record<string, {
5
+ columnName: string;
6
+ type: SupportedNotionColumnType;
7
+ }>;
8
+ export declare class DatabaseClient<DatabaseSchemaType extends Record<string, any>, ColumnNameToColumnType extends Record<keyof DatabaseSchemaType, SupportedNotionColumnType>> {
9
+ private client;
10
+ private id;
11
+ private camelPropertyNameToNameAndTypeMap;
12
+ private schema;
13
+ name: string;
14
+ private loggedSchemaValidationIssues;
15
+ constructor(args: {
16
+ id: string;
17
+ camelPropertyNameToNameAndTypeMap: camelPropertyNameToNameAndTypeMapType;
18
+ auth: string;
19
+ name: string;
20
+ schema: ZodTypeAny;
21
+ });
22
+ /** Find all matching records. When `stream` is set, returns an AsyncIterable fetching in batches of that size. */
23
+ findMany(args: FindManyArgs<DatabaseSchemaType, ColumnNameToColumnType> & {
24
+ stream: number;
25
+ }): AsyncIterable<Partial<DatabaseSchemaType> & {
26
+ id: string;
27
+ }>;
28
+ findMany<S extends Partial<Record<keyof DatabaseSchemaType, true>>>(args: Omit<FindManyArgs<DatabaseSchemaType, ColumnNameToColumnType>, "stream" | "select"> & {
29
+ select: S;
30
+ }): Promise<({
31
+ [K in Extract<keyof S, keyof DatabaseSchemaType>]?: DatabaseSchemaType[K];
32
+ } & {
33
+ id: string;
34
+ })[]>;
35
+ findMany<O extends Partial<Record<keyof DatabaseSchemaType, true>>>(args: Omit<FindManyArgs<DatabaseSchemaType, ColumnNameToColumnType>, "stream" | "omit"> & {
36
+ omit: O;
37
+ }): Promise<({
38
+ [K in Exclude<keyof DatabaseSchemaType, keyof O>]?: DatabaseSchemaType[K];
39
+ } & {
40
+ id: string;
41
+ })[]>;
42
+ findMany(args?: Omit<FindManyArgs<DatabaseSchemaType, ColumnNameToColumnType>, "stream">): Promise<(Partial<DatabaseSchemaType> & {
43
+ id: string;
44
+ })[]>;
45
+ /**
46
+ * Fetch one page of results using Notion's native cursor.
47
+ * Pass the returned `nextCursor` as `after` on the next call to get the next page.
48
+ */
49
+ paginate<S extends Partial<Record<keyof DatabaseSchemaType, true>>>(args: Omit<PaginateArgs<DatabaseSchemaType, ColumnNameToColumnType>, "select"> & {
50
+ select: S;
51
+ }): Promise<{
52
+ data: ({
53
+ [K in Extract<keyof S, keyof DatabaseSchemaType>]?: DatabaseSchemaType[K];
54
+ } & {
55
+ id: string;
56
+ })[];
57
+ nextCursor: string | null;
58
+ hasMore: boolean;
59
+ }>;
60
+ paginate<O extends Partial<Record<keyof DatabaseSchemaType, true>>>(args: Omit<PaginateArgs<DatabaseSchemaType, ColumnNameToColumnType>, "omit"> & {
61
+ omit: O;
62
+ }): Promise<{
63
+ data: ({
64
+ [K in Exclude<keyof DatabaseSchemaType, keyof O>]?: DatabaseSchemaType[K];
65
+ } & {
66
+ id: string;
67
+ })[];
68
+ nextCursor: string | null;
69
+ hasMore: boolean;
70
+ }>;
71
+ paginate(args?: PaginateArgs<DatabaseSchemaType, ColumnNameToColumnType>): Promise<{
72
+ data: (Partial<DatabaseSchemaType> & {
73
+ id: string;
74
+ })[];
75
+ nextCursor: string | null;
76
+ hasMore: boolean;
77
+ }>;
78
+ /** Find the first matching record, or null if none found. */
79
+ findFirst<S extends Partial<Record<keyof DatabaseSchemaType, true>>>(args: Omit<FindManyArgs<DatabaseSchemaType, ColumnNameToColumnType>, "stream" | "select"> & {
80
+ select: S;
81
+ }): Promise<({
82
+ [K in Extract<keyof S, keyof DatabaseSchemaType>]?: DatabaseSchemaType[K];
83
+ } & {
84
+ id: string;
85
+ }) | null>;
86
+ findFirst<O extends Partial<Record<keyof DatabaseSchemaType, true>>>(args: Omit<FindManyArgs<DatabaseSchemaType, ColumnNameToColumnType>, "stream" | "omit"> & {
87
+ omit: O;
88
+ }): Promise<({
89
+ [K in Exclude<keyof DatabaseSchemaType, keyof O>]?: DatabaseSchemaType[K];
90
+ } & {
91
+ id: string;
92
+ }) | null>;
93
+ findFirst(args?: Omit<FindManyArgs<DatabaseSchemaType, ColumnNameToColumnType>, "stream">): Promise<(Partial<DatabaseSchemaType> & {
94
+ id: string;
95
+ }) | null>;
96
+ /** Find a record by its Notion page ID. Returns null if not found. */
97
+ findUnique(args: {
98
+ where: {
99
+ id: string;
100
+ };
101
+ select?: Partial<Record<keyof DatabaseSchemaType, true>>;
102
+ omit?: Partial<Record<keyof DatabaseSchemaType, true>>;
103
+ }): Promise<(Partial<DatabaseSchemaType> & {
104
+ id: string;
105
+ }) | null>;
106
+ /** Create a new record and return it. */
107
+ create(args: {
108
+ data: DatabaseSchemaType;
109
+ icon?: CreatePageParameters["icon"];
110
+ }): Promise<Partial<DatabaseSchemaType> & {
111
+ id: string;
112
+ }>;
113
+ /** Create multiple records and return them. */
114
+ createMany(args: {
115
+ data: DatabaseSchemaType[];
116
+ icon?: CreatePageParameters["icon"];
117
+ }): Promise<(Partial<DatabaseSchemaType> & {
118
+ id: string;
119
+ })[]>;
120
+ /** Update a record by its Notion page ID. */
121
+ update(args: {
122
+ where: {
123
+ id: string;
124
+ };
125
+ data: Partial<DatabaseSchemaType>;
126
+ icon?: UpdatePageParameters["icon"];
127
+ }): Promise<void>;
128
+ /** Update all records matching the filter. Returns the count of updated records. */
129
+ updateMany(args: {
130
+ where?: QueryFilter<DatabaseSchemaType, ColumnNameToColumnType>;
131
+ data: Partial<DatabaseSchemaType>;
132
+ icon?: UpdatePageParameters["icon"];
133
+ }): Promise<{
134
+ count: number;
135
+ }>;
136
+ /** Create or update a record. If a record matches `where`, updates it; otherwise creates it. */
137
+ upsert(args: {
138
+ where: QueryFilter<DatabaseSchemaType, ColumnNameToColumnType>;
139
+ create: DatabaseSchemaType;
140
+ update: Partial<DatabaseSchemaType>;
141
+ icon?: CreatePageParameters["icon"];
142
+ }): Promise<{
143
+ created: boolean;
144
+ id: string;
145
+ }>;
146
+ /** Archive (soft-delete) a record by its Notion page ID. */
147
+ delete(args: {
148
+ where: {
149
+ id: string;
150
+ };
151
+ }): Promise<void>;
152
+ /** Archive all records matching the filter. Returns the count of deleted records. */
153
+ deleteMany(args?: {
154
+ where?: QueryFilter<DatabaseSchemaType, ColumnNameToColumnType>;
155
+ }): Promise<{
156
+ count: number;
157
+ }>;
158
+ /** Count records matching the filter. */
159
+ count(args?: {
160
+ where?: QueryFilter<DatabaseSchemaType, ColumnNameToColumnType>;
161
+ }): Promise<number>;
162
+ private buildCreateBody;
163
+ private parsePage;
164
+ private buildQueryCall;
165
+ private fetchAllPages;
166
+ private streamingIterable;
167
+ private applySelectOmit;
168
+ private validateDatabaseSchema;
169
+ }
@@ -1,6 +1,6 @@
1
1
  import type { QueryDataSourceResponse } from "@notionhq/client/build/src/api-endpoints";
2
- import type { apiFilterType, QueryFilter, SupportedNotionColumnType } from "./queryTypes";
3
- import type { camelPropertyNameToNameAndTypeMapType } from "./DatabaseClient";
2
+ import type { apiFilterType, QueryFilter, SupportedNotionColumnType } from "./types";
3
+ import type { camelPropertyNameToNameAndTypeMapType } from "./client";
4
4
  /**
5
5
  * Transforms Notion API query response into simplified format
6
6
  */
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Column types' for all query options
3
3
  */
4
- import type { DataSourceObjectResponse, QueryDataSourceParameters } from "@notionhq/client/build/src/api-endpoints";
4
+ import type { DataSourceObjectResponse } from "@notionhq/client/build/src/api-endpoints";
5
5
  type NotionPropertyTypeToConfigMap = DataSourceObjectResponse["properties"];
6
6
  export type DatabasePropertyType = NotionPropertyTypeToConfigMap[keyof NotionPropertyTypeToConfigMap]["type"];
7
7
  export declare const SUPPORTED_PROPERTY_TYPES: {
@@ -111,16 +111,33 @@ export type CompoundFilters<Y extends Record<string, any>, T extends Record<keyo
111
111
  or: Array<SingleFilter<Y, T> | CompoundFilters<Y, T>>;
112
112
  };
113
113
  export type QueryFilter<Y extends Record<string, any>, T extends Record<keyof Y, SupportedNotionColumnType>> = SingleFilter<Y, T> | CompoundFilters<Y, T>;
114
- export type Query<Y extends Record<string, any>, T extends Record<keyof Y, SupportedNotionColumnType>> = {
115
- filter?: QueryFilter<Y, T>;
116
- sort?: QueryDataSourceParameters["sorts"];
114
+ /** Prisma-style orderBy: `{ fieldName: 'asc' | 'desc' }` or an array of those */
115
+ export type OrderByInput<Y> = {
116
+ [K in keyof Y]?: "asc" | "desc";
117
+ } | {
118
+ [K in keyof Y]?: "asc" | "desc";
119
+ }[];
120
+ export type FindManyArgs<Y extends Record<string, any>, T extends Record<keyof Y, SupportedNotionColumnType>> = {
121
+ where?: QueryFilter<Y, T>;
122
+ orderBy?: OrderByInput<Y>;
123
+ select?: Partial<Record<keyof Y, true>>;
124
+ omit?: Partial<Record<keyof Y, true>>;
125
+ take?: number;
126
+ skip?: number;
127
+ /** When set, findMany returns an AsyncIterable, fetching results in batches of this size */
128
+ stream?: number;
129
+ };
130
+ export type PaginateArgs<Y extends Record<string, any>, T extends Record<keyof Y, SupportedNotionColumnType>> = {
131
+ where?: QueryFilter<Y, T>;
132
+ orderBy?: OrderByInput<Y>;
117
133
  select?: Partial<Record<keyof Y, true>>;
118
134
  omit?: Partial<Record<keyof Y, true>>;
119
- limit?: number;
120
- pagination?: {
121
- pageSize: number;
122
- };
135
+ take?: number;
136
+ /** Opaque cursor token returned by a previous paginate() call */
137
+ after?: string;
123
138
  };
139
+ /** @deprecated Use FindManyArgs */
140
+ export type Query<Y extends Record<string, any>, T extends Record<keyof Y, SupportedNotionColumnType>> = FindManyArgs<Y, T>;
124
141
  export type apiFilterQuery = {
125
142
  filter?: apiSingleFilter | apiAndFilter | apiOrFilter;
126
143
  };
package/dist/index.js CHANGED
@@ -3,15 +3,29 @@ var __getProtoOf = Object.getPrototypeOf;
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ function __accessProp(key) {
7
+ return this[key];
8
+ }
9
+ var __toESMCache_node;
10
+ var __toESMCache_esm;
6
11
  var __toESM = (mod, isNodeMode, target) => {
12
+ var canCache = mod != null && typeof mod === "object";
13
+ if (canCache) {
14
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
15
+ var cached = cache.get(mod);
16
+ if (cached)
17
+ return cached;
18
+ }
7
19
  target = mod != null ? __create(__getProtoOf(mod)) : {};
8
20
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
9
21
  for (let key of __getOwnPropNames(mod))
10
22
  if (!__hasOwnProp.call(to, key))
11
23
  __defProp(to, key, {
12
- get: () => mod[key],
24
+ get: __accessProp.bind(mod, key),
13
25
  enumerable: true
14
26
  });
27
+ if (canCache)
28
+ cache.set(mod, to);
15
29
  return to;
16
30
  };
17
31
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
@@ -1327,7 +1341,7 @@ var require_src = __commonJS((exports) => {
1327
1341
  } });
1328
1342
  });
1329
1343
 
1330
- // src/db-client/DatabaseClient.ts
1344
+ // src/db-client/client.ts
1331
1345
  var import_client = __toESM(require_src(), 1);
1332
1346
 
1333
1347
  // src/ast/constants.ts
@@ -1337,7 +1351,6 @@ var __filename2 = fileURLToPath(import.meta.url);
1337
1351
  var __dirname2 = path.dirname(__filename2);
1338
1352
  var PACKAGE_ROOT = path.resolve(__dirname2, "../../");
1339
1353
  var IS_DEV = process.cwd() === PACKAGE_ROOT;
1340
- var DATABASES_DIR = path.join(process.cwd(), "generated");
1341
1354
  var AST_RUNTIME_CONSTANTS = {
1342
1355
  NOTION_API_VERSION: "2025-09-03",
1343
1356
  PACKAGE_LOG_PREFIX: "[@elumixor/notion-orm]",
@@ -1346,6 +1359,16 @@ var AST_RUNTIME_CONSTANTS = {
1346
1359
  SCHEMA_DRIFT_HELP_MESSAGE: "Run `notion generate` to refresh all database schemas."
1347
1360
  };
1348
1361
 
1362
+ // src/helpers.ts
1363
+ function camelize(str) {
1364
+ const cleaned = str.replace(/[^a-zA-Z0-9\s]/g, "");
1365
+ return cleaned.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, function(match, index) {
1366
+ if (+match === 0)
1367
+ return "";
1368
+ return index === 0 ? match.toLowerCase() : match.toUpperCase();
1369
+ });
1370
+ }
1371
+
1349
1372
  // src/db-client/add.ts
1350
1373
  function buildPropertyValueForAddPage(args) {
1351
1374
  const { type, value } = args;
@@ -1441,16 +1464,6 @@ var emailCall = (args) => {
1441
1464
  return { email: value };
1442
1465
  };
1443
1466
 
1444
- // src/helpers.ts
1445
- function camelize(str) {
1446
- const cleaned = str.replace(/[^a-zA-Z0-9\s]/g, "");
1447
- return cleaned.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, function(match, index) {
1448
- if (+match === 0)
1449
- return "";
1450
- return index === 0 ? match.toLowerCase() : match.toUpperCase();
1451
- });
1452
- }
1453
-
1454
1467
  // src/db-client/query.ts
1455
1468
  function buildQueryResponse(res, camelPropertyNameToNameAndTypeMap, validateSchema) {
1456
1469
  const results = res.results.map((result, index) => {
@@ -1582,7 +1595,7 @@ function recursivelyBuildFilter(queryFilter, camelPropertyNameToNameAndTypeMap)
1582
1595
  }
1583
1596
  }
1584
1597
 
1585
- // src/db-client/DatabaseClient.ts
1598
+ // src/db-client/client.ts
1586
1599
  class DatabaseClient {
1587
1600
  client;
1588
1601
  id;
@@ -1603,103 +1616,181 @@ class DatabaseClient {
1603
1616
  this.name = args.name;
1604
1617
  this.loggedSchemaValidationIssues = new Set;
1605
1618
  }
1606
- async add(args) {
1607
- const { properties: pageObject, icon } = args;
1608
- const callBody = {
1609
- parent: {
1610
- data_source_id: this.id,
1611
- type: "data_source_id"
1612
- },
1613
- properties: {}
1614
- };
1615
- callBody.icon = icon;
1616
- Object.entries(pageObject).forEach(([propertyName, value]) => {
1617
- const { type, columnName } = this.camelPropertyNameToNameAndTypeMap[propertyName];
1618
- const columnObject = buildPropertyValueForAddPage({
1619
- type,
1620
- value
1621
- });
1622
- if (callBody.properties && columnObject) {
1623
- callBody.properties[columnName] = columnObject;
1624
- }
1619
+ findMany(args = {}) {
1620
+ const queryCall = this.buildQueryCall(args);
1621
+ if (args.stream)
1622
+ return this.streamingIterable(queryCall, args);
1623
+ return this.fetchAllPages(queryCall, args);
1624
+ }
1625
+ async paginate(args) {
1626
+ const queryCall = this.buildQueryCall(args ?? {});
1627
+ const response = await this.client.dataSources.query({
1628
+ ...queryCall,
1629
+ page_size: args?.take ?? 100,
1630
+ start_cursor: args?.after
1625
1631
  });
1626
- return await this.client.pages.create(callBody);
1632
+ const results = buildQueryResponse(response, this.camelPropertyNameToNameAndTypeMap, (r) => this.validateDatabaseSchema(r));
1633
+ return {
1634
+ data: this.applySelectOmit(results, args?.select, args?.omit),
1635
+ nextCursor: response.has_more ? response.next_cursor ?? null : null,
1636
+ hasMore: response.has_more
1637
+ };
1627
1638
  }
1628
- async update(id, properties, icon) {
1629
- const callBody = { page_id: id, properties: {} };
1630
- if (icon !== undefined)
1631
- callBody.icon = icon;
1632
- Object.entries(properties).forEach(([propertyName, value]) => {
1639
+ async findFirst(args) {
1640
+ const queryCall = this.buildQueryCall(args ?? {});
1641
+ const response = await this.client.dataSources.query({ ...queryCall, page_size: 1 });
1642
+ const results = buildQueryResponse(response, this.camelPropertyNameToNameAndTypeMap, (r) => this.validateDatabaseSchema(r));
1643
+ if (results.length === 0)
1644
+ return null;
1645
+ const [item] = this.applySelectOmit(results, args?.select, args?.omit);
1646
+ return item ?? null;
1647
+ }
1648
+ async findUnique(args) {
1649
+ try {
1650
+ const page = await this.client.pages.retrieve({ page_id: args.where.id });
1651
+ if (!("properties" in page))
1652
+ return null;
1653
+ const result = this.parsePage(page);
1654
+ const [item] = this.applySelectOmit([result], args.select, args.omit);
1655
+ return item ?? null;
1656
+ } catch {
1657
+ return null;
1658
+ }
1659
+ }
1660
+ async create(args) {
1661
+ const callBody = this.buildCreateBody(args.data, args.icon);
1662
+ const page = await this.client.pages.create(callBody);
1663
+ return this.parsePage(page);
1664
+ }
1665
+ async createMany(args) {
1666
+ return Promise.all(args.data.map((data) => this.create({ data, icon: args.icon })));
1667
+ }
1668
+ async update(args) {
1669
+ const callBody = { page_id: args.where.id, properties: {} };
1670
+ if (args.icon !== undefined)
1671
+ callBody.icon = args.icon;
1672
+ for (const [propertyName, value] of Object.entries(args.data)) {
1633
1673
  const { type, columnName } = this.camelPropertyNameToNameAndTypeMap[propertyName];
1634
1674
  const columnObject = buildPropertyValueForAddPage({ type, value });
1635
- if (callBody.properties && columnObject) {
1675
+ if (callBody.properties && columnObject)
1636
1676
  callBody.properties[columnName] = columnObject;
1637
- }
1638
- });
1677
+ }
1639
1678
  await this.client.pages.update(callBody);
1640
1679
  }
1641
- async delete(id) {
1642
- await this.client.pages.update({ page_id: id, archived: true });
1680
+ async updateMany(args) {
1681
+ const queryCall = this.buildQueryCall({ where: args.where });
1682
+ const results = await this.fetchAllPages(queryCall, {});
1683
+ await Promise.all(results.map((r) => this.update({ where: { id: r.id }, data: args.data, icon: args.icon })));
1684
+ return { count: results.length };
1643
1685
  }
1644
1686
  async upsert(args) {
1645
- const queryCall = this.buildQueryCall({ filter: args.where });
1687
+ const queryCall = this.buildQueryCall({ where: args.where });
1646
1688
  const response = await this.client.dataSources.query({ ...queryCall, page_size: 1 });
1647
1689
  if (response.results.length > 0) {
1648
1690
  const existingId = response.results[0].id;
1649
- await this.update(existingId, args.properties, args.icon);
1691
+ await this.update({ where: { id: existingId }, data: args.update, icon: args.icon });
1650
1692
  return { created: false, id: existingId };
1651
1693
  }
1652
- const created = await this.add({ properties: args.properties, icon: args.icon });
1694
+ const created = await this.create({ data: args.create, icon: args.icon });
1653
1695
  return { created: true, id: created.id };
1654
1696
  }
1655
- query(query) {
1656
- const queryCall = this.buildQueryCall(query);
1657
- if (query.pagination)
1658
- return this.paginatedIterable(queryCall, query);
1659
- return this.fetchAllPages(queryCall, query);
1697
+ async delete(args) {
1698
+ await this.client.pages.update({ page_id: args.where.id, archived: true });
1699
+ }
1700
+ async deleteMany(args) {
1701
+ const queryCall = this.buildQueryCall({ where: args?.where });
1702
+ const results = await this.fetchAllPages(queryCall, {});
1703
+ await Promise.all(results.map((r) => this.delete({ where: { id: r.id } })));
1704
+ return { count: results.length };
1705
+ }
1706
+ async count(args) {
1707
+ const queryCall = this.buildQueryCall({ where: args?.where });
1708
+ const results = await this.fetchAllPages(queryCall, {});
1709
+ return results.length;
1710
+ }
1711
+ buildCreateBody(data, icon) {
1712
+ const callBody = {
1713
+ parent: { data_source_id: this.id, type: "data_source_id" },
1714
+ properties: {}
1715
+ };
1716
+ if (icon)
1717
+ callBody.icon = icon;
1718
+ for (const [propertyName, value] of Object.entries(data)) {
1719
+ const { type, columnName } = this.camelPropertyNameToNameAndTypeMap[propertyName];
1720
+ const columnObject = buildPropertyValueForAddPage({ type, value });
1721
+ if (callBody.properties && columnObject)
1722
+ callBody.properties[columnName] = columnObject;
1723
+ }
1724
+ return callBody;
1725
+ }
1726
+ parsePage(page) {
1727
+ const result = { id: page.id };
1728
+ for (const [columnName, value] of Object.entries(page.properties)) {
1729
+ const camelName = camelize(columnName);
1730
+ const colType = this.camelPropertyNameToNameAndTypeMap[camelName]?.type;
1731
+ if (colType)
1732
+ result[camelName] = getResponseValue(colType, value);
1733
+ }
1734
+ return result;
1660
1735
  }
1661
- buildQueryCall(query) {
1736
+ buildQueryCall(args) {
1662
1737
  const queryCall = { data_source_id: this.id };
1663
- if (query.sort?.length) {
1664
- queryCall["sorts"] = query.sort.map((s) => {
1665
- if (!("property" in s))
1666
- return s;
1667
- return { ...s, property: this.camelPropertyNameToNameAndTypeMap[s.property]?.columnName ?? s.property };
1668
- });
1738
+ if (args.orderBy) {
1739
+ const orderByArr = Array.isArray(args.orderBy) ? args.orderBy : [args.orderBy];
1740
+ queryCall["sorts"] = orderByArr.flatMap((obj) => Object.entries(obj).map(([prop, dir]) => ({
1741
+ property: this.camelPropertyNameToNameAndTypeMap[prop]?.columnName ?? prop,
1742
+ direction: dir === "asc" ? "ascending" : "descending"
1743
+ })));
1669
1744
  }
1670
- if (query.filter) {
1671
- queryCall["filter"] = recursivelyBuildFilter(query.filter, this.camelPropertyNameToNameAndTypeMap);
1745
+ if (args.where) {
1746
+ queryCall["filter"] = recursivelyBuildFilter(args.where, this.camelPropertyNameToNameAndTypeMap);
1672
1747
  }
1673
1748
  return queryCall;
1674
1749
  }
1675
- async fetchAllPages(queryCall, query) {
1750
+ async fetchAllPages(queryCall, args) {
1676
1751
  const allResults = [];
1677
1752
  let cursor;
1678
1753
  let isFirst = true;
1754
+ const take = args.take;
1755
+ const skip = args.skip ?? 0;
1756
+ let fetched = 0;
1679
1757
  do {
1680
1758
  const response = await this.client.dataSources.query({ ...queryCall, start_cursor: cursor, page_size: 100 });
1681
1759
  const page = buildQueryResponse(response, this.camelPropertyNameToNameAndTypeMap, isFirst ? (r) => this.validateDatabaseSchema(r) : () => {});
1682
1760
  isFirst = false;
1683
- allResults.push(...this.applySelectOmit(page, query.select, query.omit));
1761
+ for (const item of this.applySelectOmit(page, args.select, args.omit)) {
1762
+ fetched++;
1763
+ if (fetched <= skip)
1764
+ continue;
1765
+ allResults.push(item);
1766
+ if (take && allResults.length >= take)
1767
+ return allResults;
1768
+ }
1684
1769
  cursor = response.has_more ? response.next_cursor ?? undefined : undefined;
1685
- } while (cursor && (!query.limit || allResults.length < query.limit));
1686
- return query.limit ? allResults.slice(0, query.limit) : allResults;
1770
+ } while (cursor);
1771
+ return allResults;
1687
1772
  }
1688
- paginatedIterable(queryCall, query) {
1773
+ streamingIterable(queryCall, args) {
1689
1774
  const self = this;
1690
- const pageSize = query.pagination.pageSize;
1775
+ const pageSize = args.stream;
1691
1776
  return {
1692
1777
  [Symbol.asyncIterator]: async function* () {
1693
1778
  let cursor;
1694
1779
  let isFirst = true;
1695
1780
  let yielded = 0;
1781
+ let skipped = 0;
1782
+ const skip = args.skip ?? 0;
1696
1783
  do {
1697
1784
  const response = await self.client.dataSources.query({ ...queryCall, start_cursor: cursor, page_size: pageSize });
1698
1785
  const page = buildQueryResponse(response, self.camelPropertyNameToNameAndTypeMap, isFirst ? (r) => self.validateDatabaseSchema(r) : () => {});
1699
1786
  isFirst = false;
1700
- for (const item of self.applySelectOmit(page, query.select, query.omit)) {
1787
+ for (const item of self.applySelectOmit(page, args.select, args.omit)) {
1788
+ if (skipped < skip) {
1789
+ skipped++;
1790
+ continue;
1791
+ }
1701
1792
  yield item;
1702
- if (query.limit && ++yielded >= query.limit)
1793
+ if (args.take && ++yielded >= args.take)
1703
1794
  return;
1704
1795
  }
1705
1796
  cursor = response.has_more ? response.next_cursor ?? undefined : undefined;
@@ -1725,22 +1816,17 @@ class DatabaseClient {
1725
1816
  });
1726
1817
  }
1727
1818
  validateDatabaseSchema(result) {
1728
- if (!this.schema) {
1819
+ if (!this.schema)
1729
1820
  return;
1730
- }
1731
1821
  const schemaLabel = this.name ?? this.id;
1732
1822
  const remoteColumnNames = new Set(Object.keys(result));
1733
1823
  const missingProperties = [];
1734
1824
  for (const propName in this.camelPropertyNameToNameAndTypeMap) {
1735
- if (!remoteColumnNames.has(propName)) {
1825
+ if (!remoteColumnNames.has(propName))
1736
1826
  missingProperties.push(propName);
1737
- }
1738
1827
  }
1739
1828
  if (missingProperties.length > 0) {
1740
- const issueSignature2 = JSON.stringify({
1741
- type: "missing_properties",
1742
- properties: missingProperties
1743
- });
1829
+ const issueSignature2 = JSON.stringify({ type: "missing_properties", properties: missingProperties });
1744
1830
  if (!this.loggedSchemaValidationIssues.has(issueSignature2)) {
1745
1831
  this.loggedSchemaValidationIssues.add(issueSignature2);
1746
1832
  console.error(`⚠️ ${AST_RUNTIME_CONSTANTS.PACKAGE_LOG_PREFIX} ${AST_RUNTIME_CONSTANTS.SCHEMA_DRIFT_PREFIX} for the following Notion database ${schemaLabel}
@@ -1756,10 +1842,7 @@ Missing properties: ${missingProperties.map((prop) => `\`${prop}\``).join(", ")}
1756
1842
  if (remoteColName === "id")
1757
1843
  continue;
1758
1844
  if (!this.camelPropertyNameToNameAndTypeMap[remoteColName]) {
1759
- const issueSignature2 = JSON.stringify({
1760
- type: "unexpected_property",
1761
- property: remoteColName
1762
- });
1845
+ const issueSignature2 = JSON.stringify({ type: "unexpected_property", property: remoteColName });
1763
1846
  if (!this.loggedSchemaValidationIssues.has(issueSignature2)) {
1764
1847
  this.loggedSchemaValidationIssues.add(issueSignature2);
1765
1848
  console.error(`⚠️ ${AST_RUNTIME_CONSTANTS.PACKAGE_LOG_PREFIX} ${AST_RUNTIME_CONSTANTS.SCHEMA_DRIFT_PREFIX} for the following Notion database ${schemaLabel}
@@ -1773,17 +1856,11 @@ Unexpected property found in remote data: \`${remoteColName}\`
1773
1856
  }
1774
1857
  }
1775
1858
  const parseResult = this.schema.safeParse(result);
1776
- if (parseResult.success) {
1859
+ if (parseResult.success)
1777
1860
  return;
1778
- }
1779
- const issueSignature = JSON.stringify(parseResult.error.issues.map((issue) => ({
1780
- code: issue.code,
1781
- path: issue.path,
1782
- message: issue.message
1783
- })));
1784
- if (this.loggedSchemaValidationIssues.has(issueSignature)) {
1861
+ const issueSignature = JSON.stringify(parseResult.error.issues.map((issue) => ({ code: issue.code, path: issue.path, message: issue.message })));
1862
+ if (this.loggedSchemaValidationIssues.has(issueSignature))
1785
1863
  return;
1786
- }
1787
1864
  this.loggedSchemaValidationIssues.add(issueSignature);
1788
1865
  console.error(`⚠️ ${AST_RUNTIME_CONSTANTS.PACKAGE_LOG_PREFIX} ${AST_RUNTIME_CONSTANTS.SCHEMA_DRIFT_PREFIX} for the following Notion database ${schemaLabel}
1789
1866
 
@@ -1792,10 +1869,7 @@ Validation issues: ${parseResult.error.issues.map((issue) => `\`${issue.path.joi
1792
1869
 
1793
1870
  ✅ ${AST_RUNTIME_CONSTANTS.SCHEMA_DRIFT_HELP_MESSAGE}
1794
1871
  `);
1795
- console.log("Validation details:", {
1796
- issues: parseResult.error.issues,
1797
- result
1798
- });
1872
+ console.log("Validation details:", { issues: parseResult.error.issues, result });
1799
1873
  }
1800
1874
  }
1801
1875
  export {
@@ -2,6 +2,6 @@
2
2
  * Public API entry point for @elumixor/notion-orm.
3
3
  * External users' generated files import DatabaseClient and types from here.
4
4
  */
5
- export { DatabaseClient } from "./db-client/DatabaseClient";
6
- export type { Query, QueryResult, QueryResultType, SupportedNotionColumnType } from "./db-client/queryTypes";
5
+ export { DatabaseClient } from "./db-client/client";
6
+ export type { FindManyArgs, PaginateArgs, Query, QueryFilter, QueryResult, QueryResultType, OrderByInput, SupportedNotionColumnType, } from "./db-client/types";
7
7
  export type { NotionConfigType } from "./config/helpers";