Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b4634be
add support to JSON-based schema seed
atilafassina May 22, 2025
78f88e6
add support for `schemaPath` on vite-plugin
atilafassina May 22, 2025
3381615
add changeset
atilafassina May 22, 2025
17c69c2
update tanstack example
atilafassina May 22, 2025
2455f50
improve error message for bad schema
atilafassina May 23, 2025
0a7a873
add test for schema validation
atilafassina Jun 19, 2025
e7cbf22
move Zod to `package.dependencies` instead of `devDependencies`
atilafassina Jun 19, 2025
6fd46fe
move Zod to `package.dependencies` instead of `devDependencies`
atilafassina Jun 19, 2025
ddb4541
improve queries logic
atilafassina Jun 19, 2025
c84d061
update `pnpm-lock.yaml`
atilafassina Jun 19, 2025
24fa369
refactor: use SQL file instead of JSON
atilafassina Jun 25, 2025
f1ba604
adjust the configuration object on vite-plugin
atilafassina Jun 25, 2025
0f14684
update changelogs
atilafassina Jun 25, 2025
3beb140
update docs
atilafassina Jun 25, 2025
0727611
cleanup code
atilafassina Jun 26, 2025
7bd481d
rename `onCreate` to `seed`
atilafassina Jun 26, 2025
ee674f9
add referrer to examples
atilafassina Jun 26, 2025
77fc18a
update release tag
atilafassina Jun 26, 2025
4bd6fb0
set tanstack example to private
atilafassina Jun 26, 2025
4a6ebae
update lockfile
atilafassina Jun 26, 2025
06abf77
remove unused schema file
atilafassina Jun 26, 2025
6768f0b
add tests, fix test run for CI
atilafassina Jun 26, 2025
6aacd91
remove unused import
atilafassina Jun 26, 2025
12b009c
adjust JSDoc and fix typos (`referer`/`referrer`)
atilafassina Jun 26, 2025
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
5 changes: 5 additions & 0 deletions .changeset/quick-loops-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@neondatabase/vite-plugin-postgres": minor
---

Add `onCreate` option to seed database with SQL script on initialization. This option accepts a path to a SQL file that will be executed after the database is created.
9 changes: 9 additions & 0 deletions .changeset/sweet-rabbits-shake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"neondb": minor
---

Add support for seeding a SQL script during database initialization. You can now provide a path to a SQL file that will be executed right after the database is created. Either via the prompt or with a command flag.

| Option | Description |
| ------------ | --------------------------------------------------- |
| `-s, --seed` | Path to SQL file to execute after database creation |
7 changes: 6 additions & 1 deletion examples/tanstack-start/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import tsConfigPaths from "vite-tsconfig-paths";
export default defineConfig({
vite: {
plugins: [
postgresPlugin(),
postgresPlugin({
onCreate: {
type: "sql-script",
path: "./schema.sql",
},
}),
tsConfigPaths({
projects: ["./tsconfig.json"],
}),
Expand Down
1 change: 1 addition & 0 deletions examples/tanstack-start/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
"neondb": "workspace:*",
"typescript": "^5.8.2",
"vite-tsconfig-paths": "^5.1.4"
}
Expand Down
11 changes: 11 additions & 0 deletions examples/tanstack-start/schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name text
);

INSERT INTO users (name) VALUES
('John Lennon'),
('Paul McCartney'),
('George Harrison'),
('Ringo Starr'),
('George Martin');
1 change: 1 addition & 0 deletions packages/neondb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Options:
- `-y, --yes` Use defaults, skip prompts
- `-e, --env` Path to .env file (default: ./.env)
- `-k, --key` Env key for connection string (default: DATABASE_URL)
- `-s, --seed` Path to SQL file to execute after database creation
- `-h, --help` Show help

---
Expand Down
8 changes: 5 additions & 3 deletions packages/neondb/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
"neondb": "dist/cli.js"
},
"exports": {
"./json-schema": {
"import": "./dist/schema.json"
},
"./sdk": {
"import": "./dist/lib/instant-neon.js",
"types": "./dist/lib/instant-neon.d.ts"
Expand All @@ -45,13 +48,11 @@
"dry:run": "pnpm build && node dist/cli.js --yes"
},
"devDependencies": {
"@release-it/conventional-changelog": "10.0.0",
"@types/node": "22.13.10",
"@vitest/coverage-v8": "3.0.9",
"console-fail-test": "0.5.0",
"husky": "9.1.7",
"lint-staged": "15.5.0",
"release-it": "18.1.2",
"tsup": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:"
Expand All @@ -65,10 +66,11 @@
},
"dependencies": {
"@clack/prompts": "0.10.1",
"@neondatabase/serverless": "^1.0.0",
"dotenv": "^16.5.0",
"gradient-string": "^3.0.0",
"open": "^10.1.0",
"p-wait-for": "^5.0.2",
"zod": "^3.24.3"
"zod": "^3.25.0"
}
}
13 changes: 13 additions & 0 deletions packages/neondb/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ async function main() {
const {
env: flagEnvPath,
key: flagEnvKey,
sql: flagSeedPath,
yes: shouldUseDefaults,
} = getArgs();

Expand Down Expand Up @@ -93,6 +94,17 @@ async function main() {
if (!userInput.dotEnvKey) {
userInput.dotEnvKey = DEFAULTS.dotEnvKey;
log.step(messages.info.defaultEnvKey(userInput.dotEnvKey));
log.step(`using ${userInput.dotEnvKey} as the .env key`);
}
}

if (!flagSeedPath) {
userInput.seedPath = (await text({
message: messages.questions.seedPath,
})) as Defaults["seedPath"];

if (!userInput.seedPath) {
userInput.seedPath = DEFAULTS.seedPath;
}
}

Expand All @@ -103,6 +115,7 @@ async function main() {
dotEnvFile: userInput.dotEnvPath,
dotEnvKey: userInput.dotEnvKey,
referrer: "npm:neondb/cli",
seedPath: userInput.seedPath,
});
}
s.stop("Database generated!");
Expand Down
11 changes: 11 additions & 0 deletions packages/neondb/src/lib/instant-neon.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { randomUUID } from "node:crypto";
import { readFile } from "node:fs/promises";
import { log } from "@clack/prompts";
import { seedDatabase } from "./seed-database.js";
import { messages } from "./texts.js";
import { InstantNeonParams } from "./types.js";
import { createClaimableDatabase } from "./utils/create-db.js";
import { getPoolerString } from "./utils/format.js";
import { writeToEnv } from "./utils/fs.js";
import { LAUNCHPAD_URLS } from "./utils/urls.js";

