From e197bfdd6767a78d6a82c5497cb39fba415e414f Mon Sep 17 00:00:00 2001 From: George MacKerron Date: Fri, 5 Apr 2024 11:08:17 +0100 Subject: [PATCH 1/2] Added README.md to JSR package --- dist/jsr/README.md | 287 ++++++++++++++++++++++++++++++++++++++++++++ dist/npm/DEVELOP.md | 14 ++- export.sh | 2 +- 3 files changed, 298 insertions(+), 5 deletions(-) create mode 100644 dist/jsr/README.md diff --git a/dist/jsr/README.md b/dist/jsr/README.md new file mode 100644 index 0000000..1cdb3e9 --- /dev/null +++ b/dist/jsr/README.md @@ -0,0 +1,287 @@ +# @neondatabase/serverless [BETA] + +`@neondatabase/serverless` is [Neon](https://neon.tech)'s PostgreSQL driver for JavaScript and TypeScript. It's: + +* **Low-latency**, thanks to [message pipelining](https://neon.tech/blog/quicker-serverless-postgres) and other optimizations +* **Ideal for serverless/edge** deployment, using https and WebSockets in place of TCP +* **A drop-in replacement** for [node-postgres](https://node-postgres.com/), aka [`pg`](https://www.npmjs.com/package/pg) (on which it's based) + + +## Get started + + +### Install it + +Install it with your preferred JavaScript package manager. For example: + +```bash +npm install @neondatabase/serverless +``` + +Using TypeScript? No worries: types are included. + + +### Configure it + +Get your connection string from the [Neon console](https://console.neon.tech/) and set it as an environment variable. Something like: + +``` +DATABASE_URL=postgres://username:password@host.neon.tech/neondb +``` + + +### Use it + +For one-shot queries, use the `neon` function. For instance: + +```javascript +import { neon } from '@neondatabase/serverless'; +const sql = neon(process.env.DATABASE_URL); + +const [post] = await sql`SELECT * FROM posts WHERE id = ${postId}`; +// `post` is now { id: 12, title: 'My post', ... } (or undefined) +``` + +Note: interpolating `${postId}` here is [safe from SQL injection](https://neon.tech/blog/sql-template-tags). + + +### Deploy it + +Turn this example into a complete API endpoint deployed on [Vercel Edge Functions](https://vercel.com/docs/concepts/functions/edge-functions) at `https://myapp.vercel.dev/api/post?postId=123` by following two simple steps: + +1. Create a new file `api/post.ts`: + +```javascript +import { neon } from '@neondatabase/serverless'; +const sql = neon(process.env.DATABASE_URL); + +export default async (req: Request, ctx: any) => { + // get and validate the `postId` query parameter + const postId = parseInt(new URL(req.url).searchParams.get('postId'), 10); + if (isNaN(postId)) return new Response('Bad request', { status: 400 }); + + // query and validate the post + const [post] = await sql`SELECT * FROM posts WHERE id = ${postId}`; + if (!post) return new Response('Not found', { status: 404 }); + + // return the post as JSON + return new Response(JSON.stringify(post), { + headers: { 'content-type': 'application/json' } + }); +} + +export const config = { + runtime: 'edge', + regions: ['iad1'], // specify the region nearest your Neon DB +}; +``` + +2. Test and deploy + +```bash +npm install -g vercel # install vercel CLI +npx vercel env add DATABASE_URL # paste Neon connection string, select all environments +npx vercel dev # check working locally, then ... +npx vercel deploy +``` + +The `neon` query function has a few [additional options](CONFIG.md). + + +## Sessions, transactions, and node-postgres compatibility + +A query using the `neon` function, as shown above, is carried by an https [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) request. + +This should work — and work fast — from any modern JavaScript environment. But you can only send one query at a time this way: sessions and transactions are not supported. + + +### `transaction()` + +Multiple queries can be issued via fetch request within a single, non-interactive transaction by using the `transaction()` function. This is exposed as a property on the query function. + +For example: + +```javascript +import { neon } from '@neondatabase/serverless'; +const sql = neon(process.env.DATABASE_URL); +const showLatestN = 10; + +const [posts, tags] = await sql.transaction([ + sql`SELECT * FROM posts ORDER BY posted_at DESC LIMIT ${showLatestN}`, + sql`SELECT * FROM tags`, +]); +``` + +There are some [additional options](CONFIG.md) when using `transaction()`. + + +### `Pool` and `Client` + +Use the `Pool` or `Client` constructors, instead of the functions described above, when you need: + +* **session or interactive transaction support**, and/or + +* **compatibility with node-postgres**, which supports query libraries like [Kysely](https://kysely.dev/) or [Zapatos](https://jawj.github.io/zapatos/). + +Queries using `Pool` and `Client` are carried by WebSockets. There are **two key things** to know about this: + +1. **In Node.js** and some other environments, there's no built-in WebSocket support. In these cases, supply a WebSocket constructor function. + +2. **In serverless environments** such as Vercel Edge Functions or Cloudflare Workers, WebSocket connections can't outlive a single request. + + That means `Pool` or `Client` objects must be connected, used and closed **within a single request handler**. Don't create them outside a request handler; don't create them in one handler and try to reuse them in another; and to avoid exhausting available connections, don't forget to close them. + +These points are demonstrated in the examples below. + + +### API + +* **The full API guide** to `Pool` and `Client` can be found in the [node-postgres docs](https://node-postgres.com/). + +* There are a few [additional configuration options](CONFIG.md) that apply to `Pool` and `Client` here. + + +## Example: Node.js with `Pool.connect()` + +In Node.js, it takes two lines to configure WebSocket support. For example: + +```javascript +import { Pool, neonConfig } from '@neondatabase/serverless'; + +import ws from 'ws'; +neonConfig.webSocketConstructor = ws; // <-- this is the key bit + +const pool = new Pool({ connectionString: process.env.DATABASE_URL }); +pool.on('error', err => console.error(err)); // deal with e.g. re-connect +// ... + +const client = await pool.connect(); + +try { + await client.query('BEGIN'); + const { rows: [{ id: postId }] } = await client.query('INSERT INTO posts (title) VALUES ($1) RETURNING id', ['Welcome']); + await client.query('INSERT INTO photos (post_id, url) VALUES ($1, $2)', [postId, 's3.bucket/photo/url']); + await client.query('COMMIT'); + +} catch (err) { + await client.query('ROLLBACK'); + throw err; + +} finally { + client.release(); +} + +// ... +await pool.end(); +``` + +Other WebSocket libraries are available. For example, you could replace `ws` in the above example with `undici`: + +```typescript +import { WebSocket } from 'undici'; +neonConfig.webSocketConstructor = WebSocket; +``` + + +## Example: Vercel Edge Function with `Pool.query()` + +We can rewrite the Vercel Edge Function shown above (under the heading 'Deploy it') to use `Pool`, as follows: + +```javascript +import { Pool } from '@neondatabase/serverless'; + +// *don't* create a `Pool` or `Client` here, outside the request handler + +export default async (req: Request, ctx: any) => { + // create a `Pool` inside the request handler + const pool = new Pool({ connectionString: process.env.DATABASE_URL }); + + // get and validate the `postId` query parameter + const postId = parseInt(new URL(req.url).searchParams.get('postId'), 10); + if (isNaN(postId)) return new Response('Bad request', { status: 400 }); + + // query and validate the post + const [post] = await pool.query('SELECT * FROM posts WHERE id = $1', [postId]); + if (!post) return new Response('Not found', { status: 404 }); + + // end the `Pool` inside the same request handler + // (unlike `await`, `ctx.waitUntil` won't hold up the response) + ctx.waitUntil(pool.end()); + + // return the post as JSON + return new Response(JSON.stringify(post), { + headers: { 'content-type': 'application/json' } + }); +} + +export const config = { + runtime: 'edge', + regions: ['iad1'], // specify the region nearest your Neon DB +}; +``` + +Note: we don't actually use the pooling capabilities of `Pool` in this example. But it's slightly briefer than using `Client` and, because `Pool.query` is designed for one-shot queries, we may in future automatically route these queries over https for lower latency. + + +## Example: Vercel Edge Function with `Client` + +Using `Client` instead, the example looks like this: + +```javascript +import { Client } from '@neondatabase/serverless'; + +// don't create a `Pool` or `Client` here, outside the request handler + +export default async (req: Request, ctx: any) => { + // create a `Client` inside the request handler + const client = new Client(process.env.DATABASE_URL); + await client.connect(); + + // get and validate the `postId` query parameter + const postId = parseInt(new URL(req.url).searchParams.get('postId'), 10); + if (isNaN(postId)) return new Response('Bad request', { status: 400 }); + + // query and validate the post + const [post] = await client.query('SELECT * FROM posts WHERE id = $1', [postId]); + if (!post) return new Response('Not found', { status: 404 }); + + // end the `Client` inside the same request handler + // (unlike `await`, `ctx.waitUntil` won't hold up the response) + ctx.waitUntil(client.end()); + + // return the post as JSON + return new Response(JSON.stringify(post), { + headers: { 'content-type': 'application/json' } + }); +} + +export const config = { + runtime: 'edge', + regions: ['iad1'], // specify the region nearest your Neon DB +}; +``` + +## More examples + +These repos show how to use `@neondatabase/serverless` with a variety of environments and tools: + +* [Raw SQL + Vercel Edge Functions](https://github.com/neondatabase/neon-vercel-rawsql) +* [Raw SQL via https + Vercel Edge Functions](https://github.com/neondatabase/neon-vercel-http) +* [Raw SQL + Cloudflare Workers](https://github.com/neondatabase/serverless-cfworker-demo) +* [Kysely + Vercel Edge Functions](https://github.com/neondatabase/neon-vercel-kysely) +* [Zapatos + Vercel Edge Functions](https://github.com/neondatabase/neon-vercel-zapatos) + + +## Bring your own Postgres database + +This package comes configured to connect to a Neon database. But you can also use it to connect to your own Postgres instances if you [run your own WebSocket proxy](DEPLOY.md). + + +## Open-source + +This code is released under the [MIT license](LICENSE). + + +## Feedback and support + +Please visit [Neon Community](https://community.neon.tech/) or [Support](https://neon.tech/docs/introduction/support). diff --git a/dist/npm/DEVELOP.md b/dist/npm/DEVELOP.md index 584017e..b69c017 100644 --- a/dist/npm/DEVELOP.md +++ b/dist/npm/DEVELOP.md @@ -2,17 +2,23 @@ The code is at https://github.com/neondatabase/serverless. Most of the interesting stuff is in `shims/net/index.ts` and the `export/` folder. -* To update the npm package: +- To update the npm & jsr package: ```bash npm run export cd dist/npm npm version patch # or minor or major npm publish + +# Copy npm version +jq --arg v "$(jq -r .version dist/npm/package.json)" '.version = $v' dist/jsr/jsr.json > dist/jsr/jsr.json.tmp && mv dist/jsr/jsr.json.tmp dist/jsr/jsr.json + +# Publish jsr package +npx jsr publish ``` -* To run or deploy the simple test app on Cloudflare, create a `.dev.vars` file containing `NEON_DB_URL=postgres://connection_string`, run `npx wrangler dev --local` or `npx wrangler publish`. +- To run or deploy the simple test app on Cloudflare, create a `.dev.vars` file containing `NEON_DB_URL=postgres://connection_string`, run `npx wrangler dev --local` or `npx wrangler publish`. -* To run the latencies test app in a browser, create a `.dev.vars` file as above, run `npm run browser` and visit `http://localhost:7070/dist/browser/`. To include debug output and avoid minification, use `npm run browserDebug` instead. +- To run the latencies test app in a browser, create a `.dev.vars` file as above, run `npm run browser` and visit `http://localhost:7070/dist/browser/`. To include debug output and avoid minification, use `npm run browserDebug` instead. -* To run the latencies test app in node, create a `.dev.vars` file as above and run `npm run node`. To include debug output and avoid minification, use `npm run nodeDebug` instead. +- To run the latencies test app in node, create a `.dev.vars` file as above and run `npm run node`. To include debug output and avoid minification, use `npm run nodeDebug` instead. diff --git a/export.sh b/export.sh index fdb9cff..5be7b08 100755 --- a/export.sh +++ b/export.sh @@ -56,7 +56,7 @@ cp dist/npm/index.d.ts dist/jsr/ echo "/// " > dist/jsr/index.js cat dist/npm/index.mjs >> dist/jsr/index.js -cp LICENSE dist/jsr/ +cp LICENSE README.md dist/jsr/ # Note: --keep-names adds about 10KB to the bundle size, but it gives us error From 4b3fcfa398261ffaf4cb2e3dc3b91d8bd8ca23c9 Mon Sep 17 00:00:00 2001 From: George MacKerron Date: Fri, 5 Apr 2024 11:22:28 +0100 Subject: [PATCH 2/2] Bump JSR patch version --- dist/jsr/jsr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/jsr/jsr.json b/dist/jsr/jsr.json index a858ed8..f83f225 100644 --- a/dist/jsr/jsr.json +++ b/dist/jsr/jsr.json @@ -1,8 +1,8 @@ { "name": "@neon/serverless", - "version": "0.9.0", + "version": "0.9.1", "exports": "./index.js", "imports": { "pg": "npm:@types/pg@8.6.6" } -} +} \ No newline at end of file