From 42c5e2fe499804c203f3020829630976ce60913f Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Fri, 27 Dec 2024 11:17:51 -0800 Subject: [PATCH 01/23] Fix STACK-SERVER-NH --- .../(protected)/projects/[projectId]/metrics/globe.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/metrics/globe.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/metrics/globe.tsx index 5399022ee..ca3a3b1df 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/metrics/globe.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/metrics/globe.tsx @@ -1,6 +1,5 @@ import useResizeObserver from '@react-hook/resize-observer'; import { useUser } from '@stackframe/stack'; -import { throwErr } from '@stackframe/stack-shared/dist/utils/errors'; import { getFlagEmoji } from '@stackframe/stack-shared/dist/utils/unicode'; import { Skeleton, Typography } from '@stackframe/stack-ui'; import { RefObject, use, useEffect, useLayoutEffect, useRef, useState } from 'react'; @@ -158,7 +157,11 @@ export function GlobeSection({ countryData, totalUsers, children }: {countryData height={globeSize?.[1] ?? 64} onGlobeReady={() => { setTimeout(() => setIsGlobeReady(true), 100); - const current = globeRef.current ?? throwErr('Globe ref not available even though globe is ready?'); + const current = globeRef.current; + if (!current) { + // User probably navigated away right at this moment + return; + } const controls = current.controls(); controls.maxDistance = 1000; controls.minDistance = 120; From 285c51bd347d5615f5b0244d516c6db8fa0b1a12 Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Fri, 27 Dec 2024 11:30:42 -0800 Subject: [PATCH 02/23] Better 429 request logging --- packages/stack-shared/src/interface/clientInterface.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/stack-shared/src/interface/clientInterface.ts b/packages/stack-shared/src/interface/clientInterface.ts index 509df2743..02f30b8ac 100644 --- a/packages/stack-shared/src/interface/clientInterface.ts +++ b/packages/stack-shared/src/interface/clientInterface.ts @@ -356,9 +356,11 @@ export class StackClientInterface { // Rate limited, so retry if we can const retryAfter = res.headers.get("Retry-After"); if (retryAfter !== null) { + console.log(`Rate limited while sending request to ${url}. Will retry after ${retryAfter} seconds...`); await wait(Number(retryAfter) * 1000); return Result.error(new Error(`Rate limited, retrying after ${retryAfter} seconds`)); } + console.log(`Rate limited while sending request to ${url}, no retry-after header received. Retrying...`); return Result.error(new Error("Rate limited, no retry-after header received")); } else { const error = await res.text(); From 8ce7c9f0793ca071a3ae7fdd6d7bf9b61c1a9c29 Mon Sep 17 00:00:00 2001 From: Konsti Wohlwend Date: Fri, 27 Dec 2024 11:31:12 -0800 Subject: [PATCH 03/23] Raw SQL query for fetching users (#381) --- .../app/api/v1/team-member-profiles/crud.tsx | 6 +- apps/backend/src/app/api/v1/users/crud.tsx | 212 ++++++++++++++++-- apps/backend/src/lib/api-keys.tsx | 55 ++++- apps/backend/src/prisma-client.tsx | 48 ++++ .../src/route-handlers/crud-handler.tsx | 2 +- .../src/route-handlers/smart-request.tsx | 55 +++-- apps/backend/src/utils/telemetry.tsx | 4 +- .../api/v1/integrations/neon/api-keys.test.ts | 165 +++++++++++++- .../endpoints/api/v1/internal-metrics.test.ts | 2 + .../api/v1/internal/api-keys.test.ts | 64 +++++- .../endpoints/api/v1/team-memberships.test.ts | 137 +++++++++++ eslint-configs/defaults.js | 1 + packages/stack-shared/src/utils/objects.tsx | 4 + 13 files changed, 678 insertions(+), 77 deletions(-) diff --git a/apps/backend/src/app/api/v1/team-member-profiles/crud.tsx b/apps/backend/src/app/api/v1/team-member-profiles/crud.tsx index dba4e1a58..754b533ff 100644 --- a/apps/backend/src/app/api/v1/team-member-profiles/crud.tsx +++ b/apps/backend/src/app/api/v1/team-member-profiles/crud.tsx @@ -76,7 +76,7 @@ export const teamMemberProfilesCrudHandlers = createLazyProxy(() => createCrudHa include: fullInclude, }); - const lastActiveAtMillis = await getUsersLastActiveAtMillis(db.map(user => user.projectUserId), db.map(user => user.createdAt)); + const lastActiveAtMillis = await getUsersLastActiveAtMillis(auth.project.id, db.map(user => user.projectUserId), db.map(user => user.createdAt)); return { items: db.map((user, index) => prismaToCrud(user, lastActiveAtMillis[index])), @@ -118,7 +118,7 @@ export const teamMemberProfilesCrudHandlers = createLazyProxy(() => createCrudHa throw new KnownErrors.TeamMembershipNotFound(params.team_id, params.user_id); } - return prismaToCrud(db, await getUserLastActiveAtMillis(db.projectUser.projectUserId) ?? db.projectUser.createdAt.getTime()); + return prismaToCrud(db, await getUserLastActiveAtMillis(auth.project.id, db.projectUser.projectUserId) ?? db.projectUser.createdAt.getTime()); }); }, onUpdate: async ({ auth, data, params }) => { @@ -151,7 +151,7 @@ export const teamMemberProfilesCrudHandlers = createLazyProxy(() => createCrudHa include: fullInclude, }); - return prismaToCrud(db, await getUserLastActiveAtMillis(db.projectUser.projectUserId) ?? db.projectUser.createdAt.getTime()); + return prismaToCrud(db, await getUserLastActiveAtMillis(auth.project.id, db.projectUser.projectUserId) ?? db.projectUser.createdAt.getTime()); }); }, })); diff --git a/apps/backend/src/app/api/v1/users/crud.tsx b/apps/backend/src/app/api/v1/users/crud.tsx index f600dcedc..553c02743 100644 --- a/apps/backend/src/app/api/v1/users/crud.tsx +++ b/apps/backend/src/app/api/v1/users/crud.tsx @@ -1,7 +1,7 @@ import { ensureTeamMembershipExists, ensureUserExists } from "@/lib/request-checks"; import { PrismaTransaction } from "@/lib/types"; import { sendTeamMembershipDeletedWebhook, sendUserCreatedWebhook, sendUserDeletedWebhook, sendUserUpdatedWebhook } from "@/lib/webhooks"; -import { prismaClient, retryTransaction } from "@/prisma-client"; +import { RawQuery, prismaClient, rawQuery, retryTransaction } from "@/prisma-client"; import { createCrudHandlers } from "@/route-handlers/crud-handler"; import { runAsynchronouslyAndWaitUntil } from "@/utils/vercel"; import { BooleanTrue, Prisma } from "@prisma/client"; @@ -11,8 +11,10 @@ import { UsersCrud, usersCrud } from "@stackframe/stack-shared/dist/interface/cr import { userIdOrMeSchema, yupBoolean, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields"; import { validateBase64Image } from "@stackframe/stack-shared/dist/utils/base64"; import { decodeBase64 } from "@stackframe/stack-shared/dist/utils/bytes"; +import { getNodeEnvironment } from "@stackframe/stack-shared/dist/utils/env"; import { StackAssertionError, StatusError, throwErr } from "@stackframe/stack-shared/dist/utils/errors"; import { hashPassword, isPasswordHashValid } from "@stackframe/stack-shared/dist/utils/hashes"; +import { deepPlainEquals } from "@stackframe/stack-shared/dist/utils/objects"; import { createLazyProxy } from "@stackframe/stack-shared/dist/utils/proxies"; import { typedToLowercase } from "@stackframe/stack-shared/dist/utils/strings"; import { teamPrismaToCrud, teamsCrudHandlers } from "../teams/crud"; @@ -203,45 +205,207 @@ async function getOtpConfig(tx: PrismaTransaction, projectConfigId: string) { return otpConfig.length === 0 ? null : otpConfig[0]; } -export const getUserLastActiveAtMillis = async (userId: string): Promise => { - const event = await prismaClient.event.findFirst({ - where: { - data: { - path: ["$.userId"], - equals: userId, - }, - }, - orderBy: { - createdAt: 'desc', - }, - }); - - return event?.createdAt.getTime() ?? null; +export const getUserLastActiveAtMillis = async (projectId: string, userId: string): Promise => { + const res = (await getUsersLastActiveAtMillis(projectId, [userId], [0]))[0]; + if (res === 0) { + return null; + } + return res; }; -// same as userIds.map(userId => getUserLastActiveAtMillis(userId, fallbackTo)), but uses a single query -export const getUsersLastActiveAtMillis = async (userIds: string[], fallbackTo: (number | Date)[]): Promise => { +// same as userIds.map(userId => getUserLastActiveAtMillis(projectId, userId)), but uses a single query +export const getUsersLastActiveAtMillis = async (projectId: string, userIds: string[], userSignedUpAtMillis: (number | Date)[]): Promise => { if (userIds.length === 0) { // Prisma.join throws an error if the array is empty, so we need to handle that case return []; } const events = await prismaClient.$queryRaw>` - SELECT data->>'userId' as "userId", MAX("createdAt") as "lastActiveAt" + SELECT data->>'userId' as "userId", MAX("eventStartedAt") as "lastActiveAt" FROM "Event" - WHERE data->>'userId' = ANY(${Prisma.sql`ARRAY[${Prisma.join(userIds)}]`}) + WHERE data->>'userId' = ANY(${Prisma.sql`ARRAY[${Prisma.join(userIds)}]`}) AND data->>'projectId' = ${projectId} AND "systemEventTypeIds" @> '{"$user-activity"}' GROUP BY data->>'userId' `; return userIds.map((userId, index) => { const event = events.find(e => e.userId === userId); return event ? event.lastActiveAt.getTime() : ( - typeof fallbackTo[index] === "number" ? (fallbackTo[index] as number) : (fallbackTo[index] as Date).getTime() + typeof userSignedUpAtMillis[index] === "number" ? (userSignedUpAtMillis[index] as number) : (userSignedUpAtMillis[index] as Date).getTime() ); }); }; +export function getUserQuery(projectId: string, userId: string): RawQuery { + return { + sql: Prisma.sql` + SELECT to_json( + ( + SELECT ( + to_jsonb("ProjectUser".*) || + jsonb_build_object( + 'lastActiveAt', ( + SELECT MAX("eventStartedAt") as "lastActiveAt" + FROM "Event" + WHERE data->>'projectId' = "ProjectUser"."projectId" AND ("data"->>'userId')::UUID = "ProjectUser"."projectUserId" AND "systemEventTypeIds" @> '{"$user-activity"}' + ), + 'ContactChannels', ( + SELECT COALESCE(ARRAY_AGG( + to_jsonb("ContactChannel") || + jsonb_build_object() + ), '{}') + FROM "ContactChannel" + WHERE "ContactChannel"."projectId" = "ProjectUser"."projectId" AND "ContactChannel"."projectUserId" = "ProjectUser"."projectUserId" AND "ContactChannel"."isPrimary" = 'TRUE' + ), + 'ProjectUserOAuthAccounts', ( + SELECT COALESCE(ARRAY_AGG( + to_jsonb("ProjectUserOAuthAccount") || + jsonb_build_object( + 'ProviderConfig', ( + SELECT to_jsonb("OAuthProviderConfig") + FROM "OAuthProviderConfig" + WHERE "ProjectConfig"."id" = "OAuthProviderConfig"."projectConfigId" AND "OAuthProviderConfig"."id" = "ProjectUserOAuthAccount"."oauthProviderConfigId" + ) + ) + ), '{}') + FROM "ProjectUserOAuthAccount" + WHERE "ProjectUserOAuthAccount"."projectId" = "ProjectUser"."projectId" AND "ProjectUserOAuthAccount"."projectUserId" = "ProjectUser"."projectUserId" + ), + 'AuthMethods', ( + SELECT COALESCE(ARRAY_AGG( + to_jsonb("AuthMethod") || + jsonb_build_object( + 'PasswordAuthMethod', ( + SELECT ( + to_jsonb("PasswordAuthMethod") || + jsonb_build_object() + ) + FROM "PasswordAuthMethod" + WHERE "PasswordAuthMethod"."projectId" = "ProjectUser"."projectId" AND "PasswordAuthMethod"."projectUserId" = "ProjectUser"."projectUserId" AND "PasswordAuthMethod"."authMethodId" = "AuthMethod"."id" + ), + 'OtpAuthMethod', ( + SELECT ( + to_jsonb("OtpAuthMethod") || + jsonb_build_object() + ) + FROM "OtpAuthMethod" + WHERE "OtpAuthMethod"."projectId" = "ProjectUser"."projectId" AND "OtpAuthMethod"."projectUserId" = "ProjectUser"."projectUserId" AND "OtpAuthMethod"."authMethodId" = "AuthMethod"."id" + ), + 'PasskeyAuthMethod', ( + SELECT ( + to_jsonb("PasskeyAuthMethod") || + jsonb_build_object() + ) + FROM "PasskeyAuthMethod" + WHERE "PasskeyAuthMethod"."projectId" = "ProjectUser"."projectId" AND "PasskeyAuthMethod"."projectUserId" = "ProjectUser"."projectUserId" AND "PasskeyAuthMethod"."authMethodId" = "AuthMethod"."id" + ), + 'OAuthAuthMethod', ( + SELECT ( + to_jsonb("OAuthAuthMethod") || + jsonb_build_object() + ) + FROM "OAuthAuthMethod" + WHERE "OAuthAuthMethod"."projectId" = "ProjectUser"."projectId" AND "OAuthAuthMethod"."projectUserId" = "ProjectUser"."projectUserId" AND "OAuthAuthMethod"."authMethodId" = "AuthMethod"."id" + ) + ) + ), '{}') + FROM "AuthMethod" + WHERE "AuthMethod"."projectId" = "ProjectUser"."projectId" AND "AuthMethod"."projectUserId" = "ProjectUser"."projectUserId" + ), + 'SelectedTeamMember', ( + SELECT ( + to_jsonb("TeamMember") || + jsonb_build_object( + 'Team', ( + SELECT + to_jsonb("Team") + FROM "Team" + WHERE "Team"."projectId" = "ProjectUser"."projectId" AND "Team"."teamId" = "TeamMember"."teamId" + ) + ) + ) + FROM "TeamMember" + WHERE "TeamMember"."projectId" = "ProjectUser"."projectId" AND "TeamMember"."projectUserId" = "ProjectUser"."projectUserId" AND "TeamMember"."isSelected" = 'TRUE' + ) + ) + ) + FROM "ProjectUser" + LEFT JOIN "Project" ON "Project"."id" = "ProjectUser"."projectId" + LEFT JOIN "ProjectConfig" ON "ProjectConfig"."id" = "Project"."configId" + WHERE "ProjectUser"."projectId" = ${projectId} AND "ProjectUser"."projectUserId" = ${userId}::UUID + ) + ) AS "row_data_json" + `, + postProcess: (queryResult) => { + if (queryResult.length !== 1) { + throw new StackAssertionError("Expected 1 result, got " + queryResult.length, queryResult); + } + + const row = queryResult[0].row_data_json; + if (!row) { + return null; + } + + const primaryEmailContactChannel = row.ContactChannels.find((c: any) => c.type === 'EMAIL' && c.isPrimary); + const passwordAuth = row.AuthMethods.find((m: any) => m.PasswordAuthMethod); + const otpAuth = row.AuthMethods.find((m: any) => m.OtpAuthMethod); + const passkeyAuth = row.AuthMethods.find((m: any) => m.PasskeyAuthMethod); + + return { + id: row.projectUserId, + display_name: row.displayName, + primary_email: primaryEmailContactChannel?.value || null, + primary_email_verified: primaryEmailContactChannel?.isVerified || false, + primary_email_auth_enabled: primaryEmailContactChannel?.usedForAuth === 'TRUE' ? true : false, + profile_image_url: row.profileImageUrl, + signed_up_at_millis: new Date(row.createdAt + "Z").getTime(), + client_metadata: row.clientMetadata, + client_read_only_metadata: row.clientReadOnlyMetadata, + server_metadata: row.serverMetadata, + has_password: !!passwordAuth, + otp_auth_enabled: !!otpAuth, + auth_with_email: !!passwordAuth || !!otpAuth, + requires_totp_mfa: row.requiresTotpMfa, + passkey_auth_enabled: !!passkeyAuth, + oauth_providers: row.ProjectUserOAuthAccounts.map((a: any) => ({ + id: a.oauthProviderConfigId, + account_id: a.providerAccountId, + email: a.email, + })), + selected_team_id: row.SelectedTeamMember?.teamId ?? null, + selected_team: row.SelectedTeamMember ? { + id: row.SelectedTeamMember.team.teamId, + display_name: row.SelectedTeamMember.team.displayName, + profile_image_url: row.SelectedTeamMember.team.profileImageUrl, + created_at_millis: row.SelectedTeamMember.team.createdAt.getTime(), + client_metadata: row.SelectedTeamMember.team.clientMetadata, + client_read_only_metadata: row.SelectedTeamMember.team.clientReadOnlyMetadata, + server_metadata: row.SelectedTeamMember.team.serverMetadata, + } : null, + last_active_at_millis: row.lastActiveAt ? new Date(row.lastActiveAt + "Z").getTime() : new Date(row.createdAt + "Z").getTime(), + }; + }, + }; +} + export async function getUser(options: { projectId: string, userId: string }) { + const result = await rawQuery(getUserQuery(options.projectId, options.userId)); + + // In non-prod environments, let's also call the legacy function and ensure the result is the same + // TODO next-release: remove this + if (!getNodeEnvironment().includes("prod")) { + const legacyResult = await getUserLegacy(options); + if (!deepPlainEquals(result, legacyResult)) { + throw new StackAssertionError("User result mismatch", { + result, + legacyResult, + }); + } + } + + return result; +} + +async function getUserLegacy(options: { projectId: string, userId: string }) { const [db, lastActiveAtMillis] = await Promise.all([ prismaClient.projectUser.findUnique({ where: { @@ -252,7 +416,7 @@ export async function getUser(options: { projectId: string, userId: string }) { }, include: userFullInclude, }), - getUserLastActiveAtMillis(options.userId), + getUserLastActiveAtMillis(options.projectId, options.userId), ]); if (!db) { @@ -333,7 +497,7 @@ export const usersCrudHandlers = createLazyProxy(() => createCrudHandlers(usersC } : {}, }); - const lastActiveAtMillis = await getUsersLastActiveAtMillis(db.map(user => user.projectUserId), db.map(user => user.createdAt)); + const lastActiveAtMillis = await getUsersLastActiveAtMillis(auth.project.id, db.map(user => user.projectUserId), db.map(user => user.createdAt)); return { // remove the last item because it's the next cursor items: db.map((user, index) => userPrismaToCrud(user, lastActiveAtMillis[index])).slice(0, query.limit), @@ -514,7 +678,7 @@ export const usersCrudHandlers = createLazyProxy(() => createCrudHandlers(usersC throw new StackAssertionError("User was created but not found", newUser); } - return userPrismaToCrud(user, await getUserLastActiveAtMillis(user.projectUserId) ?? user.createdAt.getTime()); + return userPrismaToCrud(user, await getUserLastActiveAtMillis(auth.project.id, user.projectUserId) ?? user.createdAt.getTime()); }); if (auth.project.config.create_team_on_sign_up) { @@ -826,7 +990,7 @@ export const usersCrudHandlers = createLazyProxy(() => createCrudHandlers(usersC }); } - return userPrismaToCrud(db, await getUserLastActiveAtMillis(params.user_id) ?? db.createdAt.getTime()); + return userPrismaToCrud(db, await getUserLastActiveAtMillis(auth.project.id, params.user_id) ?? db.createdAt.getTime()); }); diff --git a/apps/backend/src/lib/api-keys.tsx b/apps/backend/src/lib/api-keys.tsx index f21269e04..a9823a906 100644 --- a/apps/backend/src/lib/api-keys.tsx +++ b/apps/backend/src/lib/api-keys.tsx @@ -1,11 +1,12 @@ // TODO remove and replace with CRUD handler -import { prismaClient } from '@/prisma-client'; -import { ApiKeySet } from '@prisma/client'; +import { RawQuery, prismaClient, rawQuery } from '@/prisma-client'; +import { ApiKeySet, Prisma } from '@prisma/client'; import { ApiKeysCrud } from '@stackframe/stack-shared/dist/interface/crud/api-keys'; import { yupString } from '@stackframe/stack-shared/dist/schema-fields'; import { typedIncludes } from '@stackframe/stack-shared/dist/utils/arrays'; import { generateSecureRandomString } from '@stackframe/stack-shared/dist/utils/crypto'; +import { getNodeEnvironment } from '@stackframe/stack-shared/dist/utils/env'; import { StackAssertionError } from '@stackframe/stack-shared/dist/utils/errors'; import { generateUuid } from '@stackframe/stack-shared/dist/utils/uuids'; @@ -13,10 +14,48 @@ export const publishableClientKeyHeaderSchema = yupString().matches(/^[a-zA-Z0-9 export const secretServerKeyHeaderSchema = publishableClientKeyHeaderSchema; export const superSecretAdminKeyHeaderSchema = secretServerKeyHeaderSchema; -export async function checkApiKeySet( - ...args: Parameters -): Promise { - const set = await getApiKeySet(...args); +export function checkApiKeySetQuery(projectId: string, key: KeyType): RawQuery { + key = validateKeyType(key); + const keyType = Object.keys(key)[0] as keyof KeyType; + const keyValue = key[keyType]; + + const whereClause = Prisma.sql` + ${Prisma.raw(JSON.stringify(keyType))} = ${keyValue} + `; + + return { + sql: Prisma.sql` + SELECT 't' AS "result" + FROM "ApiKeySet" + WHERE ${whereClause} + AND "projectId" = ${projectId} + AND "manuallyRevokedAt" IS NULL + AND "expiresAt" > ${new Date()} + `, + postProcess: (rows) => rows[0]?.result === "t", + }; +} + +export async function checkApiKeySet(projectId: string, key: KeyType): Promise { + const result = await rawQuery(checkApiKeySetQuery(projectId, key)); + + // In non-prod environments, let's also call the legacy function and ensure the result is the same + // TODO next-release: remove this + if (!getNodeEnvironment().includes("prod")) { + const legacy = await checkApiKeySetLegacy(projectId, key); + if (legacy !== result) { + throw new StackAssertionError("checkApiKeySet result mismatch", { + result, + legacy, + }); + } + } + + return result; +} + +async function checkApiKeySetLegacy(projectId: string, key: KeyType): Promise { + const set = await getApiKeySet(projectId, key); if (!set) return false; if (set.manually_revoked_at_millis) return false; if (set.expires_at_millis < Date.now()) return false; @@ -29,7 +68,7 @@ type KeyType = | { secretServerKey: string } | { superSecretAdminKey: string }; -function assertKeyType(obj: any): KeyType { +function validateKeyType(obj: any): KeyType { if (typeof obj !== 'object' || obj === null) { throw new StackAssertionError('Invalid key type', { obj }); } @@ -64,7 +103,7 @@ export async function getApiKeySet( } } : { - ...assertKeyType(whereOrId), + ...validateKeyType(whereOrId), projectId, }; diff --git a/apps/backend/src/prisma-client.tsx b/apps/backend/src/prisma-client.tsx index 102cbaae7..64a478f18 100644 --- a/apps/backend/src/prisma-client.tsx +++ b/apps/backend/src/prisma-client.tsx @@ -1,6 +1,7 @@ import { Prisma, PrismaClient } from "@prisma/client"; import { withAccelerate } from "@prisma/extension-accelerate"; import { getEnvVariable, getNodeEnvironment } from '@stackframe/stack-shared/dist/utils/env'; +import { filterUndefined, typedFromEntries, typedKeys } from "@stackframe/stack-shared/dist/utils/objects"; import { Result } from "@stackframe/stack-shared/dist/utils/results"; import { traceSpan } from "./utils/telemetry"; @@ -39,3 +40,50 @@ export async function retryTransaction(fn: (...args: Parameters = { + sql: Prisma.Sql, + postProcess: (rows: any[]) => T, // Tip: If your postProcess is async, just set T = Promise (compared to doing Promise.all in rawQuery, this ensures that there are no accidental timing attacks) +}; + +export async function rawQuery>(query: Q): Promise>> { + const result = await rawQueryArray([query]); + return result[0]; +} + +export async function rawQueryAll>>(queries: Q): Promise<{ [K in keyof Q]: Awaited["postProcess"]>> }> { + const keys = typedKeys(filterUndefined(queries)); + const result = await rawQueryArray(keys.map(key => queries[key as any] as any)); + return typedFromEntries(keys.map((key, index) => [key, result[index]])) as any; +} + +async function rawQueryArray[]>(queries: Q): Promise<[] & { [K in keyof Q]: Awaited> }> { + if (queries.length === 0) return [] as any; + + const query = Prisma.sql` + WITH ${Prisma.join(queries.map((q, index) => { + return Prisma.sql`${Prisma.raw("q" + index)} AS ( + ${q.sql} + )`; + }), ",\n")} + + ${Prisma.join(queries.map((q, index) => { + return Prisma.sql` + SELECT + ${"q" + index} AS type, + row_to_json(c) AS json + FROM (SELECT * FROM ${Prisma.raw("q" + index)}) c + `; + }), "\nUNION ALL\n")} + `; + const rawResult = await prismaClient.$queryRaw(query) as { type: string, json: any }[]; + const unprocessed = new Array(queries.length).fill(null).map(() => [] as any[]); + for (const row of rawResult) { + const type = row.type; + const index = +type.slice(1); + unprocessed[index].push(row.json); + } + const postProcessed = queries.map((q, index) => q.postProcess(unprocessed[index])); + return postProcessed as any; +} + diff --git a/apps/backend/src/route-handlers/crud-handler.tsx b/apps/backend/src/route-handlers/crud-handler.tsx index 5ff7302dd..fc24bdc8d 100644 --- a/apps/backend/src/route-handlers/crud-handler.tsx +++ b/apps/backend/src/route-handlers/crud-handler.tsx @@ -257,7 +257,7 @@ export function createCrudHandlers< }, }); } catch (error) { - if (allowedErrorTypes?.some((a) => error instanceof a)) { + if (allowedErrorTypes?.some((a) => error instanceof a) || error instanceof StackAssertionError) { throw error; } throw new CrudHandlerInvocationError(error); diff --git a/apps/backend/src/route-handlers/smart-request.tsx b/apps/backend/src/route-handlers/smart-request.tsx index ed63d6218..9a43ecaa7 100644 --- a/apps/backend/src/route-handlers/smart-request.tsx +++ b/apps/backend/src/route-handlers/smart-request.tsx @@ -1,11 +1,11 @@ import "../polyfills"; -import { getUser } from "@/app/api/v1/users/crud"; -import { checkApiKeySet } from "@/lib/api-keys"; +import { getUser, getUserQuery } from "@/app/api/v1/users/crud"; +import { checkApiKeySet, checkApiKeySetQuery } from "@/lib/api-keys"; import { getProject, listManagedProjectIds } from "@/lib/projects"; import { decodeAccessToken } from "@/lib/tokens"; +import { rawQueryAll } from "@/prisma-client"; import { traceSpan, withTraceSpan } from "@/utils/telemetry"; -import { Span } from "@opentelemetry/api"; import { KnownErrors } from "@stackframe/stack-shared"; import { ProjectsCrud } from "@stackframe/stack-shared/dist/interface/crud/projects"; import { UsersCrud } from "@stackframe/stack-shared/dist/interface/crud/users"; @@ -139,7 +139,7 @@ async function parseBody(req: NextRequest, bodyBuffer: ArrayBuffer): Promise => { +const parseAuth = withTraceSpan('smart request parseAuth', async (req: NextRequest): Promise => { const projectId = req.headers.get("x-stack-project-id"); let requestType = req.headers.get("x-stack-access-type"); const publishableClientKey = req.headers.get("x-stack-publishable-client-key"); @@ -150,7 +150,7 @@ const parseAuth = withTraceSpan('smart request parseAuth', async (req: NextReque const refreshToken = req.headers.get("x-stack-refresh-token"); const developmentKeyOverride = req.headers.get("x-stack-development-override-key"); // in development, the internal project's API key can optionally be used to access any project - const extractUserFromAccessToken = async (options: { token: string, projectId: string }) => { + const extractUserIdFromAccessToken = async (options: { token: string, projectId: string }) => { const result = await decodeAccessToken(options.token); if (result.status === "error") { throw result.error; @@ -160,7 +160,12 @@ const parseAuth = withTraceSpan('smart request parseAuth', async (req: NextReque throw new KnownErrors.InvalidProjectForAccessToken(); } - const user = await getUser({ projectId: options.projectId, userId: result.data.userId }); + return result.data.userId; + }; + + const extractUserFromAccessToken = async (options: { token: string, projectId: string }) => { + const userId = await extractUserIdFromAccessToken(options); + const user = await getUser({ projectId: options.projectId, userId }); if (!user) { // this is the case when access token is still valid, but the user is deleted from the database throw new KnownErrors.AccessTokenExpired(); @@ -197,15 +202,20 @@ const parseAuth = withTraceSpan('smart request parseAuth', async (req: NextReque return user; }; - // Do all the requests in parallel + + // Prisma does a query for every function call by default, even if we batch them with transactions + // Because smart route handlers are always called, we instead send over a single raw query that fetches all the + // data at the same time, saving us a lot of requests + const bundledQueries = { + user: projectId && accessToken ? getUserQuery(projectId, await extractUserIdFromAccessToken({ token: accessToken, projectId })) : undefined, + isClientKeyValid: projectId && publishableClientKey && requestType === "client" ? checkApiKeySetQuery(projectId, { publishableClientKey }) : undefined, + isServerKeyValid: projectId && secretServerKey && requestType === "server" ? checkApiKeySetQuery(projectId, { secretServerKey }) : undefined, + isAdminKeyValid: projectId && superSecretAdminKey && requestType === "admin" ? checkApiKeySetQuery(projectId, { superSecretAdminKey }) : undefined, + }; + const queriesResults = await rawQueryAll(bundledQueries); const queryFuncs = { project: () => projectId ? getProject(projectId) : Promise.resolve(null), - isClientKeyValid: () => projectId && publishableClientKey && requestType === "client" ? checkApiKeySet(projectId, { publishableClientKey }) : Promise.resolve(false), - isServerKeyValid: () => projectId && secretServerKey && requestType === "server" ? checkApiKeySet(projectId, { secretServerKey }) : Promise.resolve(false), - isAdminKeyValid: () => projectId && superSecretAdminKey && requestType === "admin" ? checkApiKeySet(projectId, { superSecretAdminKey }) : Promise.resolve(false), - user: () => projectId && accessToken ? extractUserFromAccessToken({ token: accessToken, projectId }) : Promise.resolve(null), - internalUser: () => projectId && adminAccessToken ? extractUserFromAdminAccessToken({ token: adminAccessToken, projectId }) : Promise.resolve(null), } as const; const results: [string, Promise][] = []; for (const [key, func] of Object.entries(queryFuncs)) { @@ -234,30 +244,27 @@ const parseAuth = withTraceSpan('smart request parseAuth', async (req: NextReque const result = await checkApiKeySet("internal", { superSecretAdminKey: developmentKeyOverride }); if (!result) throw new StatusError(401, "Invalid development key override"); } else if (adminAccessToken) { - if (await queries.internalUser) { - if (!await queries.project) { - // this happens if the project is still in the user's managedProjectIds, but has since been deleted - throw new KnownErrors.InvalidProjectForAdminAccessToken(); - } - } else { - // This case should be prevented by checks inside extractUserFromAdminAccessToken, if this happens, something is wrong - throw new StackAssertionError("adminAccessToken exists but no internal user was found"); + // TODO put this into the bundled queries above (not so important because this path is quite rare) + const internalUser = await extractUserFromAdminAccessToken({ token: adminAccessToken, projectId }); + if (!await queries.project) { + // this happens if the project is still in the user's managedProjectIds, but has since been deleted + throw new KnownErrors.InvalidProjectForAdminAccessToken(); } } else { switch (requestType) { case "client": { if (!publishableClientKey) throw new KnownErrors.ClientAuthenticationRequired(); - if (!await queries.isClientKeyValid) throw new KnownErrors.InvalidPublishableClientKey(projectId); + if (!queriesResults.isClientKeyValid) throw new KnownErrors.InvalidPublishableClientKey(projectId); break; } case "server": { if (!secretServerKey) throw new KnownErrors.ServerAuthenticationRequired(); - if (!await queries.isServerKeyValid) throw new KnownErrors.InvalidSecretServerKey(projectId); + if (!queriesResults.isServerKeyValid) throw new KnownErrors.InvalidSecretServerKey(projectId); break; } case "admin": { if (!superSecretAdminKey) throw new KnownErrors.AdminAuthenticationRequired; - if (!await queries.isAdminKeyValid) throw new KnownErrors.InvalidSuperSecretAdminKey(projectId); + if (!queriesResults.isAdminKeyValid) throw new KnownErrors.InvalidSuperSecretAdminKey(projectId); break; } default: { @@ -273,7 +280,7 @@ const parseAuth = withTraceSpan('smart request parseAuth', async (req: NextReque return { project, - user: await queries.user ?? undefined, + user: queriesResults.user ?? undefined, type: requestType, }; }); diff --git a/apps/backend/src/utils/telemetry.tsx b/apps/backend/src/utils/telemetry.tsx index 0660c0b83..d1649ea10 100644 --- a/apps/backend/src/utils/telemetry.tsx +++ b/apps/backend/src/utils/telemetry.tsx @@ -2,9 +2,9 @@ import { AttributeValue, Span, trace } from "@opentelemetry/api"; const tracer = trace.getTracer('stack-backend'); -export function withTraceSpan

(optionsOrDescription: string | { description: string, attributes?: Record }, fn: (...args: readonly [...P, Span]) => Promise): (...args: P) => Promise { +export function withTraceSpan

(optionsOrDescription: string | { description: string, attributes?: Record }, fn: (...args: P) => Promise): (...args: P) => Promise { return async (...args: P) => { - return await traceSpan(optionsOrDescription, (span) => fn(...args, span)); + return await traceSpan(optionsOrDescription, (span) => fn(...args)); }; } diff --git a/apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/api-keys.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/api-keys.test.ts index 3627f86c1..34db29206 100644 --- a/apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/api-keys.test.ts +++ b/apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/api-keys.test.ts @@ -8,7 +8,7 @@ describe("without project access", () => { projectKeys: 'no-project' }); - it("should not have have access to api keys", async ({ expect }) => { + it("should not have access to api keys", async ({ expect }) => { const response = await niceBackendFetch("/api/v1/integrations/neon/api-keys", { accessType: "client" }); expect(response).toMatchInlineSnapshot(` NiceResponse { @@ -150,8 +150,21 @@ describe("with admin access to a non-internal project", () => { it("creates, list, updates, revokes api keys", async ({ expect }) => { const { adminAccessToken } = await Project.createAndGetAdminToken(); - const { createApiKeyResponse: response1 } = await ApiKey.create(adminAccessToken); - expect(response1).toMatchInlineSnapshot(` + const createApiKeyResponse = await niceBackendFetch("/api/v1/integrations/neon/api-keys", { + accessType: "admin", + method: "POST", + body: { + description: "test api key", + has_publishable_client_key: true, + has_secret_server_key: true, + has_super_secret_admin_key: true, + expires_at_millis: new Date().getTime() + 1000 * 60 * 60 * 24, + }, + headers: { + 'x-stack-admin-access-token': adminAccessToken, + } + }); + expect(createApiKeyResponse).toMatchInlineSnapshot(` NiceResponse { "status": 200, "body": { @@ -167,8 +180,28 @@ describe("with admin access to a non-internal project", () => { } `); + // ensure the api key works + const response1 = await niceBackendFetch(`/api/v1/users`, { + accessType: "admin", + headers: { + 'x-stack-super-secret-admin-key': createApiKeyResponse.body.super_secret_admin_key, + } + }); + expect(response1).toMatchInlineSnapshot(` + NiceResponse { + "status": 200, + "body": { + "is_paginated": true, + "items": [], + "pagination": { "next_cursor": null }, + }, + "headers": Headers {

Background services

name: "Svix server", port: 8113, importance: 1, - img: "https://yt3.googleusercontent.com/H3gTWq_CsJiRsDcscTFlqDBzZSL5_LcO7HkILLT7G701SL-XUZPYEDIjPu5cExrdP4YO0Lkt=s900-c-k-c0x00ffffff-no-rj", + img: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBpZD0iTGF5ZXJfMiIgZGF0YS1uYW1lPSJMYXllciAyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgdmlld0JveD0iODQuOSA4NC45IDM0NyAzNDciPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuY2xzLTEgewogICAgICAgIGZpbGw6ICNmZmY7CiAgICAgIH0KCiAgICAgIC5jbHMtMSwgLmNscy0yIHsKICAgICAgICBzdHJva2Utd2lkdGg6IDBweDsKICAgICAgfQoKICAgICAgLmNscy0yIHsKICAgICAgICBmaWxsOiAjMmM3MGZmOwogICAgICB9CiAgICA8L3N0eWxlPgogIDwvZGVmcz4KICA8ZyBpZD0iTGF5ZXJfMS0yIiBkYXRhLW5hbWU9IkxheWVyIDEtMiI+CiAgICA8Y2lyY2xlIGNsYXNzPSJjbHMtMSIgY3g9IjI1OC40IiBjeT0iMjU4LjQiIHI9IjE3My41Ii8+CiAgICA8Zz4KICAgICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMzYwLjgsMjMyLjljLTI4LjgtMS40LTU1LjctMTcuMi02OC4yLTQ1LTUuNS0xMi4zLTE3LjgtMjAuNC0zMS4zLTIwLjgtMjguNi0uOC00Ni43LDM1LjEtMjguNyw1Ny42LDYuMiw3LjgsMTUuNCwxMi4xLDI3LjUsMTIuOWgwYzIzLjQsMS43LDQzLjUsMTEuOCw1Ni44LDI4LjUsMjQuNCwzMC43LDIwLjksNzcuMS03LjQsMTA0LTM0LjEsMzIuNS05MS4xLDI1LjgtMTE3LjEtMTMuMi0yLjMtMy41LTQuMy03LjEtNi4xLTEwLjktNS41LTEyLjMtMTcuOC0yMC40LTMxLjMtMjAuOC0xMy45LS40LTI3LDQuOS0zNS4zLDEzLjgsMjcuMyw0Ni45LDc3LjcsNzguNywxMzUuOSw3OS43LDg4LjYsMS41LDE2MS43LTY5LDE2My4yLTE1Ny42LjMtMTQuOS0xLjYtMjkuNC01LjEtNDMuMi43LDIuOS0yMi4zLDEwLjktMjQuOCwxMS42LTkuMiwyLjctMTguNywzLjctMjgsMy4zaDBaIi8+CiAgICAgIDxwYXRoIGNsYXNzPSJjbHMtMiIgZD0iTTE1NiwyODMuNmMyOS40LjcsNTYuMSwxOC41LDY4LjIsNDUuMyw1LjUsMTIuMywxNy44LDIwLjQsMzEuMywyMC44LDkuNC4zLDE4LjMtMy4yLDI1LjItOS43LDEyLjgtMTIuMSwxNC41LTM0LjEsMy41LTQ4LTYuMi03LjgtMTUuNC0xMi4xLTI3LjUtMTIuOWgwYy0yMy40LTEuNy00My41LTExLjgtNTYuOC0yOC41LTI5LjYtMzcuMi0xNy05Mi41LDIzLjctMTE1LjQsMTEuOC02LjYsMjUuMi0xMC4yLDM4LjctOS44LDI5LjQuNyw1Ni4xLDE4LjUsNjguMiw0NS4zLDUuNSwxMi4zLDE3LjgsMjAuNCwzMS4zLDIwLjgsMTQsLjQsMjctNC45LDM1LjMtMTMuOC0yNy4zLTQ2LjktNzcuNy03OC43LTEzNS45LTc5LjgtODguNi0xLjUtMTYxLjYsNjkuMS0xNjMuMiwxNTcuNi0uMywxNC45LDEuNiwyOS40LDUuMiw0My4yLDE0LjktMTAuMSwzMy4zLTE1LjYsNTIuOS0xNS4yaDBaIi8+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4=", description: [ "Webhooks", ], @@ -244,11 +244,30 @@

Background services

name: "PgHero", port: 8116, description: [ - "For database monitoring", + "For database performance analysis", ], importance: 1, img: "https://pghero.dokkuapp.com/assets/pghero-88a0d052.png", }, + { + name: "PgAdmin", + port: 8117, + description: [ + "For database administration", + ], + importance: 1, + img: "https://www.w3schools.com/postgresql/screenshot_postgresql_pgadmin4_6.png", + }, + { + name: "Supabase Studio", + port: 8118, + path: "/project/default/editor", + description: [ + "For database administration", + ], + importance: 1, + img: "https://cdn.prod.website-files.com/655b60964be1a1b36c746790/655b60964be1a1b36c746d41_646dfce3b9c4849f6e401bff_supabase-logo-icon_1.png", + }, ]; const appsContainers = document.querySelectorAll(".apps-container"); @@ -259,7 +278,7 @@

Background services

if ((app.importance ?? 0) === importance) { // TODO escape HTML appContainer.innerHTML += ` - +
:${app.port}
diff --git a/dependencies.compose.yaml b/dependencies.compose.yaml index 14117b8cb..49d88f556 100644 --- a/dependencies.compose.yaml +++ b/dependencies.compose.yaml @@ -1,9 +1,11 @@ +version: '3.9' + services: # ================= PostgreSQL ================= db: - image: postgres:latest + build: ./docker/dependencies/postgres-with-extensions environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: PASSWORD-PLACEHOLDER--uqfEC1hmmv @@ -13,14 +15,8 @@ services: - 5432:5432 volumes: - postgres-data:/var/lib/postgresql/data - entrypoint: ["sh", "-c", " - if [ $POSTGRES_DELAY_MS -gt 0 ]; then - apt-get update && apt-get install -y iproute2 && \ - tc qdisc add dev eth0 root netem delay ${POSTGRES_DELAY_MS}ms; - fi; - exec docker-entrypoint.sh postgres -c shared_preload_libraries='pg_stat_statements' -c pg_stat_statements.track=all"] cap_add: - - NET_ADMIN + - NET_ADMIN # TODO why do we need this? is it for the delay? # ================= PgHero ================= @@ -31,6 +27,60 @@ services: ports: - 8116:8080 + # ================= PgAdmin ================= + + pgadmin: + image: dpage/pgadmin4 + environment: + PGADMIN_DEFAULT_EMAIL: admin@example.com + PGADMIN_DEFAULT_PASSWORD: PASSWORD-PLACEHOLDER--vu9p2iy3f + PGADMIN_CONFIG_SERVER_MODE: "False" + PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: "False" + configs: + - source: pgadmin_servers + target: /pgadmin4/servers.json + ports: + - 8117:80 + + # ================= Supabase Studio ================= + + supabase-studio: + image: supabase/studio:20241202-71e5240 + restart: unless-stopped + healthcheck: + test: + [ + "CMD", + "node", + "-e", + "fetch('http://studio:3000/api/profile').then((r) => {if (r.status !== 200) throw new Error(r.status)})" + ] + timeout: 10s + interval: 5s + retries: 3 + environment: + STUDIO_PG_META_URL: http://supabase-meta:8080 + POSTGRES_PASSWORD: PASSWORD-PLACEHOLDER--uqfEC1hmmv + + OPENAI_API_KEY: ${OPENAI_API_KEY:-} + + NEXT_PUBLIC_ENABLE_LOGS: true + NEXT_ANALYTICS_BACKEND_PROVIDER: postgres + ports: + - 8118:3000 + + supabase-meta: + image: supabase/postgres-meta:v0.84.2 + restart: unless-stopped + environment: + PG_META_PORT: 8080 + PG_META_DB_HOST: db + PG_META_DB_PORT: 5432 + PG_META_DB_NAME: stackframe + PG_META_DB_USER: postgres + PG_META_DB_PASSWORD: PASSWORD-PLACEHOLDER--uqfEC1hmmv + + # ================= Inbucket ================= inbucket: @@ -94,3 +144,21 @@ volumes: svix-redis-data: svix-postgres-data: +# ================= configs ================= + +configs: + pgadmin_servers: + content: | + { + "Servers": { + "1": { + "Name": "Local Postgres DB", + "Group": "Servers", + "Host": "db", + "Port": 5432, + "Username": "postgres", + "PasswordExecCommand": "echo 'PASSWORD-PLACEHOLDER--uqfEC1hmmv'", + "MaintenanceDB": "stackframe" + } + } + } diff --git a/docker/dependencies/postgres-with-extensions/Dockerfile b/docker/dependencies/postgres-with-extensions/Dockerfile new file mode 100644 index 000000000..7f7404938 --- /dev/null +++ b/docker/dependencies/postgres-with-extensions/Dockerfile @@ -0,0 +1,31 @@ +FROM postgres:15 + +RUN apt-get update && apt-get install -y \ + git \ + build-essential \ + libpq-dev \ + postgresql-server-dev-15 + +# Install HypoPG +RUN git clone https://github.com/HypoPG/hypopg.git /hypopg +RUN cd /hypopg && make install + +# Install index_advisor +RUN git clone https://github.com/supabase/index_advisor.git /index_advisor +RUN cd /index_advisor && make install + +# Write initialization SQL +RUN echo "CREATE EXTENSION pg_stat_statements;" >> /docker-entrypoint-initdb.d/init.sql +RUN echo "CREATE EXTENSION hypopg;" >> /docker-entrypoint-initdb.d/init.sql +RUN echo "CREATE EXTENSION index_advisor;" >> /docker-entrypoint-initdb.d/init.sql + +# Add args to Postgres entrypoint +ENTRYPOINT ["sh", "-c", "\ + # Add delay if POSTGRES_DELAY_MS is set \ + if [ $POSTGRES_DELAY_MS -gt 0 ]; then \ + apt-get update && apt-get install -y iproute2 && tc qdisc add dev eth0 root netem delay ${POSTGRES_DELAY_MS}ms; \ + fi; \ + \ + # Start Postgres with extensions enabled \ + exec docker-entrypoint.sh postgres -c shared_preload_libraries='pg_stat_statements' -c shared_preload_libraries='hypopg' -c pg_stat_statements.track=all \ +"] diff --git a/package.json b/package.json index 76bcf5a7a..51a5c10d8 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "deps-compose": "docker compose -f dependencies.compose.yaml", "stop-deps": "POSTGRES_DELAY_MS=0 pnpm run deps-compose kill && POSTGRES_DELAY_MS=0 pnpm run deps-compose down -v", "init-db": "pnpm run prisma db push && pnpm run prisma db seed", - "start-deps:no-delay": "pnpm run deps-compose up --detach && sleep 5 && pnpm run init-db && echo \"\\nDependencies started in the background as Docker containers. 'pnpm run stop-deps' to stop them\"n", + "start-deps:no-delay": "pnpm run deps-compose up --detach --build && sleep 5 && pnpm run init-db && echo \"\\nDependencies started in the background as Docker containers. 'pnpm run stop-deps' to stop them\"n", "start-deps": "POSTGRES_DELAY_MS=${POSTGRES_DELAY_MS:-20} pnpm run start-deps:no-delay", "restart-deps": "pnpm run stop-deps && pnpm run start-deps", "restart-deps:no-delay": "pnpm run stop-deps && pnpm run start-deps:no-delay", From f5539c1a32de10a1d3b910b46aa254106ea4c283 Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Sat, 28 Dec 2024 21:40:26 -0800 Subject: [PATCH 11/23] Supabase Studio index advisor --- dependencies.compose.yaml | 2 -- docker/dependencies/postgres-with-extensions/Dockerfile | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/dependencies.compose.yaml b/dependencies.compose.yaml index 49d88f556..27ad69055 100644 --- a/dependencies.compose.yaml +++ b/dependencies.compose.yaml @@ -1,5 +1,3 @@ -version: '3.9' - services: # ================= PostgreSQL ================= diff --git a/docker/dependencies/postgres-with-extensions/Dockerfile b/docker/dependencies/postgres-with-extensions/Dockerfile index 7f7404938..e26193e96 100644 --- a/docker/dependencies/postgres-with-extensions/Dockerfile +++ b/docker/dependencies/postgres-with-extensions/Dockerfile @@ -27,5 +27,5 @@ ENTRYPOINT ["sh", "-c", "\ fi; \ \ # Start Postgres with extensions enabled \ - exec docker-entrypoint.sh postgres -c shared_preload_libraries='pg_stat_statements' -c shared_preload_libraries='hypopg' -c pg_stat_statements.track=all \ + exec docker-entrypoint.sh postgres -c shared_preload_libraries='pg_stat_statements' -c pg_stat_statements.track=all \ "] From cc8aebea04ec4c76f502c6fc571caa1eab02aa88 Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Sat, 28 Dec 2024 21:42:14 -0800 Subject: [PATCH 12/23] Remove unnecessary log statement --- apps/e2e/tests/backend/backend-helpers.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/e2e/tests/backend/backend-helpers.ts b/apps/e2e/tests/backend/backend-helpers.ts index 20c5c6c78..978fc1a14 100644 --- a/apps/e2e/tests/backend/backend-helpers.ts +++ b/apps/e2e/tests/backend/backend-helpers.ts @@ -176,7 +176,6 @@ export namespace Auth { const accessToken = backendContext.value.userAuth?.accessToken; if (accessToken) { const aud = jose.decodeJwt(accessToken).aud; - console.log(accessToken, jose.decodeJwt(accessToken)); const jwks = jose.createRemoteJWKSet(new URL(`api/v1/projects/${aud}/.well-known/jwks.json`, STACK_BACKEND_BASE_URL)); const { payload } = await jose.jwtVerify(accessToken, jwks); expect(payload).toEqual({ From 6b0353635fbc703fd60e33def0a1427f4198f224 Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Sat, 28 Dec 2024 21:42:36 -0800 Subject: [PATCH 13/23] Remove verify-data-integrity test --- .../tests/general/verify-data-integrity.test.ts | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 apps/e2e/tests/general/verify-data-integrity.test.ts diff --git a/apps/e2e/tests/general/verify-data-integrity.test.ts b/apps/e2e/tests/general/verify-data-integrity.test.ts deleted file mode 100644 index a6e86e0c2..000000000 --- a/apps/e2e/tests/general/verify-data-integrity.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { exec } from "child_process"; -import { describe } from "vitest"; -import { it } from "../helpers"; - -describe("Data integrity verification", () => { - it("completes successfully", async ({ expect }) => { - const [error, stdout, stderr] = await new Promise<[Error | null, string, string]>((resolve) => { - exec("pnpm run verify-data-integrity", (error, stdout, stderr) => { - resolve([error, stdout, stderr]); - }); - }); - expect(error, `Expected no error to be thrown!\n\n\n\nstdout: ${stdout}\n\n\n\nstderr: ${stderr}`).toBeNull(); - }, 120_000); -}); From e25394bb62df73014902bcdfe6525329b47b0f65 Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Sun, 29 Dec 2024 11:34:04 -0800 Subject: [PATCH 14/23] Wrap API page main in
--- apps/backend/src/app/page.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/backend/src/app/page.tsx b/apps/backend/src/app/page.tsx index 942b29afc..613f0fdfb 100644 --- a/apps/backend/src/app/page.tsx +++ b/apps/backend/src/app/page.tsx @@ -1,13 +1,15 @@ import Link from "next/link"; export default function Home() { - return <> + return ( +
Welcome to Stack Auth's API endpoint.
-
+
Were you looking for Stack's dashboard instead?
-
- You can also return to https://stack-auth.com.
-
- API v1
- ; +
+ You can also return to https://stack-auth.com.
+
+ API v1
+
+ ); } From 2b09cfb841b7aae05327aaf0d16f8f459ffc6853 Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Sun, 29 Dec 2024 11:34:14 -0800 Subject: [PATCH 15/23] Fix indentation --- apps/backend/src/app/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/backend/src/app/page.tsx b/apps/backend/src/app/page.tsx index 613f0fdfb..ec24a3b74 100644 --- a/apps/backend/src/app/page.tsx +++ b/apps/backend/src/app/page.tsx @@ -3,9 +3,9 @@ import Link from "next/link"; export default function Home() { return (
- Welcome to Stack Auth's API endpoint.
+ Welcome to Stack Auth's API endpoint.

- Were you looking for Stack's dashboard instead?
+ Were you looking for Stack's dashboard instead?

You can also return to https://stack-auth.com.

From 6e0d6b9890207a77a4244d2f294f5763046796cb Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Sun, 29 Dec 2024 12:20:35 -0800 Subject: [PATCH 16/23] Better support for Supabase Studio index advisor --- apps/backend/src/prisma-client.tsx | 9 ++++++++- docker/dependencies/postgres-with-extensions/Dockerfile | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/backend/src/prisma-client.tsx b/apps/backend/src/prisma-client.tsx index 3167a6aec..e1703250b 100644 --- a/apps/backend/src/prisma-client.tsx +++ b/apps/backend/src/prisma-client.tsx @@ -67,7 +67,9 @@ async function rawQueryArray[]>(queries: Q): Promise<[] }, async () => { if (queries.length === 0) return [] as any; - const query = Prisma.sql` + // Prisma does a query for every rawQuery call by default, even if we batch them with transactions + // So, instead we combine all queries into one using WITH, and then return them as a single JSON result + const withQuery = Prisma.sql` WITH ${Prisma.join(queries.map((q, index) => { return Prisma.sql`${Prisma.raw("q" + index)} AS ( ${q.sql} @@ -83,6 +85,11 @@ async function rawQueryArray[]>(queries: Q): Promise<[] `; }), "\nUNION ALL\n")} `; + + // Supabase's index advisor only analyzes rows that start with "SELECT" (for some reason) + // Since ours starts with "WITH", we prepend a SELECT to it + const query = Prisma.sql`SELECT * FROM (${withQuery}) AS _`; + const rawResult = await prismaClient.$queryRaw(query) as { type: string, json: any }[]; const unprocessed = new Array(queries.length).fill(null).map(() => [] as any[]); for (const row of rawResult) { diff --git a/docker/dependencies/postgres-with-extensions/Dockerfile b/docker/dependencies/postgres-with-extensions/Dockerfile index e26193e96..b81ab1de5 100644 --- a/docker/dependencies/postgres-with-extensions/Dockerfile +++ b/docker/dependencies/postgres-with-extensions/Dockerfile @@ -18,6 +18,8 @@ RUN cd /index_advisor && make install RUN echo "CREATE EXTENSION pg_stat_statements;" >> /docker-entrypoint-initdb.d/init.sql RUN echo "CREATE EXTENSION hypopg;" >> /docker-entrypoint-initdb.d/init.sql RUN echo "CREATE EXTENSION index_advisor;" >> /docker-entrypoint-initdb.d/init.sql +RUN echo "CREATE ROLE anon;" >> /docker-entrypoint-initdb.d/init.sql +RUN echo "CREATE ROLE authenticated;" >> /docker-entrypoint-initdb.d/init.sql # Add args to Postgres entrypoint ENTRYPOINT ["sh", "-c", "\ From 4acc91fc290111e5cf64433866327b6a724187b4 Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Sun, 29 Dec 2024 12:28:53 -0800 Subject: [PATCH 17/23] chore: update package versions --- apps/backend/CHANGELOG.md | 9 +++++++++ apps/backend/package.json | 2 +- apps/dashboard/CHANGELOG.md | 11 +++++++++++ apps/dashboard/package.json | 2 +- apps/dev-launchpad/CHANGELOG.md | 6 ++++++ apps/dev-launchpad/package.json | 2 +- apps/e2e/CHANGELOG.md | 8 ++++++++ apps/e2e/package.json | 2 +- apps/oauth-mock-server/CHANGELOG.md | 7 +++++++ apps/oauth-mock-server/package.json | 2 +- docs/CHANGELOG.md | 2 ++ docs/package.json | 2 +- examples/cjs-test/CHANGELOG.md | 7 +++++++ examples/cjs-test/package.json | 2 +- examples/demo/CHANGELOG.md | 9 +++++++++ examples/demo/package.json | 2 +- examples/docs-examples/CHANGELOG.md | 9 +++++++++ examples/docs-examples/package.json | 2 +- examples/e-commerce/CHANGELOG.md | 7 +++++++ examples/e-commerce/package.json | 2 +- examples/middleware/CHANGELOG.md | 7 +++++++ examples/middleware/package.json | 2 +- examples/partial-prerendering/CHANGELOG.md | 7 +++++++ examples/partial-prerendering/package.json | 2 +- examples/supabase/CHANGELOG.md | 7 +++++++ examples/supabase/package.json | 2 +- packages/init-stack/CHANGELOG.md | 2 ++ packages/init-stack/package.json | 2 +- packages/stack-emails/CHANGELOG.md | 8 ++++++++ packages/stack-emails/package.json | 2 +- packages/stack-sc/CHANGELOG.md | 2 ++ packages/stack-sc/package.json | 2 +- packages/stack-shared/CHANGELOG.md | 7 +++++++ packages/stack-shared/package.json | 2 +- packages/stack-ui/CHANGELOG.md | 7 +++++++ packages/stack-ui/package.json | 2 +- packages/stack/CHANGELOG.md | 10 ++++++++++ packages/stack/package.json | 2 +- 38 files changed, 151 insertions(+), 19 deletions(-) diff --git a/apps/backend/CHANGELOG.md b/apps/backend/CHANGELOG.md index dceecbff0..4e81572a6 100644 --- a/apps/backend/CHANGELOG.md +++ b/apps/backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @stackframe/stack-backend +## 2.7.3 + +### Patch Changes + +- Various changes +- Updated dependencies + - @stackframe/stack-shared@2.7.3 + - @stackframe/stack-emails@2.7.3 + ## 2.7.2 ### Patch Changes diff --git a/apps/backend/package.json b/apps/backend/package.json index 40c39fe7d..9924ca09e 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/stack-backend", - "version": "2.7.2", + "version": "2.7.3", "private": true, "scripts": { "clean": "rimraf .next && rimraf node_modules", diff --git a/apps/dashboard/CHANGELOG.md b/apps/dashboard/CHANGELOG.md index 73a485875..4d4194762 100644 --- a/apps/dashboard/CHANGELOG.md +++ b/apps/dashboard/CHANGELOG.md @@ -1,5 +1,16 @@ # @stackframe/stack-dashboard +## 2.7.3 + +### Patch Changes + +- Various changes +- Updated dependencies + - @stackframe/stack-shared@2.7.3 + - @stackframe/stack@2.7.3 + - @stackframe/stack-emails@2.7.3 + - @stackframe/stack-ui@2.7.3 + ## 2.7.2 ### Patch Changes diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index 1c5b745db..23c142a6e 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/stack-dashboard", - "version": "2.7.2", + "version": "2.7.3", "private": true, "scripts": { "clean": "rimraf .next && rimraf node_modules", diff --git a/apps/dev-launchpad/CHANGELOG.md b/apps/dev-launchpad/CHANGELOG.md index 65a3e821a..eaf1e4a7b 100644 --- a/apps/dev-launchpad/CHANGELOG.md +++ b/apps/dev-launchpad/CHANGELOG.md @@ -1,5 +1,11 @@ # @stackframe/dev-launchpad +## 2.7.3 + +### Patch Changes + +- Various changes + ## 2.7.2 ## 2.7.1 diff --git a/apps/dev-launchpad/package.json b/apps/dev-launchpad/package.json index bc9ed2662..4adc8d597 100644 --- a/apps/dev-launchpad/package.json +++ b/apps/dev-launchpad/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/dev-launchpad", - "version": "2.7.2", + "version": "2.7.3", "private": true, "scripts": { "dev": "serve -p 8100 -s public" diff --git a/apps/e2e/CHANGELOG.md b/apps/e2e/CHANGELOG.md index 1a890a335..72662c45e 100644 --- a/apps/e2e/CHANGELOG.md +++ b/apps/e2e/CHANGELOG.md @@ -1,5 +1,13 @@ # @stackframe/e2e-tests +## 2.7.3 + +### Patch Changes + +- Various changes +- Updated dependencies + - @stackframe/stack-shared@2.7.3 + ## 2.7.2 ### Patch Changes diff --git a/apps/e2e/package.json b/apps/e2e/package.json index 5c809b8ec..bed96264d 100644 --- a/apps/e2e/package.json +++ b/apps/e2e/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/e2e-tests", - "version": "2.7.2", + "version": "2.7.3", "private": true, "type": "module", "scripts": { diff --git a/apps/oauth-mock-server/CHANGELOG.md b/apps/oauth-mock-server/CHANGELOG.md index 5fca4320d..053f26965 100644 --- a/apps/oauth-mock-server/CHANGELOG.md +++ b/apps/oauth-mock-server/CHANGELOG.md @@ -1,5 +1,12 @@ # @stackframe/oauth-mock-server +## 2.7.3 + +### Patch Changes + +- Updated dependencies + - @stackframe/stack-shared@2.7.3 + ## 2.7.2 ### Patch Changes diff --git a/apps/oauth-mock-server/package.json b/apps/oauth-mock-server/package.json index 8ec7b8b43..397dc5ebc 100644 --- a/apps/oauth-mock-server/package.json +++ b/apps/oauth-mock-server/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/oauth-mock-server", - "version": "2.7.2", + "version": "2.7.3", "private": true, "main": "index.js", "scripts": { diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index b71f65110..2eecb7960 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,7 @@ # @stackframe/docs +## 2.7.3 + ## 2.7.2 ## 2.7.1 diff --git a/docs/package.json b/docs/package.json index c001a8731..97a67e977 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/docs", - "version": "2.7.2", + "version": "2.7.3", "description": "", "main": "index.js", "private": true, diff --git a/examples/cjs-test/CHANGELOG.md b/examples/cjs-test/CHANGELOG.md index abd31b891..405a830cd 100644 --- a/examples/cjs-test/CHANGELOG.md +++ b/examples/cjs-test/CHANGELOG.md @@ -1,5 +1,12 @@ # @stackframe/example-cjs-test +## 2.7.3 + +### Patch Changes + +- Updated dependencies + - @stackframe/stack@2.7.3 + ## 2.7.2 ### Patch Changes diff --git a/examples/cjs-test/package.json b/examples/cjs-test/package.json index 2e82fecb1..c607c6982 100644 --- a/examples/cjs-test/package.json +++ b/examples/cjs-test/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/example-cjs-test", - "version": "2.7.2", + "version": "2.7.3", "private": true, "scripts": { "dev": "next dev --port 8110", diff --git a/examples/demo/CHANGELOG.md b/examples/demo/CHANGELOG.md index af72ea34d..1011b9eaa 100644 --- a/examples/demo/CHANGELOG.md +++ b/examples/demo/CHANGELOG.md @@ -1,5 +1,14 @@ # @stackframe/example-demo-app +## 2.7.3 + +### Patch Changes + +- Updated dependencies + - @stackframe/stack-shared@2.7.3 + - @stackframe/stack@2.7.3 + - @stackframe/stack-ui@2.7.3 + ## 2.7.2 ### Patch Changes diff --git a/examples/demo/package.json b/examples/demo/package.json index ce49accc8..1f4e920bf 100644 --- a/examples/demo/package.json +++ b/examples/demo/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/example-demo-app", - "version": "2.7.2", + "version": "2.7.3", "description": "", "private": true, "scripts": { diff --git a/examples/docs-examples/CHANGELOG.md b/examples/docs-examples/CHANGELOG.md index 5e48fc29a..b2ec1fab1 100644 --- a/examples/docs-examples/CHANGELOG.md +++ b/examples/docs-examples/CHANGELOG.md @@ -1,5 +1,14 @@ # @stackframe/docs-examples +## 2.7.3 + +### Patch Changes + +- Updated dependencies + - @stackframe/stack-shared@2.7.3 + - @stackframe/stack@2.7.3 + - @stackframe/stack-ui@2.7.3 + ## 2.7.2 ### Patch Changes diff --git a/examples/docs-examples/package.json b/examples/docs-examples/package.json index 1a7ea505e..5343c3567 100644 --- a/examples/docs-examples/package.json +++ b/examples/docs-examples/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/docs-examples", - "version": "2.7.2", + "version": "2.7.3", "description": "", "private": true, "scripts": { diff --git a/examples/e-commerce/CHANGELOG.md b/examples/e-commerce/CHANGELOG.md index d4da28057..5bae748de 100644 --- a/examples/e-commerce/CHANGELOG.md +++ b/examples/e-commerce/CHANGELOG.md @@ -1,5 +1,12 @@ # @stackframe/e-commerce-demo +## 2.7.3 + +### Patch Changes + +- Updated dependencies + - @stackframe/stack@2.7.3 + ## 2.7.2 ### Patch Changes diff --git a/examples/e-commerce/package.json b/examples/e-commerce/package.json index a922c74aa..dfdab00b6 100644 --- a/examples/e-commerce/package.json +++ b/examples/e-commerce/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/e-commerce-demo", - "version": "2.7.2", + "version": "2.7.3", "private": true, "scripts": { "dev": "next dev --port 8111", diff --git a/examples/middleware/CHANGELOG.md b/examples/middleware/CHANGELOG.md index 8fca010e7..666ff268f 100644 --- a/examples/middleware/CHANGELOG.md +++ b/examples/middleware/CHANGELOG.md @@ -1,5 +1,12 @@ # @stackframe/example-middleware-demo +## 2.7.3 + +### Patch Changes + +- Updated dependencies + - @stackframe/stack@2.7.3 + ## 2.7.2 ### Patch Changes diff --git a/examples/middleware/package.json b/examples/middleware/package.json index 61aa3fae4..04a4cd876 100644 --- a/examples/middleware/package.json +++ b/examples/middleware/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/example-middleware-demo", - "version": "2.7.2", + "version": "2.7.3", "private": true, "scripts": { "dev": "next dev --port 8112", diff --git a/examples/partial-prerendering/CHANGELOG.md b/examples/partial-prerendering/CHANGELOG.md index 32decc1f5..e33250ad5 100644 --- a/examples/partial-prerendering/CHANGELOG.md +++ b/examples/partial-prerendering/CHANGELOG.md @@ -1,5 +1,12 @@ # @stackframe/example-partial-prerendering +## 2.7.3 + +### Patch Changes + +- Updated dependencies + - @stackframe/stack@2.7.3 + ## 2.7.2 ### Patch Changes diff --git a/examples/partial-prerendering/package.json b/examples/partial-prerendering/package.json index d1a619e41..ad23a7f42 100644 --- a/examples/partial-prerendering/package.json +++ b/examples/partial-prerendering/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/example-partial-prerendering", - "version": "2.7.2", + "version": "2.7.3", "private": true, "scripts": { "dev": "next dev --port 8109", diff --git a/examples/supabase/CHANGELOG.md b/examples/supabase/CHANGELOG.md index 5360ffbdf..2732f9253 100644 --- a/examples/supabase/CHANGELOG.md +++ b/examples/supabase/CHANGELOG.md @@ -1,5 +1,12 @@ # @stackframe/example-supabase +## 2.7.3 + +### Patch Changes + +- Updated dependencies + - @stackframe/stack@2.7.3 + ## 2.7.2 ### Patch Changes diff --git a/examples/supabase/package.json b/examples/supabase/package.json index 1f7bafd01..c4cd20502 100644 --- a/examples/supabase/package.json +++ b/examples/supabase/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/example-supabase", - "version": "2.7.2", + "version": "2.7.3", "private": true, "scripts": { "dev": "next dev --turbo --port 8115", diff --git a/packages/init-stack/CHANGELOG.md b/packages/init-stack/CHANGELOG.md index dc1351835..be52f0e0e 100644 --- a/packages/init-stack/CHANGELOG.md +++ b/packages/init-stack/CHANGELOG.md @@ -1,5 +1,7 @@ # @stackframe/init-stack +## 2.7.3 + ## 2.7.2 ## 2.7.1 diff --git a/packages/init-stack/package.json b/packages/init-stack/package.json index b1004a828..f4d86d724 100644 --- a/packages/init-stack/package.json +++ b/packages/init-stack/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/init-stack", - "version": "2.7.2", + "version": "2.7.3", "description": "The setup wizard for Stack. https://stack-auth.com", "main": "index.mjs", "bin": "./index.mjs", diff --git a/packages/stack-emails/CHANGELOG.md b/packages/stack-emails/CHANGELOG.md index 31b7ab2fc..1eca73d2b 100644 --- a/packages/stack-emails/CHANGELOG.md +++ b/packages/stack-emails/CHANGELOG.md @@ -1,5 +1,13 @@ # @stackframe/stack-emails +## 2.7.3 + +### Patch Changes + +- Updated dependencies + - @stackframe/stack-shared@2.7.3 + - @stackframe/stack-ui@2.7.3 + ## 2.7.2 ### Patch Changes diff --git a/packages/stack-emails/package.json b/packages/stack-emails/package.json index 62a9eac1c..b3056a0bf 100644 --- a/packages/stack-emails/package.json +++ b/packages/stack-emails/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/stack-emails", - "version": "2.7.2", + "version": "2.7.3", "main": "./dist/index.js", "types": "./dist/index.d.ts", "private": true, diff --git a/packages/stack-sc/CHANGELOG.md b/packages/stack-sc/CHANGELOG.md index 184c02af2..de628667e 100644 --- a/packages/stack-sc/CHANGELOG.md +++ b/packages/stack-sc/CHANGELOG.md @@ -1,5 +1,7 @@ # @stackframe/stack-sc +## 2.7.3 + ## 2.7.2 ## 2.7.1 diff --git a/packages/stack-sc/package.json b/packages/stack-sc/package.json index 2b1c242f6..3abe979eb 100644 --- a/packages/stack-sc/package.json +++ b/packages/stack-sc/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/stack-sc", - "version": "2.7.2", + "version": "2.7.3", "exports": { "./force-react-server": { "types": "./dist/index.react-server.d.ts", diff --git a/packages/stack-shared/CHANGELOG.md b/packages/stack-shared/CHANGELOG.md index 379ea4d89..8a9fa3918 100644 --- a/packages/stack-shared/CHANGELOG.md +++ b/packages/stack-shared/CHANGELOG.md @@ -1,5 +1,12 @@ # @stackframe/stack-shared +## 2.7.3 + +### Patch Changes + +- Various changes + - @stackframe/stack-sc@2.7.3 + ## 2.7.2 ### Patch Changes diff --git a/packages/stack-shared/package.json b/packages/stack-shared/package.json index c16c9507b..276dc4b5d 100644 --- a/packages/stack-shared/package.json +++ b/packages/stack-shared/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/stack-shared", - "version": "2.7.2", + "version": "2.7.3", "main": "./dist/index.js", "types": "./dist/index.d.ts", "scripts": { diff --git a/packages/stack-ui/CHANGELOG.md b/packages/stack-ui/CHANGELOG.md index 6e0a83fec..672e6f8b4 100644 --- a/packages/stack-ui/CHANGELOG.md +++ b/packages/stack-ui/CHANGELOG.md @@ -1,5 +1,12 @@ # @stackframe/stack-ui +## 2.7.3 + +### Patch Changes + +- Updated dependencies + - @stackframe/stack-shared@2.7.3 + ## 2.7.2 ### Patch Changes diff --git a/packages/stack-ui/package.json b/packages/stack-ui/package.json index 9b0cc56ce..ae955ad23 100644 --- a/packages/stack-ui/package.json +++ b/packages/stack-ui/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/stack-ui", - "version": "2.7.2", + "version": "2.7.3", "main": "./dist/index.js", "types": "./dist/index.d.ts", "sideEffects": false, diff --git a/packages/stack/CHANGELOG.md b/packages/stack/CHANGELOG.md index 1af38ec20..811baeeb8 100644 --- a/packages/stack/CHANGELOG.md +++ b/packages/stack/CHANGELOG.md @@ -1,5 +1,15 @@ # @stackframe/stack +## 2.7.3 + +### Patch Changes + +- Various changes +- Updated dependencies + - @stackframe/stack-shared@2.7.3 + - @stackframe/stack-ui@2.7.3 + - @stackframe/stack-sc@2.7.3 + ## 2.7.2 ### Patch Changes diff --git a/packages/stack/package.json b/packages/stack/package.json index 9161e070b..b621bb3fa 100644 --- a/packages/stack/package.json +++ b/packages/stack/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/stack", - "version": "2.7.2", + "version": "2.7.3", "sideEffects": false, "main": "./dist/index.js", "types": "./dist/index.d.ts", From dc91c259374d9993f829fc0817420c54099cad08 Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Sun, 29 Dec 2024 19:07:35 -0800 Subject: [PATCH 18/23] Improve metrics page chart sizing on Safari --- .gitignore | 3 +++ .../projects/[projectId]/metrics/line-chart.tsx | 4 ++-- .../projects/[projectId]/metrics/page-client.tsx | 6 ++++++ apps/dashboard/src/components/ui/chart.tsx | 15 ++++++++++++--- packages/stack-ui/src/components/ui/table.tsx | 13 ++++--------- 5 files changed, 27 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index b6e934f7c..7a4662cdb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,9 @@ *.untracked.* +*.cpuprofile + + .vercel diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/metrics/line-chart.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/metrics/line-chart.tsx index 4f25d1b51..f0d00a40e 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/metrics/line-chart.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/metrics/line-chart.tsx @@ -31,7 +31,7 @@ export function LineChartDisplay({ - + - + Recent Sign Ups + {data.recently_registered.length === 0 && ( + No recent sign ups + )} {data.recently_registered.map((user: any) => @@ -77,6 +80,9 @@ export default function PageClient() { Recently Active Users + {data.recently_active.length === 0 && ( + No recent active users + )}
{data.recently_active.map((user: any) => diff --git a/apps/dashboard/src/components/ui/chart.tsx b/apps/dashboard/src/components/ui/chart.tsx index 214070dff..f78c9ce42 100644 --- a/apps/dashboard/src/components/ui/chart.tsx +++ b/apps/dashboard/src/components/ui/chart.tsx @@ -37,12 +37,17 @@ function useChart() { const ChartContainer = React.forwardRef< HTMLDivElement, React.ComponentProps<"div"> & { + /** + * Some browsers follow the spec more closely and `height: 100%` does not inherit max-height (eg. Safari), so we + * need to set the maxHeight for both the outer container and the ResponsiveContainer. + */ + maxHeight?: number, config: ChartConfig, children: React.ComponentProps< typeof RechartsPrimitive.ResponsiveContainer >["children"], } ->(({ id, className, children, config, ...props }, ref) => { +>(({ id, className, children, config, maxHeight, ...props }, ref) => { const uniqueId = React.useId(); const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`; @@ -56,16 +61,20 @@ const ChartContainer = React.forwardRef< className )} {...props} + style={{ + ...props.style, + maxHeight, + }} > - + {children} ); }); -ChartContainer.displayName = "Chart"; +ChartContainer.displayName = "ChartContainer"; const ChartStyle = ({ id, config }: { id: string, config: ChartConfig }) => { const colorConfig = Object.entries(config).filter( diff --git a/packages/stack-ui/src/components/ui/table.tsx b/packages/stack-ui/src/components/ui/table.tsx index 85d24bdc7..69c0deec2 100644 --- a/packages/stack-ui/src/components/ui/table.tsx +++ b/packages/stack-ui/src/components/ui/table.tsx @@ -1,5 +1,5 @@ -import React from "react"; import { forwardRefIfNeeded } from "@stackframe/stack-shared/dist/utils/react"; +import React from "react"; import { cn } from "../../lib/utils"; @@ -111,12 +111,7 @@ const TableCaption = forwardRefIfNeeded< TableCaption.displayName = "TableCaption"; export { - Table, - TableHeader, - TableBody, - TableFooter, - TableHead, - TableRow, - TableCell, - TableCaption, + Table, TableBody, TableCaption, TableCell, TableFooter, + TableHead, TableHeader, TableRow }; + From f11bd9ec49dd32877d8f8edc70ab3eec435815cf Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Sun, 29 Dec 2024 20:22:34 -0800 Subject: [PATCH 19/23] Make onboarding dialog non-blocking --- .../(main)/(protected)/projects/[projectId]/layout.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/layout.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/layout.tsx index b1ad8b73d..b37f146c1 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/layout.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/layout.tsx @@ -1,11 +1,15 @@ -import { AdminAppProvider } from "./use-admin-app"; +import { Suspense } from "react"; import { OnboardingDialog } from "./onboarding-dialog"; import SidebarLayout from "./sidebar-layout"; +import { AdminAppProvider } from "./use-admin-app"; export default function Layout(props: { children: React.ReactNode, params: { projectId: string } }) { return ( - + {/* Don't block the rest of the page for the dialog, so wrap it with a Suspense */} + }> + + {props.children} From c1a2c9da89030d74a3ba41fc8f3e7ebaf13fa830 Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Mon, 30 Dec 2024 16:08:42 -0800 Subject: [PATCH 20/23] Move metrics page to Overview --- .../country-data.geo.json | 0 .../country-data.original.geo.json | 0 .../{metrics => (metrics)}/globe.tsx | 0 .../{metrics => (metrics)}/line-chart.tsx | 0 .../{metrics => (metrics)}/page-client.tsx | 0 .../{metrics => (metrics)}/page.tsx | 0 .../(protected)/projects/[projectId]/page.tsx | 5 ----- .../projects/[projectId]/sidebar-layout.tsx | 18 +++++++++--------- 8 files changed, 9 insertions(+), 14 deletions(-) rename apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/{metrics => (metrics)}/country-data.geo.json (100%) rename apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/{metrics => (metrics)}/country-data.original.geo.json (100%) rename apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/{metrics => (metrics)}/globe.tsx (100%) rename apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/{metrics => (metrics)}/line-chart.tsx (100%) rename apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/{metrics => (metrics)}/page-client.tsx (100%) rename apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/{metrics => (metrics)}/page.tsx (100%) delete mode 100644 apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/page.tsx diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/metrics/country-data.geo.json b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(metrics)/country-data.geo.json similarity index 100% rename from apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/metrics/country-data.geo.json rename to apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(metrics)/country-data.geo.json diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/metrics/country-data.original.geo.json b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(metrics)/country-data.original.geo.json similarity index 100% rename from apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/metrics/country-data.original.geo.json rename to apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(metrics)/country-data.original.geo.json diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/metrics/globe.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(metrics)/globe.tsx similarity index 100% rename from apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/metrics/globe.tsx rename to apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(metrics)/globe.tsx diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/metrics/line-chart.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(metrics)/line-chart.tsx similarity index 100% rename from apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/metrics/line-chart.tsx rename to apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(metrics)/line-chart.tsx diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/metrics/page-client.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(metrics)/page-client.tsx similarity index 100% rename from apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/metrics/page-client.tsx rename to apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(metrics)/page-client.tsx diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/metrics/page.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(metrics)/page.tsx similarity index 100% rename from apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/metrics/page.tsx rename to apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(metrics)/page.tsx diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/page.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/page.tsx deleted file mode 100644 index ad33e5605..000000000 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { redirect } from "next/navigation"; - -export default function Page({ params }: { params: { projectId: string } }) { - redirect(`/projects/${params.projectId}/users`); -} diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx index a6512dd28..938edb8fa 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx @@ -22,6 +22,7 @@ import { } from "@stackframe/stack-ui"; import { Book, + Globe, KeyRound, Link as LinkIcon, LockKeyhole, @@ -31,10 +32,9 @@ import { Settings, Settings2, ShieldEllipsis, - TrendingUp, User, Users, - Webhook, + Webhook } from "lucide-react"; import { useTheme } from "next-themes"; import { usePathname } from "next/navigation"; @@ -63,6 +63,13 @@ type Hidden = { }; const navigationItems: (Label | Item | Hidden)[] = [ + { + name: "Overview", + href: "/", + regex: /^\/projects\/[^\/]+$/, + icon: Globe, + type: 'item' + }, { name: "Users", type: 'label' @@ -81,13 +88,6 @@ const navigationItems: (Label | Item | Hidden)[] = [ icon: ShieldEllipsis, type: 'item' }, - { - name: "Metrics", - href: "/metrics", - regex: /^\/projects\/[^\/]+\/metrics$/, - icon: TrendingUp, - type: 'item' - }, { name: "Teams", type: 'label' From b3d1dfbdcb8a96bb75b29c199bf4ba4f0b968398 Mon Sep 17 00:00:00 2001 From: Zai Shi Date: Fri, 3 Jan 2025 16:23:55 -0800 Subject: [PATCH 21/23] fixed otp mobile number pad --- packages/stack/src/components/magic-link-sign-in.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/stack/src/components/magic-link-sign-in.tsx b/packages/stack/src/components/magic-link-sign-in.tsx index 01658da85..4e11d015f 100644 --- a/packages/stack/src/components/magic-link-sign-in.tsx +++ b/packages/stack/src/components/magic-link-sign-in.tsx @@ -54,6 +54,8 @@ function OTP(props: { {t('Enter the code from your email')} setOtp(value.toUpperCase())} From ab26bc8d3a0f35969b4ebde369ee589b49f0870c Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Fri, 3 Jan 2025 17:52:43 -0800 Subject: [PATCH 22/23] Use 30d intervals for metrics page --- apps/backend/src/app/api/v1/internal/metrics/route.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/backend/src/app/api/v1/internal/metrics/route.tsx b/apps/backend/src/app/api/v1/internal/metrics/route.tsx index 6bd601e07..04b4f3d88 100644 --- a/apps/backend/src/app/api/v1/internal/metrics/route.tsx +++ b/apps/backend/src/app/api/v1/internal/metrics/route.tsx @@ -47,7 +47,7 @@ async function loadTotalUsers(projectId: string, now: Date): Promise return (await prismaClient.$queryRaw<{date: Date, dailyUsers: bigint, cumUsers: bigint}[]>` WITH date_series AS ( SELECT GENERATE_SERIES( - ${now}::date - INTERVAL '1 month', + ${now}::date - INTERVAL '30 days', ${now}::date, '1 day' ) @@ -72,7 +72,7 @@ async function loadDailyActiveUsers(projectId: string, now: Date) { const res = await prismaClient.$queryRaw<{day: Date, dau: bigint}[]>` WITH date_series AS ( SELECT GENERATE_SERIES( - ${now}::date - INTERVAL '1 month', + ${now}::date - INTERVAL '30 days', ${now}::date, '1 day' ) @@ -83,7 +83,7 @@ async function loadDailyActiveUsers(projectId: string, now: Date) { DATE_TRUNC('day', "eventStartedAt") AS "day", COUNT(DISTINCT "data"->'userId') AS "dau" FROM "Event" - WHERE "eventStartedAt" >= ${now} - INTERVAL '1 month' + WHERE "eventStartedAt" >= ${now} - INTERVAL '30 days' AND "eventStartedAt" < ${now} AND '$user-activity' = ANY("systemEventTypeIds"::text[]) AND "data"->>'projectId' = ${projectId} From bca8202fee9ca690d482532bd15f4e6b62fde27b Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Fri, 3 Jan 2025 18:01:00 -0800 Subject: [PATCH 23/23] chore: update package versions --- apps/backend/CHANGELOG.md | 8 ++++++++ apps/backend/package.json | 2 +- apps/dashboard/CHANGELOG.md | 11 +++++++++++ apps/dashboard/package.json | 2 +- apps/dev-launchpad/CHANGELOG.md | 2 ++ apps/dev-launchpad/package.json | 2 +- apps/e2e/CHANGELOG.md | 6 ++++++ apps/e2e/package.json | 2 +- apps/oauth-mock-server/CHANGELOG.md | 6 ++++++ apps/oauth-mock-server/package.json | 2 +- docs/CHANGELOG.md | 2 ++ docs/package.json | 2 +- examples/cjs-test/CHANGELOG.md | 7 +++++++ examples/cjs-test/package.json | 2 +- examples/demo/CHANGELOG.md | 9 +++++++++ examples/demo/package.json | 2 +- examples/docs-examples/CHANGELOG.md | 9 +++++++++ examples/docs-examples/package.json | 2 +- examples/e-commerce/CHANGELOG.md | 7 +++++++ examples/e-commerce/package.json | 2 +- examples/middleware/CHANGELOG.md | 7 +++++++ examples/middleware/package.json | 2 +- examples/partial-prerendering/CHANGELOG.md | 7 +++++++ examples/partial-prerendering/package.json | 2 +- examples/supabase/CHANGELOG.md | 7 +++++++ examples/supabase/package.json | 2 +- packages/init-stack/CHANGELOG.md | 2 ++ packages/init-stack/package.json | 2 +- packages/stack-emails/CHANGELOG.md | 7 +++++++ packages/stack-emails/package.json | 2 +- packages/stack-sc/CHANGELOG.md | 2 ++ packages/stack-sc/package.json | 2 +- packages/stack-shared/CHANGELOG.md | 6 ++++++ packages/stack-shared/package.json | 2 +- packages/stack-ui/CHANGELOG.md | 6 ++++++ packages/stack-ui/package.json | 2 +- packages/stack/CHANGELOG.md | 9 +++++++++ packages/stack/package.json | 2 +- 38 files changed, 139 insertions(+), 19 deletions(-) diff --git a/apps/backend/CHANGELOG.md b/apps/backend/CHANGELOG.md index 4e81572a6..48cf3dd28 100644 --- a/apps/backend/CHANGELOG.md +++ b/apps/backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @stackframe/stack-backend +## 2.7.4 + +### Patch Changes + +- Various changes + - @stackframe/stack-emails@2.7.4 + - @stackframe/stack-shared@2.7.4 + ## 2.7.3 ### Patch Changes diff --git a/apps/backend/package.json b/apps/backend/package.json index 9924ca09e..125408e7f 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/stack-backend", - "version": "2.7.3", + "version": "2.7.4", "private": true, "scripts": { "clean": "rimraf .next && rimraf node_modules", diff --git a/apps/dashboard/CHANGELOG.md b/apps/dashboard/CHANGELOG.md index 4d4194762..a20979c8b 100644 --- a/apps/dashboard/CHANGELOG.md +++ b/apps/dashboard/CHANGELOG.md @@ -1,5 +1,16 @@ # @stackframe/stack-dashboard +## 2.7.4 + +### Patch Changes + +- Various changes +- Updated dependencies + - @stackframe/stack@2.7.4 + - @stackframe/stack-emails@2.7.4 + - @stackframe/stack-shared@2.7.4 + - @stackframe/stack-ui@2.7.4 + ## 2.7.3 ### Patch Changes diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index 23c142a6e..ff12100ab 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/stack-dashboard", - "version": "2.7.3", + "version": "2.7.4", "private": true, "scripts": { "clean": "rimraf .next && rimraf node_modules", diff --git a/apps/dev-launchpad/CHANGELOG.md b/apps/dev-launchpad/CHANGELOG.md index eaf1e4a7b..f5e2ea524 100644 --- a/apps/dev-launchpad/CHANGELOG.md +++ b/apps/dev-launchpad/CHANGELOG.md @@ -1,5 +1,7 @@ # @stackframe/dev-launchpad +## 2.7.4 + ## 2.7.3 ### Patch Changes diff --git a/apps/dev-launchpad/package.json b/apps/dev-launchpad/package.json index 4adc8d597..07029f6f9 100644 --- a/apps/dev-launchpad/package.json +++ b/apps/dev-launchpad/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/dev-launchpad", - "version": "2.7.3", + "version": "2.7.4", "private": true, "scripts": { "dev": "serve -p 8100 -s public" diff --git a/apps/e2e/CHANGELOG.md b/apps/e2e/CHANGELOG.md index 72662c45e..5e8f0b9eb 100644 --- a/apps/e2e/CHANGELOG.md +++ b/apps/e2e/CHANGELOG.md @@ -1,5 +1,11 @@ # @stackframe/e2e-tests +## 2.7.4 + +### Patch Changes + +- @stackframe/stack-shared@2.7.4 + ## 2.7.3 ### Patch Changes diff --git a/apps/e2e/package.json b/apps/e2e/package.json index bed96264d..eddab4c3b 100644 --- a/apps/e2e/package.json +++ b/apps/e2e/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/e2e-tests", - "version": "2.7.3", + "version": "2.7.4", "private": true, "type": "module", "scripts": { diff --git a/apps/oauth-mock-server/CHANGELOG.md b/apps/oauth-mock-server/CHANGELOG.md index 053f26965..b924de24b 100644 --- a/apps/oauth-mock-server/CHANGELOG.md +++ b/apps/oauth-mock-server/CHANGELOG.md @@ -1,5 +1,11 @@ # @stackframe/oauth-mock-server +## 2.7.4 + +### Patch Changes + +- @stackframe/stack-shared@2.7.4 + ## 2.7.3 ### Patch Changes diff --git a/apps/oauth-mock-server/package.json b/apps/oauth-mock-server/package.json index 397dc5ebc..6eaa48292 100644 --- a/apps/oauth-mock-server/package.json +++ b/apps/oauth-mock-server/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/oauth-mock-server", - "version": "2.7.3", + "version": "2.7.4", "private": true, "main": "index.js", "scripts": { diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 2eecb7960..1c31b5389 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,7 @@ # @stackframe/docs +## 2.7.4 + ## 2.7.3 ## 2.7.2 diff --git a/docs/package.json b/docs/package.json index 97a67e977..f23ebeee0 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/docs", - "version": "2.7.3", + "version": "2.7.4", "description": "", "main": "index.js", "private": true, diff --git a/examples/cjs-test/CHANGELOG.md b/examples/cjs-test/CHANGELOG.md index 405a830cd..19ec705af 100644 --- a/examples/cjs-test/CHANGELOG.md +++ b/examples/cjs-test/CHANGELOG.md @@ -1,5 +1,12 @@ # @stackframe/example-cjs-test +## 2.7.4 + +### Patch Changes + +- Updated dependencies + - @stackframe/stack@2.7.4 + ## 2.7.3 ### Patch Changes diff --git a/examples/cjs-test/package.json b/examples/cjs-test/package.json index c607c6982..0285f51da 100644 --- a/examples/cjs-test/package.json +++ b/examples/cjs-test/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/example-cjs-test", - "version": "2.7.3", + "version": "2.7.4", "private": true, "scripts": { "dev": "next dev --port 8110", diff --git a/examples/demo/CHANGELOG.md b/examples/demo/CHANGELOG.md index 1011b9eaa..e7f529ac4 100644 --- a/examples/demo/CHANGELOG.md +++ b/examples/demo/CHANGELOG.md @@ -1,5 +1,14 @@ # @stackframe/example-demo-app +## 2.7.4 + +### Patch Changes + +- Updated dependencies + - @stackframe/stack@2.7.4 + - @stackframe/stack-shared@2.7.4 + - @stackframe/stack-ui@2.7.4 + ## 2.7.3 ### Patch Changes diff --git a/examples/demo/package.json b/examples/demo/package.json index 1f4e920bf..66a8a8f12 100644 --- a/examples/demo/package.json +++ b/examples/demo/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/example-demo-app", - "version": "2.7.3", + "version": "2.7.4", "description": "", "private": true, "scripts": { diff --git a/examples/docs-examples/CHANGELOG.md b/examples/docs-examples/CHANGELOG.md index b2ec1fab1..a971ac150 100644 --- a/examples/docs-examples/CHANGELOG.md +++ b/examples/docs-examples/CHANGELOG.md @@ -1,5 +1,14 @@ # @stackframe/docs-examples +## 2.7.4 + +### Patch Changes + +- Updated dependencies + - @stackframe/stack@2.7.4 + - @stackframe/stack-shared@2.7.4 + - @stackframe/stack-ui@2.7.4 + ## 2.7.3 ### Patch Changes diff --git a/examples/docs-examples/package.json b/examples/docs-examples/package.json index 5343c3567..d98bd205a 100644 --- a/examples/docs-examples/package.json +++ b/examples/docs-examples/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/docs-examples", - "version": "2.7.3", + "version": "2.7.4", "description": "", "private": true, "scripts": { diff --git a/examples/e-commerce/CHANGELOG.md b/examples/e-commerce/CHANGELOG.md index 5bae748de..b9ba0fd37 100644 --- a/examples/e-commerce/CHANGELOG.md +++ b/examples/e-commerce/CHANGELOG.md @@ -1,5 +1,12 @@ # @stackframe/e-commerce-demo +## 2.7.4 + +### Patch Changes + +- Updated dependencies + - @stackframe/stack@2.7.4 + ## 2.7.3 ### Patch Changes diff --git a/examples/e-commerce/package.json b/examples/e-commerce/package.json index dfdab00b6..637230856 100644 --- a/examples/e-commerce/package.json +++ b/examples/e-commerce/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/e-commerce-demo", - "version": "2.7.3", + "version": "2.7.4", "private": true, "scripts": { "dev": "next dev --port 8111", diff --git a/examples/middleware/CHANGELOG.md b/examples/middleware/CHANGELOG.md index 666ff268f..8c5bd7eba 100644 --- a/examples/middleware/CHANGELOG.md +++ b/examples/middleware/CHANGELOG.md @@ -1,5 +1,12 @@ # @stackframe/example-middleware-demo +## 2.7.4 + +### Patch Changes + +- Updated dependencies + - @stackframe/stack@2.7.4 + ## 2.7.3 ### Patch Changes diff --git a/examples/middleware/package.json b/examples/middleware/package.json index 04a4cd876..effec8fe1 100644 --- a/examples/middleware/package.json +++ b/examples/middleware/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/example-middleware-demo", - "version": "2.7.3", + "version": "2.7.4", "private": true, "scripts": { "dev": "next dev --port 8112", diff --git a/examples/partial-prerendering/CHANGELOG.md b/examples/partial-prerendering/CHANGELOG.md index e33250ad5..946511170 100644 --- a/examples/partial-prerendering/CHANGELOG.md +++ b/examples/partial-prerendering/CHANGELOG.md @@ -1,5 +1,12 @@ # @stackframe/example-partial-prerendering +## 2.7.4 + +### Patch Changes + +- Updated dependencies + - @stackframe/stack@2.7.4 + ## 2.7.3 ### Patch Changes diff --git a/examples/partial-prerendering/package.json b/examples/partial-prerendering/package.json index ad23a7f42..782faec69 100644 --- a/examples/partial-prerendering/package.json +++ b/examples/partial-prerendering/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/example-partial-prerendering", - "version": "2.7.3", + "version": "2.7.4", "private": true, "scripts": { "dev": "next dev --port 8109", diff --git a/examples/supabase/CHANGELOG.md b/examples/supabase/CHANGELOG.md index 2732f9253..6ed3ab6c7 100644 --- a/examples/supabase/CHANGELOG.md +++ b/examples/supabase/CHANGELOG.md @@ -1,5 +1,12 @@ # @stackframe/example-supabase +## 2.7.4 + +### Patch Changes + +- Updated dependencies + - @stackframe/stack@2.7.4 + ## 2.7.3 ### Patch Changes diff --git a/examples/supabase/package.json b/examples/supabase/package.json index c4cd20502..65547aa2e 100644 --- a/examples/supabase/package.json +++ b/examples/supabase/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/example-supabase", - "version": "2.7.3", + "version": "2.7.4", "private": true, "scripts": { "dev": "next dev --turbo --port 8115", diff --git a/packages/init-stack/CHANGELOG.md b/packages/init-stack/CHANGELOG.md index be52f0e0e..9fcce41b5 100644 --- a/packages/init-stack/CHANGELOG.md +++ b/packages/init-stack/CHANGELOG.md @@ -1,5 +1,7 @@ # @stackframe/init-stack +## 2.7.4 + ## 2.7.3 ## 2.7.2 diff --git a/packages/init-stack/package.json b/packages/init-stack/package.json index f4d86d724..a680b682a 100644 --- a/packages/init-stack/package.json +++ b/packages/init-stack/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/init-stack", - "version": "2.7.3", + "version": "2.7.4", "description": "The setup wizard for Stack. https://stack-auth.com", "main": "index.mjs", "bin": "./index.mjs", diff --git a/packages/stack-emails/CHANGELOG.md b/packages/stack-emails/CHANGELOG.md index 1eca73d2b..9637fd91c 100644 --- a/packages/stack-emails/CHANGELOG.md +++ b/packages/stack-emails/CHANGELOG.md @@ -1,5 +1,12 @@ # @stackframe/stack-emails +## 2.7.4 + +### Patch Changes + +- @stackframe/stack-shared@2.7.4 +- @stackframe/stack-ui@2.7.4 + ## 2.7.3 ### Patch Changes diff --git a/packages/stack-emails/package.json b/packages/stack-emails/package.json index b3056a0bf..919db7bdc 100644 --- a/packages/stack-emails/package.json +++ b/packages/stack-emails/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/stack-emails", - "version": "2.7.3", + "version": "2.7.4", "main": "./dist/index.js", "types": "./dist/index.d.ts", "private": true, diff --git a/packages/stack-sc/CHANGELOG.md b/packages/stack-sc/CHANGELOG.md index de628667e..0cc2bf12c 100644 --- a/packages/stack-sc/CHANGELOG.md +++ b/packages/stack-sc/CHANGELOG.md @@ -1,5 +1,7 @@ # @stackframe/stack-sc +## 2.7.4 + ## 2.7.3 ## 2.7.2 diff --git a/packages/stack-sc/package.json b/packages/stack-sc/package.json index 3abe979eb..59f2705da 100644 --- a/packages/stack-sc/package.json +++ b/packages/stack-sc/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/stack-sc", - "version": "2.7.3", + "version": "2.7.4", "exports": { "./force-react-server": { "types": "./dist/index.react-server.d.ts", diff --git a/packages/stack-shared/CHANGELOG.md b/packages/stack-shared/CHANGELOG.md index 8a9fa3918..aa1a3958e 100644 --- a/packages/stack-shared/CHANGELOG.md +++ b/packages/stack-shared/CHANGELOG.md @@ -1,5 +1,11 @@ # @stackframe/stack-shared +## 2.7.4 + +### Patch Changes + +- @stackframe/stack-sc@2.7.4 + ## 2.7.3 ### Patch Changes diff --git a/packages/stack-shared/package.json b/packages/stack-shared/package.json index 276dc4b5d..7273f015d 100644 --- a/packages/stack-shared/package.json +++ b/packages/stack-shared/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/stack-shared", - "version": "2.7.3", + "version": "2.7.4", "main": "./dist/index.js", "types": "./dist/index.d.ts", "scripts": { diff --git a/packages/stack-ui/CHANGELOG.md b/packages/stack-ui/CHANGELOG.md index 672e6f8b4..5c1530f2a 100644 --- a/packages/stack-ui/CHANGELOG.md +++ b/packages/stack-ui/CHANGELOG.md @@ -1,5 +1,11 @@ # @stackframe/stack-ui +## 2.7.4 + +### Patch Changes + +- @stackframe/stack-shared@2.7.4 + ## 2.7.3 ### Patch Changes diff --git a/packages/stack-ui/package.json b/packages/stack-ui/package.json index ae955ad23..fc562d898 100644 --- a/packages/stack-ui/package.json +++ b/packages/stack-ui/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/stack-ui", - "version": "2.7.3", + "version": "2.7.4", "main": "./dist/index.js", "types": "./dist/index.d.ts", "sideEffects": false, diff --git a/packages/stack/CHANGELOG.md b/packages/stack/CHANGELOG.md index 811baeeb8..63b876197 100644 --- a/packages/stack/CHANGELOG.md +++ b/packages/stack/CHANGELOG.md @@ -1,5 +1,14 @@ # @stackframe/stack +## 2.7.4 + +### Patch Changes + +- Various changes + - @stackframe/stack-sc@2.7.4 + - @stackframe/stack-shared@2.7.4 + - @stackframe/stack-ui@2.7.4 + ## 2.7.3 ### Patch Changes diff --git a/packages/stack/package.json b/packages/stack/package.json index b621bb3fa..8606fe378 100644 --- a/packages/stack/package.json +++ b/packages/stack/package.json @@ -1,6 +1,6 @@ { "name": "@stackframe/stack", - "version": "2.7.3", + "version": "2.7.4", "sideEffects": false, "main": "./dist/index.js", "types": "./dist/index.d.ts",