Use API cache endpoint and service binding in Cloudflare (#2952)

This commit is contained in:
Samy Pessé
2025-03-11 13:06:06 +01:00
committed by GitHub
parent 6cda2dd57b
commit f32bf1f15d
33 changed files with 354 additions and 475 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@gitbook/cache-tags': minor
---
Export function `getCacheTagForURL` to easily get the cache tag for a URL.
@@ -44,6 +44,7 @@ runs:
GITBOOK_SECRET: ${{ inputs.opItem }}/GITBOOK_SECRET GITBOOK_SECRET: ${{ inputs.opItem }}/GITBOOK_SECRET
GITBOOK_APP_URL: ${{ inputs.opItem }}/GITBOOK_APP_URL GITBOOK_APP_URL: ${{ inputs.opItem }}/GITBOOK_APP_URL
GITBOOK_API_URL: ${{ inputs.opItem }}/GITBOOK_API_URL GITBOOK_API_URL: ${{ inputs.opItem }}/GITBOOK_API_URL
GITBOOK_API_PUBLIC_URL: ${{ inputs.opItem }}/GITBOOK_API_PUBLIC_URL
GITBOOK_API_TOKEN: ${{ inputs.opItem }}/GITBOOK_API_TOKEN GITBOOK_API_TOKEN: ${{ inputs.opItem }}/GITBOOK_API_TOKEN
GITBOOK_INTEGRATIONS_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_HOST GITBOOK_INTEGRATIONS_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_HOST
GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY
@@ -49,6 +49,7 @@ runs:
GITBOOK_SECRET: ${{ inputs.opItem }}/GITBOOK_SECRET GITBOOK_SECRET: ${{ inputs.opItem }}/GITBOOK_SECRET
GITBOOK_APP_URL: ${{ inputs.opItem }}/GITBOOK_APP_URL GITBOOK_APP_URL: ${{ inputs.opItem }}/GITBOOK_APP_URL
GITBOOK_API_URL: ${{ inputs.opItem }}/GITBOOK_API_URL GITBOOK_API_URL: ${{ inputs.opItem }}/GITBOOK_API_URL
GITBOOK_API_PUBLIC_URL: ${{ inputs.opItem }}/GITBOOK_API_PUBLIC_URL
GITBOOK_API_TOKEN: ${{ inputs.opItem }}/GITBOOK_API_TOKEN GITBOOK_API_TOKEN: ${{ inputs.opItem }}/GITBOOK_API_TOKEN
GITBOOK_INTEGRATIONS_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_HOST GITBOOK_INTEGRATIONS_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_HOST
GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY
-17
View File
@@ -1,17 +0,0 @@
.next
.vercel
.open-next
# Generated
packages/emoji-codepoints/index.ts
packages/gitbook/public/~gitbook/static/
packages/icons/src/data/*.json
# Build files
dist/
README.md
CHANGELOG.md
# v2
packages/gitbook-v2/public/~gitbook/static/
-5
View File
@@ -1,5 +0,0 @@
{
"printWidth": 100,
"singleQuote": true,
"tabWidth": 4
}
+3 -1
View File
@@ -18,7 +18,9 @@
"**/.vercel/**/*", "**/.vercel/**/*",
"**/.cache/**/*", "**/.cache/**/*",
"**/.wrangler/**/*", "**/.wrangler/**/*",
"packages/openapi-parser/src/fixtures/**/*" "packages/openapi-parser/src/fixtures/**/*",
"packages/emoji-codepoints/index.ts",
"packages/icons/src/data/*.json"
] ]
}, },
"formatter": { "formatter": {
+12 -11
View File
@@ -26,7 +26,7 @@
"name": "@gitbook/cache-tags", "name": "@gitbook/cache-tags",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@gitbook/api": "0.96.1", "@gitbook/api": "*",
"assert-never": "^1.2.1", "assert-never": "^1.2.1",
}, },
"devDependencies": { "devDependencies": {
@@ -49,9 +49,9 @@
}, },
"packages/gitbook": { "packages/gitbook": {
"name": "gitbook", "name": "gitbook",
"version": "0.7.2", "version": "0.7.3",
"dependencies": { "dependencies": {
"@gitbook/api": "0.96.1", "@gitbook/api": "*",
"@gitbook/cache-do": "workspace:*", "@gitbook/cache-do": "workspace:*",
"@gitbook/cache-tags": "workspace:*", "@gitbook/cache-tags": "workspace:*",
"@gitbook/colors": "workspace:*", "@gitbook/colors": "workspace:*",
@@ -134,7 +134,7 @@
"name": "gitbook-v2", "name": "gitbook-v2",
"version": "0.2.0", "version": "0.2.0",
"dependencies": { "dependencies": {
"@gitbook/api": "0.96.1", "@gitbook/api": "*",
"@gitbook/cache-tags": "workspace:*", "@gitbook/cache-tags": "workspace:*",
"@sindresorhus/fnv1a": "^3.1.0", "@sindresorhus/fnv1a": "^3.1.0",
"next": "^15.2.1", "next": "^15.2.1",
@@ -144,7 +144,7 @@
"warn-once": "^0.1.1", "warn-once": "^0.1.1",
}, },
"devDependencies": { "devDependencies": {
"@opennextjs/cloudflare": "https://pkg.pr.new/opennextjs/opennextjs-cloudflare/@opennextjs/cloudflare@236c84d", "@opennextjs/cloudflare": "^0.5.10",
"gitbook": "*", "gitbook": "*",
"postcss": "^8", "postcss": "^8",
"tailwindcss": "^3.4.0", "tailwindcss": "^3.4.0",
@@ -152,7 +152,7 @@
}, },
"packages/icons": { "packages/icons": {
"name": "@gitbook/icons", "name": "@gitbook/icons",
"version": "0.1.0", "version": "0.2.0",
"bin": { "bin": {
"gitbook-icons": "./bin/gitbook-icons.js", "gitbook-icons": "./bin/gitbook-icons.js",
}, },
@@ -194,9 +194,9 @@
}, },
"packages/react-contentkit": { "packages/react-contentkit": {
"name": "@gitbook/react-contentkit", "name": "@gitbook/react-contentkit",
"version": "0.6.1", "version": "0.6.2",
"dependencies": { "dependencies": {
"@gitbook/api": "0.96.1", "@gitbook/api": "*",
"@gitbook/icons": "workspace:*", "@gitbook/icons": "workspace:*",
"classnames": "^2.5.1", "classnames": "^2.5.1",
}, },
@@ -226,7 +226,7 @@
}, },
"packages/react-openapi": { "packages/react-openapi": {
"name": "@gitbook/react-openapi", "name": "@gitbook/react-openapi",
"version": "1.1.2", "version": "1.1.3",
"dependencies": { "dependencies": {
"@gitbook/openapi-parser": "workspace:*", "@gitbook/openapi-parser": "workspace:*",
"@scalar/api-client-react": "^1.1.36", "@scalar/api-client-react": "^1.1.36",
@@ -254,6 +254,7 @@
}, },
"overrides": { "overrides": {
"@codemirror/state": "6.4.1", "@codemirror/state": "6.4.1",
"@gitbook/api": "0.97.0",
"react": "18.3.1", "react": "18.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
}, },
@@ -626,7 +627,7 @@
"@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@6.6.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" } }, "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg=="], "@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@6.6.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" } }, "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg=="],
"@gitbook/api": ["@gitbook/api@0.96.1", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-E1hpaFNFoEyc8E4rIQyC6UD3kErGXAI0lKt04zYQQYOBLjRIQ57D4oNIIJBjWn4hHeSxRxWvW3NcxzvOGMmIqQ=="], "@gitbook/api": ["@gitbook/api@0.97.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-bBTy7nprwlwTW/v8S6A9wGFWatU+F33232xF5Tt4GmSBTZSrAKha5wD205Hr+618va+/zipA8o4hdAGPyUNj0A=="],
"@gitbook/cache-do": ["@gitbook/cache-do@workspace:packages/cache-do"], "@gitbook/cache-do": ["@gitbook/cache-do@workspace:packages/cache-do"],
@@ -792,7 +793,7 @@
"@opennextjs/aws": ["@opennextjs/aws@https://pkg.pr.new/@opennextjs/aws@756", { "dependencies": { "@aws-sdk/client-cloudfront": "3.398.0", "@aws-sdk/client-dynamodb": "^3.398.0", "@aws-sdk/client-lambda": "^3.398.0", "@aws-sdk/client-s3": "^3.398.0", "@aws-sdk/client-sqs": "^3.398.0", "@node-minify/core": "^8.0.6", "@node-minify/terser": "^8.0.6", "@tsconfig/node18": "^1.0.1", "aws4fetch": "^1.0.18", "chalk": "^5.3.0", "esbuild": "0.19.2", "express": "5.0.1", "path-to-regexp": "^6.3.0", "urlpattern-polyfill": "^10.0.0" }, "bin": { "open-next": "./dist/index.js" } }], "@opennextjs/aws": ["@opennextjs/aws@https://pkg.pr.new/@opennextjs/aws@756", { "dependencies": { "@aws-sdk/client-cloudfront": "3.398.0", "@aws-sdk/client-dynamodb": "^3.398.0", "@aws-sdk/client-lambda": "^3.398.0", "@aws-sdk/client-s3": "^3.398.0", "@aws-sdk/client-sqs": "^3.398.0", "@node-minify/core": "^8.0.6", "@node-minify/terser": "^8.0.6", "@tsconfig/node18": "^1.0.1", "aws4fetch": "^1.0.18", "chalk": "^5.3.0", "esbuild": "0.19.2", "express": "5.0.1", "path-to-regexp": "^6.3.0", "urlpattern-polyfill": "^10.0.0" }, "bin": { "open-next": "./dist/index.js" } }],
"@opennextjs/cloudflare": ["@opennextjs/cloudflare@https://pkg.pr.new/opennextjs/opennextjs-cloudflare/@opennextjs/cloudflare@236c84d", { "dependencies": { "@ast-grep/napi": "^0.34.1", "@dotenvx/dotenvx": "1.31.0", "@opennextjs/aws": "https://pkg.pr.new/@opennextjs/aws@756", "enquirer": "^2.4.1", "glob": "^11.0.0", "yaml": "^2.7.0" }, "peerDependencies": { "wrangler": "^3.111.0" }, "bin": { "opennextjs-cloudflare": "dist/cli/index.js" } }], "@opennextjs/cloudflare": ["@opennextjs/cloudflare@0.5.10", "", { "dependencies": { "@ast-grep/napi": "^0.34.1", "@dotenvx/dotenvx": "1.31.0", "@opennextjs/aws": "https://pkg.pr.new/@opennextjs/aws@756", "enquirer": "^2.4.1", "glob": "^11.0.0", "yaml": "^2.7.0" }, "peerDependencies": { "wrangler": "^3.111.0" }, "bin": { "opennextjs-cloudflare": "dist/cli/index.js" } }, "sha512-L/D472YT5OW1LwpFtD/aVXHJYcVPbFVX7XdphlUjCR4+2osSQIDnsuNgfDRydHMDJZMKxeZDc251ZBzUVKpCqw=="],
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
+2 -1
View File
@@ -11,7 +11,8 @@
"overrides": { "overrides": {
"@codemirror/state": "6.4.1", "@codemirror/state": "6.4.1",
"react": "18.3.1", "react": "18.3.1",
"react-dom": "18.3.1" "react-dom": "18.3.1",
"@gitbook/api": "0.97.0"
}, },
"private": true, "private": true,
"scripts": { "scripts": {
+1 -1
View File
@@ -10,7 +10,7 @@
}, },
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@gitbook/api": "0.96.1", "@gitbook/api": "*",
"assert-never": "^1.2.1" "assert-never": "^1.2.1"
}, },
"devDependencies": { "devDependencies": {
+11
View File
@@ -111,6 +111,17 @@ export function getCacheTag(
} }
} }
/**
* Get the cache tag for a given URL.
*/
export function getCacheTagForURL(url: string | URL) {
const parsedURL = url instanceof URL ? url : new URL(url);
return getCacheTag({
tag: 'url',
hostname: parsedURL.hostname,
});
}
/** /**
* Get the tags for a computed content source. * Get the tags for a computed content source.
*/ */
+4 -3
View File
@@ -6,7 +6,7 @@
"next": "^15.2.1", "next": "^15.2.1",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"@gitbook/api": "0.96.1", "@gitbook/api": "*",
"@gitbook/cache-tags": "workspace:*", "@gitbook/cache-tags": "workspace:*",
"@sindresorhus/fnv1a": "^3.1.0", "@sindresorhus/fnv1a": "^3.1.0",
"server-only": "^0.0.1", "server-only": "^0.0.1",
@@ -14,7 +14,7 @@
}, },
"devDependencies": { "devDependencies": {
"gitbook": "*", "gitbook": "*",
"@opennextjs/cloudflare": "https://pkg.pr.new/opennextjs/opennextjs-cloudflare/@opennextjs/cloudflare@236c84d", "@opennextjs/cloudflare": "^0.5.10",
"tailwindcss": "^3.4.0", "tailwindcss": "^3.4.0",
"postcss": "^8" "postcss": "^8"
}, },
@@ -26,6 +26,7 @@
"start": "next start", "start": "next start",
"build:v2:cloudflare": "opennextjs-cloudflare", "build:v2:cloudflare": "opennextjs-cloudflare",
"dev:v2:cloudflare": "wrangler dev --port 8771", "dev:v2:cloudflare": "wrangler dev --port 8771",
"unit": "bun test" "unit": "bun test",
"typecheck": "tsc --noEmit"
} }
} }
+2
View File
@@ -1,6 +1,7 @@
import { type NextRequest, NextResponse } from 'next/server'; import { type NextRequest, NextResponse } from 'next/server';
import { import {
GITBOOK_API_PUBLIC_URL,
GITBOOK_API_TOKEN, GITBOOK_API_TOKEN,
GITBOOK_API_URL, GITBOOK_API_URL,
GITBOOK_APP_URL, GITBOOK_APP_URL,
@@ -22,6 +23,7 @@ export async function GET(_req: NextRequest) {
GITBOOK_URL, GITBOOK_URL,
GITBOOK_APP_URL, GITBOOK_APP_URL,
GITBOOK_API_URL, GITBOOK_API_URL,
GITBOOK_API_PUBLIC_URL,
GITBOOK_ASSETS_URL, GITBOOK_ASSETS_URL,
GITBOOK_ICONS_URL, GITBOOK_ICONS_URL,
GITBOOK_USER_AGENT, GITBOOK_USER_AGENT,
@@ -4,7 +4,6 @@ import {
fetchSpaceContextByIds, fetchSpaceContextByIds,
} from '@v2/lib/context'; } from '@v2/lib/context';
import { createDataFetcher } from '@v2/lib/data'; import { createDataFetcher } from '@v2/lib/data';
import { GITBOOK_API_URL } from '@v2/lib/env';
import { createLinker } from '@v2/lib/links'; import { createLinker } from '@v2/lib/links';
import { getAPITokenFromMiddleware } from '@v2/lib/middleware'; import { getAPITokenFromMiddleware } from '@v2/lib/middleware';
@@ -26,7 +25,6 @@ export async function getSpacePDFContext(
}); });
const dataFetcher = createDataFetcher({ const dataFetcher = createDataFetcher({
apiToken: apiToken, apiToken: apiToken,
apiEndpoint: GITBOOK_API_URL,
}); });
const baseContext: GitBookBaseContext = { const baseContext: GitBookBaseContext = {
+2 -3
View File
@@ -16,7 +16,7 @@ import type {
import { type GitBookDataFetcher, createDataFetcher, throwIfDataError } from '@v2/lib/data'; import { type GitBookDataFetcher, createDataFetcher, throwIfDataError } from '@v2/lib/data';
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';
import { assert } from 'ts-essentials'; import { assert } from 'ts-essentials';
import { GITBOOK_API_TOKEN, GITBOOK_API_URL, GITBOOK_URL } from './env'; import { GITBOOK_URL } from './env';
import { type ImageResizer, createImageResizer } from './images'; import { type ImageResizer, createImageResizer } from './images';
import { type GitBookSpaceLinker, createLinker } from './links'; import { type GitBookSpaceLinker, createLinker } from './links';
@@ -113,8 +113,7 @@ export function getBaseContext(input: {
const urlMode = input.urlMode; const urlMode = input.urlMode;
const dataFetcher = createDataFetcher({ const dataFetcher = createDataFetcher({
apiToken: input.apiToken ?? GITBOOK_API_TOKEN, apiToken: input.apiToken ?? null,
apiEndpoint: GITBOOK_API_URL,
}); });
const linker = getLinkerForSiteURL({ const linker = getLinkerForSiteURL({
+152 -96
View File
@@ -1,42 +1,40 @@
import { type ComputedContentSource, GitBookAPI } from '@gitbook/api'; import { trace } from '@/lib/tracing';
import { getCacheTag, getComputedContentSourceCacheTags } from '@gitbook/cache-tags'; import {
type ComputedContentSource,
GitBookAPI,
type GitBookAPIServiceBinding,
} from '@gitbook/api';
import {
getCacheTag,
getCacheTagForURL,
getComputedContentSourceCacheTags,
} from '@gitbook/cache-tags';
import { GITBOOK_API_TOKEN, GITBOOK_API_URL, GITBOOK_USER_AGENT } from '@v2/lib/env'; import { GITBOOK_API_TOKEN, GITBOOK_API_URL, GITBOOK_USER_AGENT } from '@v2/lib/env';
import { unstable_cacheLife as cacheLife, unstable_cacheTag as cacheTag } from 'next/cache'; import { unstable_cacheLife as cacheLife, unstable_cacheTag as cacheTag } from 'next/cache';
import { wrapDataFetcherError } from './errors'; import { wrapDataFetcherError } from './errors';
import type { GitBookDataFetcher } from './types'; import type { GitBookDataFetcher } from './types';
interface DataFetcherInput { interface DataFetcherInput {
/**
* API host to use.
*/
apiEndpoint: string;
/** /**
* API token. * API token.
*/ */
apiToken: string | null; apiToken: string | null;
} }
const commonInput: DataFetcherInput = {
apiEndpoint: GITBOOK_API_URL,
apiToken: GITBOOK_API_TOKEN,
};
/** /**
* Create a data fetcher using an API token. * Create a data fetcher using an API token.
* The data are being cached by Next.js built-in cache. * The data are being cached by Next.js built-in cache.
*/ */
export function createDataFetcher(input: DataFetcherInput = commonInput): GitBookDataFetcher { export function createDataFetcher(
input: DataFetcherInput = { apiToken: null }
): GitBookDataFetcher {
return { return {
apiEndpoint: input.apiEndpoint,
async api() { async api() {
return getAPI(input); return apiClient(input);
}, },
withToken({ apiToken }) { withToken({ apiToken }) {
return createDataFetcher({ return createDataFetcher({
...input,
apiToken, apiToken,
}); });
}, },
@@ -45,94 +43,120 @@ export function createDataFetcher(input: DataFetcherInput = commonInput): GitBoo
// API that are tied to the token // API that are tied to the token
// //
getPublishedContentSite(params) { getPublishedContentSite(params) {
return getPublishedContentSite(input, { return trace('getPublishedContentSite', () =>
getPublishedContentSite(input, {
organizationId: params.organizationId, organizationId: params.organizationId,
siteId: params.siteId, siteId: params.siteId,
siteShareKey: params.siteShareKey, siteShareKey: params.siteShareKey,
}); })
);
}, },
getSiteRedirectBySource(params) { getSiteRedirectBySource(params) {
return getSiteRedirectBySource(input, { return trace('getSiteRedirectBySource', () =>
getSiteRedirectBySource(input, {
organizationId: params.organizationId, organizationId: params.organizationId,
siteId: params.siteId, siteId: params.siteId,
siteShareKey: params.siteShareKey, siteShareKey: params.siteShareKey,
source: params.source, source: params.source,
}); })
);
}, },
getRevision(params) { getRevision(params) {
return getRevision(input, { return trace('getRevision', () =>
getRevision(input, {
spaceId: params.spaceId, spaceId: params.spaceId,
revisionId: params.revisionId, revisionId: params.revisionId,
metadata: params.metadata, metadata: params.metadata,
}); })
);
}, },
getRevisionPages(params) { getRevisionPages(params) {
return getRevisionPages(input, { return trace('getRevisionPages', () =>
getRevisionPages(input, {
spaceId: params.spaceId, spaceId: params.spaceId,
revisionId: params.revisionId, revisionId: params.revisionId,
metadata: params.metadata, metadata: params.metadata,
}); })
);
}, },
getRevisionFile(params) { getRevisionFile(params) {
return getRevisionFile(input, { return trace('getRevisionFile', () =>
getRevisionFile(input, {
spaceId: params.spaceId, spaceId: params.spaceId,
revisionId: params.revisionId, revisionId: params.revisionId,
fileId: params.fileId, fileId: params.fileId,
}); })
);
}, },
getRevisionPageByPath(params) { getRevisionPageByPath(params) {
return getRevisionPageByPath(input, { return trace('getRevisionPageByPath', () =>
getRevisionPageByPath(input, {
spaceId: params.spaceId, spaceId: params.spaceId,
revisionId: params.revisionId, revisionId: params.revisionId,
path: params.path, path: params.path,
}); })
);
}, },
getReusableContent(params) { getReusableContent(params) {
return getReusableContent(input, { return trace('getReusableContent', () =>
getReusableContent(input, {
spaceId: params.spaceId, spaceId: params.spaceId,
revisionId: params.revisionId, revisionId: params.revisionId,
reusableContentId: params.reusableContentId, reusableContentId: params.reusableContentId,
}); })
);
}, },
getLatestOpenAPISpecVersionContent(params) { getLatestOpenAPISpecVersionContent(params) {
return getLatestOpenAPISpecVersionContent(input, { return trace('getLatestOpenAPISpecVersionContent', () =>
getLatestOpenAPISpecVersionContent(input, {
organizationId: params.organizationId, organizationId: params.organizationId,
slug: params.slug, slug: params.slug,
}); })
);
}, },
getSpace(params) { getSpace(params) {
return getSpace(input, { return trace('getSpace', () =>
getSpace(input, {
spaceId: params.spaceId, spaceId: params.spaceId,
shareKey: params.shareKey, shareKey: params.shareKey,
}); })
);
}, },
getChangeRequest(params) { getChangeRequest(params) {
return getChangeRequest(input, { return trace('getChangeRequest', () =>
getChangeRequest(input, {
spaceId: params.spaceId, spaceId: params.spaceId,
changeRequestId: params.changeRequestId, changeRequestId: params.changeRequestId,
}); })
);
}, },
getDocument(params) { getDocument(params) {
return getDocument(input, { return trace('getDocument', () =>
getDocument(input, {
spaceId: params.spaceId, spaceId: params.spaceId,
documentId: params.documentId, documentId: params.documentId,
}); })
);
}, },
getComputedDocument(params) { getComputedDocument(params) {
return getComputedDocument(input, { return trace('getComputedDocument', () =>
getComputedDocument(input, {
organizationId: params.organizationId, organizationId: params.organizationId,
spaceId: params.spaceId, spaceId: params.spaceId,
source: params.source, source: params.source,
}); })
);
}, },
getEmbedByUrl(params) { getEmbedByUrl(params) {
return getEmbedByUrl(input, { return trace('getEmbedByUrl', () =>
getEmbedByUrl(input, {
url: params.url, url: params.url,
spaceId: params.spaceId, spaceId: params.spaceId,
}); })
);
}, },
searchSiteContent(params) { searchSiteContent(params) {
return searchSiteContent(input, params); return trace('searchSiteContent', () => searchSiteContent(input, params));
}, },
// //
@@ -140,25 +164,31 @@ export function createDataFetcher(input: DataFetcherInput = commonInput): GitBoo
// where the data is the same for all users // where the data is the same for all users
// //
getUserById(userId) { getUserById(userId) {
return getUserById(commonInput, userId); return trace('getUserById', () => getUserById({ apiToken: null }, { userId }));
}, },
getPublishedContentByUrl(params) { getPublishedContentByUrl(params) {
return getPublishedContentByUrl(commonInput, { return trace('getPublishedContentByUrl', () =>
getPublishedContentByUrl(
{ apiToken: null },
{
url: params.url, url: params.url,
visitorAuthToken: params.visitorAuthToken, visitorAuthToken: params.visitorAuthToken,
redirectOnError: params.redirectOnError, redirectOnError: params.redirectOnError,
}); }
)
);
}, },
}; };
} }
async function getUserById(input: DataFetcherInput, userId: string) { async function getUserById(input: DataFetcherInput, params: { userId: string }) {
'use cache'; 'use cache';
cacheLife('days'); cacheLife('days');
return wrapDataFetcherError(async () => { return wrapDataFetcherError(async () => {
const res = await getAPI(input).users.getUserById(userId); const api = await apiClient(input);
const res = await api.users.getUserById(params.userId);
return res.data; return res.data;
}); });
} }
@@ -181,7 +211,8 @@ async function getSpace(
); );
return wrapDataFetcherError(async () => { return wrapDataFetcherError(async () => {
const res = await getAPI(input).spaces.getSpaceById(params.spaceId, { const api = await apiClient(input);
const res = await api.spaces.getSpaceById(params.spaceId, {
shareKey: params.shareKey, shareKey: params.shareKey,
}); });
return res.data; return res.data;
@@ -200,10 +231,8 @@ async function getChangeRequest(
cacheLife('minutes'); cacheLife('minutes');
return wrapDataFetcherError(async () => { return wrapDataFetcherError(async () => {
const res = await getAPI(input).spaces.getChangeRequestById( const api = await apiClient(input);
params.spaceId, const res = await api.spaces.getChangeRequestById(params.spaceId, params.changeRequestId);
params.changeRequestId
);
cacheTag( cacheTag(
getCacheTag({ getCacheTag({
tag: 'change-request', tag: 'change-request',
@@ -228,7 +257,8 @@ async function getRevision(
cacheLife('max'); cacheLife('max');
return wrapDataFetcherError(async () => { return wrapDataFetcherError(async () => {
const res = await getAPI(input).spaces.getRevisionById(params.spaceId, params.revisionId, { const api = await apiClient(input);
const res = await api.spaces.getRevisionById(params.spaceId, params.revisionId, {
metadata: params.metadata, metadata: params.metadata,
}); });
return res.data; return res.data;
@@ -248,13 +278,10 @@ async function getRevisionPages(
cacheLife('max'); cacheLife('max');
return wrapDataFetcherError(async () => { return wrapDataFetcherError(async () => {
const res = await getAPI(input).spaces.listPagesInRevisionById( const api = await apiClient(input);
params.spaceId, const res = await api.spaces.listPagesInRevisionById(params.spaceId, params.revisionId, {
params.revisionId,
{
metadata: params.metadata, metadata: params.metadata,
} });
);
return res.data.pages; return res.data.pages;
}); });
} }
@@ -272,10 +299,12 @@ async function getRevisionFile(
cacheLife('max'); cacheLife('max');
return wrapDataFetcherError(async () => { return wrapDataFetcherError(async () => {
const res = await getAPI(input).spaces.getFileInRevisionById( const api = await apiClient(input);
const res = await api.spaces.getFileInRevisionById(
params.spaceId, params.spaceId,
params.revisionId, params.revisionId,
params.fileId params.fileId,
{}
); );
return res.data; return res.data;
}); });
@@ -295,10 +324,12 @@ async function getRevisionPageByPath(
const encodedPath = encodeURIComponent(params.path); const encodedPath = encodeURIComponent(params.path);
return wrapDataFetcherError(async () => { return wrapDataFetcherError(async () => {
const res = await getAPI(input).spaces.getPageInRevisionByPath( const api = await apiClient(input);
const res = await api.spaces.getPageInRevisionByPath(
params.spaceId, params.spaceId,
params.revisionId, params.revisionId,
encodedPath encodedPath,
{}
); );
return res.data; return res.data;
@@ -317,7 +348,8 @@ async function getDocument(
cacheLife('max'); cacheLife('max');
return wrapDataFetcherError(async () => { return wrapDataFetcherError(async () => {
const res = await getAPI(input).spaces.getDocumentById(params.spaceId, params.documentId); const api = await apiClient(input);
const res = await api.spaces.getDocumentById(params.spaceId, params.documentId, {});
return res.data; return res.data;
}); });
} }
@@ -345,7 +377,8 @@ async function getComputedDocument(
); );
return wrapDataFetcherError(async () => { return wrapDataFetcherError(async () => {
const res = await getAPI(input).spaces.getComputedDocument(params.spaceId, { const api = await apiClient(input);
const res = await api.spaces.getComputedDocument(params.spaceId, {
source: params.source, source: params.source,
}); });
return res.data; return res.data;
@@ -365,7 +398,8 @@ async function getReusableContent(
cacheLife('max'); cacheLife('max');
return wrapDataFetcherError(async () => { return wrapDataFetcherError(async () => {
const res = await getAPI(input).spaces.getReusableContentInRevisionById( const api = await apiClient(input);
const res = await api.spaces.getReusableContentInRevisionById(
params.spaceId, params.spaceId,
params.revisionId, params.revisionId,
params.reusableContentId params.reusableContentId
@@ -393,7 +427,8 @@ async function getLatestOpenAPISpecVersionContent(
cacheLife('days'); cacheLife('days');
return wrapDataFetcherError(async () => { return wrapDataFetcherError(async () => {
const res = await getAPI(input).orgs.getLatestOpenApiSpecVersionContent( const api = await apiClient(input);
const res = await api.orgs.getLatestOpenApiSpecVersionContent(
params.organizationId, params.organizationId,
params.slug params.slug
); );
@@ -413,17 +448,12 @@ async function getPublishedContentByUrl(
const { url, visitorAuthToken, redirectOnError } = params; const { url, visitorAuthToken, redirectOnError } = params;
const hostname = new URL(url).hostname; cacheTag(getCacheTagForURL(url));
cacheTag(
getCacheTag({
tag: 'url',
hostname,
})
);
cacheLife('days'); cacheLife('days');
return wrapDataFetcherError(async () => { return wrapDataFetcherError(async () => {
const res = await getAPI(input).urls.getPublishedContentByUrl({ const api = await apiClient(input);
const res = await api.urls.getPublishedContentByUrl({
url, url,
visitorAuthToken: visitorAuthToken ?? undefined, visitorAuthToken: visitorAuthToken ?? undefined,
redirectOnError, redirectOnError,
@@ -452,22 +482,19 @@ async function getPublishedContentSite(
) { ) {
'use cache'; 'use cache';
cacheLife('days');
cacheTag( cacheTag(
getCacheTag({ getCacheTag({
tag: 'site', tag: 'site',
site: params.siteId, site: params.siteId,
}) })
); );
cacheLife('days');
return wrapDataFetcherError(async () => { return wrapDataFetcherError(async () => {
const res = await getAPI(input).orgs.getPublishedContentSite( const api = await apiClient(input);
params.organizationId, const res = await api.orgs.getPublishedContentSite(params.organizationId, params.siteId, {
params.siteId,
{
shareKey: params.siteShareKey, shareKey: params.siteShareKey,
} });
);
return res.data; return res.data;
}); });
} }
@@ -492,14 +519,11 @@ async function getSiteRedirectBySource(
cacheLife('days'); cacheLife('days');
return wrapDataFetcherError(async () => { return wrapDataFetcherError(async () => {
const res = await getAPI(input).orgs.getSiteRedirectBySource( const api = await apiClient(input);
params.organizationId, const res = await api.orgs.getSiteRedirectBySource(params.organizationId, params.siteId, {
params.siteId,
{
shareKey: params.siteShareKey, shareKey: params.siteShareKey,
source: params.source, source: params.source,
} });
);
return res.data; return res.data;
}); });
@@ -517,7 +541,7 @@ async function getEmbedByUrl(
cacheLife('weeks'); cacheLife('weeks');
return wrapDataFetcherError(async () => { return wrapDataFetcherError(async () => {
const api = getAPI(input); const api = await apiClient(input);
const res = await api.spaces.getEmbedByUrlInSpace(params.spaceId, { url: params.url }); const res = await api.spaces.getEmbedByUrlInSpace(params.spaceId, { url: params.url });
return res.data; return res.data;
}); });
@@ -534,7 +558,8 @@ async function searchSiteContent(
cacheLife('days'); cacheLife('days');
return wrapDataFetcherError(async () => { return wrapDataFetcherError(async () => {
const res = await getAPI(input).orgs.searchSiteContent(organizationId, siteId, { const api = await apiClient(input);
const res = await api.orgs.searchSiteContent(organizationId, siteId, {
query, query,
...scope, ...scope,
}); });
@@ -542,12 +567,43 @@ async function searchSiteContent(
}); });
} }
function getAPI(input: DataFetcherInput) { let loggedServiceBinding = false;
const { apiEndpoint, apiToken } = input;
/**
* Create a new API client.
*/
export async function apiClient(input: DataFetcherInput = { apiToken: null }) {
const { apiToken } = input;
let serviceBinding: GitBookAPIServiceBinding | undefined;
try {
// HACK: This is a workaround to avoid webpack trying to bundle this cloudflare only module
// @ts-ignore
const { env } = await import(
/* webpackIgnore: true */ `${'__cloudflare:workers'.replaceAll('_', '')}`
);
serviceBinding = env.GITBOOK_API;
if (!loggedServiceBinding) {
loggedServiceBinding = true;
if (serviceBinding) {
// biome-ignore lint/suspicious/noConsole: we want to log here
console.log(`using service binding for the API (${GITBOOK_API_URL})`);
} else {
// biome-ignore lint/suspicious/noConsole: we want to log here
console.warn(`no service binding for the API (${GITBOOK_API_URL})`);
}
}
} catch (error) {
if (process.env.NODE_ENV === 'production' && !process.env.VERCEL) {
throw error;
}
}
const api = new GitBookAPI({ const api = new GitBookAPI({
authToken: apiToken ?? undefined, authToken: apiToken || GITBOOK_API_TOKEN || undefined,
endpoint: apiEndpoint, endpoint: GITBOOK_API_URL,
userAgent: GITBOOK_USER_AGENT, userAgent: GITBOOK_USER_AGENT,
serviceBinding,
}); });
return api; return api;
+12 -19
View File
@@ -1,7 +1,8 @@
import { race, tryCatch } from '@/lib/async'; import { race, tryCatch } from '@/lib/async';
import { joinPath } from '@/lib/paths'; import { joinPath } from '@/lib/paths';
import { GitBookAPI, type PublishedSiteContentLookup } from '@gitbook/api'; import { trace } from '@/lib/tracing';
import { GITBOOK_API_TOKEN, GITBOOK_API_URL, GITBOOK_USER_AGENT } from '@v2/lib/env'; import type { PublishedSiteContentLookup } from '@gitbook/api';
import { apiClient } from './api';
import { getExposableError } from './errors'; import { getExposableError } from './errors';
import type { DataFetcherResponse } from './types'; import type { DataFetcherResponse } from './types';
import { getURLLookupAlternatives, stripURLSearch } from './urls'; import { getURLLookupAlternatives, stripURLSearch } from './urls';
@@ -20,34 +21,26 @@ export async function getPublishedContentByURL(input: {
const lookup = getURLLookupAlternatives(url); const lookup = getURLLookupAlternatives(url);
const result = await race(lookup.urls, async (alternative, { signal }) => { const result = await race(lookup.urls, async (alternative, { signal }) => {
const api = new GitBookAPI({ const api = await apiClient();
authToken: GITBOOK_API_TOKEN ?? undefined,
endpoint: GITBOOK_API_URL,
userAgent: GITBOOK_USER_AGENT,
});
const startTime = performance.now(); const callResult = await trace(
const callResult = await tryCatch( {
operation: 'getPublishedContentByURL',
name: alternative.url,
},
() =>
tryCatch(
api.urls.getPublishedContentByUrl( api.urls.getPublishedContentByUrl(
{ {
url: alternative.url, url: alternative.url,
visitorAuthToken: input.visitorAuthToken ?? undefined, visitorAuthToken: input.visitorAuthToken ?? undefined,
redirectOnError: input.redirectOnError, redirectOnError: input.redirectOnError,
cache: true,
}, },
{ {
signal, signal,
headers: {
'x-gitbook-force-cache': 'true',
},
} }
) )
); )
const endTime = performance.now();
// biome-ignore lint/suspicious/noConsole: we want to log performance data
console.log(
`getPublishedContentByURL(${alternative.url}) Time taken: ${endTime - startTime}ms`
); );
if (callResult.error) { if (callResult.error) {
@@ -20,11 +20,6 @@ export type DataFetcherResponse<T> =
* It is used between v1 and v2. * It is used between v1 and v2.
*/ */
export interface GitBookDataFetcher { export interface GitBookDataFetcher {
/**
* Endpoint of the API.
*/
apiEndpoint: string;
/** /**
* Get an API client for the current context. * Get an API client for the current context.
*/ */
+5
View File
@@ -34,6 +34,11 @@ export const GITBOOK_APP_URL = process.env.GITBOOK_APP_URL || 'https://app.gitbo
*/ */
export const GITBOOK_API_URL = process.env.GITBOOK_API_URL || 'https://api.gitbook.com'; export const GITBOOK_API_URL = process.env.GITBOOK_API_URL || 'https://api.gitbook.com';
/**
* Default GitBook API URL endpoint, to be shared with the client.
*/
export const GITBOOK_API_PUBLIC_URL = process.env.GITBOOK_API_PUBLIC_URL || GITBOOK_API_URL;
/** /**
* Default GitBook API token. * Default GitBook API token.
* It can be use to avoid rate-limiting. * It can be use to avoid rate-limiting.
+14 -2
View File
@@ -1,7 +1,7 @@
main = ".open-next/worker.js" main = ".open-next/worker.js"
name = "gitbook-open-v2" name = "gitbook-open-v2"
compatibility_date = "2024-09-23" compatibility_date = "2025-03-11"
compatibility_flags = ["nodejs_compat"] compatibility_flags = ["nodejs_compat", "allow_importable_env"]
assets = { directory = ".open-next/assets", binding = "ASSETS" } assets = { directory = ".open-next/assets", binding = "ASSETS" }
observability = { enabled = true } observability = { enabled = true }
@@ -12,6 +12,10 @@ kv_namespaces = [
d1_databases = [ d1_databases = [
{ binding = "NEXT_CACHE_D1", database_id = "f59ddb40-ad72-4312-9395-0ac6a129af8e", database_name = "gitbook-open-v2-tags-preview" } { binding = "NEXT_CACHE_D1", database_id = "f59ddb40-ad72-4312-9395-0ac6a129af8e", database_name = "gitbook-open-v2-tags-preview" }
] ]
services = [
{ binding = "NEXT_CACHE_REVALIDATION_WORKER", service = "gitbook-open-v2-preview" },
{ binding = "GITBOOK_API", service = "gitbook-x-prod-api-cache" }
]
[env.staging] [env.staging]
routes = [ routes = [
@@ -24,6 +28,10 @@ kv_namespaces = [
d1_databases = [ d1_databases = [
{ binding = "NEXT_CACHE_D1", database_id = "9df62e39-1f35-4066-83aa-e9b8ed3ac8d5", database_name = "gitbook-open-v2-tags-staging" } { binding = "NEXT_CACHE_D1", database_id = "9df62e39-1f35-4066-83aa-e9b8ed3ac8d5", database_name = "gitbook-open-v2-tags-staging" }
] ]
services = [
{ binding = "NEXT_CACHE_REVALIDATION_WORKER", service = "gitbook-open-v2-staging" },
{ binding = "GITBOOK_API", service = "gitbook-x-staging-api-cache" }
]
[env.production] [env.production]
routes = [ routes = [
@@ -36,3 +44,7 @@ kv_namespaces = [
d1_databases = [ d1_databases = [
{ binding = "NEXT_CACHE_D1", database_id = "a6f16fce-5f45-43a9-89a4-7b83ddf25b77", database_name = "gitbook-open-v2-tags-production" } { binding = "NEXT_CACHE_D1", database_id = "a6f16fce-5f45-43a9-89a4-7b83ddf25b77", database_name = "gitbook-open-v2-tags-production" }
] ]
services = [
{ binding = "NEXT_CACHE_REVALIDATION_WORKER", service = "gitbook-open-v2-production" },
{ binding = "GITBOOK_API", service = "gitbook-x-prod-api-cache" }
]
+2 -2
View File
@@ -2,7 +2,7 @@ import { argosScreenshot } from '@argos-ci/playwright';
import { import {
CustomizationBackground, CustomizationBackground,
CustomizationCorners, CustomizationCorners,
CustomizationFont, CustomizationDefaultFont,
type CustomizationHeaderItem, type CustomizationHeaderItem,
CustomizationHeaderPreset, CustomizationHeaderPreset,
CustomizationIconsStyle, CustomizationIconsStyle,
@@ -249,7 +249,7 @@ export function getCustomizationURL(partial: DeepPartial<SiteCustomizationSettin
dangerColor: { light: '#FB2C36', dark: '#FB2C36' }, dangerColor: { light: '#FB2C36', dark: '#FB2C36' },
successColor: { light: '#00C950', dark: '#00C950' }, successColor: { light: '#00C950', dark: '#00C950' },
corners: CustomizationCorners.Rounded, corners: CustomizationCorners.Rounded,
font: CustomizationFont.Inter, font: CustomizationDefaultFont.Inter,
background: CustomizationBackground.Plain, background: CustomizationBackground.Plain,
icons: CustomizationIconsStyle.Regular, icons: CustomizationIconsStyle.Regular,
links: CustomizationLinksStyle.Default, links: CustomizationLinksStyle.Default,
+1 -1
View File
@@ -16,7 +16,7 @@
"clean": "rm -rf ./.next && rm -rf ./public/~gitbook/static/icons && rm -rf ./public/~gitbook/static/math" "clean": "rm -rf ./.next && rm -rf ./public/~gitbook/static/icons && rm -rf ./public/~gitbook/static/math"
}, },
"dependencies": { "dependencies": {
"@gitbook/api": "0.96.1", "@gitbook/api": "*",
"@gitbook/cache-do": "workspace:*", "@gitbook/cache-do": "workspace:*",
"@gitbook/cache-tags": "workspace:*", "@gitbook/cache-tags": "workspace:*",
"@gitbook/colors": "workspace:*", "@gitbook/colors": "workspace:*",
@@ -12,7 +12,7 @@ import { SpaceLayout } from '@/components/SpaceLayout';
import { buildVersion } from '@/lib/build'; import { buildVersion } from '@/lib/build';
import { isSiteIndexable } from '@/lib/seo'; import { isSiteIndexable } from '@/lib/seo';
import { GITBOOK_API_URL, GITBOOK_ASSETS_URL } from '@v2/lib/env'; import { GITBOOK_API_PUBLIC_URL, GITBOOK_ASSETS_URL, GITBOOK_ICONS_URL } from '@v2/lib/env';
import { ClientContexts } from './ClientContexts'; import { ClientContexts } from './ClientContexts';
import { RocketLoaderDetector } from './RocketLoaderDetector'; import { RocketLoaderDetector } from './RocketLoaderDetector';
@@ -31,7 +31,8 @@ export async function SiteLayout(props: {
const { scripts, customization } = context; const { scripts, customization } = context;
ReactDOM.preconnect(GITBOOK_API_URL); ReactDOM.preconnect(GITBOOK_API_PUBLIC_URL);
ReactDOM.preconnect(GITBOOK_ICONS_URL);
if (GITBOOK_ASSETS_URL) { if (GITBOOK_ASSETS_URL) {
ReactDOM.preconnect(GITBOOK_ASSETS_URL); ReactDOM.preconnect(GITBOOK_ASSETS_URL);
} }
@@ -11,7 +11,7 @@ import { getSpaceLanguage } from '@/intl/server';
import { t } from '@/intl/translate'; import { t } from '@/intl/translate';
import { tcls } from '@/lib/tailwind'; import { tcls } from '@/lib/tailwind';
import { GITBOOK_APP_URL } from '@v2/lib/env'; import { GITBOOK_API_PUBLIC_URL, GITBOOK_APP_URL } from '@v2/lib/env';
import { SpacesDropdown } from '../Header/SpacesDropdown'; import { SpacesDropdown } from '../Header/SpacesDropdown';
import { InsightsProvider } from '../Insights'; import { InsightsProvider } from '../Insights';
import { SiteSectionList, encodeClientSiteSections } from '../SiteSections'; import { SiteSectionList, encodeClientSiteSections } from '../SiteSections';
@@ -51,7 +51,7 @@ export function SpaceLayout(props: {
<InsightsProvider <InsightsProvider
enabled={withTracking} enabled={withTracking}
appURL={GITBOOK_APP_URL} appURL={GITBOOK_APP_URL}
apiHost={context.dataFetcher.apiEndpoint} apiHost={GITBOOK_API_PUBLIC_URL}
visitorAuthToken={visitorAuthToken} visitorAuthToken={visitorAuthToken}
organizationId={context.organizationId} organizationId={context.organizationId}
siteId={context.site.id} siteId={context.site.id}
+17 -17
View File
@@ -1,4 +1,4 @@
import { CustomizationFont } from '@gitbook/api'; import { CustomizationDefaultFont } from '@gitbook/api';
import { import {
Fira_Sans_Extra_Condensed, Fira_Sans_Extra_Condensed,
IBM_Plex_Mono, IBM_Plex_Mono,
@@ -188,20 +188,20 @@ const abcFavorit = localFont({
/** /**
* Font definitions. * Font definitions.
*/ */
export const fonts: { [fontName in CustomizationFont]: { variable: string } } = { export const fonts: { [fontName in CustomizationDefaultFont]: { variable: string } } = {
[CustomizationFont.Inter]: inter, [CustomizationDefaultFont.Inter]: inter,
[CustomizationFont.FiraSans]: firaSans, [CustomizationDefaultFont.FiraSans]: firaSans,
[CustomizationFont.IBMPlexSerif]: ibmPlexSerif, [CustomizationDefaultFont.IBMPlexSerif]: ibmPlexSerif,
[CustomizationFont.Lato]: lato, [CustomizationDefaultFont.Lato]: lato,
[CustomizationFont.Merriweather]: merriweather, [CustomizationDefaultFont.Merriweather]: merriweather,
[CustomizationFont.NotoSans]: notoSans, [CustomizationDefaultFont.NotoSans]: notoSans,
[CustomizationFont.OpenSans]: openSans, [CustomizationDefaultFont.OpenSans]: openSans,
[CustomizationFont.Overpass]: overpass, [CustomizationDefaultFont.Overpass]: overpass,
[CustomizationFont.Poppins]: poppins, [CustomizationDefaultFont.Poppins]: poppins,
[CustomizationFont.Raleway]: raleway, [CustomizationDefaultFont.Raleway]: raleway,
[CustomizationFont.Roboto]: roboto, [CustomizationDefaultFont.Roboto]: roboto,
[CustomizationFont.RobotoSlab]: robotoSlab, [CustomizationDefaultFont.RobotoSlab]: robotoSlab,
[CustomizationFont.SourceSansPro]: sourceSansPro, [CustomizationDefaultFont.SourceSansPro]: sourceSansPro,
[CustomizationFont.Ubuntu]: ubuntu, [CustomizationDefaultFont.Ubuntu]: ubuntu,
[CustomizationFont.ABCFavorit]: abcFavorit, [CustomizationDefaultFont.ABCFavorit]: abcFavorit,
}; };
+6 -6
View File
@@ -1,5 +1,9 @@
import { AsyncLocalStorage } from 'node:async_hooks'; import { AsyncLocalStorage } from 'node:async_hooks';
import { getCacheTag, getComputedContentSourceCacheTags } from '@gitbook/cache-tags'; import {
getCacheTag,
getCacheTagForURL,
getComputedContentSourceCacheTags,
} from '@gitbook/cache-tags';
import 'server-only'; import 'server-only';
import { import {
@@ -238,11 +242,7 @@ export const getLatestOpenAPISpecVersionContent = cache({
*/ */
export const getPublishedContentByUrl = cache({ export const getPublishedContentByUrl = cache({
name: 'api.getPublishedContentByUrl.v4', name: 'api.getPublishedContentByUrl.v4',
tag: (url) => tag: (url) => getCacheTagForURL(url),
getCacheTag({
tag: 'url',
hostname: new URL(url).hostname,
}),
get: async ( get: async (
url: string, url: string,
visitorAuthToken: string | undefined, visitorAuthToken: string | undefined,
+1 -86
View File
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'bun:test'; import { describe, expect, it } from 'bun:test';
import { asyncMutexFunction, race } from './async'; import { race } from './async';
import { flushWaitUntil } from './waitUntil'; import { flushWaitUntil } from './waitUntil';
describe('race', () => { describe('race', () => {
@@ -380,88 +380,3 @@ describe('race', () => {
}); });
}); });
}); });
describe('asyncMutexFunction', () => {
describe('self', () => {
it('should run only once', async () => {
let running = 0;
const fn = async () => {
running += 1;
await new Promise((resolve) => setTimeout(resolve, 10));
};
const mutexFn = asyncMutexFunction();
await Promise.all([
mutexFn(fn),
mutexFn(fn),
mutexFn(fn),
mutexFn(fn),
mutexFn(fn),
mutexFn(fn),
]);
expect(running).toBe(1);
});
});
describe('runBlocking', () => {
it('should run only one function at a time', async () => {
let running = 0;
let maxRunning = 0;
const fn = async () => {
running += 1;
maxRunning = Math.max(running, maxRunning);
await new Promise((resolve) => setTimeout(resolve, 10));
running -= 1;
};
const mutexFn = asyncMutexFunction();
await Promise.all([
mutexFn.runBlocking(fn),
mutexFn.runBlocking(fn),
mutexFn.runBlocking(fn),
mutexFn.runBlocking(fn),
mutexFn.runBlocking(fn),
mutexFn.runBlocking(fn),
]);
expect(maxRunning).toBe(1);
});
});
describe('runAfter', () => {
it('should run only one function at a time', async () => {
let running = 0;
let maxRunning = 0;
const fn = async () => {
running += 1;
maxRunning = Math.max(running, maxRunning);
await new Promise((resolve) => setTimeout(resolve, 10));
running -= 1;
};
const mutexFn = asyncMutexFunction();
await Promise.all([
mutexFn.runAfter(fn),
mutexFn.runAfter(fn),
mutexFn.runAfter(fn),
mutexFn.runAfter(fn),
mutexFn.runAfter(fn),
mutexFn.runAfter(fn),
]);
expect(maxRunning).toBe(1);
});
});
});
-81
View File
@@ -388,87 +388,6 @@ export type AsyncMutexFunction<T> = ((fn: () => Promise<T>) => Promise<T>) & {
) => Promise<ReturnType>; ) => Promise<ReturnType>;
}; };
/**
* Creates a function that will only call the given function once at a time.
*/
export function asyncMutexFunction<T>(): AsyncMutexFunction<T> {
let pending:
| undefined
| { kind: 'value'; promise: Promise<T> }
| { kind: 'blocking'; promise: Promise<unknown> };
const mutex: AsyncMutexFunction<T> = async (fn) => {
if (pending?.kind === 'value') {
return pending.promise;
}
while (pending) {
await pending.promise;
}
const promise = fn();
pending = { kind: 'value', promise };
try {
const result = await promise;
return result;
} finally {
pending = undefined;
}
};
mutex.wait = async () => {
return pending?.promise;
};
mutex.runBlocking = async (fn, options) => {
const failOnMutexError = options?.failOnMutexError ?? true;
while (pending) {
try {
await pending.promise;
} catch (err) {
if (failOnMutexError) {
throw err;
}
}
}
const promise = fn();
pending = { kind: 'blocking', promise };
try {
const result = await promise;
return result;
} finally {
pending = undefined;
}
};
mutex.runAfter = async (fn, options) => {
const failOnMutexError = options?.failOnMutexError ?? true;
while (pending) {
try {
await pending.promise;
} catch (err) {
if (failOnMutexError) {
throw err;
}
}
}
const promise = fn();
pending = { kind: 'value', promise };
try {
const result = await pending.promise;
return result;
} finally {
pending = undefined;
}
};
return mutex;
}
/** /**
* Try catch a promise and return the result or the error. * Try catch a promise and return the result or the error.
*/ */
-11
View File
@@ -1,5 +1,3 @@
import { startSpan } from '@sentry/nextjs';
export interface TraceSpan { export interface TraceSpan {
setAttribute: (label: string, value: boolean | string | number) => void; setAttribute: (label: string, value: boolean | string | number) => void;
} }
@@ -22,17 +20,10 @@ export async function trace<T>(
typeof name === 'string' ? { operation: name, name: undefined } : name; typeof name === 'string' ? { operation: name, name: undefined } : name;
const completeName = executionName ? `${operation}(${executionName})` : operation; const completeName = executionName ? `${operation}(${executionName})` : operation;
return await startSpan(
{
name: completeName,
op: operation,
},
async (sentrySpan) => {
const attributes: Record<string, boolean | string | number> = {}; const attributes: Record<string, boolean | string | number> = {};
const span: TraceSpan = { const span: TraceSpan = {
setAttribute(label, value) { setAttribute(label, value) {
attributes[label] = value; attributes[label] = value;
sentrySpan?.setAttribute(label, value);
}, },
}; };
@@ -50,8 +41,6 @@ export async function trace<T>(
} }
} }
} }
);
}
/** /**
* Return the current time in milliseconds. * Return the current time in milliseconds.
+1 -1
View File
@@ -15,7 +15,7 @@ export function defaultCustomizationForSpace(): api.CustomizationSettings {
light: '#346DDB', light: '#346DDB',
}, },
corners: api.CustomizationCorners.Rounded, corners: api.CustomizationCorners.Rounded,
font: api.CustomizationFont.Inter, font: api.CustomizationDefaultFont.Inter,
background: api.CustomizationBackground.Plain, background: api.CustomizationBackground.Plain,
}, },
favicon: {}, favicon: {},
+17 -17
View File
@@ -1,4 +1,4 @@
import { CustomizationFont, CustomizationHeaderPreset } from '@gitbook/api'; import { CustomizationDefaultFont, CustomizationHeaderPreset } from '@gitbook/api';
import { colorContrast } from '@gitbook/colors'; import { colorContrast } from '@gitbook/colors';
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';
import { ImageResponse } from 'next/og'; import { ImageResponse } from 'next/og';
@@ -8,22 +8,22 @@ import { getAssetURL } from '@/lib/assets';
import { filterOutNullable } from '@/lib/typescript'; import { filterOutNullable } from '@/lib/typescript';
import type { GitBookSiteContext } from '@v2/lib/context'; import type { GitBookSiteContext } from '@v2/lib/context';
const googleFontsMap: { [fontName in CustomizationFont]: string } = { const googleFontsMap: { [fontName in CustomizationDefaultFont]: string } = {
[CustomizationFont.Inter]: 'Inter', [CustomizationDefaultFont.Inter]: 'Inter',
[CustomizationFont.FiraSans]: 'Fira Sans Extra Condensed', [CustomizationDefaultFont.FiraSans]: 'Fira Sans Extra Condensed',
[CustomizationFont.IBMPlexSerif]: 'IBM Plex Serif', [CustomizationDefaultFont.IBMPlexSerif]: 'IBM Plex Serif',
[CustomizationFont.Lato]: 'Lato', [CustomizationDefaultFont.Lato]: 'Lato',
[CustomizationFont.Merriweather]: 'Merriweather', [CustomizationDefaultFont.Merriweather]: 'Merriweather',
[CustomizationFont.NotoSans]: 'Noto Sans', [CustomizationDefaultFont.NotoSans]: 'Noto Sans',
[CustomizationFont.OpenSans]: 'Open Sans', [CustomizationDefaultFont.OpenSans]: 'Open Sans',
[CustomizationFont.Overpass]: 'Overpass', [CustomizationDefaultFont.Overpass]: 'Overpass',
[CustomizationFont.Poppins]: 'Poppins', [CustomizationDefaultFont.Poppins]: 'Poppins',
[CustomizationFont.Raleway]: 'Raleway', [CustomizationDefaultFont.Raleway]: 'Raleway',
[CustomizationFont.Roboto]: 'Roboto', [CustomizationDefaultFont.Roboto]: 'Roboto',
[CustomizationFont.RobotoSlab]: 'Roboto Slab', [CustomizationDefaultFont.RobotoSlab]: 'Roboto Slab',
[CustomizationFont.SourceSansPro]: 'Source Sans 3', [CustomizationDefaultFont.SourceSansPro]: 'Source Sans 3',
[CustomizationFont.Ubuntu]: 'Ubuntu', [CustomizationDefaultFont.Ubuntu]: 'Ubuntu',
[CustomizationFont.ABCFavorit]: 'Inter', [CustomizationDefaultFont.ABCFavorit]: 'Inter',
}; };
/** /**
+5 -11
View File
@@ -49,18 +49,12 @@ export function getTestURL(urlRest: string): string {
* Get an API token for a site by its URL. * Get an API token for a site by its URL.
*/ */
export async function getSiteAPIToken(url: string) { export async function getSiteAPIToken(url: string) {
const api = new GitBookAPI(); const api = new GitBookAPI({
const { data } = await api.urls.getPublishedContentByUrl( endpoint: 'https://api.gitbook.com/cache',
{ });
const { data } = await api.urls.getPublishedContentByUrl({
url, url,
cache: true, });
},
{
headers: {
'x-gitbook-force-cache': 'true',
},
}
);
if ('redirect' in data) { if ('redirect' in data) {
throw new Error(`Invalid site URL, it resulted in a redirect: ${data.redirect}`); throw new Error(`Invalid site URL, it resulted in a redirect: ${data.redirect}`);
+1 -1
View File
@@ -10,7 +10,7 @@
}, },
"dependencies": { "dependencies": {
"classnames": "^2.5.1", "classnames": "^2.5.1",
"@gitbook/api": "0.96.1", "@gitbook/api": "*",
"@gitbook/icons": "workspace:*" "@gitbook/icons": "workspace:*"
}, },
"peerDependencies": { "peerDependencies": {
+1 -1
View File
@@ -37,7 +37,7 @@
}, },
// Check the package for type errors // Check the package for type errors
"typecheck": { "typecheck": {
"dependsOn": ["^typecheck", "build", "generate"] "dependsOn": ["^typecheck", "build"]
}, },
// Lint the package for style errors // Lint the package for style errors
"lint": { "lint": {