Skip to content

Commit f1af12f

Browse files
authored
Merge pull request #577 from chaibuilder/576-better-organization-of-types-across-codebase
better organization of types across codebase
2 parents b092dc0 + f67c0c7 commit f1af12f

File tree

126 files changed

+1041
-4771
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

126 files changed

+1041
-4771
lines changed

frameworks/nextjs/app/(public)/[[...slug]]/page.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export default async function Page({ params }: { params: Promise<{ slug: string[
2727
ChaiBuilder.init(process.env.CHAIBUILDER_APP_KEY!, isEnabled);
2828
let page = null;
2929
try {
30-
page = await ChaiBuilder.getPageBySlug(slug);
30+
page = await ChaiBuilder.getPage(slug);
3131
if ("error" in page) {
3232
return notFound();
3333
}
@@ -36,10 +36,11 @@ export default async function Page({ params }: { params: Promise<{ slug: string[
3636
}
3737

3838
//NOTE: pageProps are received in your dataProvider functions for block and page
39+
const siteSettings = await ChaiBuilder.getSiteSettings();
3940
const pageProps: ChaiPageProps = {
4041
slug,
4142
pageType: page.pageType,
42-
fallbackLang: page.fallbackLang,
43+
fallbackLang: siteSettings.fallbackLang,
4344
pageLang: page.lang,
4445
};
4546
return (

frameworks/nextjs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"@supabase/ssr": "^0.8.0",
1414
"@supabase/supabase-js": "^2.90.1",
1515
"date-fns": "^4.1.0",
16-
"drizzle-orm": "^0.45.1",
16+
"drizzle-orm": "0.45.1",
1717
"lodash": "^4.17.21",
1818
"next": "^16.1.2",
1919
"react": "^19.2.3",

frameworks/nextjs/package/ChaiBuilder.ts

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { applyChaiDataBinding } from "@chaibuilder/sdk/render";
2-
import { ChaiBlock, ChaiPageProps } from "@chaibuilder/sdk/runtime";
2+
import type { ChaiBlock, ChaiPage, ChaiPageProps, ChaiWebsiteSetting } from "@chaibuilder/sdk/types";
33
import { unstable_cache } from "next/cache";
4+
import { notFound } from "next/navigation";
45
import { cache } from "react";
5-
import type { ChaiBuilderPage } from "./lib";
66
import * as utils from "./lib";
7+
import { ChaiFullPage, ChaiPartialPage } from "./types";
78

89
class ChaiBuilder {
910
private static appId?: string;
@@ -51,53 +52,71 @@ class ChaiBuilder {
5152
return ChaiBuilder.fallbackLang;
5253
}
5354

54-
static async getSiteSettings(): Promise<{ fallbackLang?: string; [key: string]: unknown }> {
55+
static async getSiteSettings(): Promise<ChaiWebsiteSetting> {
5556
ChaiBuilder.verifyInit();
5657
return await unstable_cache(
5758
async () => await utils.getSiteSettings(ChaiBuilder.appId!, ChaiBuilder.draftMode),
5859
[`website-settings-${ChaiBuilder.getAppId()}`],
59-
{
60-
tags: [`website-settings`, `website-settings-${ChaiBuilder.getAppId()}`],
61-
},
60+
{ tags: [`website-settings`, `website-settings-${ChaiBuilder.getAppId()}`] },
6261
)();
6362
}
6463

65-
static async getPageBySlug(slug: string): Promise<ChaiBuilderPage | { error: string }> {
64+
static async getPageBySlug(slug: string): Promise<ChaiPartialPage> {
6665
ChaiBuilder.verifyInit();
67-
return await utils.getPageBySlug(slug, this.appId!, this.draftMode);
66+
try {
67+
return await utils.getPageBySlug(slug, this.appId!, this.draftMode);
68+
} catch (error) {
69+
if (error instanceof Error && error.message === "PAGE_NOT_FOUND") {
70+
throw notFound();
71+
}
72+
throw error;
73+
}
6874
}
6975

70-
static async getFullPage(pageId: string): Promise<Record<string, unknown>> {
76+
static async getFullPage(pageId: string): Promise<ChaiFullPage> {
7177
ChaiBuilder.verifyInit();
7278
return await utils.getFullPage(pageId, this.appId!, this.draftMode);
7379
}
7480

75-
static async getPartialPageBySlug(slug: string): Promise<Record<string, unknown>> {
81+
static async getPartialPageBySlug(
82+
slug: string,
83+
): Promise<
84+
Pick<
85+
ChaiPage,
86+
| "id"
87+
| "name"
88+
| "slug"
89+
| "lang"
90+
| "primaryPage"
91+
| "seo"
92+
| "currentEditor"
93+
| "pageType"
94+
| "lastSaved"
95+
| "dynamic"
96+
| "parent"
97+
| "blocks"
98+
>
99+
> {
76100
ChaiBuilder.verifyInit();
77101
// if the slug is of format /_partial/{langcode}/{uuid}, get the uuid. langcode is optional
78102
const uuid = slug.split("/").pop();
79103
if (!uuid) {
80-
throw new Error("Invalid slug format for partial page");
104+
throw new Error("PAGE_NOT_FOUND");
81105
}
82106

83107
// Fetch the partial page data
84108
const siteSettings = await ChaiBuilder.getSiteSettings();
85109
const data = await ChaiBuilder.getFullPage(uuid);
86-
const fallbackLang = siteSettings?.fallbackLang;
87-
const lang = slug.split("/").length > 3 ? slug.split("/")[2] : fallbackLang;
88-
return { ...data, fallbackLang, lang };
110+
const lang = slug.split("/").length > 3 ? slug.split("/")[2] : siteSettings?.fallbackLang;
111+
return { ...data, lang };
89112
}
90113

91-
static getPage = cache(async (slug: string): Promise<ChaiBuilderPage | Record<string, unknown>> => {
114+
static getPage = cache(async (slug: string): Promise<ChaiFullPage> => {
92115
ChaiBuilder.verifyInit();
93116
if (slug.startsWith("/_partial/")) {
94117
return await ChaiBuilder.getPartialPageBySlug(slug);
95118
}
96-
const page: ChaiBuilderPage = await ChaiBuilder.getPageBySlug(slug);
97-
if ("error" in page) {
98-
return page;
99-
}
100-
119+
const page = await ChaiBuilder.getPageBySlug(slug);
101120
const siteSettings = await ChaiBuilder.getSiteSettings();
102121
const tagPageId = page.id;
103122
const languagePageId = page.languagePageId || page.id;
@@ -129,16 +148,12 @@ class ChaiBuilder {
129148
};
130149
}
131150
const page = await ChaiBuilder.getPage(slug);
132-
if ("error" in page) {
133-
return {
134-
title: "Page Not Found",
135-
description: "The requested page could not be found.",
136-
robots: { index: false, follow: false },
137-
};
151+
if ("error" in page && page.error === "PAGE_NOT_FOUND") {
152+
throw notFound();
138153
}
139154

140155
// Type assertion after error check
141-
const pageData = page as Exclude<ChaiBuilderPage, { error: string }>;
156+
const pageData = page as ChaiPage;
142157

143158
const externalData = await ChaiBuilder.getPageExternalData({
144159
blocks: pageData.blocks,
@@ -194,7 +209,7 @@ class ChaiBuilder {
194209

195210
static async getPageData(args: {
196211
blocks: ChaiBlock[];
197-
pageProps: Record<string, unknown>;
212+
pageProps: ChaiPageProps;
198213
pageType: string;
199214
lang: string;
200215
}): Promise<Record<string, unknown>> {

frameworks/nextjs/package/lib/get-blocks-styles.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { getStylesForBlocks } from "@chaibuilder/sdk/render";
2-
import { ChaiBlock } from "@chaibuilder/sdk/runtime";
2+
import type { ChaiBlock } from "@chaibuilder/sdk/types";
33

44
export async function getBlocksStyles(blocks: ChaiBlock[]): Promise<string> {
55
return await getStylesForBlocks(blocks, false);

frameworks/nextjs/package/lib/get-full-page.ts

Lines changed: 28 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { db, safeQuery, schema } from "@chaibuilder/sdk/actions";
2-
import { ChaiBlock } from "@chaibuilder/sdk/runtime";
3-
import { differenceInMinutes } from "date-fns";
4-
import { and, eq, inArray, sql } from "drizzle-orm";
2+
import { ChaiBlock, ChaiPage } from "@chaibuilder/sdk/types";
3+
import { and, eq, inArray } from "drizzle-orm";
54
import { get, has, isEmpty } from "lodash";
65

76
export type GetFullPageOptions = {
@@ -13,28 +12,28 @@ export type GetFullPageOptions = {
1312
userId?: string;
1413
};
1514

16-
export type FullPageResponse = {
17-
id: string;
18-
name: string;
19-
slug: string;
20-
lang: string;
21-
primaryPage?: string | null;
22-
seo: any;
23-
currentEditor?: string | null;
24-
pageType?: string | null;
25-
lastSaved?: string | null;
26-
dynamic: boolean | null;
27-
parent?: string | null;
28-
blocks: ChaiBlock[];
29-
languagePageId: string;
30-
};
31-
3215
export async function getFullPage(
3316
pageId: string,
3417
appId: string,
3518
draftMode: boolean,
3619
options: Partial<GetFullPageOptions> = {},
37-
): Promise<FullPageResponse> {
20+
): Promise<
21+
Pick<
22+
ChaiPage,
23+
| "id"
24+
| "name"
25+
| "slug"
26+
| "lang"
27+
| "primaryPage"
28+
| "seo"
29+
| "currentEditor"
30+
| "pageType"
31+
| "lastSaved"
32+
| "dynamic"
33+
| "parent"
34+
| "blocks"
35+
>
36+
> {
3837
const table = draftMode ? schema.appPages : schema.appPagesOnline;
3938

4039
// Get page data with selected fields
@@ -59,12 +58,12 @@ export async function getFullPage(
5958
);
6059

6160
if (error || !pageResult || pageResult.length === 0) {
62-
throw new Error("Page not found");
61+
throw new Error("PAGE_NOT_FOUND");
6362
}
6463

6564
const page = pageResult[0];
6665
if (!page) {
67-
throw new Error("Page data is invalid");
66+
throw new Error("PAGE_NOT_FOUND");
6867
}
6968

7069
const primaryPageId = page.primaryPage ?? page.id;
@@ -81,7 +80,7 @@ export async function getFullPage(
8180
);
8281

8382
if (blocksError) {
84-
throw new Error("Failed to fetch page blocks");
83+
throw new Error("FAILED_TO_FETCH_PAGE_BLOCKS");
8584
}
8685

8786
let blocks = (blocksResult?.[0]?.blocks as ChaiBlock[]) ?? [];
@@ -95,76 +94,22 @@ export async function getFullPage(
9594
// Handle editor locking for draft pages
9695
let currentEditor = page.currentEditor;
9796

98-
if (draftMode && options.editor && options.userId) {
99-
const lockResult = await handleEditorLock(page, primaryPageId, options.userId, appId);
100-
currentEditor = lockResult.currentEditor;
101-
}
102-
10397
return {
10498
id: pageId,
10599
name: page.name,
106100
slug: page.slug,
107101
lang: page.lang,
108-
primaryPage: page.primaryPage ?? null,
109-
seo: page.seo,
102+
primaryPage: page.primaryPage,
103+
seo: page.seo ?? {},
110104
currentEditor,
111-
pageType: page.pageType ?? null,
112-
lastSaved: page.lastSaved ?? null,
113-
dynamic: page.dynamic ?? null,
114-
parent: page.parent ?? null,
105+
pageType: page.pageType!,
106+
lastSaved: page.lastSaved!,
107+
dynamic: page.dynamic!,
108+
parent: page.parent,
115109
blocks,
116-
languagePageId: page.id,
117110
};
118111
}
119112

120-
/**
121-
* Handle editor locking logic
122-
*/
123-
async function handleEditorLock(
124-
page: any,
125-
primaryPageId: string,
126-
userId: string,
127-
appId: string,
128-
): Promise<{ currentEditor: string | null }> {
129-
const now = new Date();
130-
let canTakePage = false;
131-
132-
// Check if we can take over the page (no editor or last save > 5 minutes ago)
133-
if (page.lastSaved) {
134-
const lastSaved = new Date(page.lastSaved);
135-
canTakePage = differenceInMinutes(now, lastSaved) > 5;
136-
}
137-
138-
const isCurrentEditorNull = page.currentEditor === null;
139-
140-
if (isCurrentEditorNull || canTakePage) {
141-
// Take over the page
142-
await safeQuery(() =>
143-
db
144-
.update(schema.appPages)
145-
.set({
146-
currentEditor: userId,
147-
lastSaved: sql`now()`,
148-
})
149-
.where(and(eq(schema.appPages.id, primaryPageId), eq(schema.appPages.app, appId))),
150-
);
151-
return { currentEditor: userId };
152-
} else if (page.currentEditor === userId) {
153-
// Update last saved time for current editor
154-
await safeQuery(() =>
155-
db
156-
.update(schema.appPages)
157-
.set({
158-
lastSaved: sql`now()`,
159-
})
160-
.where(and(eq(schema.appPages.id, primaryPageId), eq(schema.appPages.app, appId))),
161-
);
162-
return { currentEditor: userId };
163-
}
164-
165-
return { currentEditor: page.currentEditor };
166-
}
167-
168113
/**
169114
* Merge partial blocks into the main blocks array
170115
* Optimized to fetch all partial blocks in a single query

0 commit comments

Comments
 (0)