nuxt-hub / core

Build full-stack applications with Nuxt on CloudFlare, with zero configuration.
https://hub.nuxt.com
Apache License 2.0
911 stars 51 forks source link

Notion sdk package compatibility #235

Open mathieucollet opened 3 weeks ago

mathieucollet commented 3 weeks ago

Is your feature request related to a problem? Please describe. The error may be mine, but I think the js package Notion-sdk-js is not compatible with deployment via NuxtHub. Here's my little request, I don't realize how much work it would take, but nothing ventured, nothing gained.

Describe the solution you'd like I mean, juste having the package working would be great 👍🏼

Describe alternatives you've considered Doing requests to the Notion API without the use of the package.

Additional context Here is the simplified code for server/api and the component using it. Code that, just for the record, works when running the dev version locally, but doesn't work in production or via wrangler.

// /server/api/database.get.ts
import {Client, collectPaginatedAPI} from "@notionhq/client"

const { notionApiToken, databaseId} = useRuntimeConfig()
const notion = new Client({
    auth: notionApiToken
})
const getData = async () => await collectPaginatedAPI(notion.databases.query, {
    database_id: databaseId,
    page_size: 100
})
export default defineEventHandler(() => getData())
<!--/components/Dashboard.vue-->
<template>
  <div>
    <p v-if="status === 'pending'">Loading....</p>
    <div v-else>{{ data }}</div>
  </div>
</template>

<script setup lang="ts">
const {status, data} = await useLazyFetch("/api/database")
watch(data, (newData) => {})
</script>

Thanks for reading

atinux commented 3 weeks ago

Hey @mathieucollet

Happy to read https://hub.nuxt.com/docs/recipes/debug and give investigate about what can cause such issue?

mathieucollet commented 3 weeks ago

I had the logs ready and forgot to add them to my request, sorry...

✘ [ERROR] [nuxt] [request error] [unhandled] [500] Cannot read properties of undefined (reading 'call')

    at Client.request
  (file:./home/mct/dev/notion-charts/.wrangler/tmp/pages-i7ppWE/chunks/_/index.mjs:958:131)
    at Object.query
  (file:./home/mct/dev/notion-charts/.wrangler/tmp/pages-i7ppWE/chunks/_/index.mjs:719:29)
    at Object.handler
  (file:./home/mct/dev/notion-charts/.wrangler/tmp/pages-i7ppWE/chunks/routes/api/database.get.mjs:10:39)
    at null.<anonymous>
  (file:./home/mct/dev/notion-charts/.wrangler/tmp/pages-i7ppWE/chunks/runtime.mjs:5108:43)
    at async Object.handler
  (file:./home/mct/dev/notion-charts/.wrangler/tmp/pages-i7ppWE/chunks/runtime.mjs:5177:19)
    at async toNodeHandle
  (file:./home/mct/dev/notion-charts/.wrangler/tmp/pages-i7ppWE/chunks/runtime.mjs:5447:7)
    at async ufetch
  (file:./home/mct/dev/notion-charts/.wrangler/tmp/pages-i7ppWE/chunks/runtime.mjs:5820:17)
    at async $fetchRaw2
  (file:./home/mct/dev/notion-charts/.wrangler/tmp/pages-i7ppWE/chunks/runtime.mjs:5693:26)
    at async $fetch22
  (file:./home/mct/dev/notion-charts/.wrangler/tmp/pages-i7ppWE/chunks/runtime.mjs:5739:15)
  [nuxt] [request error] [unhandled] [500] Cannot read properties of undefined (reading 'call')
    at Client.request
  (file:./home/mct/dev/notion-charts/.wrangler/tmp/pages-i7ppWE/chunks/_/index.mjs:958:131)
    at Object.query
  (file:./home/mct/dev/notion-charts/.wrangler/tmp/pages-i7ppWE/chunks/_/index.mjs:719:29)
    at Object.handler
  (file:./home/mct/dev/notion-charts/.wrangler/tmp/pages-i7ppWE/chunks/routes/api/database.get.mjs:10:39)
    at Object.handler
  (file:./home/mct/dev/notion-charts/.wrangler/tmp/pages-i7ppWE/chunks/runtime.mjs:5106:24)
    at Object.handler
  (file:./home/mct/dev/notion-charts/.wrangler/tmp/pages-i7ppWE/chunks/runtime.mjs:5415:34)
    at Object.handler
  (file:./home/mct/dev/notion-charts/.wrangler/tmp/pages-i7ppWE/chunks/runtime.mjs:5177:31)
    at async toNodeHandle
  (file:./home/mct/dev/notion-charts/.wrangler/tmp/pages-i7ppWE/chunks/runtime.mjs:5447:7)
    at async ufetch
  (file:./home/mct/dev/notion-charts/.wrangler/tmp/pages-i7ppWE/chunks/runtime.mjs:5820:17)
    at async $fetchRaw2
  (file:./home/mct/dev/notion-charts/.wrangler/tmp/pages-i7ppWE/chunks/runtime.mjs:5693:26)
    at async $fetchRaw2
  (file:./home/mct/dev/notion-charts/.wrangler/tmp/pages-i7ppWE/chunks/runtime.mjs:5734:14)
atinux commented 3 weeks ago

Now you need to investigate by open those chunks as the right line and see where the call is read from

mathieucollet commented 3 weeks ago

Ok, looks like it comes from the async method request (from the Notion-sdk), in the first line of the try:

