Closed JClackett closed 2 years ago
Also, I used to have a React component that could render out the RichTextBlock like below:
import * as React from "react"
import { Text, Link, StyleProps } from "@chakra-ui/react"
import type { Annotations, RichText } from "@notionhq/client/build/src/api-types"
interface RichTextBlockProps {
text: RichText[]
}
export function RichTextBlock(props: RichTextBlockProps) {
const textDecorationAttr = React.useCallback((annotations: Annotations): Omit<StyleProps, "apply"> => {
return {
fontWeight: annotations.bold ? 600 : undefined,
textDecor: `${annotations.underline ? "underline " : ""}${
annotations.strikethrough ? "line-through" : ""
}`,
fontStyle: annotations.italic ? "italic" : undefined,
}
}, [])
if (props.text.length === 0) return <br />
return (
<>
{props.text.map((text, i) => {
if (text.href) {
return (
<Link
key={i}
href={text.href}
isExternal
color="gray.600"
_hover={{ color: "gray.900" }}
{...textDecorationAttr(text.annotations)}
textDecor="underline"
>
{text.plain_text}
</Link>
)
} else {
return (
<Text as="span" key={i} {...textDecorationAttr(text.annotations)}>
{text.plain_text}
</Text>
)
}
})}
</>
)
}
Which was super nice as I could just import the "RichText" type, now it seems I have to manually copy it from your types file:
type RichText = {
type: "text"
text: {
content: string
link: {
url: string
} | null
}
annotations: {
bold: boolean
italic: boolean
strikethrough: boolean
underline: boolean
code: boolean
color:
| "default"
| "gray"
| "brown"
| "orange"
| "yellow"
| "green"
| "blue"
| "purple"
| "pink"
| "red"
| "gray_background"
| "brown_background"
| "orange_background"
| "yellow_background"
| "green_background"
| "blue_background"
| "purple_background"
| "pink_background"
| "red_background"
}
plain_text: string
href: string | null
}
You can extract more to grt what you need. Check this out to see how I do it: https://github.com/nartc/notion-stuff/blob/6e22fbffafb528e41cc31258c34b5234048ebb25/libs/v4-types/src/lib/types.ts#L6
I'm also finding the removal/change of these types frustrating. Was really nice to just be able to import them directly.
This made the client much much harder to work with when using typescript. Also the type hints that you get from visual code etc is more or less unreadable :/
Also finding this frustrating :(
I was actually trying to build their types from scratch for them and raise a PR, but its extremely long
I'm assuming they auto generated them?
Apologies for the inconvenience of the new types. In an effort to provide a more accurate and up to date set of types we started auto-generating the types directly from our codebase but in the process we have made the types less ergonomic to work with. In the meantime, using the strategy suggested by @nartc should work to get access to specific block types as shown below:
import { GetBlockResponse } from './api-endpoints'
type HeadingOneBlockResponse = Extract<GetBlockResponse, { type: "heading_1"}>
type ImageBlockResponse = Extract<GetBlockResponse, { type: "image"}>
Note: The types will be slightly different for each endpoint request/responses. For example all responses will have type
as required since it's alway returned, but it's optional for all requests. Another example difference is when appending blocks, you're able to add children, but when updating blocks, you can't.
Thanks @rhart92! I think this workaround is nice.
However, I wanted to note that a major problem I'm having with the latest typings is that they appear to be so large that my VSCode refuses to allow me to introspect them in the way that I'm used to.
For example, here's a snippet of code that I'd like to explore:
import notion from "@notionhq/client";
const client = new notion.Client({});
const stuff = await client.databases.query({
database_id: "fake"
});
console.log(stuff);
Using VSCode with version 0.3.3 of @notionhq/client
, hovering my mouse over stuff
reveals that it is of type DatabasesQueryResponse
:
Furthermore, if I right-click on the variable and choose "Go to Type Definition", I am taken to the data type's definition in api-endpoints.d.ts
, which leads me to more types which I can easily explore by right-clicking on them and choosing "Go to Definition". This is a great learning tool for me and it's how I explore many libraries.
It also looks like version 0.3.3 has reasonably-sized typings:
$ du -c -h node_modules/@notionhq/client/build/src/*.d.ts
8.0K node_modules/@notionhq/client/build/src/Client.d.ts
16K node_modules/@notionhq/client/build/src/api-endpoints.d.ts
28K node_modules/@notionhq/client/build/src/api-types.d.ts
4.0K node_modules/@notionhq/client/build/src/errors.d.ts
4.0K node_modules/@notionhq/client/build/src/fetch-types.d.ts
4.0K node_modules/@notionhq/client/build/src/helpers.d.ts
4.0K node_modules/@notionhq/client/build/src/index.d.ts
4.0K node_modules/@notionhq/client/build/src/logging.d.ts
4.0K node_modules/@notionhq/client/build/src/type-utils.d.ts
76K total
Now let's fast-forward to version 0.4.0 of the same library. Now right-clicking on stuff
and choosing "Go to Type Definition" still works, but it takes a few seconds for VSCode to show me the file.
I think this is because the typings are much larger in 0.4.0:
$ du -c -h node_modules/@notionhq/client/build/src/*.d.ts
8.0K node_modules/@notionhq/client/build/src/Client.d.ts
868K node_modules/@notionhq/client/build/src/api-endpoints.d.ts
4.0K node_modules/@notionhq/client/build/src/errors.d.ts
4.0K node_modules/@notionhq/client/build/src/fetch-types.d.ts
4.0K node_modules/@notionhq/client/build/src/helpers.d.ts
4.0K node_modules/@notionhq/client/build/src/index.d.ts
4.0K node_modules/@notionhq/client/build/src/logging.d.ts
4.0K node_modules/@notionhq/client/build/src/type-utils.d.ts
900K total
Now let's go to the very latest version of the library, 0.4.6. At this point, hovering my mouse over stuff
shows the tooltip (loading...) const stuff: any
for several seconds before it resolves to its actual type. But what's really unfortunate is that right-clicking on the symbol and choosing "Go to Type Definition" doesn't work at all:
I suspect this is because the typings have now become even larger:
$ du -c -h node_modules/@notionhq/client/build/src/*.d.ts
8.0K node_modules/@notionhq/client/build/src/Client.d.ts
6.6M node_modules/@notionhq/client/build/src/api-endpoints.d.ts
4.0K node_modules/@notionhq/client/build/src/errors.d.ts
4.0K node_modules/@notionhq/client/build/src/fetch-types.d.ts
4.0K node_modules/@notionhq/client/build/src/helpers.d.ts
4.0K node_modules/@notionhq/client/build/src/index.d.ts
4.0K node_modules/@notionhq/client/build/src/logging.d.ts
4.0K node_modules/@notionhq/client/build/src/type-utils.d.ts
6.6M total
My guess is that TypeScript is seeing that api-endpoints.d.ts
is 6.6 megabytes and simply refusing to take me there, but I'm not sure. I'm also guessing that these huge typings have an effect on the performance of the TypeScript compiler, both in its speed and memory use, but I haven't done any work to verify that.
In any case, it would be great if the typings could be nice and small again, as it would likely bring back the great developer ergonomics that version 0.3.3 had.
Good news--it looks like the latest release, 0.4.8, has slimmed down the api-endpoints.d.ts
file to 2.2 megabytes (in particular, an EmojiRequest
type is now declared once and reused in many places, instead of being inlined everywhere). This makes my VSCode show me type definitions once more, which is great! Though further slimming of the SDK would be quite appreciated for the reasons mentioned in my previous comment.
Hi @rhart92 ,
thanks for the work on the client. I see some comments about the fact that the code-generation trade-offs kind of kills DX in Typescript, just wanted to know if there was a roadmap to improve that in the future? (To implement better composition and more modularity rather than just exhaustive structure)?
Thank you for your clarifications :)
@rhart92 First of all, I want to say that I absolutely LOVE being able to use the notion API, let alone having an SDK for it. Thank you for your (and your colleague's) hard work on this, I'm sure it's no easy task !
I want to explain my understanding of my current problem with a simple example first and then explain my use case. I tried to see how I can apply your workaround with Extract<>
but I'm not good enough with Typescript yet to understand how to do that with an Array of Union types. I searched as much as I could but I'm struggling here.
I'm using the SDK @ version 0.4.12
with typescript 4.5.4
I seem to notice a fundamental problem with the way some types seem to be generated, especially with how Union types are designed within Typescript. It took me a while to get to the root of the problem and I learned something new so there's that :D
From the Handbook
TypeScript will only allow an operation if it is valid for every member of the union. For example, if you have the union string | number, you can’t use methods that are only available on string:
This constraint is better explained with the example below
type GetAllPeople = () => { results: Array<{ name: string, age: number} | { age: number}>}
type GetAllPeopleReturnType = ReturnType<GetAllPeople>['results']
const someArray: GetAllPeople = [{ name: 'Simon', age: 30 }, [{ age: 22}]
// Will autocomplete and TS is happy
const allAges = someArray.map(element => element.age)
// Will not autocomplete and TS will complain because the property 'name' is not applicable
// to every member of Array<{ name: string, age: number} | { age: number}>
const allNames = someArray.map(element => element.name)
I am using the QueryDatabaseResponse
type is exported in auto-generated types in: @notionhq/client/build/src/api-endpoints
I build a little helper function to help me handle pagination automatically and I only need to feed it the parameters for the query
function.
The return value's type is QueryDatabaseResponse['results']
which I am extracting from the return type of databases.query
-> Awaited<ReturnType<typeof query>>['results']
.
/**
* Query all pages and return all records from a Notion database object
* Will log a warning if database has no records
* @param parameters To specify database id, control sorting, filtering and pagination, directly using Notion's sdk types
* @returns A list of all the results from the database or an empty array
*/
export const queryAll = async (
parameters: QueryDatabaseParameters,
) => {
const params: typeof parameters = { ...parameters };
// getNotionClient() returns an authenticated instance of the notion SDK
const query = getNotionClient().databases.query;
const allResults: Awaited<ReturnType<typeof query>>['results'] = [];
let hasNextPage = true;
while (hasNextPage) {
const currentPage = await query(params);
allResults.push(...currentPage.results);
hasNextPage = currentPage.has_more;
params.start_cursor = currentPage.next_cursor || undefined; // next_cursor is string | null while params.start_cursor is string | undefined -.-
}
if (!allResults.length) {
logger.warn(`No results found in database ${params.database_id}`);
}
return allResults;
};
Unfortunately, it seems that the type of allResults
is stuck to { id: string; object: 'page'}[]
while the generated type for a database response (included in an expandable toggle right below) seem to indicate that the results
property can be a lot of things. I'm unsure how to pick the right one, I would rather have the SDK infer it correctly based on the responses' contents but I don't know if that's even doable given the complexity and possible permutations of objects we can have in a Database.
Thoughts ? :/
I've found that the generate-random-data
SDK example shows the same issue with the newer partial-response typings. So here's how I fixed it:
const queryResponse = await notion.databases.query({
database_id: databaseId,
})
queryResponse.results.forEach(pageIn => {
// if (pageIn.object) return;
const page = pageIn as Extract<typeof pageIn, {parent: {}}>;
I really expected the if (page.object) return;
check to narrow the union, but it didn't actually change anything in the types so the Extract cast is doing the actual work. I'm not sure if I believe that 'object' isn't always there at this point.
Hello,
I have released my personal Notion API toolkit to NPM here: @jitl/notion-api. This package exports types like Block<BlockType>
, Page
, RichText
, etc derived from the official types exported from this repo (which I recently shrank from 2.2mb to 300kb). @jitl/notion-api also provides utility functions for iterating paginated APIs, narrowing types like GetBlockResponse
, and more. See the README section on API helpers.
To re-write @danopia's example with my library:
import { isFullPage, iteratePaginatedAPI } from '@jitl/notion-api';
for await (const page of iteratePaginatedAPI(notion.databases.query, {
database_id: databaseId,
})) {
if (isFullPage(page)) {
// page is a "full" page object
doThings(page.properties)
}
}
To re-write @Niceplace's example with my library:
import { isFullPage, iteratePaginatedAPI, asyncIterableToArray } from '@jitl/notion-api';
/**
* Query all pages and return all records from a Notion database object
* Will log a warning if database has no records
* @param parameters To specify database id, control sorting, filtering and
* pagination, directly using Notion's sdk types
* @returns A list of all the results from the database or an empty array
*/
export const queryAll = async (
parameters: QueryDatabaseParameters
): Promise<Page[]> => {
const params: typeof parameters = { ...parameters };
const resultsWithPartialPages = await asyncIterableToArray(
// getNotionClient() returns an authenticated instance of the notion SDK
iteratePaginatedAPI(getNotionClient().databases.query, parameters)
);
// Filter out partial pages
const fullPages = resultsWithPartialPages.filter(isFullPage);
if (!fullPages.length) {
logger.warn(`No results found in database ${params.database_id}`);
}
return fullPages;
};
@jitl/notion-api is not an official Notion product. I wrote it for my own use, to support my website and other personal projects, although I welcome contributions of any kind.
I'm experimenting with writing a Notion integration, but I cannot figure out how to use the types generated for search. The API documentation says that the results have properties like created_by
, title
, etc. But the only properties TS thinks they have is id
and object
:
Even if I check the value of object
, I still cannot access any of these metadata fields:
Am I missing something?
@jonrimmer there are two types here - “partial” object types, which are returned for objects that an integration can locate, but not access, and “full” object types that have all of the properties referenced in the documentation.
You need to check for the presence of a full-object-only key in the response to prove to Typescript that the object is “full”. This is annoying to do, so I published a helper library (linked in the above post) that exports functions like isFullPage
, etc.
From my library’s README:
The Notion API can sometimes return "partial" object data that contain only the block's ID:
// In @notionhq/client typings:
type PartialBlockObjectResponse = { object: 'block'; id: string };
export type GetBlockResponse = PartialBlockObjectResponse | BlockObjectResponse;
Checking that a GetBlockResponse (or similar type) is a "full" block gets old pretty fast, so this library exports type guard functions to handle common cases, like isFullPage(page) and isFullBlock(block).
isFullBlock can optionally narrow the type of block as well:
if (isFullBlock(block, 'paragraph')) {
// It's a full paragraph block
console.log(richTextAsPlainText(block.paragraph.text));
}
@jonrimmer This is most likely because of the heavy usage of union types, as explained in my previous comment here: https://github.com/makenotion/notion-sdk-js/issues/219#issuecomment-1015943168
It acts like a least common denominator for type properties that you can see.
This is still a gross situation as of @notionhq/client@1.04 ....
One way to solve is to use @justjake 's @jitl/notion-api as mentioned above, the types are good and utils are helpful. However they are not up to date with latest API types from notion (e.g. status) that have been recently released.
Alternatively if you want to battle your way with pulling out the types from the unions here are some type util helpers:
type ArrayType<ArrType> = ArrType extends readonly (infer ElementType)[]
? ElementType
: never;
export type DatabaseItem = ArrayType<QueryDatabaseResponse["results"]>;
export type Page = Extract<DatabaseItem, { properties: any }>;
export type PageReference = Exclude<DatabaseItem, Page>;
@rhart92 you guys should consider exporting these as helper types as it does take a lot of messing around to start getting value out of the notion database queries atm.
Hi, I recently made some changes to name many more response types and export them from this package in #319, so I am going to close this issue. These changes are available in version 2.1.0 of the SDK. If there are any more specific issues around types please open a new GitHub issue, thank you!
I see that the individual block types have been removed and replaced with a large generic union on the responses of some queries.
for example (have removed some properties for clarity):
So a Block I guess can now be represented by:
The same goes for the Database type
But when I have a database that has properties on it like "Name" or "Slug", how do I correctly extend the properties type so that its possible to access it without TS complaining, before I could do something like:
But now that the TitlePropertyValue type doesn't exist, not really sure to handle the custom properties?
Any ideas?
Thanks