Skip to content

Terminal Methods

All queries must end with a terminal method to execute. These methods actually perform the query and return results.

Quick Navigation

Quick Reference

MethodDescriptionExample
toArrayAsync()Get all results as an arrayawait ctx.products.toArrayAsync()
firstAsync()Get first item (throws if none)await ctx.products.firstAsync()
firstOrUndefinedAsync()Get first item or undefinedawait ctx.products.firstOrUndefinedAsync()
someAsync()Check if any items existawait ctx.products.someAsync()
everyAsync(predicate)Check if all items matchawait ctx.products.everyAsync(p => p.inStock)
countAsync()Count total itemsawait ctx.products.countAsync()
sumAsync(field)Sum numeric fieldawait ctx.products.sumAsync(p => p.price)
minAsync(field)Get minimum valueawait ctx.products.minAsync(p => p.price)
maxAsync(field)Get maximum valueawait ctx.products.maxAsync(p => p.price)
distinctAsync()Get unique valuesawait ctx.products.distinctAsync()
toGroupAsync(selector)Group items by keyawait ctx.products.toGroupAsync(p => p.category)
removeAsync()Delete matching itemsawait ctx.products.removeAsync()

Detailed Examples

Getting All Results

ts
// Get all products
const allProducts = await ctx.products.toArrayAsync();

// Get all products with filtering
const expensiveProducts = await ctx.products
  .where((p) => p.price > 100)
  .toArrayAsync();

Getting Single Items

ts
// Get first product (throws if none exist)
const firstProduct = await ctx.products.firstAsync();

// Get first product or undefined if none exist
const firstOrUndefined = await ctx.products.firstOrUndefinedAsync();

// Get first expensive product
const firstExpensive = await ctx.products
  .where((p) => p.price > 100)
  .firstOrUndefinedAsync();

Checking Existence

ts
// Check if any products exist
const hasProducts = await ctx.products.someAsync();

// Check if any expensive products exist
const hasExpensiveProducts = await ctx.products
  .where((p) => p.price > 100)
  .someAsync();

// Check if all products are in stock
const allInStock = await ctx.products.everyAsync((p) => p.inStock);

// Check if all electronics are in stock
const allElectronicsInStock = await ctx.products
  .where((p) => p.category === "electronics")
  .everyAsync((p) => p.inStock);

Counting Items

ts
// Count all products
const totalCount = await ctx.products.countAsync();

// Count products in specific category
const electronicsCount = await ctx.products
  .where((p) => p.category === "electronics")
  .countAsync();

// Count expensive products
const expensiveCount = await ctx.products
  .where((p) => p.price > 100)
  .countAsync();

Aggregation Operations

ts
// Sum all product prices
const totalValue = await ctx.products.sumAsync((p) => p.price);

// Sum prices of electronics
const electronicsValue = await ctx.products
  .where((p) => p.category === "electronics")
  .sumAsync((p) => p.price);

// Get minimum price
const minPrice = await ctx.products.minAsync((p) => p.price);

// Get maximum price
const maxPrice = await ctx.products.maxAsync((p) => p.price);

// Get minimum price of electronics
const minElectronicsPrice = await ctx.products
  .where((p) => p.category === "electronics")
  .minAsync((p) => p.price);

Distinct Values

ts
// Get unique categories
const uniqueCategories = await ctx.products
  .map((p) => p.category)
  .distinctAsync();

// Get unique prices
const uniquePrices = await ctx.products.map((p) => p.price).distinctAsync();

// Get unique product names
const uniqueNames = await ctx.products.map((p) => p.name).distinctAsync();

Grouping Data

Group items by a key value, returning a record where keys are the grouped values and values are arrays of items with that key:

ts
// Group products by category
const productsByCategory = await ctx.products.toGroupAsync((p) => p.category);
// Result: { "electronics": Product[], "clothing": Product[], "books": Product[] }

// Group products by status
const productsByStatus = await ctx.products.toGroupAsync((p) => p.status);
// Result: { "active": Product[], "inactive": Product[] }

// Group with filtering
const expensiveByCategory = await ctx.products
  .where((p) => p.price > 100)
  .toGroupAsync((p) => p.category);

// Group by numeric field
const productsByPriceRange = await ctx.products.toGroupAsync((p) =>
  p.price < 50 ? "budget" : p.price < 200 ? "mid" : "premium"
);
// Result: { "budget": Product[], "mid": Product[], "premium": Product[] }

The selector function must return a value that can be used as an object key (string, number, or Date). Each group contains an array of all items that share the same key value.

Removal Operations

ts
// Remove all products
await ctx.products.removeAsync();

// Remove expensive products
await ctx.products.where((p) => p.price > 100).removeAsync();

// Remove products in specific category
await ctx.products.where((p) => p.category === "electronics").removeAsync();

// Remove out of stock products
await ctx.products.where((p) => p.inStock === false).removeAsync();

Important Notes

Lazy Evaluation

  • Queries are lazy - nothing executes until you call a terminal method
  • You can chain multiple operations before calling a terminal method
  • The query is built up and executed only when the terminal method is called

Async vs Callback Styles

Both async Promises and callback styles are supported:

ts
// Async style (recommended)
const products = await ctx.products.toArrayAsync();

// Callback style
ctx.products.toArray((result) => {
  if (result.ok === "success") {
    console.log("Products:", result.data);
  } else {
    console.error("Error:", result.error);
  }
});

Live Queries

For live results that update automatically, you can chain .subscribe() before a terminal method:

ts
// Live query - will update when data changes
ctx.products
  .where((p) => p.price > 100)
  .subscribe()
  .toArrayAsync();

Common Patterns

Data Validation

ts
// Check if user exists before creating
const userExists = await ctx.users.where((u) => u.email === email).someAsync();

if (userExists) {
  throw new Error("User already exists");
}

Pagination with Count

ts
// Get page data and total count
const pageSize = 10;
const page = 1;
const offset = (page - 1) * pageSize;

const [products, totalCount] = await Promise.all([
  ctx.products.skip(offset).take(pageSize).toArrayAsync(),
  ctx.products.countAsync(),
]);

Conditional Operations

ts
// Only proceed if data exists
const hasData = await ctx.products.someAsync();
if (hasData) {
  const firstProduct = await ctx.products.firstAsync();
  // Process first product
}

Organizing Data by Category

ts
// Group products by category for display
const productsByCategory = await ctx.products.toGroupAsync((p) => p.category);

// Iterate over groups
for (const [category, products] of Object.entries(productsByCategory)) {
  console.log(`${category}: ${products.length} products`);
}

// Group with filtering
const activeProductsByStatus = await ctx.products
  .where((p) => p.active)
  .toGroupAsync((p) => p.status);

Released under the MIT License.