async request({ path, method, query, body, auth, }) {
        this.log(logging_1.LogLevel.INFO, "request start", { method, path });
        // If the body is empty, don't send the body in the HTTP request
        const bodyAsJsonString = !body || Object.entries(body).length === 0
            ? undefined
            : JSON.stringify(body);
        const url = new URL(`${__classPrivateFieldGet(this, _Client_prefixUrl, "f")}${path}`);
        if (query) {
            for (const [key, value] of Object.entries(query)) {
                if (value !== undefined) {
                    if (Array.isArray(value)) {
                        value.forEach(val => url.searchParams.append(key, decodeURIComponent(val)));
                    }
                    else {
                        url.searchParams.append(key, String(value));
                    }
                }
            }
        }
        // Allow both client ID / client secret based auth as well as token based auth.
        let authorizationHeader;
        if (typeof auth === "object") {
            // Client ID and secret based auth is **ONLY** supported when using the
            // `/oauth/token` endpoint. If this is the case, handle formatting the
            // authorization header as required by `Basic` auth.
            const unencodedCredential = `${auth.client_id}:${auth.client_secret}`;
            const encodedCredential = Buffer.from(unencodedCredential).toString("base64");
            authorizationHeader = { authorization: `Basic ${encodedCredential}` };
        }
        else {
            // Otherwise format authorization header as `Bearer` token auth.
            authorizationHeader = this.authAsHeaders(auth);
        }
        const headers = {
            ...authorizationHeader,
            "Notion-Version": __classPrivateFieldGet(this, _Client_notionVersion, "f"),
            "user-agent": __classPrivateFieldGet(this, _Client_userAgent, "f"),
        };
        if (bodyAsJsonString !== undefined) {
            headers["content-type"] = "application/json";
        }
        try {
            const response = await errors_1.RequestTimeoutError.rejectAfterTimeout(__classPrivateFieldGet(this, _Client_fetch, "f").call(this, url.toString(), {
                method: method.toUpperCase(),
                headers,
                body: bodyAsJsonString,
                agent: __classPrivateFieldGet(this, _Client_agent, "f"),
            }), __classPrivateFieldGet(this, _Client_timeoutMs, "f"));
            const responseText = await response.text();
            if (!response.ok) {
                throw (0, errors_1.buildRequestError)(response, responseText);
            }
            const responseJson = JSON.parse(responseText);
            this.log(logging_1.LogLevel.INFO, `request success`, { method, path });
            return responseJson;
        }
        catch (error) {
            if (!(0, errors_1.isNotionClientError)(error)) {
                throw error;
            }
            // Log the error if it's one of our known error types
            this.log(logging_1.LogLevel.WARN, `request fail`, {
                code: error.code,
                message: error.message,
            });
            if ((0, errors_1.isHTTPResponseError)(error)) {
                // The response body may contain sensitive information so it is logged separately at the DEBUG level
                this.log(logging_1.LogLevel.DEBUG, `failed response body`, {
                    body: error.body,
                });
            }
            throw error;
        }
    }
remihuigen commented 3 weeks ago

@mathieucollet Same issue here

I can work around it by only using the SDK in local dev (Im using it to write a replica of notion DB's to R1), but I'm curious to know if you've found a solution!

mathieucollet commented 3 weeks ago

Hey @remihuigen,

No, I haven't made any further progress. I understand that __classPrivateFieldGet(this, _Client_fetch, “f”) is undefined but at the moment I'm unable to say why!

atinux commented 3 weeks ago

Would you mind sharing a small GitHub repository with it so I can investigate?

atinux commented 3 weeks ago

Alright it seems Pooya (author of Nuxt 3 & Nitro) opened an issue in May 2021 about it 😄

https://github.com/makenotion/notion-sdk-js/issues/38

Would you mind trying this comment?

https://github.com/makenotion/notion-sdk-js/issues/38#issuecomment-1053066166

mathieucollet commented 3 weeks ago

As I tried, I got another error:

 ERROR  [nuxt] [request error] [unhandled] [500] Failed to parse URL from [object Object]
  at node:internal/deps/undici/undici:12618:11  
  at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

 ERROR  [nuxt] [request error] [unhandled] [500] Failed to parse URL from [object Object]
  at node:internal/deps/undici/undici:12618:11

 ERROR  [nuxt] [request error] [unhandled] [500] Failed to parse URL from [object Object]
  at node:internal/deps/undici/undici:12618:11  
  at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

 ERROR  [nuxt] [request error] [unhandled] [500] Failed to parse URL from [object Object]
  at node:internal/deps/undici/undici:12618:11

I'll share you a project tomorow ;)

mathieucollet commented 3 weeks ago

I just saw that the code in the comment is wrong, here is the right one.

const fetchWithMutableResponse = async (
  url: Parameters<typeof fetch>[0],
  init: Parameters<typeof fetch>[1]
): ReturnType<typeof fetch> => {
  const responseOriginal = await fetch(url?.toString(), init);
  return responseOriginal.clone();
};

const notion = new Client({
    auth: process.env.NUXT_NOTION_API_TOKEN,
    fetch: fetchWithMutableResponse
})

Typescript complaints about typing of the fetch passed + I have to make the notion token available.

I'll test it tomorow.

mathieucollet commented 3 weeks ago

I can confirm that, using the code above and, of course, the useRuntimeConfig() for environment variables, Wrangler runs smoothly.

There's just a typing problem with this fetchWithMutableResponse. image

atinux commented 3 weeks ago

I am afraid that we cannot do much about it on our side and must be fixed in the Notion sdk package

mathieucollet commented 3 weeks ago

No worries, it makes sense!