/**
* Creates an instant Postgres connection string from Instagres by Neon
* if not already set in the specified .env file.
Expand All @@ -16,6 +19,7 @@ export const instantNeon = async ({
dotEnvFile = ".env",
dotEnvKey = "DATABASE_URL",
referrer = "unknown",
seedPath = undefined,
}: InstantNeonParams) => {
const dbId = randomUUID();
const claimExpiresAt = new Date(Date.now() + 3 * 24 * 60 * 60 * 1000);
Expand All @@ -27,6 +31,7 @@ export const instantNeon = async ({
);
const claimUrl = new URL(LAUNCHPAD_URLS.CLAIM_DATABASE(dbId));
log.step(messages.botCheck(createDbUrl.href));

const connString = await createClaimableDatabase(dbId, createDbUrl);
const poolerString = getPoolerString(connString);

Expand All @@ -45,6 +50,12 @@ export const instantNeon = async ({
log.success(messages.envSuccess(dotEnvFile, dotEnvKey));
log.info(messages.databaseGenerated(claimUrl.href));

if (seedPath) {
log.step("Pushing schema to database");
await seedDatabase(seedPath, connString);
log.success("Schema pushed to database");
}

return {
databaseUrl: connString,
poolerUrl: poolerString,
Expand Down
104 changes: 63 additions & 41 deletions packages/neondb/src/lib/neon-schema.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,67 @@
const neonColumnTypeStrings = [
"numeric",
"decimal",
"integer",
"bigint",
"smallint",
"real",
"double",
"serial",
"bigserial",
"char",
"varchar",
"text",
"date",
"time",
"timestamp",
"timestamptz",
"interval",
"boolean",
"bytea",
"inet",
"cidr",
"macaddr",
"point",
"line",
"lseg",
"box",
"path",
"polygon",
"circle",
"json",
"jsonb",
"array",
"uuid",
"money",
"xml",
] as const;
import { z } from "zod/v4";

export const neonColumnTypeOptions = neonColumnTypeStrings.map((type) => ({
value: type,
label: type,
}));
export const neonColumnTypeStrings = z.union([
z.literal("numeric"),
z.literal("decimal"),
z.literal("integer"),
z.literal("bigint"),
z.literal("smallint"),
z.literal("real"),
z.literal("double"),
z.literal("serial"),
z.literal("bigserial"),
z.literal("char"),
z.literal("varchar"),
z.literal("text"),
z.literal("date"),
z.literal("time"),
z.literal("timestamp"),
z.literal("timestamptz"),
z.literal("interval"),
z.literal("boolean"),
z.literal("bytea"),
z.literal("inet"),
z.literal("cidr"),
z.literal("macaddr"),
z.literal("point"),
z.literal("line"),
z.literal("lseg"),
z.literal("box"),
z.literal("path"),
z.literal("polygon"),
z.literal("circle"),
z.literal("json"),
z.literal("jsonb"),
z.literal("array"),
z.literal("uuid"),
z.literal("money"),
z.literal("xml"),
]);

export const awsRegionsSchema = z.union([
z.literal("us-east-1"),
z.literal("us-east-2"),
z.literal("us-west-2"),
z.literal("eu-central-1"),
z.literal("eu-west-1"),
z.literal("ap-southeast-1"),
z.literal("ap-northeast-1"),
]);

export const azureRegionsSchema = z.union([
z.literal("eastus"),
z.literal("eastus2"),
z.literal("westus"),
z.literal("westus2"),
z.literal("westus3"),
z.literal("northeurope"),
z.literal("westeurope"),
]);

export const neonRegionsSchema = z.object({
aws: awsRegionsSchema,
azure: azureRegionsSchema,
});

export const neonRegions = {
aws: [
Expand Down
14 changes: 14 additions & 0 deletions packages/neondb/src/lib/seed-database.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { neon } from "@neondatabase/serverless";
import { getSqlCommands } from "./utils/fs.js";

export async function seedDatabase(
schemaFilePath: string,
connectionString: string,
) {
const client = neon(connectionString);
const commands = getSqlCommands(schemaFilePath);

for (const command of commands) {
await client.query(command);
}
}
3 changes: 3 additions & 0 deletions packages/neondb/src/lib/texts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ ${url}
questions: {
dotEnvFilePath: `Enter the path to your environment file (default: ${DEFAULTS.dotEnvPath})`,
dotEnvKey: `Enter the key for the database connection string (default: ${DEFAULTS.dotEnvKey})`,
seedPath: `Enter the path to your seed (.sql) file (default: ${DEFAULTS.seedPath})`,
},

info: {
Expand All @@ -66,5 +67,7 @@ ${url}
failedToSaveConnectionString: "Failed to save connection string",
failedToSavePoolerString: "Failed to save pooler string",
failedToSaveEnvFile: "Failed to save .env file",
failedToParseJsonFile: (path: string) =>
`Failed to read or parse JSON file: ${path}`,
},
} as const;
6 changes: 3 additions & 3 deletions packages/neondb/src/lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { neonColumnTypeOptions } from "./neon-schema.js";
/**
* Parameters for configuring Instagres database connection
* @param {string} dotEnvFile - Path to the .env file where the connection string will be saved
* @param {string} dotEnvKey - Environment variable name to store the connection string
* @param {boolean} withPooler - Whether to use connection pooling
* @param {string} referer - referrer name for tracking
* @param {string} seedPath - Path to the `.sql` file to be pushed to the database
*/
export interface InstantNeonParams {
dotEnvFile?: string;
dotEnvKey?: string;
referrer?: string;
seedPath?: string | undefined;
}

