UNPKG

workers-qb

Version:

Zero dependencies Query Builder for Cloudflare Workers

528 lines (381 loc) 12.3 kB
# Basic Queries This section covers the fundamental query operations in `workers-qb`, including connecting to databases, table operations, and basic CRUD (Create, Read, Update, Delete) operations. ## Connecting to Databases Before you can start querying your database, you need to establish a connection using the appropriate `workers-qb` class for your database. ### Cloudflare D1 For Cloudflare D1, you'll use the `D1QB` class, passing your D1 database binding from your Worker environment: ```typescript import { D1QB } from 'workers-qb'; export interface Env { DB: D1Database; } export default { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { const qb = new D1QB(env.DB); // ... your queries using qb ... }, }; ``` ### Cloudflare Durable Objects For Cloudflare Durable Objects storage, use the `DOQB` class, passing the `DurableObjectStorage` instance: ```typescript import { DOQB } from 'workers-qb'; export class MyDurableObject extends DurableObject { async fetch(request: Request): Promise<Response> { const qb = new DOQB(this.storage.sql); // ... your queries using qb ... } } ``` ### PostgreSQL For PostgreSQL, use the `PGQB` class and instantiate the `pg.Client` with your database connection URL. Remember to install `pg` and enable Node compatibility in your `wrangler.toml`. ```typescript import { PGQB } from 'workers-qb'; import { Client } from 'pg'; export interface Env { DB_URL: string; } export default { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { const dbClient = new Client(env.DB_URL); const qb = new PGQB(dbClient); await qb.connect(); // Connect to PostgreSQL // ... your queries using qb ... ctx.waitUntil(qb.close()); // Close the connection when done }, }; ``` ## Table Operations `workers-qb` provides methods for creating and dropping database tables. ### Creating Tables Use the `createTable` method to define and create a new table. You need to specify the `tableName` and the `schema` as a string defining the table columns and their types. ```typescript import { D1QB, Raw } from 'workers-qb'; // ... (D1QB initialization) ... await qb.createTable({ tableName: 'users', schema: ` id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT UNIQUE NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP `, }).execute(); console.log('Table "users" created successfully.'); ``` You can also use the `ifNotExists: true` option to prevent errors if the table already exists: ```typescript await qb.createTable({ tableName: 'users', schema: ` id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT UNIQUE NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP `, ifNotExists: true, }).execute(); ``` ### Dropping Tables Use the `dropTable` method to remove a table from the database. Specify the `tableName` to be dropped. ```typescript import { D1QB } from 'workers-qb'; // ... (D1QB initialization) ... await qb.dropTable({ tableName: 'users', }).execute(); console.log('Table "users" dropped successfully.'); ``` You can use `ifExists: true` to avoid errors if the table doesn't exist: ```typescript await qb.dropTable({ tableName: 'users', ifExists: true, }).execute(); ``` ## Insert ### Insert One Use the `insert` method with a single data object to insert a new row into a table. ```typescript import { D1QB } from 'workers-qb'; // ... (D1QB initialization) ... type User = { id: number; name: string; email: string; }; const newUser = await qb.insert<User>({ tableName: 'users', data: { name: 'John Doe', email: 'john.doe@example.com', }, returning: ['id', 'name', 'email'], // Specify fields to return after insertion }).execute(); console.log('New user inserted:', newUser.results); ``` ### Insert Multiple To insert multiple rows efficiently, provide an array of data objects to the `insert` method. ```typescript import { D1QB } from 'workers-qb'; // ... (D1QB initialization) ... type User = { id: number; name: string; email: string; }; const newUsers = await qb.insert<User>({ tableName: 'users', data: [ { name: 'Jane Doe', email: 'jane.doe@example.com' }, { name: 'Peter Pan', email: 'peter.pan@example.com' }, ], returning: ['id', 'name', 'email'], }).execute(); console.log('New users inserted:', newUsers.results); ``` ### Insert Without Returning If you don't need to retrieve data after insertion, you can omit the `returning` option for a slightly more performant operation. ```typescript import { D1QB } from 'workers-qb'; // ... (D1QB initialization) ... await qb.insert({ tableName: 'users', data: { name: 'Anonymous User', email: 'anonymous@example.com', }, }).execute(); console.log('User inserted without returning data.'); ``` ### On Conflict - IGNORE Use `onConflict: 'IGNORE'` to skip insertion if a conflict occurs (e.g., due to a unique constraint). ```typescript import { D1QB } from 'workers-qb'; // ... (D1QB initialization) ... await qb.insert({ tableName: 'users', data: { email: 'john.doe@example.com', // Assuming 'email' is a unique key and already exists name: 'Duplicate User', }, onConflict: 'IGNORE', }).execute(); console.log('Insert attempted, conflict ignored if email exists.'); ``` ### On Conflict - REPLACE Use `onConflict: 'REPLACE'` to replace the existing row if a conflict occurs. ```typescript import { D1QB } from 'workers-qb'; // ... (D1QB initialization) ... await qb.insert({ tableName: 'users', data: { email: 'john.doe@example.com', // Assuming 'email' is a unique key and already exists name: 'Updated John Doe', // Will update the name if email exists }, onConflict: 'REPLACE', }).execute(); console.log('Insert attempted, row replaced if email exists.'); ``` ### On Conflict - UPSERT (UPDATE) For more complex conflict resolution, you can perform an UPSERT operation, updating specific columns if a conflict occurs. Use `onConflict` with an object to define the columns causing conflict, the data to update, and optional `where` conditions for the update. ```typescript import { D1QB, Raw } from 'workers-qb'; // Ensure Raw is imported here // ... (D1QB initialization) ... await qb.insert({ tableName: 'users', data: { email: 'john.doe@example.com', // Assuming 'email' is a unique key and already exists name: 'John Doe', login_count: 1, // New login, should increment if user exists }, onConflict: { column: 'email', // Column that might cause conflict data: { // Data to update on conflict login_count: new Raw('login_count + 1'), // Increment login_count using Raw SQL updated_at: new Raw('CURRENT_TIMESTAMP'), // Update timestamp }, }, }).execute(); console.log('Insert attempted, row updated (UPSERT) if email exists.'); ``` **Note:** `Raw` is used here to execute raw SQL functions like `login_count + 1` and `CURRENT_TIMESTAMP` within the `data` object for `onConflict`. ## Select ### Simple Select All The most basic select operation retrieves all columns and rows from a table. ```typescript import { D1QB } from 'workers-qb'; // ... (D1QB initialization) ... type User = { id: number; name: string; email: string; }; const allUsers = await qb.fetchAll<User>({ tableName: 'users', }).execute(); console.log('All users:', allUsers.results); ``` ### Select Specific Fields To retrieve only specific columns, use the `fields` option with an array of column names or a comma-separated string. ```typescript import { D1QB } from 'workers-qb'; // ... (D1QB initialization) ... type UserNameAndEmail = { name: string; email: string; }; const userNamesAndEmails = await qb.fetchAll<UserNameAndEmail>({ tableName: 'users', fields: ['name', 'email'], // Or fields: 'name, email' }).execute(); console.log('User names and emails:', userNamesAndEmails.results); ``` ### `fetchOne` Use `fetchOne` to retrieve a single row that matches the specified criteria. It's ideal for fetching records by ID or unique identifiers. ```typescript import { D1QB } from 'workers-qb'; // ... (D1QB initialization) ... type User = { id: number; name: string; email: string; }; const singleUser = await qb.fetchOne<User>({ tableName: 'users', where: { conditions: 'id = ?', params: 1, // Assuming you want to fetch user with ID 1 }, }).execute(); console.log('Single user:', singleUser.results); ``` ### `fetchAll` `fetchAll` retrieves multiple rows based on your query. It's used for fetching lists of data, potentially with filters and ordering. ```typescript import { D1QB } from 'workers-qb'; // ... (D1QB initialization) ... type User = { id: number; name: string; email: string; }; const activeUsers = await qb.fetchAll<User>({ tableName: 'users', where: { conditions: 'created_at > ?', params: new Date(Date.now() - 30 * 24 * 3600 * 1000).toISOString(), // Users created in last 30 days }, orderBy: 'created_at DESC', }).execute(); console.log('Active users:', activeUsers.results); ``` ## Update ### Simple Update Update rows in a table using the `update` method. Specify the `tableName`, the `data` to update (as an object), and `where` conditions to target specific rows. ```typescript import { D1QB } from 'workers-qb'; // ... (D1QB initialization) ... await qb.update({ tableName: 'users', data: { name: 'Updated Name', }, where: { conditions: 'email = ?', params: 'john.doe@example.com', }, }).execute(); console.log('User name updated.'); ``` ### Update Returning Fields To retrieve the updated rows, use the `returning` option. ```typescript import { D1QB } from 'workers-qb'; // ... (D1QB initialization) ... type User = { // Assuming User type includes id, name, email id: number; name: string; email: string; }; const updatedUser = await qb.update<User>({ tableName: 'users', data: { name: 'Corrected John Doe', }, where: { conditions: 'id = ?', params: 1, }, returning: ['id', 'name', 'email'], }).execute(); console.log('Updated user:', updatedUser.results); ``` ### Update Without Returning For performance optimization when you don't need the updated rows, omit the `returning` option. ```typescript import { D1QB } from 'workers-qb'; // ... (D1QB initialization) ... await qb.update({ tableName: 'users', data: { last_login: new Raw('CURRENT_TIMESTAMP'), // Update with current timestamp }, where: { conditions: 'email = ?', params: 'john.doe@example.com', }, }).execute(); console.log('User last login updated without returning data.'); ``` ## Delete ### Simple Delete Delete rows from a table using the `delete` method. Specify the `tableName` and `where` conditions to target rows for deletion. **Be cautious when using `delete` without `where` conditions as it will delete all rows in the table.** ```typescript import { D1QB } from 'workers-qb'; // ... (D1QB initialization) ... await qb.delete({ tableName: 'users', where: { conditions: 'email = ?', params: 'anonymous@example.com', }, }).execute(); console.log('Anonymous user deleted.'); ``` ### Delete Returning Fields To retrieve the deleted rows, use the `returning` option. ```typescript import { D1QB } from 'workers-qb'; // ... (D1QB initialization) ... type User = { // Assuming User type includes id, name, email id: number; name: string; email: string; }; const deletedUser = await qb.delete<User>({ tableName: 'users', where: { conditions: 'id = ?', params: 5, // Assuming you want to delete user with ID 5 }, returning: ['id', 'name', 'email'], }).execute(); console.log('Deleted user:', deletedUser.results); ``` ### Delete Without Returning For performance when you don't need the deleted rows, omit the `returning` option. ```typescript import { D1QB } from 'workers-qb'; // ... (D1QB initialization) ... await qb.delete({ tableName: 'users', where: { conditions: 'created_at < ?', params: new Date(Date.now() - 365 * 24 * 3600 * 1000).toISOString(), // Delete users created more than a year ago }, }).execute(); console.log('Old users deleted without returning data.'); ```