{"id":47609884,"url":"https://github.com/surrealdb/surqlize","last_synced_at":"2026-04-01T20:00:05.049Z","repository":{"id":333935077,"uuid":"919040826","full_name":"surrealdb/surqlize","owner":"surrealdb","description":"A type-safe TypeScript ORM for SurrealDB with full type inference, a fluent query builder, and native support for graph relationships","archived":false,"fork":false,"pushed_at":"2026-02-13T13:04:11.000Z","size":99,"stargazers_count":18,"open_issues_count":0,"forks_count":3,"subscribers_count":11,"default_branch":"main","last_synced_at":"2026-02-13T21:43:57.235Z","etag":null,"topics":["orm","orm-library","surreal","surrealdb","surrealql","typescript"],"latest_commit_sha":null,"homepage":"https://surrealdb.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/surrealdb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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-01-19T15:03:50.000Z","updated_at":"2026-02-13T13:04:13.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/surrealdb/surqlize","commit_stats":null,"previous_names":["surrealdb/surqlize"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/surrealdb/surqlize","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/surrealdb%2Fsurqlize","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/surrealdb%2Fsurqlize/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/surrealdb%2Fsurqlize/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/surrealdb%2Fsurqlize/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/surrealdb","download_url":"https://codeload.github.com/surrealdb/surqlize/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/surrealdb%2Fsurqlize/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31291333,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T13:12:26.723Z","status":"ssl_error","status_checked_at":"2026-04-01T13:12:25.102Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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","orm-library","surreal","surrealdb","surrealql","typescript"],"created_at":"2026-04-01T20:00:03.138Z","updated_at":"2026-04-01T20:00:05.031Z","avatar_url":"https://github.com/surrealdb.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cbr\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003cimg width=120 src=\"https://raw.githubusercontent.com/surrealdb/icons/main/surreal.svg\" /\u003e\n    \u0026nbsp;\n    \u003cimg width=120 src=\"https://raw.githubusercontent.com/surrealdb/icons/main/javascript.svg\" /\u003e\n\u003c/p\u003e\n\n\u003ch3 align=\"center\"\u003eA type-safe TypeScript ORM for SurrealDB.\u003c/h3\u003e\n\n\u003cbr\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://github.com/surrealdb/surqlize\"\u003e\u003cimg src=\"https://img.shields.io/badge/status-dev-ff00bb.svg?style=flat-square\"\u003e\u003c/a\u003e\n    \u0026nbsp;\n    \u003ca href=\"https://surrealdb.com/docs/sdk/javascript\"\u003e\u003cimg src=\"https://img.shields.io/badge/docs-view-44cc11.svg?style=flat-square\"\u003e\u003c/a\u003e\n    \u0026nbsp;\n    \u003ca href=\"https://www.npmjs.com/package/surqlize\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/surqlize?style=flat-square\"\u003e\u003c/a\u003e\n    \u003c!--\u0026nbsp;\n    \u003ca href=\"https://www.npmjs.com/package/surqlize\"\u003e\u003cimg src=\"https://img.shields.io/npm/dm/surqlize?style=flat-square\"\u003e\u003c/a\u003e\n    \u0026nbsp;\n    \u003ca href=\"https://deno.land/x/surqlize\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/surqlize?style=flat-square\u0026label=deno\"\u003e\u003c/a\u003e--\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://surrealdb.com/discord\"\u003e\u003cimg src=\"https://img.shields.io/discord/902568124350599239?label=discord\u0026style=flat-square\u0026color=5a66f6\"\u003e\u003c/a\u003e\n    \u0026nbsp;\n    \u003ca href=\"https://twitter.com/surrealdb\"\u003e\u003cimg src=\"https://img.shields.io/badge/twitter-follow_us-1d9bf0.svg?style=flat-square\"\u003e\u003c/a\u003e\n    \u0026nbsp;\n    \u003ca href=\"https://www.linkedin.com/company/surrealdb/\"\u003e\u003cimg src=\"https://img.shields.io/badge/linkedin-connect_with_us-0a66c2.svg?style=flat-square\"\u003e\u003c/a\u003e\n    \u0026nbsp;\n    \u003ca href=\"https://www.youtube.com/@SurrealDB\"\u003e\u003cimg src=\"https://img.shields.io/badge/youtube-subscribe-fc1c1c.svg?style=flat-square\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n# Surqlize\n\nA type-safe TypeScript ORM for SurrealDB that provides full type inference, a fluent query builder, comprehensive CRUD operations, and first-class support for graph relationships, and database functions.\n\n## Features\n\n- **Type-safe schema definitions** - Define your database schema using intuitive `t.*` builders\n- **Automatic type inference** - Get full TypeScript types without code generation\n- **Fluent query builder** - Chain `.select()`, `.where()`, `.return()` with full type safety\n- **Complete CRUD operations** - SELECT, CREATE, UPDATE, DELETE, and UPSERT queries\n- **Graph relationships** - First-class support for edges and graph traversal\n- **Rich type system** - Objects, arrays, unions, literals, options, and more\n- **SurrealDB functions** - Integrated string, array, and record operations\n\n## Installation\n\n```bash\nbun add surqlize\n# or\nnpm install surqlize\n```\n\n## Quick start\n\n```typescript\nimport { Surreal } from \"surrealdb\";\nimport { orm, table, t } from \"surqlize\";\n\n// Define a table schema\nconst user = table(\"user\", {\n  name: t.string(),\n  email: t.string(),\n  age: t.number(),\n  created: t.date(),\n});\n\n// Create ORM instance\nconst db = orm(new Surreal(), user);\n\n// Build type-safe queries\nconst query = db\n  .select(\"user\")\n  .where((user) =\u003e user.age.gte(18))\n  .return((user) =\u003e ({\n    name: user.name,\n    email: user.email,\n  }));\n\n// TypeScript knows the exact return type!\ntype Result = t.infer\u003ctypeof query\u003e;\n// Result: Array\u003c{ name: string; email: string }\u003e\n```\n\n## Schema definition\n\n### Tables\n\nDefine tables using the `table()` function with a rich type system:\n\n```typescript\nimport { table, t } from \"surqlize\";\n\nconst user = table(\"user\", {\n  // Basic types\n  name: t.string(),\n  age: t.number(),\n  isActive: t.bool(),\n  created: t.date(),\n  userId: t.uuid(),\n\n  // Complex objects\n  address: t.object({\n    street: t.string(),\n    city: t.string(),\n    zipCode: t.string(),\n  }),\n\n  // Arrays\n  tags: t.array(t.string()),\n  scores: t.array(t.number()),\n\n  // Mixed-type arrays (tuples)\n  mixedData: t.array([t.string(), t.number(), t.bool()]),\n\n  // Optional fields\n  bio: t.option(t.string()),\n  \n  // Record references (foreign keys)\n  authorId: t.record(\"author\"),\n\n  // Union types\n  status: t.union([\n    t.literal(\"active\"),\n    t.literal(\"inactive\"),\n    t.literal(\"pending\"),\n  ]),\n\n  // Literals\n  role: t.literal(\"admin\"),\n});\n```\n\n**Note**: Every table automatically includes an `id` field of type `RecordId\u003cTableName\u003e`.\n\n### Edges and graph relations\n\nDefine graph edges to model relationships between tables:\n\n```typescript\nimport { edge, table, t } from \"surqlize\";\n\nconst user = table(\"user\", {\n  name: t.string(),\n  email: t.string(),\n});\n\nconst post = table(\"post\", {\n  title: t.string(),\n  content: t.string(),\n});\n\n// Define an edge from user to post\nconst authored = edge(\"user\", \"authored\", \"post\", {\n  created: t.date(),\n  role: t.union([t.literal(\"author\"), t.literal(\"co-author\")]),\n});\n\nconst db = orm(new Surreal(), user, post, authored);\n```\n\n**Automatic fields**: Edges automatically include:\n- `id`: RecordId of the edge\n- `in`: RecordId of the source table (user)\n- `out`: RecordId of the target table (post)\n\n## CRUD Operations\n\n### SELECT statements\n\n```typescript\n// Select all records\nconst allUsers = db.select(\"user\");\n\n// Select with WHERE clause\nconst adults = db\n  .select(\"user\")\n  .where((user) =\u003e user.age.gte(18));\n\n// Project specific fields with RETURN\nconst userNames = db\n  .select(\"user\")\n  .return((user) =\u003e ({\n    fullName: user.name,\n    email: user.email,\n  }));\n\n// Pagination\nconst paginatedUsers = db\n  .select(\"user\")\n  .start(10)\n  .limit(20);\n\n// Select a single record by ID (returns array with 0 or 1 item)\nconst specificUser = await db.select(new RecordId(\"user\", \"john\"));\n// To get the first item, use .val() or .at(0):\nconst specificUser = await db.select(new RecordId(\"user\", \"john\")).then.val();\n// Or get a specific item:\nconst specificUser = await db.select(new RecordId(\"user\", \"john\")).then.at(0);\n\n// Nested queries (JOIN-like)\nconst postsWithAuthors = db.select(\"post\").return((post) =\u003e ({\n  title: post.title,\n  author: post.authorId.select().return((author) =\u003e ({\n    name: author.name,\n    email: author.email,\n  })),\n}));\n```\n\n#### Sorting with ORDER BY\n\n```typescript\n// Order by single field\nconst sorted = db.select(\"user\")\n  .orderBy(\"age\", \"DESC\");\n\n// Order by multiple fields\nconst multiSort = db.select(\"user\")\n  .orderBy(\"lastName\", \"ASC\")\n  .orderBy(\"firstName\", \"ASC\");\n\n// Order by with callback (for nested fields)\nconst nestedSort = db.select(\"user\")\n  .orderBy(user =\u003e user.name.last, \"ASC\");\n\n// Numeric sorting\nconst numericSort = db.select(\"user\")\n  .orderByNumeric(\"age\", \"DESC\");\n\n// Collation sorting\nconst collateSort = db.select(\"user\")\n  .orderByCollate(\"name\", \"ASC\");\n```\n\n#### Grouping with GROUP BY\n\n```typescript\n// Group by field(s)\nconst grouped = db.select(\"post\")\n  .groupBy(\"author\");\n\n// Group all (for table-wide aggregates)\nconst totalCount = db.select(\"user\")\n  .groupAll();\n```\n\n#### Loading relations with FETCH\n\n```typescript\n// Fetch linked records\nconst withAuthor = db.select(\"post\")\n  .fetch(\"author\");\n\n// Fetch multiple relations\nconst deepFetch = db.select(\"post\")\n  .fetch(\"author\", \"comments\");\n```\n\n#### Splitting arrays with SPLIT\n\n```typescript\n// Split array field into multiple records\nconst splitTags = db.select(\"post\")\n  .split(\"tags\");\n\n// Split multiple arrays\nconst multiSplit = db.select(\"post\")\n  .split(\"tags\", \"categories\");\n```\n\n#### Setting query timeout\n\n```typescript\n// Set timeout duration\nconst withTimeout = db.select(\"user\")\n  .where(user =\u003e user.age.gt(18))\n  .timeout(\"5s\");\n```\n\n#### Combining clauses\n\n```typescript\n// Complex query with multiple clauses\nconst complexQuery = db.select(\"post\")\n  .where(post =\u003e post.title.startsWith(\"Hello\"))\n  .split(\"tags\")\n  .orderBy(\"created\", \"DESC\")\n  .limit(20)\n  .fetch(\"author\")\n  .timeout(\"10s\");\n```\n\n### CREATE statements\n\nCreate a new record with a specific id or a generated id.\n\n```typescript\n// Create with SET\nconst newUser = await db.create(\"user\").set({\n  name: \"Alice\",\n  email: \"alice@example.com\",\n  age: 30,\n  created: new Date(),\n});\n\n// Create with CONTENT\nconst newPost = await db.create(\"post\").content({\n  title: \"Hello World\",\n  body: \"First post!\",\n  authorId: new RecordId(\"user\", \"alice\"),\n  published: true,\n});\n\n// Create with explicit ID\nconst user = await db.create(\"user\", \"alice123\").set({\n  name: \"Alice\",\n  email: \"alice@example.com\",\n});\n\n// Control return value\nconst created = await db.create(\"user\")\n  .set({ name: \"Bob\" })\n  .return(\"after\"); // or \"before\", \"none\", \"diff\"\n```\n\n### INSERT statements\n\nInsert one or multiple records with support for bulk operations and conflict handling.\n\n```typescript\n// Insert single record (object style)\nawait db.insert(\"user\", {\n  name: \"Alice\",\n  email: \"alice@example.com\",\n  age: 30,\n});\n\n// Bulk insert (object style)\nawait db.insert(\"user\", [\n  { name: \"Alice\", email: \"alice@example.com\", age: 30 },\n  { name: \"Bob\", email: \"bob@example.com\", age: 25 },\n  { name: \"Charlie\", email: \"charlie@example.com\", age: 28 },\n]);\n\n// VALUES tuple syntax\nawait db.insert(\"user\")\n  .fields([\"name\", \"email\", \"age\"])\n  .values(\n    [\"Alice\", \"alice@example.com\", 30],\n    [\"Bob\", \"bob@example.com\", 25]\n  );\n\n// IGNORE duplicates (skip conflicts silently)\nawait db.insert(\"user\", userData).ignore();\n\n// ON DUPLICATE KEY UPDATE (update on conflict)\nawait db.insert(\"user\", { \n  id: \"alice\", \n  name: \"Alice\", \n  age: 30 \n})\n.onDuplicate({\n  age: { \"+=\": 1 },\n  lastSeen: new Date(),\n});\n\n// With operators in ON DUPLICATE\nawait db.insert(\"post\", posts)\n  .onDuplicate({\n    views: { \"+=\": 1 },\n    tags: { \"+=\": [\"updated\"] },\n  });\n\n// With RETURN clause\nconst inserted = await db.insert(\"user\", data).return(\"after\");\n\n// With RETURN projection\nconst insertedNames = await db.insert(\"user\", data)\n  .return(u =\u003e ({ name: u.name }));\n```\n\n### UPSERT statements\n\nCreate a record if it doesn't exist, update records if matching records exist.\n\n```typescript\n// Upsert with SET\nawait db.upsert(\"user\", \"alice\")\n  .set({\n    name: \"Alice\",\n    email: \"alice@example.com\",\n    age: 30,\n  });\n\n// Upsert with operators (atomic increment)\nawait db.upsert(\"pageview\", \"homepage\")\n  .set({\n    count: { \"+=\": 1 },\n    lastViewed: new Date(),\n  });\n\n// Upsert with MERGE\nawait db.upsert(\"user\", \"alice\")\n  .merge({ lastLogin: new Date() });\n\n// Bulk upsert with WHERE\nawait db.upsert(\"user\")\n  .where((u) =\u003e u.email.eq(\"alice@example.com\"))\n  .set({ lastSeen: new Date() });\n```\n\n### UPDATE statements\n\nUpdate a record or multiple records in a table.\n\n```typescript\n// Update with SET\nawait db.update(\"user\", \"alice\")\n  .set({ age: 31 });\n\n// Bulk update with WHERE\nawait db.update(\"user\")\n  .where((u) =\u003e u.age.lt(18))\n  .set({ status: \"minor\" });\n\n// Array and number operators\nawait db.update(\"user\", \"alice\")\n  .set({\n    age: { \"+=\": 1 },                // Increment\n    tags: { \"+=\": [\"developer\"] },   // Append to array\n    oldTags: { \"-=\": [\"beginner\"] }, // Remove from array\n  });\n\n// CONTENT (replace entire record)\nawait db.update(\"user\", \"alice\")\n  .content({\n    name: \"Alice Smith\",\n    email: \"alice@example.com\",\n    age: 31,\n  });\n\n// MERGE (partial update)\nawait db.update(\"user\", \"alice\")\n  .merge({ email: \"newemail@example.com\" });\n\n// PATCH (JSON Patch operations)\nawait db.update(\"user\", \"alice\")\n  .patch([\n    { op: \"replace\", path: \"/age\", value: 32 },\n    { op: \"remove\", path: \"/oldField\" },\n  ]);\n\n// UNSET (remove fields)\nawait db.update(\"user\", \"alice\")\n  .set({ name: \"Alice\" })\n  .unset([\"oldField1\", \"oldField2\"]);\n\n// Return modified records\nconst updated = await db.update(\"user\")\n  .where((u) =\u003e u.age.gt(65))\n  .set({ status: \"senior\" })\n  .return(\"after\");\n```\n\n### RELATE statements\n\nCreate graph edges between records using defined edge schemas.\n\n```typescript\n// Single edge between two records\nconst edge = await db.relate(\n  \"authored\",\n  new RecordId(\"user\", \"alice\"),\n  new RecordId(\"post\", \"hello-world\")\n);\n\n// With edge data using content()\nconst friendship = await db.relate(\n  \"knows\",\n  new RecordId(\"user\", \"user1\"),\n  new RecordId(\"user\", \"user2\")\n).content({\n  since: new Date(),\n  strength: 5,\n});\n\n// With edge data using set()\nconst likes = await db.relate(\n  \"likes\",\n  new RecordId(\"user\", \"userId\"),\n  new RecordId(\"post\", \"postId\")\n).set({\n  created: new Date(),\n  rating: 5,\n});\n\n// Cartesian product: create multiple edges\n// Creates: alice-\u003eauthored-\u003epost1, alice-\u003eauthored-\u003epost2,\n//          bob-\u003eauthored-\u003epost1, bob-\u003eauthored-\u003epost2\nconst edges = await db.relate(\n  \"authored\",\n  [new RecordId(\"user\", \"alice\"), new RecordId(\"user\", \"bob\")],\n  [new RecordId(\"post\", \"post1\"), new RecordId(\"post\", \"post2\")]\n);\n\n// Control return mode\nawait db.relate(\n  \"authored\",\n  new RecordId(\"user\", \"user\"),\n  new RecordId(\"post\", \"post\")\n).content({ created: new Date() })\n.return(\"after\"); // or \"before\", \"none\", \"diff\"\n\n// With return projection\nconst edgeInfo = await db.relate(\n  \"follows\",\n  new RecordId(\"user\", \"follower\"),\n  new RecordId(\"user\", \"followee\")\n).set({ since: new Date() })\n.return(edge =\u003e ({\n  id: edge.id,\n  from: edge.in,\n  to: edge.out,\n  since: edge.since,\n}));\n\n\n// Using with query results\nconst userQuery = db.select(\"user\", \"alice\");\nconst postQuery = db.select(\"post\", \"hello\");\nawait db.relate(\"authored\", userQuery, postQuery);\n```\n\n### DELETE statements\n\n```typescript\n// Delete single record (returns array with 0 or 1 item)\nawait db.delete(\"user\", \"alice\");\n\n// Bulk delete with WHERE\nawait db.delete(\"user\")\n  .where((u) =\u003e u.age.lt(13));\n\n// Return deleted records\nconst deleted = await db.delete(\"user\")\n  .where((u) =\u003e u.status.eq(\"inactive\"))\n  .return(\"before\");\n\n// Delete with projection\nconst deletedNames = await db.delete(\"user\")\n  .where((u) =\u003e u.email.endsWith(\"@spam.com\"))\n  .return((u) =\u003e ({ name: u.name }));\n```\n\n## Batch\n\nExecute multiple queries as a single atomic operation in one round-trip. No intermediate results are available — all queries succeed or all fail together.\n\n```typescript\n// Multiple queries in a single atomic operation\nconst [user, updated, allUsers] = await db.batch(\n  db.create(\"user\").set({ name: \"Alice\", age: 30 }),\n  db.update(\"user\", \"bob\").set({ age: 31 }),\n  db.select(\"user\"),\n);\n// Results are fully typed as a tuple\n```\n\nYou can also inspect the generated SurrealQL before executing:\n\n```typescript\nconst b = db.batch(\n  db.create(\"user\").set({ name: \"Alice\" }),\n  db.update(\"user\", \"bob\").set({ age: 31 }),\n);\n\nconsole.log(b.toString());\n// BEGIN TRANSACTION; CREATE user SET name = $_v0; UPDATE user:bob SET age = $_v1; COMMIT TRANSACTION;\n\n// Execute when ready\nconst [created, updated] = await b;\n```\n\n## Transactions\n\nOpen a server-side transaction, execute queries one-by-one with intermediate results, and decide whether to commit or cancel based on the outcomes.\n\n### Callback form (auto-commit/cancel)\n\nThe callback form automatically commits on success and cancels on error:\n\n```typescript\nconst result = await db.transaction(async (tx) =\u003e {\n  const user = await tx.create(\"user\").set({\n    name: \"Alice\",\n    age: 30,\n  });\n\n  // Use intermediate results to make decisions\n  if (user.age \u003e 25) {\n    await tx.update(\"user\", user.id).set({ status: \"senior\" });\n  }\n\n  return user;\n});\n// Transaction is committed automatically\n```\n\n### Manual form (explicit commit/cancel)\n\nFor full control, use the manual form:\n\n```typescript\nconst tx = await db.transaction();\ntry {\n  const user = await tx.create(\"user\").set({ name: \"Alice\" });\n  await tx.relate(\"authored\", user.id, new RecordId(\"post\", \"hello\"));\n  await tx.commit();\n} catch (e) {\n  await tx.cancel();\n  throw e;\n}\n```\n\nThe transaction object (`tx`) has all the same query-builder methods as the main `db` instance — `select`, `create`, `insert`, `update`, `upsert`, `delete`, and `relate`.\n\n## Accessing Single Records\n\nAll queries in Surqlize return arrays, even when selecting by a specific record ID. To access the first item from a query result, use `.val()` or `.at(index)`:\n\n```typescript\n// .val() - Returns the first item or undefined\nconst user = await db.select(\"user\", \"alice\").then.val();\n// user: User | undefined\n\n// .at(index) - Returns the item at the specified index or undefined\nconst firstUser = await db.select(\"user\").then.at(0);\nconst secondUser = await db.select(\"user\").then.at(1);\nconst lastUser = await db.select(\"user\").then.at(-1); // negative indexing supported\n\n// Working with arrays directly\nconst users = await db.select(\"user\", \"alice\");\n// users: User[]\nif (users.length \u003e 0) {\n  const user = users[0];\n}\n\n// Use with update, delete, and upsert\nconst updated = await db.update(\"user\", \"alice\")\n  .set({ age: 31 })\n  .return(\"after\")\n  .then.val();\n\nconst deleted = await db.delete(\"user\", \"alice\")\n  .return(\"before\")\n  .then.val();\n```\n\n## Filtering operations\n\nAll types support these comparison operators:\n\n```typescript\ndb.select(\"user\").where((user) =\u003e \n  // Equality\n  user.name.eq(\"John\")              // =\n  user.age.ne(25)                   // !=\n  user.email.ex(\"john@example.com\") // == (exact match)\n\n  // Comparison\n  user.age.gt(18)                   // \u003e\n  user.age.gte(21)                  // \u003e=\n  user.age.lt(65)                   // \u003c\n  user.age.lte(64)                  // \u003c=\n\n  // Array membership\n  user.status.inside([\"active\", \"pending\"])    // IN\n  user.status.notInside([\"banned\", \"deleted\"]) // NOT IN\n\n  // Logical operators\n  user.age.gte(18).and(user.isActive.eq(true))\n  user.role.eq(\"admin\").or(user.role.eq(\"moderator\"))\n  user.isActive.not()\n  \n  // Truthiness checks\n  user.bio.trueish()    // !! (double negation - checks for truthy value)\n  user.archived.falseish() // ! (negation - checks for falsy value)\n);\n```\n\n### Compound conditions\n\nFor complex conditions, use the standalone `and()` and `or()` combiners. These make precedence explicit and produce correctly parenthesized SurrealQL:\n\n```typescript\nimport { orm, table, t, and, or } from \"surqlize\";\n\n// Simple compound: age \u003e= 18 AND email ends with @example.com\ndb.select(\"user\").where((user) =\u003e\n  and(user.age.gte(18), user.email.endsWith(\"@example.com\"))\n);\n// WHERE (age \u003e= 18 AND string::ends_with(email, \"@example.com\"))\n\n// OR with multiple options\ndb.select(\"user\").where((user) =\u003e\n  or(user.role.eq(\"admin\"), user.role.eq(\"moderator\"), user.role.eq(\"owner\"))\n);\n// WHERE (role = \"admin\" OR role = \"moderator\" OR role = \"owner\")\n\n// Nested: AND with inner OR for grouped conditions\ndb.select(\"user\").where((user) =\u003e\n  and(\n    user.age.gte(18),\n    or(user.role.eq(\"admin\"), user.role.eq(\"moderator\")),\n    user.email.endsWith(\"@example.com\"),\n  )\n);\n// WHERE (age \u003e= 18 AND (role = \"admin\" OR role = \"moderator\") AND string::ends_with(email, \"@example.com\"))\n\n// Chaining .and() / .or() on individual conditions also works\ndb.select(\"user\").where((user) =\u003e\n  user.age.gte(18).and(user.name.first.eq(\"Alice\"))\n);\n// WHERE (age \u003e= 18 AND name.first = \"Alice\")\n```\n\nBoth `and()` and `or()` require at least two conditions and accept any number of additional conditions. Nesting them produces correctly parenthesized output, so precedence is always explicit.\n\n## Type-specific functions\n\n### String functions\n\n```typescript\ndb.select(\"user\").where((user) =\u003e\n  user.email.startsWith(\"admin@\")\n  user.name.endsWith(\"son\")\n  user.email.contains(\"@example.com\")\n  user.email.isEmail()\n);\n\ndb.select(\"user\").return((user) =\u003e ({\n  fullName: user.firstName.join(\" \", user.lastName),\n  nameLength: user.name.len(),\n  upper: user.name.uppercase(),\n  lower: user.email.lowercase(),\n  trimmed: user.name.trim(),\n  slug: user.name.slug(),\n  words: user.name.words(),\n  reversed: user.name.reverse(),\n  replaced: user.email.replace(\"@old.com\", \"@new.com\"),\n  parts: user.email.split(\"@\"),\n}));\n```\n\nAdditional string functions include `capitalize`, `repeat`, `slice`, `matches`, distance functions (`distanceLevenshtein`, `distanceHamming`, etc.), HTML functions (`htmlEncode`, `htmlSanitize`), validation (`isUrl`, `isDomain`, `isUuid`, etc.), semver operations, and similarity scoring.\n\n### Array functions\n\n```typescript\ndb.select(\"user\").where((user) =\u003e\n  // Single element checks\n  user.tags.contains(\"typescript\")          // Array contains element\n  user.tags.containsNot(\"java\")             // Array doesn't contain element\n  \n  // Multiple element checks\n  user.tags.containsAll([\"javascript\", \"typescript\"]) // Contains all elements\n  user.tags.containsAny([\"rust\", \"go\", \"python\"])     // Contains any element\n  user.tags.containsNone([\"php\", \"perl\"])             // Contains none of elements\n  \n  // Inside checks (array subset operations)\n  user.tags.allInside(allowedTags)   // All elements are in allowedTags\n  user.tags.anyInside(popularTags)   // Any element is in popularTags\n  user.tags.noneInside(bannedTags)   // No elements are in bannedTags\n  \n  // Empty check\n  user.tags.isEmpty()                // Array is empty\n);\n\ndb.select(\"post\").return((post) =\u003e ({\n  title: post.title,\n  firstTag: post.tags.at(0),      // Get element at index\n  tagCount: post.tags.len(),       // Array length\n  first: post.tags.first(),        // First element\n  last: post.tags.last(),          // Last element\n  sorted: post.tags.sort(),        // Sort array\n  unique: post.tags.distinct(),    // Unique values\n  flat: post.tags.flatten(),       // Flatten nested arrays\n  reversed: post.tags.reverse(),   // Reverse array\n}));\n```\n\nAdditional array functions include mutation (`add`, `append`, `prepend`, `push`, `pop`, `insert`, `remove`, `fill`, `swap`), set operations (`combine`, `complement`, `concat`, `difference`, `intersect`, `union`, `transpose`), boolean operations (`booleanAnd`, `booleanOr`, `logicalAnd`, etc.), and search functions (`findIndex`, `filterIndex`, `max`, `min`).\n\n### Number functions\n\n```typescript\ndb.select(\"user\").return((user) =\u003e ({\n  absAge: user.age.abs(),\n  rounded: user.age.round(),\n  ceiling: user.age.ceil(),\n  floored: user.age.floor(),\n  squareRoot: user.age.sqrt(),\n  squared: user.age.pow(2),\n  fixed: user.age.fixed(2),\n  clamped: user.age.clamp(0, 100),\n  radians: user.age.deg2rad(),\n  sine: user.age.sin(),\n  cosine: user.age.cos(),\n  naturalLog: user.age.ln(),\n  log10: user.age.log10(),\n}));\n```\n\nAdditional number functions include `tan`, `cot`, `acos`, `asin`, `atan`, `acot`, `log`, `log2`, `rad2deg`, `sign`, `lerp`, `lerpangle`.\n\n### Date functions\n\n```typescript\ndb.select(\"user\").return((user) =\u003e ({\n  year: user.created.year(),\n  month: user.created.month(),\n  day: user.created.day(),\n  hour: user.created.hour(),\n  minute: user.created.minute(),\n  second: user.created.second(),\n  weekDay: user.created.wday(),\n  dayOfYear: user.created.yday(),\n  unix: user.created.unix(),\n  millis: user.created.millis(),\n  formatted: user.created.format(\"%Y-%m-%d\"),\n  isLeap: user.created.isLeapYear(),\n}));\n```\n\nAdditional date functions include `week`, `micros`, `nano`, and rounding functions (`timeCeil`, `timeFloor`, `timeRound`).\n\n### Option functions\n\nWhen working with optional values (created with `t.option()`), you can use `map()` to transform the value if it exists:\n\n```typescript\nconst user = table(\"user\", {\n  name: t.string(),\n  bio: t.option(t.string()),\n});\n\ndb.select(\"user\").return((user) =\u003e ({\n  name: user.name,\n  // Transform bio to uppercase if it exists\n  bioUpper: user.bio.map((b) =\u003e b.toUpperCase()),\n  // Chain multiple operations\n  bioLength: user.bio.map((b) =\u003e b.len()),\n}));\n```\n\n### Record functions\n\nWhen you have a record reference, you can perform nested queries:\n\n```typescript\nconst post = table(\"post\", {\n  title: t.string(),\n  authorId: t.record(\"user\"),\n});\n\n// Nested query with .select()\nconst query = db.select(\"post\").return((post) =\u003e ({\n  title: post.title,\n  author: post.authorId.select().return((author) =\u003e ({\n    name: author.name,\n    email: author.email,\n  })),\n}));\n\n// TypeScript infers the complete nested type!\ntype Result = t.infer\u003ctypeof query\u003e;\n// Result: Array\u003c{\n//   title: string;\n//   author: { name: string; email: string } | undefined;\n// }\u003e\n```\n\n## Standalone functions\n\nStandalone functions are not called on a field but used independently within query callbacks. Functions with value parameters extract the query context automatically from the first value. Zero-arg functions and constants require an explicit context source (any `Workable` from the callback).\n\n```typescript\nimport { count, math, time, crypto, rand, parse } from \"surqlize\";\n\n// Count and aggregation\ndb.select(\"user\")\n  .groupAll()\n  .return((user) =\u003e ({\n    total: count(user),\n    adults: count(user, user.age.gte(18)),\n    avgAge: math.mean(user.age),\n    totalAge: math.sum(user.age),\n    maxAge: math.max(user.age),\n  }));\n\n// Time and crypto\ndb.select(\"user\").return((user) =\u003e ({\n  now: time.now(user),\n  emailHash: crypto.sha256(user.email),\n  randomId: rand.uuid(user),\n  emailDomain: parse.emailHost(user.email),\n}));\n\n// Math constants (zero-arg, need context source)\ndb.select(\"user\").return((user) =\u003e ({\n  pi: math.pi(user),\n  e: math.e(user),\n  tau: math.tau(user),\n}));\n```\n\nAvailable standalone function families: `count`, `math` (aggregation + constants), `time`, `crypto`, `rand`, `duration`, `type_`, `encoding`, `geo`, `http`, `meta`, `object`, `parse`, `search`, `session`, `set_`, `sleep`, `value`, `vector`, `bytes`, `not`.\n\n## Advanced Features\n\n### Return Clauses\n\nControl what gets returned from mutations:\n\n```typescript\n// Return nothing\nawait db.update(\"user\", \"alice\").set({ age: 31 }).return(\"none\");\n\n// Return state before modification\nconst before = await db.update(\"user\", \"alice\")\n  .set({ age: 31 })\n  .return(\"before\");\n\n// Return state after modification (default)\nconst after = await db.update(\"user\", \"alice\")\n  .set({ age: 31 })\n  .return(\"after\");\n\n// Return diff of changes\nconst diff = await db.update(\"user\", \"alice\")\n  .set({ age: 31 })\n  .return(\"diff\");\n\n// Return specific fields with projection\nconst projection = await db.update(\"user\", \"alice\")\n  .set({ age: 31, email: \"new@email.com\" })\n  .return((u) =\u003e ({ name: u.name, age: u.age }));\n```\n\n### Query Timeouts\n\n```typescript\nconst users = await db.select(\"user\")\n  .where((u) =\u003e u.age.gt(18))\n  .timeout(\"5s\");\n\nawait db.update(\"user\", \"alice\")\n  .set({ age: 31 })\n  .timeout(\"10s\");\n```\n\n### Operators\n\nUse operators for atomic operations:\n\n```typescript\n// Increment/decrement numbers\ndb.update(\"user\", \"alice\").set({\n  age: { \"+=\": 1 },\n  score: { \"-=\": 10 },\n});\n\n// Add/remove from arrays\ndb.update(\"post\", \"post1\").set({\n  tags: { \"+=\": [\"typescript\", \"database\"] },\n  oldTags: { \"-=\": [\"deprecated\"] },\n});\n```\n\n## Type inference\n\nExtract TypeScript types from your queries using `t.infer\u003c\u003e`:\n\n```typescript\n// Infer query result type\nconst query = db.select(\"user\").return((user) =\u003e ({\n  name: user.name,\n  age: user.age,\n}));\n\ntype QueryResult = t.infer\u003ctypeof query\u003e;\n// QueryResult: Array\u003c{ name: string; age: number }\u003e\n\n// Infer table type\nconst userTable = table(\"user\", {\n  name: t.string(),\n  age: t.number(),\n});\n\ntype User = t.infer\u003ctypeof userTable\u003e;\n// User: { id: RecordId\u003c\"user\"\u003e; name: string; age: number }\n\n// Infer individual type definitions\nconst emailType = t.string();\ntype Email = t.infer\u003ctypeof emailType\u003e;\n// Email: string\n```\n\n## Debugging Queries\n\nInspect generated SurrealQL:\n\n```typescript\nimport { displayContext, __display } from \"surqlize\";\n\nconst query = db.select(\"user\").where((u) =\u003e u.age.gte(18));\n\nconst ctx = displayContext();\nconst sql = query[__display](ctx);\n\nconsole.log(sql);           // Generated SurrealQL\nconsole.log(ctx.variables); // Parameterized values\n```\n\n## Graph relationships\n\nSurqlize provides type-safe graph traversal through the `lookup` system:\n\n```typescript\nconst user = table(\"user\", { name: t.string() });\nconst post = table(\"post\", { title: t.string() });\nconst authored = edge(\"user\", \"authored\", \"post\", {});\n\nconst db = orm(new Surreal(), user, post, authored);\n\n// TypeScript knows which edges connect to which tables\ndb.lookup.to;   // { user: [\"authored\"], authored: [\"post\"], post: [] }\ndb.lookup.from; // { user: [], authored: [\"user\"], post: [\"authored\"] }\n\n// Use in queries for type-safe graph navigation\n// (This feature is under active development)\n```\n\n## Complex example\n\nHere's a complete example showcasing multiple features:\n\n```typescript\nconst user = table(\"user\", {\n  name: t.object({\n    first: t.string(),\n    last: t.string(),\n  }),\n  age: t.number(),\n  email: t.string(),\n  tags: t.array(t.string()),\n  bio: t.option(t.string()),\n});\n\nconst post = table(\"post\", {\n  title: t.string(),\n  content: t.string(),\n  authorId: t.record(\"user\"),\n  created: t.date(),\n});\n\nconst authored = edge(\"user\", \"authored\", \"post\", {\n  created: t.date(),\n});\n\nconst db = orm(new Surreal(), user, post, authored);\n\n// Complex query with nested data and string operations\nconst query = db\n  .select(\"post\")\n  .where((post) =\u003e \n    post.title.startsWith(\"Guide\").and(\n      post.created.gte(new Date(\"2024-01-01\"))\n    )\n  )\n  .return((post) =\u003e ({\n    title: post.title,\n    author: post.authorId.select().return((author) =\u003e ({\n      fullName: author.name.first.join(\" \", author.name.last),\n      age: author.age,\n      hasBio: author.bio.trueish(),\n    })),\n  }))\n  .orderBy(\"created\", \"DESC\")\n  .limit(10);\n\n// Fully typed result\ntype Result = t.infer\u003ctypeof query\u003e;\n\n// Fetch resolves record references into full objects\nconst posts = await db\n  .select(\"post\")\n  .fetch(\"authorId\")\n  .execute();\n// posts[0].authorId is now the full user object, not a RecordId\n```\n\n## Multi-session support\n\nSurqlize accepts any `SurrealSession` (or `Surreal`, which extends it), enabling multiple ORM instances scoped to different sessions over a single connection. Each session maintains its own namespace, database, authentication state, and variables.\n\n### Multiple databases over one connection\n\n```typescript\nimport { Surreal } from \"surrealdb\";\nimport { orm, table, t } from \"surqlize\";\n\nconst user = table(\"user\", { name: t.string(), age: t.number() });\n\nconst surreal = new Surreal();\nawait surreal.connect(\"ws://localhost:8000\");\nawait surreal.signin({ username: \"root\", password: \"root\" });\n\n// Create separate sessions for different tenants\nconst tenantA = await surreal.newSession();\nawait tenantA.signin({ username: \"root\", password: \"root\" });\nawait tenantA.use({ namespace: \"app\", database: \"tenant_a\" });\n\nconst tenantB = await surreal.newSession();\nawait tenantB.signin({ username: \"root\", password: \"root\" });\nawait tenantB.use({ namespace: \"app\", database: \"tenant_b\" });\n\n// Same schema, same connection, different databases\nconst dbA = orm(tenantA, user);\nconst dbB = orm(tenantB, user);\n\nawait dbA.create(\"user\").set({ name: \"Alice\", age: 30 });\nawait dbB.create(\"user\").set({ name: \"Bob\", age: 25 });\n```\n\n### Forking sessions\n\nUse `forkSession()` to clone an existing session (inheriting its namespace, database, auth, and variables) and then diverge:\n\n```typescript\nconst surreal = new Surreal();\nawait surreal.connect(\"ws://localhost:8000\");\nawait surreal.signin({ username: \"root\", password: \"root\" });\nawait surreal.use({ namespace: \"app\", database: \"main\" });\n\n// Fork inherits namespace, database, auth, and variables\nconst session = await surreal.forkSession();\nawait session.authenticate(userToken);\n\nconst db = orm(session, user);\nconst users = await db.select(\"user\");\n\n// Clean up when done\nawait session.closeSession();\n```\n\n### Disposable sessions\n\nSince `SurrealSession` implements `Symbol.asyncDispose`, sessions work with `await using` for automatic cleanup:\n\n```typescript\n{\n  await using session = await surreal.forkSession();\n  await session.authenticate(userToken);\n\n  const db = orm(session, user);\n  const users = await db.select(\"user\");\n  // session automatically disposed when scope exits\n}\n```\n\n## Comparison with other ORMs\n\n| Feature | Surqlize | SurrealDB.js | Prisma | Drizzle | TypeORM |\n|---------|----------|--------------|--------|---------|---------|\n| SurrealDB support | ✅ | ✅ | ❌ | ❌ | ❌ |\n| Schema definition | ✅ \u003csup\u003e\u003csub\u003eCode-first\u003c/sub\u003e\u003c/sup\u003e | ❌ | ✅ \u003csup\u003e\u003csub\u003eSchema file\u003c/sub\u003e\u003c/sup\u003e | ✅ \u003csup\u003e\u003csub\u003eCode-first\u003c/sub\u003e\u003c/sup\u003e | ⚠️ \u003csup\u003e\u003csub\u003eDecorators\u003c/sub\u003e\u003c/sup\u003e |\n| Type inference | ✅ \u003csup\u003e\u003csub\u003eFull\u003c/sub\u003e\u003c/sup\u003e | ⚠️ \u003csup\u003e\u003csub\u003ePartial\u003c/sub\u003e\u003c/sup\u003e | ✅ \u003csup\u003e\u003csub\u003eWith codegen\u003c/sub\u003e\u003c/sup\u003e | ✅ \u003csup\u003e\u003csub\u003eFull\u003c/sub\u003e\u003c/sup\u003e | ⚠️ \u003csup\u003e\u003csub\u003eDecorators\u003c/sub\u003e\u003c/sup\u003e |\n| CRUD operations | ✅ \u003csup\u003e\u003csub\u003eAll operations\u003c/sub\u003e\u003c/sup\u003e | ✅ | ✅ | ✅ | ✅ |\n| Graph and edges | ✅ \u003csup\u003e\u003csub\u003eNative\u003c/sub\u003e\u003c/sup\u003e | ✅ \u003csup\u003e\u003csub\u003eManual\u003c/sub\u003e\u003c/sup\u003e | ❌ | ❌ | ❌ |\n| Query builder | ✅ \u003csup\u003e\u003csub\u003eType-safe\u003c/sub\u003e\u003c/sup\u003e | ⚠️ \u003csup\u003e\u003csub\u003eManual\u003c/sub\u003e\u003c/sup\u003e | ⚠️ \u003csup\u003e\u003csub\u003eLimited\u003c/sub\u003e\u003c/sup\u003e | ✅ \u003csup\u003e\u003csub\u003eType-safe\u003c/sub\u003e\u003c/sup\u003e | ✅ \u003csup\u003e\u003csub\u003eQuery builder\u003c/sub\u003e\u003c/sup\u003e |\n| Database Functions | ✅ \u003csup\u003e\u003csub\u003eIntegrated\u003c/sub\u003e\u003c/sup\u003e | ⚠️ \u003csup\u003e\u003csub\u003eManual\u003c/sub\u003e\u003c/sup\u003e | ⚠️ \u003csup\u003e\u003csub\u003eLimited\u003c/sub\u003e\u003c/sup\u003e | ✅ \u003csup\u003e\u003csub\u003eSQL functions\u003c/sub\u003e\u003c/sup\u003e | ✅ \u003csup\u003e\u003csub\u003eQuery functions\u003c/sub\u003e\u003c/sup\u003e |\n| Nested Queries | ✅ \u003csup\u003e\u003csub\u003eType-safe\u003c/sub\u003e\u003c/sup\u003e | ⚠️ \u003csup\u003e\u003csub\u003eManual\u003c/sub\u003e\u003c/sup\u003e | ✅ \u003csup\u003e\u003csub\u003eRelations\u003c/sub\u003e\u003c/sup\u003e | ✅ \u003csup\u003e\u003csub\u003eJoins\u003c/sub\u003e\u003c/sup\u003e | ✅ \u003csup\u003e\u003csub\u003eRelations\u003c/sub\u003e\u003c/sup\u003e |\n| Fluent API | ✅ | ❌ | ❌ | ✅ | ✅ |\n\n**Why Surqlize?**\n\n- **Native SurrealDB support**: Built specifically for SurrealDB's unique features including graph relationships, flexible schemas, and SurrealQL\n- **No code generation**: Full type inference using TypeScript's type system—no codegen required\n- **Fluent API**: Natural, chainable syntax that mirrors SurrealQL while providing complete type safety\n- **Graph-first**: Edges and relationships are first-class citizens, not an afterthought\n- **Complete CRUD**: Full support for SELECT, CREATE, UPSERT, UPDATE, RELATE, and DELETE operations\n\n## Roadmap\n\nThis project is in active development. Planned features include:\n\n- [x] **SurrealDB functions** - All 25 built-in function families (string, array, math, time, crypto, rand, and more)\n- [x] **Advanced query clauses** - ORDER BY, GROUP BY, FETCH, SPLIT\n- [x] **Transaction support** - Batch and interactive transactions\n- [x] **Multi-session support** - Multiple sessions over a single connection\n- [ ] **Runtime validation** - Validate data at runtime using schema definitions\n- [ ] **Advanced graph traversal** - Path finding, recursive queries, graph algorithms\n- [ ] **Performance optimizations** - Query caching, connection pooling\n- [ ] **Schema migrations** - Version control for database schemas\n- [ ] **Documentation site** - Comprehensive guides and API reference\n\n## Development\n\n```bash\n# Install dependencies\nbun install\n\n# Build the project\nbun run build\n\n# Run the example file\nbun run examples/demo.ts\n\n# Run tests\nbun run test:unit          # Unit tests\nbun run test:integration   # Integration tests (requires SurrealDB)\nbun run type-check         # TypeScript type checking\n\n# Lint and format\nbun run qc   # Check for issues\nbun run qa   # Auto-fix issues\nbun run qau  # Auto-fix with unsafe changes\n```\n\n## Contributing\n\nContributions are welcome! This project is in an experimental stage, so expect breaking changes. If you'd like to contribute:\n\n1. Open an issue to discuss your idea\n2. Fork the repository\n3. Create a feature branch\n4. Submit a pull request\n\nPlease ensure your code passes the linting checks (`bun run qc`).\n\n## License\n\nApache-2.0\n\n---\n\n**Built with ❤️ for the SurrealDB community**\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsurrealdb%2Fsurqlize","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsurrealdb%2Fsurqlize","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsurrealdb%2Fsurqlize/lists"}