export interface Defaults {
dotEnvPath: string;
dotEnvKey: string;
seedPath?: string | undefined;
}

export type NeonColumnTypes = (typeof neonColumnTypeOptions)[number]["value"];
12 changes: 11 additions & 1 deletion packages/neondb/src/lib/utils/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { type Defaults } from "../types.js";
export const DEFAULTS: Defaults = {
dotEnvPath: "./.env",
dotEnvKey: "DATABASE_URL",
seedPath: undefined,
};

export function getArgs() {
Expand All @@ -22,6 +23,10 @@ export function getArgs() {
type: "string",
short: "k",
},
sql: {
type: "string",
short: "s",
},
help: {
type: "boolean",
short: "h",
Expand All @@ -36,7 +41,12 @@ Usage: neondb [options]
Options:
-y, --yes Skip all prompts and use defaults
-e, --env Path to the .env file (default: "${DEFAULTS.dotEnvPath}")
-k, --key Key for the database connection string (default: "${DEFAULTS.dotEnvKey}")
-k, --key Key for the database connection string (default: "${
DEFAULTS.dotEnvKey
}")
-s, --sql Path to the seed (.sql) file (default: "${
DEFAULTS.seedPath || "none"
}")
-h, --help Show this help message
`);
process.exit(0);
Expand Down
22 changes: 22 additions & 0 deletions packages/neondb/src/lib/utils/format.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,26 @@
import { log } from "@clack/prompts";
import { type ZodError } from "zod/v4";

export function getPoolerString(connString: string) {
const [start, ...end] = connString.split(".");
return `${start}-pooler.${end.join(".")}`;
}

export function reportZodIssues(error: ZodError) {
error.issues.forEach((issue) => {
log.error(
issue.message +
" at " +
issue.path.reduce<string>(
(acc, curr: string | number | symbol, index) =>
acc +
(typeof curr === "string"
? index === 0
? curr
: `.${curr}`
: `[${String(curr)}]`),
"",
),
);
});
}
Loading