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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Changelog

## 0.2.0-next.18

- `bool create` now verifies the new project can be developed against *before*
writing any files. A project that isn't on the gateway runtime can't be used
as a local backend; previously `create` scaffolded the whole app and only then
hit the error, leaving an orphaned folder. It now fails immediately with the
server's reason and scaffolds nothing.
- The scaffolded app pins `bool-sdk@0.2.0-next.17` (the latest published
release), catching the template pin up from `next.16`.

## 0.2.0-next.17

- `createBoolClient` routes the gateway same-origin for any deployment
subdomain, not just the canonical one — fixes a 404 when a deployed app is
reached via a renamed slug.

## 0.2.0-next.16

- `bool create` no longer requires a name — a bare `bool create` generates a
Expand Down
24 changes: 11 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,30 +111,28 @@ const todos = await bool.entities.todos.list();

### Documentation

Complete guides and API reference at **[bool.com/docs](https://bool.com/docs)**:
Complete guides at **[bool.com/docs](https://bool.com/docs)**:

- **[Local Development](https://bool.com/docs/local-development)** — complete
walkthrough with use cases, workflows, and tips
- **[CLI Reference](https://bool.com/docs/cli)** — command-line tools
- **[SDK Reference](https://bool.com/docs/sdk-reference)** — API documentation
- **[Data Design](https://bool.com/docs/database)** — schema patterns and
privacy
- **[Develop locally (CLI)](https://bool.com/docs/cli)** — the CLI commands, the
local workflow, client setup, and deploying
- **[Database](https://bool.com/docs/database)** — entities, records, and your
data model

### Admin Key Gotcha

When using the admin key (`apiKey`), on a **private** entity (one with
`user_id` owner field), you must set `user_id` explicitly:
When using the admin key (`apiKey`), on a **private** entity (one Bool gives an
`owner_id` owner column), you must set `owner_id` explicitly:

```ts
// ❌ Fails on private entity (NOT NULL constraint)
// ❌ Fails on private entity (owner_id has no value to default to)
await bool.entities.tasks.create({ title: "Task" });

// ✅ Works
await bool.entities.tasks.create({ title: "Task", user_id: userId });
await bool.entities.tasks.create({ title: "Task", owner_id: userId });
```

The admin key has no user identity, so it can't default `user_id`. End-user
clients and `boolk_` keys carry the user and default automatically.
The admin key has no user identity, so it can't default `owner_id`. End-user
clients and `boolk_` keys carry the user and default it automatically.

Coding agents can do all of the above through Bool's MCP server instead
(`list_entities`, `define_entity`, `list_records`, `get_entity_types`,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bool-sdk",
"version": "0.2.0-next.17",
"version": "0.2.0-next.18",
"description": "Client SDK for apps built on Bool — gateway data access, end-user auth, the AI battery, the React auth layer, and the local-dev CLI (link, types, deploy).",
"type": "module",
"main": "./dist/index.js",
Expand Down
17 changes: 17 additions & 0 deletions src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,23 @@ describe("create", () => {
expect(logs.join("\n")).toMatch(/Created project ".+"/);
expect(logs.join("\n")).toMatch(/Scaffolded a todo app in [a-z]+-[a-z]+-\d+\//);
});

test("fails fast (no scaffold) when the new project isn't a gateway project", async () => {
const routes = createRoutes();
routes["/api/projects/new1/connection"] = () =>
json({ error: "not_gateway_project", message: "This project runs on the v1 runtime" }, 409);
const { deps, calls, errors } = makeDeps(cwd, routes);

expect(await runCli(["create", "my-todo"], deps)).toBe(1);
// Surfaces the server's reason plus the reassurance that nothing was written.
expect(errors.join("\n")).toContain("v1 runtime");
expect(errors.join("\n")).toContain("nothing was scaffolded");
// No files scaffolded — the target dir was never created.
expect(existsSync(join(cwd, "my-todo"))).toBe(false);
// Bailed at the connection check: never scaffolded, linked, or pushed.
expect(calls.some((c) => c.url.endsWith("/api/projects/new1/api-key"))).toBe(false);
expect(calls.some((c) => c.url.endsWith("/api/projects/new1/entities"))).toBe(false);
});
});

describe("link", () => {
Expand Down
57 changes: 46 additions & 11 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,13 +248,19 @@ async function writeConfigAndKey(
tok: string,
typesPath: string,
deps: CliDeps,
// A connection descriptor the caller already fetched (e.g. `create` checks
// the runtime before scaffolding). When omitted we fetch it here — the case
// for `link`, which has nothing to check first.
prefetched?: Connection,
): Promise<{ config: BoolConfig; name: string }> {
const conn = await apiJson<Connection>(
deps,
tok,
apiUrl,
`/api/projects/${projectId}/connection`,
);
const conn =
prefetched ??
(await apiJson<Connection>(
deps,
tok,
apiUrl,
`/api/projects/${projectId}/connection`,
));

const config: BoolConfig = {
projectId: conn.projectId,
Expand Down Expand Up @@ -365,7 +371,28 @@ async function cmdCreate(
);
deps.log(`Created project "${project.name ?? name}" (${project.id}).`);

// 2. Scaffold the todo app into dir.
// 2. Confirm the project can actually be developed against BEFORE writing any
// files. Local dev requires the gateway runtime; a non-gateway (v1)
// project 409s here. Checking now means a v1 project fails with a clear
// message and leaves no half-scaffolded folder behind (previously we
// scaffolded first and only discovered the problem afterward).
let conn: Connection;
try {
conn = await apiJson<Connection>(
deps,
tok,
apiUrl,
`/api/projects/${project.id}/connection`,
);
} catch (e) {
throw new CliError(
`${e instanceof Error ? e.message : String(e)}\n` +
`Project ${project.id} was created but can't be used for local development, ` +
`so nothing was scaffolded.`,
);
}

// 3. Scaffold the todo app into dir.
mkdirSync(dir, { recursive: true });
const files = todoTemplate(name);
for (const [rel, content] of Object.entries(files)) {
Expand All @@ -380,11 +407,19 @@ async function cmdCreate(
// Everything below runs inside the new project dir.
const sub: CliDeps = { ...deps, cwd: dir };

// 3. Write bool.config.json + .env.bool into the project dir.
const { config } = await writeConfigAndKey(project.id, apiUrl, tok, DEFAULT_TYPES_PATH, sub);
// 4. Write bool.config.json + .env.bool into the project dir, reusing the
// connection descriptor we already fetched in step 2.
const { config } = await writeConfigAndKey(
project.id,
apiUrl,
tok,
DEFAULT_TYPES_PATH,
sub,
conn,
);
deps.log(`Linked ${CONFIG_FILE} to project ${project.id}.`);

// 4. Declare the todos entity so the table exists, then refresh types.
// 5. Declare the todos entity so the table exists, then refresh types.
// If this fails the app has no data — don't ship a broken deploy; tell the
// user to re-push once the cause is fixed.
const pushCode = await cmdEntitiesPush(flags, sub);
Expand All @@ -395,7 +430,7 @@ async function cmdCreate(
}
await pullTypes(config, tok, sub);

// 5. Optionally publish.
// 6. Optionally publish.
if (flags.deploy) {
await cmdDeploy(flags, sub);
} else {
Expand Down
8 changes: 5 additions & 3 deletions src/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
// `todos` entity is PUBLIC (one shared list, no per-user isolation) so the
// deployed app works for any visitor with no sign-in.

// Keep in sync with the CLI's own version so the scaffolded app pulls the
// matching client. Injected into package.json at scaffold time.
export const TEMPLATE_BOOL_SDK_VERSION = "0.2.0-next.16";
// The bool-sdk version the scaffolded app pins, injected into its package.json.
// This must be an ALREADY-PUBLISHED version (the scaffold runs `npm install`),
// so it tracks the latest published release, not this in-flight package.json
// version — they can differ by the release currently being cut.
export const TEMPLATE_BOOL_SDK_VERSION = "0.2.0-next.17";

function packageJson(name: string): string {
return (
Expand Down
Loading