Como seu código é organizado
O Compiler coloca cada parte do seu app Bubble em um local fixo no código. O mesmo app Bubble sempre gera o mesmo código. Os exemplos nesta página usam um tipo de dados Project, um conjunto de opções Project Status e uma página Reset password.
Onde cada parte do seu app Bubble fica
No Bubble | No seu código |
|---|---|
Página |
|
Elemento reutilizável |
|
Tipo de dados |
|
Regras de privacidade |
|
Conjunto de opções |
|
Fluxo de trabalho de backend |
|
Fluxo de trabalho de backend exposto como uma API |
|
Chamada do API Connector |
|
Estilos |
|
Estrutura de pastas
Um projeto em Next.js é assim:
my-app/
├── AGENTS.md Instructions for coding agents
├── TODO.md The parts that you must finish
├── SECURITY.md Where the app is stricter than Bubble
├── bubble-map.json Where each Bubble page, element and workflow is in the code
├── .env.example The keys your app needs
└── src/
├── app/ The address of each page
├── views/ Your pages
│ └── reset-password/
│ ├── ResetPasswordPage.tsx What the page shows
│ ├── ResetPasswordPage.store.ts Custom states and input values
│ ├── ResetPasswordPage.workflows.ts The page's workflows
│ └── ResetPasswordPage.actions.ts Actions that run on the server
├── components/ Your reusable elements, in the same layout as pages
├── server/
│ ├── db/schema/ Your data types
│ └── data/ Searches, with your privacy rules
├── lib/options/ Your option sets
├── workflows/ Your backend workflows, in one folder for each area
├── integrations/ Your API Connector calls
├── ui/ Buttons, inputs, icons and other Bubble elements
└── styles/ Your colors, fonts and named stylesUm projeto no TanStack Start tem src/routes/ em vez de src/app/. Todas as outras pastas são iguais.
Arquivos para ler primeiro
TODO.mdlista cada parte que o Compiler não conseguiu copiar. Cada item indica o arquivo correspondente. Nesse arquivo, a parte é um marcador provisório que ainda não faz nada. Você, um desenvolvedor ou um agente de programação, precisa implementá-la.SECURITY.mdlista cada lugar em que seu app é mais restritivo que seu app Bubble. Por exemplo, um fluxo de trabalho que só um administrador pode executar.bubble-map.jsonmostra onde cada parte do seu app Bubble fica no código. Pesquise nele pelo nome de algo do Bubble.AGENTS.mdexplica a um agente de programação como o projeto funciona. Forneça-o ao agente antes que ele altere o código.
Exemplo: um tipo de dados
O tipo de dados Project tem os campos Name, Owner, Status, Due date e Budget. Ele se transforma em uma tabela do banco de dados:
// Bubble data type "Project".
export const projects = pgTable(
"projects",
{
id: text().primaryKey(),
createdDate: timestamp({ withTimezone: true, mode: "date" }).notNull().defaultNow(),
modifiedDate: timestamp({ withTimezone: true, mode: "date" }).notNull().defaultNow(),
slug: text().unique(),
createdBy: text(),
budget: doublePrecision(),
dueDate: timestamp({ withTimezone: true, mode: "date" }),
name: text(),
owner: text().references(() => users.id, { onDelete: "set null" }),
status: text().$type<ProjectStatus>(),
},
(table) => [
index("projects_created_date_idx").on(table.createdDate),
index("projects_owner_idx").on(table.owner),
],
);
export type Project = typeof projects.$inferSelect;Cada registro mantém seu ID do Bubble. Os vínculos entre registros permanecem iguais depois que você migra seus dados.
ownerfaz referência à tabela User. Se você excluir o usuário, o projeto permanece e o campo do proprietário fica vazio.Um índice torna as pesquisas mais rápidas. O Compiler adiciona um índice para cada campo usado pelo seu app nas pesquisas.
Exemplo: um conjunto de opções
O conjunto de opções Project Status tem as opções Ativo e Arquivado:
export type ProjectStatus = "active" | "archived";
export type ProjectStatusOption = { display: string };
export const projectStatus: Readonly<Record<ProjectStatus, ProjectStatusOption>> = {
active: { display: "Active" },
archived: { display: "Archived" },
};
export const projectStatuses: readonly ProjectStatus[] = ["active", "archived"];Seu código usa o conjunto de opções da mesma forma que o Bubble. Por exemplo, projectStatus.active.display é "Ativo", e projectStatuses é a lista de todos os status.
Exemplo: um fluxo de trabalho de página
Na página Reset password, o fluxo de trabalho "Quando o botão Confirmar é clicado" redefine a senha, desconecta o usuário e o encaminha para a página de login. Ele se transforma nesta função:
export async function clickConfirm({ resetPasswordPage }: { resetPasswordPage: ResetPasswordPageStore }) {
await resetPassword(
resetPasswordPage.getState().passwordInput ?? "",
resetPasswordPage.getState().confirmInput ?? "",
);
await logOut();
await router().navigate({ to: "/login/$", search: { reset_success: "yes" } });
}Cada ação do fluxo de trabalho no Bubble se transforma em uma linha de código, na mesma ordem. resetPasswordPage armazena os valores das entradas e dos estados personalizados da página.
Exemplo: um fluxo de trabalho de backend
Um gatilho de banco de dados que limpa o nome de um usuário sempre que ele é alterado se transforma nesta função:
export async function updateFullName(ctx: Context, { now: user }: Trigger<User>): Promise<void> {
await change(ctx, users, user?.id, {
firstName: user?.firstName?.trim() ?? "",
lastName: user?.lastName?.trim() ?? "",
fullName: `${user?.firstName?.trim() ?? ""} ${user?.lastName?.trim() ?? ""}`.trim(),
});
}change corresponde a "Fazer alterações em uma coisa" no Bubble. now corresponde a "Coisa agora" no gatilho.