Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"use client";

import type { ColumnDef } from "@tanstack/react-table";

import type { ActionRun } from "./getActionRunsTableColumns";
import { DataTable } from "~/app/components/DataTable/DataTable";
import { getActionRunsTableColumns } from "./getActionRunsTableColumns";
Expand All @@ -12,5 +14,10 @@ export const ActionRunsTable = ({
communitySlug: string;
}) => {
const actionRunsColumns = getActionRunsTableColumns(communitySlug);
return <DataTable columns={actionRunsColumns} data={actionRuns} />;
return (
<DataTable
columns={actionRunsColumns as ColumnDef<ActionRun, unknown>[]}
data={actionRuns}
/>
);
};
21 changes: 15 additions & 6 deletions core/lib/server/cache/autoRevalidate.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { revalidatePath, revalidateTag } from "next/cache";
import { revalidatePath, revalidateTag, updateTag } from "next/cache";

import { logger } from "logger";

import type { autoCache } from "./autoCache";
import type { AutoRevalidateOptions, DirectAutoOutput, ExecuteFn, QB } from "./types";
import { env } from "~/lib/env/env";
import { getCommunitySlug } from "./getCommunitySlug";
import { getCommunitySlug, getIsApiRoute } from "./getCommunitySlug";
import { revalidateTagsForCommunity } from "./revalidate";
import { cachedFindTables, directAutoOutput } from "./sharedAuto";
import { setTransactionStore } from "./transactionStorage";
Expand All @@ -19,21 +19,24 @@ const executeWithRevalidate = <
options?: AutoRevalidateOptions
) => {
const executeFn = async (...args: Parameters<Q[M]>) => {
const communitySlug = options?.communitySlug ?? (await getCommunitySlug());
const [communitySlug, isApiRoute] = await Promise.all([
options?.communitySlug ?? (await getCommunitySlug()),
getIsApiRoute(),
]);

const communitySlugs = Array.isArray(communitySlug) ? communitySlug : [communitySlug];

const compiledQuery = qb.compile();

const tables = await cachedFindTables(compiledQuery, "mutation");
const tables = cachedFindTables(compiledQuery, "mutation");

// necessary assertion here due to
// https://github.com/microsoft/TypeScript/issues/241
const result = await (qb[method](...args) as ReturnType<Q[M]>);

const additionalRevalidateTags = options?.additionalRevalidateTags ?? [];

const communityTags = await revalidateTagsForCommunity(tables, communitySlugs);
const communityTags = await revalidateTagsForCommunity(tables, communitySlugs, isApiRoute);

// so we can later check whether we need to use autocache or not
setTransactionStore({
Expand All @@ -44,7 +47,13 @@ const executeWithRevalidate = <
if (env.CACHE_LOG) {
logger.debug(`AUTOREVALIDATE: Revalidating tag: ${tag}`);
}
revalidateTag(tag);
if (isApiRoute) {
revalidateTag(tag, {
expire: 0,
});
} else {
updateTag(tag);
}
});

if (options?.additionalRevalidatePaths) {
Expand Down
10 changes: 5 additions & 5 deletions core/lib/server/cache/cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,9 +466,9 @@ describe("cachedFindTables", () => {
.selectFrom("new_pub")
.selectAll();

await expect(() => compileAndFindTables(query, "select")).rejects.toThrowError(/Insert/);
expect(() => compileAndFindTables(query, "select")).toThrow(/Insert/);

await expect(compileAndFindTables(query, "mutation")).resolves.not.toThrow();
expect(() => compileAndFindTables(query, "mutation")).not.toThrow();

() => {
const cachedQuery = autoCache(query);
Expand All @@ -486,9 +486,9 @@ describe("cachedFindTables", () => {

const query = mockedDb.selectFrom("pubs").selectAll();

await expect(compileAndFindTables(query, "select")).resolves.not.toThrow();
expect(() => compileAndFindTables(query, "select")).not.toThrow();

await expect(() => compileAndFindTables(query, "mutation")).rejects.toThrowError(
expect(() => compileAndFindTables(query, "mutation")).toThrow(
/Invalid use of `autoRevalidate`/
);

Expand All @@ -502,7 +502,7 @@ describe("cachedFindTables", () => {
});

it("should add extra tags for tables that are linked to other tables", async () => {
const tables = await compileAndFindTables(mockedDb.selectFrom("pubs"), "select");
const tables = compileAndFindTables(mockedDb.selectFrom("pubs"), "select");

expect(tables).toEqual(["pubs"]);

Expand Down
4 changes: 2 additions & 2 deletions core/lib/server/cache/constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export const PUBPUB_COMMUNITY_SLUG_COOKIE_NAME = "pubpub_community_slug" as const;

export const PUBPUB_COMMUNITY_SLUG_HEADER_NAME = "x-pubpub-community-slug" as const;

export const PUBPUB_API_ROUTE_HEADER_NAME = "x-pubpub-api-route" as const;

/**
* In seconds
*/
Expand Down
16 changes: 15 additions & 1 deletion core/lib/server/cache/getCommunitySlug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { cache } from "react";
import { headers } from "next/headers";
import { getParams } from "@nimpl/getters/get-params";

import { PUBPUB_COMMUNITY_SLUG_HEADER_NAME } from "./constants";
import { PUBPUB_API_ROUTE_HEADER_NAME, PUBPUB_COMMUNITY_SLUG_HEADER_NAME } from "./constants";

/**
* Experimental and likely unstable way to get the community slug.
Expand Down Expand Up @@ -40,3 +40,17 @@ export const getCommunitySlug = cache(async () => {

return communitySlugHeader;
});

/**
* checks whether we are in an api route
* since next 16, they introduce a distinction between `updateTag` and `revalidateTag`, the latter not being available in api routes.
*
*/
export const getIsApiRoute = cache(async () => {
const header = await headers();
const apiRouteHeader = header.get(PUBPUB_API_ROUTE_HEADER_NAME);
if (!apiRouteHeader) {
return false;
}
return true;
});
13 changes: 10 additions & 3 deletions core/lib/server/cache/revalidate.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { revalidateTag } from "next/cache";
import { revalidateTag, updateTag } from "next/cache";

import { logger } from "logger";

Expand All @@ -21,7 +21,8 @@ import { getCommunitySlug } from "./getCommunitySlug";
*/
export const revalidateTagsForCommunity = async <S extends CacheScope>(
scope: S | S[],
communitySlug?: string | string[]
communitySlug?: string | string[],
isApiRoute?: boolean
): Promise<string[]> => {
const slug = communitySlug ?? (await getCommunitySlug());

Expand All @@ -32,7 +33,13 @@ export const revalidateTagsForCommunity = async <S extends CacheScope>(
const tags = slugs.flatMap((slug) => {
const tags = createCommunityCacheTags(scopes, slug);
return tags.map((tag) => {
revalidateTag(tag);
if (isApiRoute) {
revalidateTag(tag, {
expire: 0,
});
} else {
updateTag(tag);
}
return tag;
});
});
Expand Down
7 changes: 3 additions & 4 deletions core/lib/server/cache/sharedAuto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,10 @@ export class AutoCacheWithMutationError extends Error {
}
}

export const cachedFindTables = async <T extends CompiledQuery<Simplify<any>>>(
export const cachedFindTables = <T extends CompiledQuery<Simplify<any>>>(
query: T,
type: "select" | "mutation"
): Promise<(keyof Database)[]> => {
const getTables = async () => findTables(query.query, type);
): (keyof Database)[] => {
// TODO: benchmark whether memoization is worth it

// const getTables = memoize(() => findTables(query.query, type), {
Expand All @@ -97,7 +96,7 @@ export const cachedFindTables = async <T extends CompiledQuery<Simplify<any>>>(
// duration: ONE_DAY,
// });

const { tables, operations } = await getTables();
const { tables, operations } = findTables(query.query, type);

/**
* basically, you should not be able to `autoRevalidate` select queries
Expand Down
4 changes: 2 additions & 2 deletions core/lib/server/maybeWithTrx.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Kysely } from "kysely";

import { revalidateTag } from "next/cache";
import { updateTag } from "next/cache";
import { Transaction } from "kysely";

import type { Database } from "db/Database";
Expand Down Expand Up @@ -51,7 +51,7 @@ export const maybeWithTrx = async <T>(
if (env.CACHE_LOG === "true") {
logger.debug(`MANUAL REVALIDATE: revalidating tag bc of failed transaction: ${tag}`);
}
revalidateTag(tag);
updateTag(tag);
}

throw error;
Expand Down
30 changes: 10 additions & 20 deletions core/next.config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// @ts-check

import type { NextConfig, normalizeConfig } from "next/dist/server/config";
import type { NextConfig } from "next";

import { PHASE_PRODUCTION_BUILD } from "next/dist/shared/lib/constants.js";
import { PHASE_PRODUCTION_BUILD } from "next/constants.js";
import withPreconstruct from "@preconstruct/next";
import { withSentryConfig } from "@sentry/nextjs";

Expand All @@ -16,10 +16,6 @@ const nextConfig: NextConfig = {
// this gets checked in CI already
ignoreBuildErrors: true,
},
eslint: {
// this gets checked in CI already
ignoreDuringBuilds: true,
},
reactStrictMode: true,
/**
* This is necessary to get around Next.js hard 2MB limit
Expand All @@ -46,34 +42,26 @@ const nextConfig: NextConfig = {
turbopack: {
root: new URL("./..", import.meta.url).pathname,
},
typedRoutes: true,
serverExternalPackages: [
"@aws-sdk",
// without this here, next will sort of implode and no longer compile and serve pages properly
// if graphile-worker is used in server actions
"graphile-worker",
"@node-rs/argon2",
],

experimental: {
turbopackFileSystemCacheForDev: true,
browserDebugInfoInTerminal: true,
optimizePackageImports: ["@icons-pack/react-simple-icons", "lucide-react"],
webpackBuildWorker: true,
parallelServerBuildTraces: true,
serverActions: {
bodySizeLimit: "1gb",
},
},
// open telemetry cries a lot during build, don't think it's serious
// https://github.com/open-telemetry/opentelemetry-js/issues/4173
webpack: (config, { buildId, dev, isServer, defaultLoaders, nextRuntime, webpack }) => {
if (config.cache && !dev) {
config.cache = Object.freeze({
type: "memory",
});
}
if (isServer) {
config.ignoreWarnings = [{ module: /opentelemetry/ }];
}
return config;
},

async headers() {
// otherwise SSE doesn't work
// also recommended by Next: https://nextjs.org/docs/app/guides/self-hosting#streaming-and-suspense
Expand Down Expand Up @@ -123,7 +111,9 @@ const modifiedConfig = withPreconstruct(
})
);

const config: typeof normalizeConfig = async (phase, { defaultConfig }) => {
const config = async (
phase: "phase-development-server" | "phase-production-server" | "phase-production-build"
): Promise<NextConfig> => {
if (!env.SENTRY_AUTH_TOKEN) {
console.warn("⚠️ SENTRY_AUTH_TOKEN is not set");
}
Expand Down
2 changes: 0 additions & 2 deletions core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,6 @@
"postcss": "catalog:",
"prisma": "^5.22.0",
"prisma-dbml-generator": "^0.12.0",
"react": "catalog:react19",
"react-dom": "catalog:react19",
"storybook": "^9.1.2",
"styled-jsx": "^5.1.7",
"tailwindcss": "catalog:",
Expand Down
1 change: 1 addition & 0 deletions core/prisma/seed/stubs/stubs.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ export {
stubFn as reactErrorHandler,
stubFn as withSentryConfig,
stubFn as revalidateTag,
stubFn as updateTag,
};
30 changes: 19 additions & 11 deletions core/middleware.ts → core/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,47 @@ import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";

import {
PUBPUB_COMMUNITY_SLUG_COOKIE_NAME,
PUBPUB_API_ROUTE_HEADER_NAME,
PUBPUB_COMMUNITY_SLUG_HEADER_NAME,
} from "./lib/server/cache/constants";

const communityRouteRegexp = /^\/c\/([^/]*?)(?:$|\/)|\/api\/v\d\/c\/([^/]*?)\//;

const apiRouteRegexp = /^\/api\/v\d\/([^/]*?)\//;

/**
* if you are in /c/[communitySlug] or in /api/v0/c/[communitySlug],
* we add a `pubpub_community_slug=${communitySlug}` cookie.
* That way we can at any depth of server component/api route/server action
* use the communitySlug to tag or invalidate cached queries.
*/
const communitySlugMiddleware = async (request: NextRequest) => {
const matched = request.nextUrl.pathname.match(communityRouteRegexp);

if (!matched) {
return NextResponse.next();
}
const communitySlug = matched[1] || matched[2];
const communitySlugMatched = request.nextUrl.pathname.match(communityRouteRegexp);
const apiRouteMatched = request.nextUrl.pathname.match(apiRouteRegexp);

if (!communitySlug) {
// TODO: Handle strange case where no community slug is found.
if (!communitySlugMatched && !apiRouteMatched) {
return NextResponse.next();
}

const response = NextResponse.next();

response.headers.set(PUBPUB_COMMUNITY_SLUG_HEADER_NAME, communitySlug);
// Set header to indicate we're in an API route
if (apiRouteMatched) {
response.headers.set(PUBPUB_API_ROUTE_HEADER_NAME, "true");
}

// Set community slug header if available
const communitySlug = communitySlugMatched
? (communitySlugMatched[1] ?? communitySlugMatched[2])
: null;
if (communitySlug) {
response.headers.set(PUBPUB_COMMUNITY_SLUG_HEADER_NAME, communitySlug);
}

return response;
};

export async function middleware(request: NextRequest) {
export async function proxy(request: NextRequest) {
const response = await communitySlugMiddleware(request);
return response;
}
Expand Down
2 changes: 1 addition & 1 deletion core/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"**/*.tsx",
".next/types/**/*.ts",
"instrumentation.node.mts",
"cache-handler.js",
"cache-handler.mjs",
"prisma/generate-history-table.mts",
"prisma/seed.cts",
"prisma/scripts/comment-generator.mts",
Expand Down
3 changes: 0 additions & 3 deletions docs/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ const nextConfig: NextConfig = withNextra({
typescript: {
ignoreBuildErrors: true,
},
eslint: {
ignoreDuringBuilds: true,
},
experimental: {
parallelServerBuildTraces: true,
webpackBuildWorker: true,
Expand Down
4 changes: 2 additions & 2 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
},
"dependencies": {
"next": "catalog:",
"nextra": "^4.3.0",
"nextra-theme-docs": "^4.3.0",
"nextra": "^4.6.0",
"nextra-theme-docs": "^4.6.0",
"pagefind": "^1.3.0",
"react": "catalog:react19",
"react-dom": "catalog:react19"
Expand Down
Loading
Loading