Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
10 changes: 8 additions & 2 deletions create-db-worker/src/delete-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from 'cloudflare:work

type Params = {
projectID: string;
ttlSeconds?: number;
};

type Env = {
Expand All @@ -10,13 +11,18 @@ type Env = {

export class DeleteDbWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep): Promise<void> {
const { projectID } = event.payload;
const { projectID, ttlSeconds } = event.payload;

if (!projectID) {
throw new Error('No projectID provided.');
}

await step.sleep('wait 24 hours', '24 hours');
const effectiveTtlSeconds =
typeof ttlSeconds === 'number' && Number.isFinite(ttlSeconds) && ttlSeconds > 0
? Math.floor(ttlSeconds)
: 24 * 60 * 60;
Comment thread
aidankmcalister marked this conversation as resolved.
Outdated

await step.sleep(`wait ${effectiveTtlSeconds} seconds`, `${effectiveTtlSeconds} seconds`);

const res = await fetch(`https://api.prisma.io/v1/projects/${projectID}`, {
method: 'DELETE',
Expand Down
16 changes: 14 additions & 2 deletions create-db-worker/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export default {
analytics?: { eventName?: string; properties?: Record<string, unknown> };
userAgent?: string;
source?: 'programmatic' | 'cli';
ttlSeconds?: number;
};

let body: CreateDbBody = {};
Expand All @@ -142,7 +143,16 @@ export default {
return new Response('Invalid JSON body', { status: 400 });
}

const { region, name, analytics: analyticsData, userAgent, source } = body;
const { region, name, analytics: analyticsData, userAgent, source, ttlSeconds } = body;

const parsedTtlSeconds =
typeof ttlSeconds === 'number' && Number.isFinite(ttlSeconds)
? Math.floor(ttlSeconds)
: undefined;

if (ttlSeconds !== undefined && (!parsedTtlSeconds || parsedTtlSeconds <= 0)) {
return new Response('Invalid ttlSeconds in request body', { status: 400 });
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Apply stricter rate limiting for programmatic requests
if (source === 'programmatic') {
Expand Down Expand Up @@ -211,7 +221,9 @@ export default {
const response = JSON.parse(prismaText);
const projectID = response.data ? response.data.id : response.id;

const workflowPromise = env.DELETE_DB_WORKFLOW.create({ params: { projectID } });
const workflowPromise = env.DELETE_DB_WORKFLOW.create({
params: { projectID, ttlSeconds: parsedTtlSeconds },
});

const analyticsPromise = env.CREATE_DB_DATASET.writeDataPoint({
blobs: ['database_created'],
Expand Down
21 changes: 21 additions & 0 deletions create-db/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ npx create-db regions # List available regions
| `--interactive` | `-i` | Interactive mode to select a region |
| `--json` | `-j` | Output machine-readable JSON |
| `--env <path>` | `-e` | Write DATABASE_URL and CLAIM_URL to specified .env file |
| `--ttl <duration>` | `-t` | Custom database TTL (`30m`, `1h` ... `24h`) |
| `--copy` | `-c` | Copy connection string to clipboard |
| `--quiet` | `-q` | Output only the connection string |
| `--open` | `-o` | Open claim URL in browser |
| `--help` | `-h` | Show help message |
| `--version` | | Show version |

Expand Down Expand Up @@ -72,9 +76,26 @@ npx create-db -j
npx create-db --env .env
npx create-db -e .env.local

# Set custom TTL
npx create-db --ttl 1h
npx create-db -t 12h

# Copy connection string to clipboard
npx create-db --copy
npx create-db -c

# Only print connection string
npx create-db --quiet
npx create-db -q

# Open claim URL in browser
npx create-db --open
npx create-db -o

# Combine flags
npx create-db -r eu-central-1 -j
npx create-db -i -e .env
npx create-db -t 24h -c -o

# List available regions
npx create-db regions
Expand Down
4 changes: 4 additions & 0 deletions create-db/__tests__/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ describe("CLI help and version", () => {
expect(result.all).toContain("--interactive");
expect(result.all).toContain("--json");
expect(result.all).toContain("--env");
expect(result.all).toContain("--ttl");
expect(result.all).toContain("--copy");
expect(result.all).toContain("--quiet");
expect(result.all).toContain("--open");
});

it("displays regions command help", async () => {
Expand Down
116 changes: 116 additions & 0 deletions create-db/__tests__/flags.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,102 @@ describe("CreateFlags schema", () => {
});
});

describe("ttl field", () => {
it("accepts valid ttl strings", () => {
const validTtls = ["30m", "1h", "6h", "12h", "24h"];

for (const ttl of validTtls) {
const result = CreateFlags.safeParse({ ttl });
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.ttl).toBe(ttl);
}
}
});

it("passes through ttl strings for command-level validation", () => {
const ttlInputs = ["25h", "7d", "45s", "one-hour", "24"];

for (const ttl of ttlInputs) {
const result = CreateFlags.safeParse({ ttl });
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.ttl).toBe(ttl);
}
}
});

it("coerces boolean ttl to empty string when value is missing", () => {
const result = CreateFlags.safeParse({ ttl: true });
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.ttl).toBe("");
}
});

it("allows undefined", () => {
const result = CreateFlags.safeParse({});
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.ttl).toBeUndefined();
}
});
});

describe("copy field", () => {
it("defaults to false", () => {
const result = CreateFlags.safeParse({});
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.copy).toBe(false);
}
});

it("accepts true", () => {
const result = CreateFlags.safeParse({ copy: true });
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.copy).toBe(true);
}
});
});

