NukeBase

Query Operations

Query allows you to search through collections and find items that match specific conditions. The query string uses JavaScript expressions where child represents each item being evaluated:

How queries work: NukeBase iterates through each child at the specified path and evaluates your condition. Items where the condition returns true are included in the results.

Querying data examples
// Basic equality check
query({
    path: ["users"],
    query: "child.age == 32"
}).then(response => {
    console.log(response.data);  // All users who are exactly 32
});

// Using comparison operators
query({
    path: ["products"],
    query: "child.price < 50"
}).then(response => {
    console.log(response.data);  // All products under $50
});

// Compound conditions with AND (&&)
query({
    path: ["products"],
    query: "child.price < 100 && child.category == 'electronics'"
}).then(response => {
    console.log(response.data);  // Affordable electronics
});

// Compound conditions with OR (||)
query({
    path: ["users"],
    query: "child.role == 'admin' || child.role == 'moderator'"
}).then(response => {
    console.log(response.data);  // All admins and moderators
});

// Text search with includes()
query({
    path: ["posts"],
    query: "child.title.includes('JavaScript')"
}).then(response => {
    console.log(response.data);  // Posts with "JavaScript" in the title
});

// Checking nested properties with childPath
query({
    path: ["users"],
    childPath: ["profile", "location"],
    query: "child == 'New York'"  // child refers to the location value
}).then(response => {
    // Returns: { matt123: { profile: { location: "New York" } } }
    // `child` in the query is the value at childPath; the response wraps
    // that value back in the childPath structure to mirror the DB shape.
    console.log(response.data);
});

// Combining multiple conditions
query({
    path: ["orders"],
    query: "child.status == 'pending' && child.total > 100 && child.items.length > 2"
}).then(response => {
    console.log(response.data);  // Large pending orders with multiple items
});

// Checking if a property exists
query({
    path: ["users"],
    query: "child.premiumAccount == true"
}).then(response => {
    console.log(response.data);  // All premium users
});

// Using NOT operator
query({
    path: ["tasks"],
    query: "child.completed != true"
}).then(response => {
    console.log(response.data);  // All incomplete tasks
});

// Date comparisons (assuming timestamps)
query({
    path: ["events"],
    query: "child.date > " + Date.now()
}).then(response => {
    console.log(response.data);  // Future events
});

Query Syntax Reference

Queries are evaluated by a restricted safe expression engine — not full JavaScript. The query string is parsed by a hand-written evaluator that supports only the operators and methods listed below. Anything outside this list (arithmetic, regex, typeof, Array.isArray, .startsWith, .toLowerCase, ternaries, function calls other than .includes(), array indexing with []) will not parse correctly and will fail or return an empty result.

This is different from Security Rules, which compile via new Function and have access to the full JavaScript language. Don't copy a complex rule expression into a query and expect it to work.

Supported in queries:

  • Boolean: ||, &&, !
  • Comparison: ===, !==, ==, !=, >, <, >=, <=
  • Method: .includes(arg) on strings or arrays (one literal or path argument)
  • Property access: dotted paths only (child.foo.bar); .length works as a plain property read on arrays/strings
  • Literals: numbers, single- or double-quoted strings, true, false, null, undefined
  • Parentheses for grouping

Queries support these operators and methods:

Operator/Method Description Example
== Equal to child.status == 'active'
!= Not equal to child.deleted != true
<, >, <=, >= Comparison child.age >= 18
&& Logical AND child.active && child.verified
|| Logical OR child.role == 'admin' || child.role == 'mod'
.includes() String contains child.email.includes('@gmail.com')
.length Array/string length child.tags.length > 3

Important: The child variable represents each item at the path you're querying. For example, when querying "users", child represents each individual user object.

Using childPath to Query Nested Data

The childPath parameter allows you to query and return only specific nested portions of your data. This is especially useful for separating public and private data, improving performance, or working with complex data structures.

How childPath works:

  • Navigation: childPath navigates to a nested position in your data
  • Query context: The child variable in your query refers to the data at that nested position
  • Response structure: Results include the full path with childPath, so you know which parent item matched
childPath examples
// Data structure:
// {
//   users: {
//     matt123: {
//       public: { name: "Matt", age: 25, city: "NYC" },
//       private: { ssn: "123-45-6789", salary: 80000 }
//     },
//     john456: {
//       public: { name: "John", age: 30, city: "LA" },
//       private: { ssn: "987-65-4321", salary: 90000 }
//     }
//   }
// }

