{"id":28855388,"url":"https://github.com/flux159/spanner-orm","last_synced_at":"2026-06-08T14:32:34.931Z","repository":{"id":295703301,"uuid":"986059477","full_name":"Flux159/spanner-orm","owner":"Flux159","description":"A typescript ORM for spanner \u0026 postgres","archived":false,"fork":false,"pushed_at":"2025-08-30T01:40:46.000Z","size":2471,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-08-30T02:26:17.926Z","etag":null,"topics":["orm","spanner-database","typescript-library"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/spanner-orm","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Flux159.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-05-19T03:42:30.000Z","updated_at":"2025-08-30T01:40:49.000Z","dependencies_parsed_at":"2025-07-04T18:24:21.950Z","dependency_job_id":"75e276ff-27c9-4894-8faf-71b8a6015e15","html_url":"https://github.com/Flux159/spanner-orm","commit_stats":null,"previous_names":["flux159/spanner-orm"],"tags_count":98,"template":false,"template_full_name":null,"purl":"pkg:github/Flux159/spanner-orm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flux159%2Fspanner-orm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flux159%2Fspanner-orm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flux159%2Fspanner-orm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flux159%2Fspanner-orm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Flux159","download_url":"https://codeload.github.com/Flux159/spanner-orm/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flux159%2Fspanner-orm/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34067348,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-08T02:00:07.615Z","response_time":111,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["orm","spanner-database","typescript-library"],"created_at":"2025-06-19T23:05:02.182Z","updated_at":"2026-06-08T14:32:34.917Z","avatar_url":"https://github.com/Flux159.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# spanner-orm\n\nA TypeScript ORM for Google Spanner \u0026 PostgreSQL, designed for Node.js and Bun. Inspired by Drizzle ORM, `spanner-orm` aims to provide a single, elegant object model for defining your schema and querying your data across both database systems.\n\n\u003e [!NOTE]\n\u003e spanner-orm was almost entirely written by prompting Gemini 2.5 Pro via Cline. See notes/ProjectRoadmap.md, TaskPrefix.md and the other notes to understand how.\n\nRead the published docs [here](https://flux159.github.io/spanner-orm) to get started.\n\n## Key Requirements \u0026 Design Goals\n\n`spanner-orm` is built to address the following key requirements for developers working with PostgreSQL and Google Spanner :\n\n- **Single Object Model for PostgreSQL \u0026 Spanner:** Supports both postgres \u0026 google spanner with a single object model, inspired by Drizzle. This allows you to define your schema once and use it across both database systems.\n- **Cross-Dialect Migrations:** Produces migrations for both postgres \u0026 spanner that can be run via a migrate command or via cli (specifically, the `spanner-orm-cli migrate` command), and also programmatically.\n- **Flexible Querying:** Can build queries with a type-safe query builder or fallback to SQL (for instance, using the `sql` template literal tag when needed).\n- **Dialect-Aware SQL:**\n  - **Spanner:** Supports Google SQL as the dialect for Spanner.\n  - **PostgreSQL/PGLite:** Uses almost equivalent SQL for PostgreSQL \u0026 PGLite.\n  - This allows users to leverage PostgreSQL for non-Spanner deployments, PGLite for local development or embedded applications, and Spanner for global-scale web apps, all from a unified codebase.\n- **Composable Schemas (Drizzle-Inspired):** Easily create and reuse schema components (e.g., common fields like `id`, `timestamps`, or base entity structures like `ownableResource`), promoting DRY (Don't Repeat Yourself) principles and leading to more maintainable and understandable data models.\n- **Rich Query Conditions:** Provides a comprehensive set of SQL condition functions (`eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `and`, `or`, `not`, etc.) for building expressive `WHERE` clauses in the query builder.\n\n## Core Features\n\n`spanner-orm` provides a comprehensive suite of features for modern data management:\n\n- **Unified Object Model:** Define schemas once for PostgreSQL (including Pglite) and Google Spanner using a Drizzle-inspired, composable syntax.\n- **Dialect-Specific Migrations:** Generate and execute precise DDL migrations for both PostgreSQL and Spanner via CLI or programmatically.\n- **Fluent Query API \u0026 Query Builder:** Enjoy a high-level fluent API (`db` object) for common database interactions, or use the powerful `QueryBuilder` for more granular control. Both support type-safe queries.\n- **Raw SQL Fallback:** Seamlessly integrate raw SQL queries using the `sql` template tag when needed.\n\n- **Optimized SQL for Each Dialect:**\n\n  - **Google Spanner:** Generates Google SQL, leveraging Spanner's unique features and syntax for optimal performance and compatibility.\n  - **PostgreSQL/Pglite:** Produces standard, highly compatible SQL, ensuring broad compatibility with PostgreSQL versions and Pglite.\n  - This dual-dialect approach empowers developers to use the right database for the right job—Pglite for ultra-lightweight local development or client-side applications, PostgreSQL for traditional server-based deployments, and Google Spanner for applications requiring web-scale, global distribution—all managed from a single, consistent codebase.\n\n- **Composable Schemas (Drizzle-Inspired):** Easily create and reuse schema components (e.g., for common fields like `id`, `createdAt`, `updatedAt`, or base entity structures like `ownableResource`), promoting DRY principles and highly maintainable data models.\n\n- **Dynamic Default Value Generation:** Supports dynamic default values at the application level via `$defaultFn()`, allowing you to execute functions (e.g., `crypto.randomUUID()`) to generate default values during insert operations.\n\n- **TypeScript First:** Built from the ground up with TypeScript, `spanner-orm` offers a robust, type-safe, and enjoyable developer experience, with strong type inference from your schema definitions to your query results.\n\n## Why spanner-orm?\n\nIn today's diverse application landscape, developers often need to target multiple database backends. You might start with Pglite for rapid prototyping or local-first applications, move to PostgreSQL for self-hosted or managed deployments, and eventually require the massive scalability and global consistency of Google Spanner. `spanner-orm` addresses the critical challenge of managing data models and queries across these different systems without rewriting your data access layer.\n\nThe Node.js \u0026 Bun ecosystem lacked a dedicated ORM that elegantly bridges PostgreSQL and Google Spanner with a single, consistent object model and a unified migration strategy. `spanner-orm` fills this gap by:\n\n- **Enabling a Single Codebase:** Define your schema and write your queries once. `spanner-orm` handles the dialect-specific SQL generation.\n- **Streamlining Development \u0026 Deployment:** Simplify the transition between local development (Pglite/Postgres), testing, and production environments (Spanner or Postgres).\n- **Reducing Complexity:** Abstract away the differences between Google SQL and PostgreSQL DDL/DML where possible, while still allowing access to dialect-specific features when needed.\n- **Providing a Productive API:** Offer a familiar and productive Drizzle-inspired API that TypeScript developers will appreciate.\n\n## Architecture Overview\n\n```mermaid\ngraph TD\n    A[User Code: Schema Definitions \u0026 Queries] --\u003e B{ORM Core};\n    B --\u003e C[Abstract Schema Representation];\n    C --\u003e D1[PostgreSQL SQL Generator];\n    C --\u003e D2[Google Spanner SQL Generator];\n    B --\u003e E[Query Builder AST];\n    E --\u003e D1;\n    E --\u003e D2;\n    D1 --\u003e F1[PostgreSQL Adapter pg, pglite];\n    D2 --\u003e F2[Spanner Adapter @google-cloud/spanner];\n    F1 --\u003e G1[PostgreSQL/Pglite Database];\n    F2 --\u003e G2[Google Spanner Database];\n\n    M[Migration Engine] --\u003e C;\n    M --\u003e D1;\n    M --\u003e D2;\n    M --\u003e F1;\n    M --\u003e F2;\n\n    style A fill:#fff,color:#000,stroke:#333,stroke-width:2px\n    style B fill:#fff,color:#000,stroke:#333,stroke-width:2px\n    style C fill:#lightgrey,stroke:#333,stroke-width:2px\n    style E fill:#lightgrey,stroke:#333,stroke-width:2px\n    style M fill:#fff,color:#000,stroke:#333,stroke-width:2px\n```\n\n## Getting Started\n\n1.  **Installation:**\n\nUsing NPM:\n\n```bash\nnpm install spanner-orm\n```\n\nOr using Bun:\n\n```\nbun install spanner-orm\n```\n\nAdditionally, `spanner-orm` relies on peer dependencies for the specific database clients. You'll need to install the ones corresponding to the databases you intend to use:\n\n- For **PostgreSQL**: `pg`\n- For **Google Cloud Spanner**: `@google-cloud/spanner`\n- For **PGLite** (optional, for local/embedded use): `@electric-sql/pglite`\n\nYou can install them like so:\n\nUsing NPM:\n\n```bash\nnpm install pg @google-cloud/spanner @electric-sql/pglite # Install all, or pick the ones you need\n```\n\nOr using Bun:\n\n```bash\nbun add pg @google-cloud/spanner @electric-sql/pglite # Install all, or pick the ones you need\n```\n\n2.  **Define your schema (Drizzle-Inspired):** Create a `schema.ts` (or similar) file. `spanner-orm` allows you to define your data model in a way that's familiar to Drizzle ORM users, emphasizing composability and type safety.\n\n    ```typescript\n    // src/schema.ts\n    import {\n      table,\n      text,\n      timestamp,\n      varchar,\n      integer,\n      boolean,\n      jsonb,\n      uuid,\n      index,\n      uniqueIndex,\n      sql,\n    } from \"spanner-orm\";\n\n    export const users = table(\"users\", {\n      id: uuid(\"id\").primaryKey(),\n      email: text(\"email\").notNull().unique(),\n      name: text(\"name\"),\n      // ... other user fields\n    });\n\n    // --- Shared Schema Components (Example: place in 'src/lib/sharedSchemas.ts') ---\n\n    // Common timestamp fields\n    export const timestamps = {\n      createdAt: timestamp(\"created_at\", { withTimezone: true })\n        .default(sql`CURRENT_TIMESTAMP`) // Use backticks for sql template literal\n        .notNull(),\n      updatedAt: timestamp(\"updated_at\", { withTimezone: true })\n        .default(sql`CURRENT_TIMESTAMP`) // Use backticks for sql template literal\n        .notNull(),\n    };\n\n    // Base model with ID and timestamps\n    export const baseModel = {\n      id: uuid(\"id\").primaryKey(),\n      ...timestamps,\n    };\n\n    // For resources that are owned by a user\n    export const ownableResource = {\n      ...baseModel,\n      userId: uuid(\"user_id\")\n        .notNull()\n        .references(() =\u003e users.id, { onDelete: \"cascade\" }),\n    };\n\n    // For resources that have visibility permissions\n    type VisibilityStatus = \"private\" | \"shared\" | \"public\"; // Example type for visibility\n\n    export const permissibleResource = {\n      ...ownableResource,\n      visibility: varchar(\"visibility\", { length: 10 }) // e.g., 'private', 'shared', 'public'\n        .default(\"private\")\n        .notNull()\n        .$type\u003cVisibilityStatus\u003e(), // For type assertion if needed\n    };\n\n    // --- Example Table: Uploads (using shared components) ---\n    export const uploads = table(\n      \"uploads\",\n      {\n        ...permissibleResource, // Includes id, createdAt, updatedAt, userId, visibility\n        gcsObjectName: text(\"gcs_object_name\").notNull(), // Full path in GCS\n        fileName: text(\"file_name\").notNull(),\n        fileType: text(\"file_type\").notNull(), // General type: 'image', 'audio', etc.\n        mimeType: text(\"mime_type\").notNull(), // Specific MIME type: 'image/jpeg'\n        size: integer(\"size\").notNull(), // File size in bytes\n        isProcessed: boolean(\"is_processed\").default(false),\n        metadata: jsonb(\"metadata\"), // Example for JSONB\n      },\n      (t) =\u003e ({\n        tableIndexes: [\n          // Renamed from indexes\n          index({ columns: [t.fileType.name] }), // t.fileType is already the column config\n          uniqueIndex({\n            name: \"uq_gcs_object\",\n            columns: [t.gcsObjectName.name], // t.gcsObjectName is already the column config\n          }),\n        ],\n      })\n    );\n\n    // Example with composite primary keys\n    export const myTableWithCompositePk = table(\n      \"my_table\",\n      {\n        partOne: text(\"part_one\").notNull(),\n        partTwo: integer(\"part_two\").notNull(),\n        data: text(\"data\"),\n      },\n      (t) =\u003e ({\n        // 't' provides access to the defined columns like t.partOne, t.partTwo\n        primaryKey: {\n          columns: [t.partOne.name, t.partTwo.name], // Use .name to get the actual column name string\n          name: \"my_table_pk\", // Optional constraint name\n        },\n      })\n    );\n\n    // You can then use these definitions to generate DDL or build queries.\n    ```\n\n    This example demonstrates how you can compose schemas from shared building blocks, similar to patterns used in Drizzle ORM, and showcases the usage of new features like `uuid()`.\n\n## Usage Examples\n\n### Managing Migrations with the CLI\n\nThe CLI also provides tools to manage your database schema migrations. Migration files are stored in the `./spanner-orm-migrations` directory by default.\n\n**1. Create a new migration:**\n\nThis command generates a single timestamped migration file (e.g., `YYYYMMDDHHMMSS-add-posts-table.ts`) containing dialect-specific `up` and `down` functions.\n\n```bash\n# Example: Create a migration file for adding a 'posts' table\nnpx spanner-orm-cli migrate create add-posts-table --schema ./dist/schema.js\n\n# This will create a file like:\n# ./spanner-orm-migrations/YYYYMMDDHHMMSS-add-posts-table.ts\n```\n\nThis single file will contain the necessary DDL for both PostgreSQL and Spanner, organized into their respective functions. A `latest.snapshot.json` file is also created/updated to track the current schema state for future migrations.\n\n**2. Apply pending migrations:**\n\nThis command applies all pending migrations to your database for the specified dialect.\n(Requires database connection to be configured - e.g., via environment variables, specific adapter setup needed).\n\n```bash\n# Apply latest migrations to postgres database (dialect determined by DB_DIALECT environment variable)\nnpx spanner-orm-cli migrate latest --schema ./dist/schema.js DB_DIALECT=postgres DATABASE_URL=postgresql://user:pass@host:port/db\n\n# Apply latest migrations for pglite - use postgres dialect with a local file for DATABASE_URL\nnpx spanner-orm-cli migrate latest --schema ./dist/schema.js DB_DIALECT=postgres DATABASE_URL=\"./spannerormtest.db\"\n\n# Example for Spanner\nnpx spanner-orm-cli migrate latest --schema ./dist/schema.js DB_DIALECT=spanner SPANNER_PROJECT_ID=my-gcp-project SPANNER_INSTANCE_ID=my-spanner-instance SPANNER_DATABASE_ID=my-spanner-database\n```\n\n**3. Revert the last applied migration:**\n\nThis command reverts the last applied migration (dialect determined by `DB_DIALECT` environment variable).\n\n```bash\n# Revert the last migration\nnpx spanner-orm-cli migrate down --schema ./dist/schema.js\n```\n\n_(Note: The migration CLI commands `migrate latest` and `migrate down` use environment variables such as `DB_DIALECT`, `DATABASE_URL` (for PG/Pglite), and Spanner-specific variables like `SPANNER_PROJECT_ID`, `SPANNER_INSTANCE_ID`, `SPANNER_DATABASE_ID` to connect to your database and apply/revert migrations.)_\n\n### Querying Examples with Fluent API (`db` object)\n\nThe `OrmClient` (typically instantiated as `db`) provides a fluent, chainable API for database interactions.\n\n```typescript\nimport { OrmClient, sql, users, posts, count } from \"spanner-orm\";\n// Assuming 'users' and 'posts' tables are defined in your schema (e.g., from './schema.ts')\n\n// Example: Initialize with a Pglite adapter in a module then reuse across app\n// import { PGlite } from \"@electric-sql/pglite\";\n// import { PgliteAdapter } from \"spanner-orm\"; // Or your specific adapter\n// const pglite = new PGlite(); // In-memory, can also pass folder path to persist to disk\n// const adapter = new PgliteAdapter(pglite);\n// const db = new OrmClient(adapter, \"postgres\"); // 'postgres' is the dialect for PGlite\n\nasync function runFluentExamples(db: OrmClient) {\n  // 1. Basic SELECT with WHERE, ORDER BY, LIMIT\n  const recentUsers = await db\n    .select({ id: users.id, name: users.name })\n    .from(users)\n    .where(\n      sql`${users.createdAt} \u003e ${new Date(Date.now() - 24 * 60 * 60 * 1000)}`\n    ) // Using raw sql for date comparison\n    .orderBy(users.createdAt, \"DESC\")\n    .limit(10);\n  console.log(\"Recent Users:\", recentUsers);\n  // recentUsers is typed as: Array\u003c{ id: string; name: string; }\u003e (assuming id is uuid/string)\n\n  // 2. Debugging Queries\n  // The `debug()` method can be chained into your fluent query to log the generated SQL and parameters.\n  // This is useful for understanding what the ORM is generating or troubleshooting issues.\n  const usersForDebugging = await db\n    .select({ id: users.id, name: users.name })\n    .from(users)\n    .where(eq(users.name, \"Alice\"))\n    .debug() // Logs SQL and parameters to the console\n    .limit(1);\n  console.log(\"User for debugging:\", usersForDebugging);\n  // The console output from .debug() would look something like:\n  // SQL: SELECT \"id\", \"name\" FROM \"users\" WHERE \"name\" = $1 LIMIT $2\n  // Parameters: [\"Alice\", 1]\n\n  // 2. INSERT a new user (with returning)\n  const [insertedUser] = await db\n    .insert(users)\n    .values({ name: \"Alice Wonderland\", email: \"alice@example.com\" })\n    .returning(); // Returns all columns by default\n  console.log(\"Inserted User:\", insertedUser);\n  // insertedUser is typed as InferModelType\u003ctypeof users\u003e\n  // e.g., { id: 'uuid-string', name: \"Alice Wonderland\", email: \"alice@example.com\", ... }\n\n  // Example of returning specific columns:\n  const [insertedId] = await db\n    .insert(users)\n    .values({ name: \"Bob The Second\", email: \"bob2@example.com\" })\n    .returning({ id: users.columns.id });\n  console.log(\"Inserted User ID:\", insertedId.id);\n\n  // 3. UPDATE an existing user (with returning)\n  const [updatedUser] = await db\n    .update(users)\n    .set({ name: \"Alice in Chains\" })\n    .where(sql`${users.email} = ${\"alice@example.com\"}`)\n    .returning({ name: users.columns.name, email: users.columns.email });\n  console.log(\"Updated User Info:\", updatedUser);\n  // updatedUser is typed as { name: string; email: string; }\n\n  // 4. SELECT with Eager Loading (include)\n  // Assuming 'posts' table has a 'userId' column referencing 'users.id'\n  // And your schema definitions correctly set up this relation for the ORM to understand.\n  const usersWithPosts = await db\n    .select({ id: users.id, userName: users.name })\n    .from(users)\n    .where(sql`${users.email} = ${\"alice@example.com\"}`)\n    .include({\n      posts: {\n        // 'posts' is the relation name defined in your schema or inferred\n        // relationTable: posts, // This might be needed if relation name isn't enough\n        options: { select: { title: true, content: true } },\n      },\n    });\n  console.log(\"User with Posts:\", JSON.stringify(usersWithPosts, null, 2));\n  // usersWithPosts would be typed, e.g.:\n  // Array\u003c{ id: string; userName: string; posts: Array\u003c{ title: string; content: string; }\u003e }\u003e\n\n  // 5. DELETE a user (with returning)\n  const [deletedUser] = await db\n    .deleteFrom(users)\n    .where(sql`${users.email} = ${\"alice@example.com\"}`)\n    .returning();\n  console.log(\"Deleted User:\", deletedUser);\n  // deletedUser contains all columns of the deleted user\n\n  // 6. Raw SQL Query\n  const rawUsersCount = await db.raw\u003c{ total_users: number }[]\u003e(\n    sql`SELECT COUNT(*) as total_users FROM ${users}`\n  );\n  console.log(\"Raw Users Count:\", rawUsersCount[0].total_users);\n\n  // 7. Transaction Example\n  await db.transaction(async (txDb) =\u003e {\n    const user = await txDb\n      .select({ id: users.id })\n      .from(users)\n      .where(sql`${users.name} = ${\"Bob The Builder\"}`)\n      .limit(1);\n\n    if (user.length \u003e 0) {\n      await txDb.insert(posts).values({\n        userId: user[0].id,\n        title: \"My New Post\",\n        content: \"Content here...\",\n      });\n    } else {\n      console.log(\"User Bob not found, post not created.\");\n    }\n  });\n  console.log(\"Transaction example completed.\");\n\n  // 8. Debugging with .sql and .params properties\n  // For more direct access to the SQL and parameters without executing the query,\n  // you can access the .sql and .params properties after preparing the query.\n  // Note: This is more aligned with QueryBuilder, but debug() on the fluent API is the primary way.\n  const preparedQuery = db\n    .select({ id: users.id })\n    .from(users)\n    .where(eq(users.name, \"Test User\"))\n    .prepare(); // Prepare the query\n\n  console.log(\"Prepared SQL:\", preparedQuery.sql);\n  console.log(\"Prepared Params:\", preparedQuery.params);\n  // You can then execute this prepared query using an adapter if needed:\n  // const result = await db.adapter.query(preparedQuery.sql, preparedQuery.params);\n}\n\n// To run these examples:\n// 1. Set up your adapter and db instance as shown above.\n// 2. Ensure your schema (users, posts tables) is defined and migrations are run.\n// runFluentExamples(db).catch(console.error);\n```\n\n### Using Condition Functions in `where` Clauses\n\n`spanner-orm` provides a rich set of functions to build expressive `WHERE` clauses, similar to Drizzle ORM. These functions can be imported from `spanner-orm` (or directly from `spanner-orm/core/functions` if you prefer more granular imports).\n\n```typescript\nimport {\n  OrmClient,\n  sql,\n  users,\n  posts,\n  eq,\n  ne,\n  gt,\n  gte,\n  lt,\n  lte,\n  and,\n  or,\n  not,\n} from \"spanner-orm\";\n// Assuming 'db' is an initialized OrmClient instance\n// Assuming 'users' and 'posts' tables are defined\n\nasync function runConditionExamples(db: OrmClient) {\n  // Example 1: Equal to (eq)\n  const alice = await db.select().from(users).where(eq(users.name, \"Alice\"));\n  console.log(\"Alice:\", alice);\n\n  // Example 2: Not equal to (ne)\n  const notAlice = await db.select().from(users).where(ne(users.name, \"Alice\"));\n  console.log(\"Not Alice:\", notAlice);\n\n  // Example 3: Greater than (gt)\n  // Assuming 'posts' has a 'viewCount' column\n  const popularPosts = await db\n    .select()\n    .from(posts)\n    .where(gt(posts.viewCount, 1000));\n  console.log(\"Popular Posts:\", popularPosts);\n\n  // Example 4: Greater than or equal to (gte)\n  const postsGte100 = await db\n    .select()\n    .from(posts)\n    .where(gte(posts.viewCount, 100));\n  console.log(\"Posts with \u003e= 100 views:\", postsGte100);\n\n  // Example 5: Less than (lt)\n  const unpopularPosts = await db\n    .select()\n    .from(posts)\n    .where(lt(posts.viewCount, 10));\n  console.log(\"Unpopular Posts:\", unpopularPosts);\n\n  // Example 6: Less than or equal to (lte)\n  const postsLte10 = await db\n    .select()\n    .from(posts)\n    .where(lte(posts.viewCount, 10));\n  console.log(\"Posts with \u003c= 10 views:\", postsLte10);\n\n  // Example 7: AND operator\n  const specificUser = await db\n    .select()\n    .from(users)\n    .where(and(eq(users.name, \"Bob\"), eq(users.email, \"bob@example.com\")));\n  console.log(\"Specific User (Bob):\", specificUser);\n\n  // Example 8: OR operator\n  const usersAliceOrBob = await db\n    .select()\n    .from(users)\n    .where(or(eq(users.name, \"Alice\"), eq(users.name, \"Bob\")));\n  console.log(\"Users Alice or Bob:\", usersAliceOrBob);\n\n  // Example 9: NOT operator\n  const usersNotCarol = await db\n    .select()\n    .from(users)\n    .where(not(eq(users.name, \"Carol\")));\n  console.log(\"Users not Carol:\", usersNotCarol);\n\n  // Example 10: Complex combination\n  const complexQuery = await db\n    .select()\n    .from(users)\n    .where(\n      and(\n        eq(users.status, \"active\"), // Assuming a 'status' column\n        or(gt(users.age, 30), eq(users.department, \"Sales\")), // Assuming 'age' and 'department'\n        not(eq(users.country, \"US\")) // Assuming 'country'\n      )\n    );\n  console.log(\"Complex Query Users:\", complexQuery);\n}\n\n// To run these examples:\n// 1. Ensure your schema has the relevant tables (users, posts) and columns (name, email, viewCount, status, age, department, country).\n// 2. Initialize and pass your `db` (OrmClient) instance.\n// runConditionExamples(db).catch(console.error);\n```\n\n## Advanced\n\n### Generating DDL manually with the CLI\n\nOnce you have defined your schema (e.g., in `src/schema.ts`), you can generate DDL for PostgreSQL or Spanner:\n\n```bash\n# Generate PostgreSQL DDL\nnpx spanner-orm-cli ddl --schema ./path/to/your/schema.js --dialect postgres\n\n# Generate Spanner DDL\nnpx spanner-orm-cli ddl --schema ./dist/schema.js --dialect spanner\n```\n\nThis will print the generated `CREATE TABLE` statements to standard output or the specified file.\n\nNote if you're using bunx, you can write your schema files in Typescript without having to compile them to JS.\n\n### Querying Examples with `QueryBuilder` (Lower-Level)\n\nHere's how you can use the `QueryBuilder` to construct and execute queries. This is a lower-level API compared to the `db` object and requires manual execution via a database adapter.\n\n```typescript\nimport { QueryBuilder, sql, users, posts, count } from \"spanner-orm\";\n// import { PgliteAdapter } from \"spanner-orm\"; // Or your specific adapter\n// import { PGlite } from \"@electric-sql/pglite\";\n\n// const pglite = new PGlite();\n// const adapter = new PgliteAdapter(pglite);\n// await adapter.connect(); // Ensure adapter is connected\n\nasync function runQueryBuilderExamples(adapter) {\n  // Pass a connected adapter\n  const qb = new QueryBuilder(); // Generic QueryBuilder\n\n  // 1. Basic SELECT with WHERE\n  const recentUsersQuery = new QueryBuilder() // Create a new QB for each query\n    .select({ id: users.id, name: users.name })\n    .from(users)\n    .where(\n      sql`${users.createdAt} \u003e ${new Date(Date.now() - 24 * 60 * 60 * 1000)}`\n    )\n    .orderBy(users.createdAt, \"DESC\")\n    .limit(10);\n\n  const recentUsersPrepared = recentUsersQuery.prepare(adapter.dialect);\n  // const recentUsers = await adapter.query(recentUsersPrepared.sql, recentUsersPrepared.parameters);\n  // console.log(\"Recent Users (QB):\", recentUsers);\n\n  // ... (other QueryBuilder examples can be similarly adapted) ...\n  // For INSERT, UPDATE, DELETE, use adapter.execute()\n}\n\n// runQueryBuilderExamples(adapter).catch(console.error);\n```\n\n### Managing Migrations Programmatically\n\nIn addition to the CLI, you can run migrations directly from your code using the `OrmClient` instance. This is useful for scenarios like automated deployments, programmatic setup, or embedding migration logic within your application.\n\nThe `OrmClient` (typically instantiated as `db`) provides the following methods:\n\n- `db.migrateLatest(options?: { migrationsPath?: string }): Promise\u003cvoid\u003e`: Applies all pending \"up\" migrations.\n- `db.migrateDown(options?: { migrationsPath?: string }): Promise\u003cvoid\u003e`: Reverts the last applied migration.\n\nThe optional `migrationsPath` parameter allows you to specify the directory where your migration files are stored. If omitted, it defaults to `./spanner-orm-migrations`.\n\n**Example:**\n\n```typescript\nimport { OrmClient, PgliteAdapter } from \"spanner-orm\";\nimport { PGlite } from \"@electric-sql/pglite\";\n\nasync function runProgrammaticMigrations() {\n  // 1. Initialize your adapter and OrmClient\n  const pglite = new PGlite(); // Or your preferred adapter (PostgresAdapter, SpannerAdapter)\n  const adapter = new PgliteAdapter(pglite);\n  await adapter.connect(); // Ensure the adapter is connected\n\n  const db = new OrmClient(adapter, \"postgres\"); // Or \"spanner\"\n\n  try {\n    // 2. Apply all pending migrations\n    console.log(\"Applying latest migrations programmatically...\");\n    await db.migrateLatest(); // Uses default migrations path \"./spanner-orm-migrations\"\n    // Or specify a custom path:\n    // await db.migrateLatest({ migrationsPath: \"./my-custom-migrations-folder\" });\n    console.log(\"Latest migrations applied.\");\n\n    // 3. Optionally, revert the last migration\n    // console.log(\"Reverting last migration programmatically...\");\n    // await db.migrateDown();\n    // console.log(\"Last migration reverted.\");\n  } catch (error) {\n    console.error(\"Error during programmatic migration:\", error);\n  } finally {\n    await adapter.disconnect();\n  }\n}\n\n// runProgrammaticMigrations().catch(console.error);\n```\n\nThis approach gives you fine-grained control over when and how migrations are executed within your application's lifecycle.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflux159%2Fspanner-orm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflux159%2Fspanner-orm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflux159%2Fspanner-orm/lists"}