describe("quiet field", () => {
it("defaults to false", () => {
const result = CreateFlags.safeParse({});
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.quiet).toBe(false);
}
});

it("accepts true", () => {
const result = CreateFlags.safeParse({ quiet: true });
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.quiet).toBe(true);
}
});
});

describe("open field", () => {
it("defaults to false", () => {
const result = CreateFlags.safeParse({});
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.open).toBe(false);
}
});

it("accepts true", () => {
const result = CreateFlags.safeParse({ open: true });
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.open).toBe(true);
}
});
});

describe("userAgent field", () => {
it("accepts custom user agent string", () => {
const result = CreateFlags.safeParse({ userAgent: "myapp/1.0.0" });
Expand All @@ -137,6 +233,10 @@ describe("CreateFlags schema", () => {
interactive: true,
json: false,
env: ".env.local",
ttl: "12h",
copy: true,
quiet: false,
open: true,
userAgent: "test/2.0.0",
};

Expand All @@ -148,6 +248,10 @@ describe("CreateFlags schema", () => {
interactive: true,
json: false,
env: ".env.local",
ttl: "12h",
copy: true,
quiet: false,
open: true,
userAgent: "test/2.0.0",
});
}
Expand All @@ -161,6 +265,10 @@ describe("CreateFlags schema", () => {
expect(result.data.interactive).toBe(false);
expect(result.data.json).toBe(false);
expect(result.data.env).toBeUndefined();
expect(result.data.ttl).toBeUndefined();
expect(result.data.copy).toBe(false);
expect(result.data.quiet).toBe(false);
expect(result.data.open).toBe(false);
expect(result.data.userAgent).toBeUndefined();
}
});
Expand All @@ -173,6 +281,10 @@ describe("CreateFlags schema", () => {
interactive: false,
json: true,
env: ".env",
ttl: "24h",
copy: true,
quiet: false,
open: true,
userAgent: "test/1.0",
};

Expand All @@ -181,6 +293,10 @@ describe("CreateFlags schema", () => {
expect(result.interactive).toBe(input.interactive);
expect(result.json).toBe(input.json);
expect(result.env).toBe(input.env);
expect(result.ttl).toBe(input.ttl);
expect(result.copy).toBe(input.copy);
expect(result.quiet).toBe(input.quiet);
expect(result.open).toBe(input.open);
expect(result.userAgent).toBe(input.userAgent);
});
});
Expand Down
25 changes: 25 additions & 0 deletions create-db/__tests__/ttl.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { describe, it, expect } from "vitest";
import { parseTtlToSeconds } from "../src/utils/ttl.js";

describe("parseTtlToSeconds", () => {
it("parses supported ttl values", () => {
expect(parseTtlToSeconds("30m")).toBe(1800);
expect(parseTtlToSeconds("1h")).toBe(3600);
expect(parseTtlToSeconds("6h")).toBe(21600);
expect(parseTtlToSeconds("24h")).toBe(86400);
});

it("is case-insensitive", () => {
expect(parseTtlToSeconds("2H")).toBe(7200);
expect(parseTtlToSeconds("24H")).toBe(86400);
});

it("rejects values outside the allowed range", () => {
expect(parseTtlToSeconds("")).toBeNull();
expect(parseTtlToSeconds("0h")).toBeNull();
expect(parseTtlToSeconds("25h")).toBeNull();
expect(parseTtlToSeconds("7d")).toBeNull();
expect(parseTtlToSeconds("45s")).toBeNull();
expect(parseTtlToSeconds("abc")).toBeNull();
});
});
6 changes: 3 additions & 3 deletions create-db/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@
"@clack/prompts": "^0.11.0",
"@orpc/server": "^1.12.2",
"dotenv": "^17.2.3",
"execa": "^9.6.1",
"picocolors": "^1.1.1",
"terminal-link": "^5.0.0",
"trpc-cli": "^0.12.1",
"zod": "^4.1.13",
"execa": "^9.6.1"
"trpc-cli": "^0.12.4",
"zod": "^4.1.13"
},
"publishConfig": {
"access": "public"
Expand Down
38 changes: 37 additions & 1 deletion create-db/src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,39 @@
import { createDbCli } from "./index.js";

createDbCli().run();
type OptionLike = {
flags?: string;
long?: string;
};

type CommandLike = {
options?: OptionLike[];
commands?: CommandLike[];
};

function simplifyHelpFlags(command: CommandLike) {
for (const option of command.options ?? []) {
if (!option.flags) {
continue;
}

option.flags = option.flags
.replace(" [boolean]", "")
.replace(" [string]", " [value]")
.replace(" <string>", " <value>");

if (option.long === "--ttl" || option.flags.includes("--ttl")) {
option.flags = option.flags
.replace("<value>", "<duration>")
.replace("[value]", "[duration]");
}
}

for (const subcommand of command.commands ?? []) {
simplifyHelpFlags(subcommand);
}
}

const cli = createDbCli();
const program = cli.buildProgram() as unknown as CommandLike;
simplifyHelpFlags(program);
void cli.run(undefined, program as never);
Loading
Loading