// Query WITHOUT childPath - queries full user objects
query({
    path: ["users"],
    query: "child.public.age > 21"
}).then(response => {
    console.log(response.data);
    // Returns: {
    //   matt123: { public: {...}, private: {...} },
    //   john456: { public: {...}, private: {...} }
    // }
    // You get FULL user objects including private data
});

// Query WITH childPath - queries only public portion.
// `child` inside the query refers to the data AT childPath ("public").
// The response value MIRRORS the database shape: each match is wrapped
// in the same childPath structure, so you can read it the same way you'd
// read the original tree.
query({
    path: ["users"],
    childPath: ["public"],
    query: "child.age > 21"  // child still refers to the "public" object
}).then(response => {
    console.log(response.data);
    // Returns: {
    //   matt123: { public: { name: "Matt", age: 25, city: "NYC" } },
    //   john456: { public: { name: "John", age: 30, city: "LA" } }
    // }
    // The "public" wrapper is preserved; "private" is not present because
    // the query never walked into it.
});

// Multiple childPath levels — wrapper is nested to match
query({
    path: ["users"],
    childPath: ["public", "address"],
    query: "child.city == 'NYC'"  // child refers to the "address" object
}).then(response => {
    console.log(response.data);
    // Returns: {
    //   matt123: { public: { address: { city: "NYC", state: "NY" } } }
    // }
    // Each childPath segment shows up in the response, in order.
});

childPath Use Cases

Practical childPath use cases
// Use Case 1: Security - Exclude private data
// If users.matt123.private is blocked by read rules, childPath ensures
// you only query the accessible portion
query({
    path: ["users"],
    childPath: ["public"],
    query: "child.verified == true"
}).then(response => {
    // Only returns public data, won't fail if private is restricted
    displayPublicProfiles(response.data);
});

// Use Case 2: Performance - Return only needed data
// When clients only need profile info, not full user objects
query({
    path: ["users"],
    childPath: ["profile"],
    query: "child.country == 'USA'"
}).then(response => {
    // Smaller response payload, faster transmission
    renderUserProfiles(response.data);
});

// Use Case 3: Complex filtering on nested arrays
// Query specific nested collections
query({
    path: ["orders"],
    childPath: ["items"],
    query: "child.quantity > 5"
}).then(response => {
    // Returns: {
    //   order123: { items: { itemA: {quantity: 10, ...}, ... } }
    // }
    // The "items" wrapper is preserved so the result mirrors the DB shape.
    console.log("Orders with high-quantity items:", response.data);
});

// Use Case 4: Separating data concerns
// Different parts of your app query different data sections
query({
    path: ["products"],
    childPath: ["inventory"],
    query: "child.stock < 10"
}).then(response => {
    // Warehouse dashboard only needs inventory data
    showLowStockAlert(response.data);
});

When to use childPath:

  • You want to exclude certain fields from results (public vs private data)
  • You need to improve query performance by returning less data
  • Your read rules block certain paths, and childPath ensures you only query accessible data
  • You're querying nested collections or arrays within parent objects

Important: When using childPath, remember that child in your query refers to the data AT the childPath position, not the root object. Adjust your query conditions accordingly.

Numeric childPath segments preserve array shape. Segments inside childPath can be non-negative integers, and they walk into arrays in your data. The response wrapper is built to match: numeric segments produce arrays, string segments produce objects.

Numeric childPath segments
// Data structure:
// users.matt123 = { scores: [42, 87, 99], name: "Matt" }
// users.john456 = { scores: [10, 20, 30], name: "John" }

// childPath ending at index 0 of "scores"
query({
    path: ["users"],
    childPath: ["scores", 0],
    query: "child > 25"   // child refers to the integer at scores[0]
}).then(response => {
    console.log(response.data);
    // Returns: {
    //   matt123: { scores: [42] }
    // }
    // The wrapper preserves the array shape from the DB.
});

// childPath ending at index 1 of "scores"
query({
    path: ["users"],
    childPath: ["scores", 1],
    query: "child > 25"
}).then(response => {
    console.log(response.data);
    // Returns: {
    //   matt123: { scores: [null, 87] }
    // }
    // Index 1 is preserved. The leading slot is null because the query only
    // matched scores[1] — sparse-array slots become explicit nulls when JSON
    // is sent over the WebSocket. The matched value is still at index 1.
});

JSON null padding for non-zero indices: When a numeric childPath segment is greater than 0, the slots before the matched index are filled with null in transit. This keeps the index correct on the client (so response.data.matt123.scores[1] works) at the cost of leading nulls. Iterate with care, or filter null entries if your code can't tolerate them.