The engine ranks a search's hits by score until you ask for another order. You build one file here, search.ts, in which you sort a bicycle catalogue by field values, collapse the hits into groups, and page through them by offset and by cursor. At the end, you use two short files to browse the catalogue without a search term and to pin a sponsored result in place. Each code block below holds a whole file, with green lines marking what each step adds and struck lines marking what it takes away.
Order the hits by field values
With sort, the engine orders the hits by field values in place of their scores, and it uses each further field to break the ties that the one before it leaves. A query may sort on a number, enum, boolean, or string:sortable field. The schema declares maker as string:sortable for that reason, because a sort on a plain string field fails with SEARCH_INVALID_FIELD.
import { createNarsil } from '@delali/narsil'
const narsil = await createNarsil()
await narsil.createIndex('bicycles', {
schema: {
name: 'string',
summary: 'string',
maker: 'string:sortable',
category: 'enum',
price: 'number',
rating: 'number',
inStock: 'boolean',
tags: 'string[]',
},
})
await narsil.insertBatch('bicycles', [
{ id: 'urban-commuter', name: 'Gomes Urban commuter', summary: 'A lightweight aluminium commuter bicycle with hydraulic disc brakes.', maker: 'Gomes Cycles', category: 'city', price: 749, rating: 4.5, inStock: true, tags: ['commuter', 'aluminium'] },
{ id: 'city-step', name: 'Gomes Step-through city bicycle', summary: 'A step-through city bicycle with a chain guard and a hub dynamo.', maker: 'Gomes Cycles', category: 'city', price: 549, rating: 4.0, inStock: true, tags: ['commuter', 'steel'] },
{ id: 'gravel-explorer', name: 'Mori Gravel explorer', summary: 'A steel gravel bicycle built for long unpaved rides.', maker: 'Mori Works', category: 'gravel', price: 1299, rating: 4.8, inStock: true, tags: ['gravel', 'steel'] },
{ id: 'gravel-allroad', name: 'Mori Allroad gravel bicycle', summary: 'An aluminium gravel bicycle with mounts for three bottles.', maker: 'Mori Works', category: 'gravel', price: 999, rating: 4.4, inStock: false, tags: ['gravel', 'aluminium'] },
{ id: 'folding-metro', name: 'Obi Metro folding bicycle', summary: 'A folding bicycle that fits under a train seat.', maker: 'Obi Mobility', category: 'folding', price: 899, rating: 4.2, inStock: true, tags: ['folding', 'commuter'] },
{ id: 'folding-tourer', name: 'Obi Tourer folding bicycle', summary: 'A folding bicycle with a rear rack and eight gears.', maker: 'Obi Mobility', category: 'folding', price: 1150, rating: 4.1, inStock: true, tags: ['folding', 'aluminium'] },
{ id: 'cargo-hauler', name: 'Larsson Cargo hauler', summary: 'An electric cargo bicycle rated to carry two children.', maker: 'Larsson Bikes', category: 'cargo', price: 3499, rating: 4.6, inStock: true, tags: ['cargo', 'electric'] },
{ id: 'cargo-trike', name: 'Larsson Cargo trike', summary: 'A three-wheeled cargo bicycle with a lockable box.', maker: 'Larsson Bikes', category: 'cargo', price: 4195, rating: 4.3, inStock: false, tags: ['cargo', 'electric'] },
{ id: 'road-racer', name: 'Khoury Road racer', summary: 'A carbon road bicycle with electronic shifting.', maker: 'Khoury Composites', category: 'road', price: 4250, rating: 4.9, inStock: true, tags: ['road', 'carbon'] },
{ id: 'road-endurance', name: 'Khoury Endurance road bicycle', summary: 'A carbon road bicycle tuned for comfort over long distances.', maker: 'Khoury Composites', category: 'road', price: 2799, rating: 4.7, inStock: true, tags: ['road', 'carbon'] },
{ id: 'kids-first', name: 'Adams First ride bicycle', summary: 'A first bicycle for a child, with stabilisers that come off in one minute.', maker: 'Adams Assembly', category: 'kids', price: 189, rating: 4.3, inStock: true, tags: ['kids'] },
{ id: 'kids-junior', name: 'Adams Junior mountain bicycle', summary: 'A junior mountain bicycle with a suspension fork and six gears.', maker: 'Adams Assembly', category: 'kids', price: 329, rating: 4.1, inStock: true, tags: ['kids', 'aluminium'] },
])
const results = await narsil.query('bicycles', {
term: 'bicycle',
filters: { fields: { inStock: { eq: true } } },
sort: { maker: 'asc', price: 'asc' },
document: { include: ['maker', 'price'] },
limit: 6,
})
console.log(JSON.stringify(results.hits, null, 2))
await narsil.shutdown()[
{
"id": "kids-first",
"document": {
"maker": "Adams Assembly",
"price": 189
}
},
{
"id": "kids-junior",
"document": {
"maker": "Adams Assembly",
"price": 329
}
},
{
"id": "city-step",
"document": {
"maker": "Gomes Cycles",
"price": 549
}
},
{
"id": "urban-commuter",
"document": {
"maker": "Gomes Cycles",
"price": 749
}
},
{
"id": "road-endurance",
"document": {
"maker": "Khoury Composites",
"price": 2799
}
},
{
"id": "road-racer",
"document": {
"maker": "Khoury Composites",
"price": 4250
}
}
]The engine orders the hits by maker and then by price within each maker. The hits hold no score, because the engine ranks a sorted query by its sort values alone, so set includeScores: true where you want the scores back. The engine compares text by its case-folded form, so apple sorts before Banana; it leaves accents unfolded, however, so Ärla and Ćevapi sort after cevapi. A document that lacks the sorted field comes last under asc and under desc alike.
A sort takes at most eight fields, because the cursor holds one value for each of them. Write the sort as a list of { field, direction } entries wherever a field name is a whole number such as 2024, since JavaScript moves such keys to the front of an object and the engine would then apply that field first. A hybrid query takes no sort, because its fusion sets the order, so the engine fails a query that asks for both with SEARCH_INVALID_MODE.
Collapse the hits into groups
With group, the engine gathers the hits that share values in the named fields. maxPerGroup sets how many hits the engine keeps in each group, while reduce gives the engine a function that folds each group's hits into one value. The engine groups the whole matched set before limit applies, so limit: 0 returns every group with no hits beside them.
import { createNarsil } from '@delali/narsil'
const narsil = await createNarsil()
await narsil.createIndex('bicycles', {
schema: {
name: 'string',
summary: 'string',
maker: 'string:sortable',
category: 'enum',
price: 'number',
rating: 'number',
inStock: 'boolean',
tags: 'string[]',
},
})
await narsil.insertBatch('bicycles', [
{ id: 'urban-commuter', name: 'Gomes Urban commuter', summary: 'A lightweight aluminium commuter bicycle with hydraulic disc brakes.', maker: 'Gomes Cycles', category: 'city', price: 749, rating: 4.5, inStock: true, tags: ['commuter', 'aluminium'] },
{ id: 'city-step', name: 'Gomes Step-through city bicycle', summary: 'A step-through city bicycle with a chain guard and a hub dynamo.', maker: 'Gomes Cycles', category: 'city', price: 549, rating: 4.0, inStock: true, tags: ['commuter', 'steel'] },
{ id: 'gravel-explorer', name: 'Mori Gravel explorer', summary: 'A steel gravel bicycle built for long unpaved rides.', maker: 'Mori Works', category: 'gravel', price: 1299, rating: 4.8, inStock: true, tags: ['gravel', 'steel'] },
{ id: 'gravel-allroad', name: 'Mori Allroad gravel bicycle', summary: 'An aluminium gravel bicycle with mounts for three bottles.', maker: 'Mori Works', category: 'gravel', price: 999, rating: 4.4, inStock: false, tags: ['gravel', 'aluminium'] },
{ id: 'folding-metro', name: 'Obi Metro folding bicycle', summary: 'A folding bicycle that fits under a train seat.', maker: 'Obi Mobility', category: 'folding', price: 899, rating: 4.2, inStock: true, tags: ['folding', 'commuter'] },
{ id: 'folding-tourer', name: 'Obi Tourer folding bicycle', summary: 'A folding bicycle with a rear rack and eight gears.', maker: 'Obi Mobility', category: 'folding', price: 1150, rating: 4.1, inStock: true, tags: ['folding', 'aluminium'] },
{ id: 'cargo-hauler', name: 'Larsson Cargo hauler', summary: 'An electric cargo bicycle rated to carry two children.', maker: 'Larsson Bikes', category: 'cargo', price: 3499, rating: 4.6, inStock: true, tags: ['cargo', 'electric'] },
{ id: 'cargo-trike', name: 'Larsson Cargo trike', summary: 'A three-wheeled cargo bicycle with a lockable box.', maker: 'Larsson Bikes', category: 'cargo', price: 4195, rating: 4.3, inStock: false, tags: ['cargo', 'electric'] },
{ id: 'road-racer', name: 'Khoury Road racer', summary: 'A carbon road bicycle with electronic shifting.', maker: 'Khoury Composites', category: 'road', price: 4250, rating: 4.9, inStock: true, tags: ['road', 'carbon'] },
{ id: 'road-endurance', name: 'Khoury Endurance road bicycle', summary: 'A carbon road bicycle tuned for comfort over long distances.', maker: 'Khoury Composites', category: 'road', price: 2799, rating: 4.7, inStock: true, tags: ['road', 'carbon'] },
{ id: 'kids-first', name: 'Adams First ride bicycle', summary: 'A first bicycle for a child, with stabilisers that come off in one minute.', maker: 'Adams Assembly', category: 'kids', price: 189, rating: 4.3, inStock: true, tags: ['kids'] },
{ id: 'kids-junior', name: 'Adams Junior mountain bicycle', summary: 'A junior mountain bicycle with a suspension fork and six gears.', maker: 'Adams Assembly', category: 'kids', price: 329, rating: 4.1, inStock: true, tags: ['kids', 'aluminium'] },
])
const results = await narsil.query('bicycles', {
term: 'bicycle',
filters: { fields: { inStock: { eq: true } } },
sort: { maker: 'asc', price: 'asc' },
document: { include: ['maker', 'price'] },
limit: 6,
sort: { price: 'asc' },
group: {
fields: ['category'],
maxPerGroup: 2,
reduce: { initialValue: () => 0, reducer: (count) => Number(count) + 1 },
},
limit: 0,
})
console.log(JSON.stringify(results.hits, null, 2))
console.log(
JSON.stringify(
results.groups?.map((group) => ({ ...group.values, hits: group.hits.map((hit) => hit.id), counted: group.reduced })),
null,
2
)
)
await narsil.shutdown()[
{
"category": "folding",
"hits": [
"folding-metro",
"folding-tourer"
],
"counted": 2
},
{
"category": "city",
"hits": [
"city-step",
"urban-commuter"
],
"counted": 2
},
{
"category": "kids",
"hits": [
"kids-first",
"kids-junior"
],
"counted": 2
},
{
"category": "road",
"hits": [
"road-endurance",
"road-racer"
],
"counted": 2
},
{
"category": "cargo",
"hits": [
"cargo-hauler"
],
"counted": 1
},
{
"category": "gravel",
"hits": [
"gravel-explorer"
],
"counted": 1
}
]The engine keeps the two cheapest bicycles in stock for each category, since it sorts the hits by price before it groups them. The engine passes the reducer every member of the group, whatever maxPerGroup keeps for display, so counted gives the full size of a category even where the category holds more bicycles than its group shows. Where a group query leaves maxPerGroup out, the engine keeps one hit for each group.
Page through the results
A page comes from limit together with either offset, which skips that many hits, or searchAfter, which takes the cursor from the page before. You keep the shared options in one QueryParams object, so that both requests for the second page send the same query. The annotation also types 'asc' as a sort direction in place of a plain string.
import { createNarsil } from '@delali/narsil'
import { createNarsil, type QueryParams } from '@delali/narsil'
const narsil = await createNarsil()
await narsil.createIndex('bicycles', {
schema: {
name: 'string',
summary: 'string',
maker: 'string:sortable',
category: 'enum',
price: 'number',
rating: 'number',
inStock: 'boolean',
tags: 'string[]',
},
})
await narsil.insertBatch('bicycles', [
{ id: 'urban-commuter', name: 'Gomes Urban commuter', summary: 'A lightweight aluminium commuter bicycle with hydraulic disc brakes.', maker: 'Gomes Cycles', category: 'city', price: 749, rating: 4.5, inStock: true, tags: ['commuter', 'aluminium'] },
{ id: 'city-step', name: 'Gomes Step-through city bicycle', summary: 'A step-through city bicycle with a chain guard and a hub dynamo.', maker: 'Gomes Cycles', category: 'city', price: 549, rating: 4.0, inStock: true, tags: ['commuter', 'steel'] },
{ id: 'gravel-explorer', name: 'Mori Gravel explorer', summary: 'A steel gravel bicycle built for long unpaved rides.', maker: 'Mori Works', category: 'gravel', price: 1299, rating: 4.8, inStock: true, tags: ['gravel', 'steel'] },
{ id: 'gravel-allroad', name: 'Mori Allroad gravel bicycle', summary: 'An aluminium gravel bicycle with mounts for three bottles.', maker: 'Mori Works', category: 'gravel', price: 999, rating: 4.4, inStock: false, tags: ['gravel', 'aluminium'] },
{ id: 'folding-metro', name: 'Obi Metro folding bicycle', summary: 'A folding bicycle that fits under a train seat.', maker: 'Obi Mobility', category: 'folding', price: 899, rating: 4.2, inStock: true, tags: ['folding', 'commuter'] },
{ id: 'folding-tourer', name: 'Obi Tourer folding bicycle', summary: 'A folding bicycle with a rear rack and eight gears.', maker: 'Obi Mobility', category: 'folding', price: 1150, rating: 4.1, inStock: true, tags: ['folding', 'aluminium'] },
{ id: 'cargo-hauler', name: 'Larsson Cargo hauler', summary: 'An electric cargo bicycle rated to carry two children.', maker: 'Larsson Bikes', category: 'cargo', price: 3499, rating: 4.6, inStock: true, tags: ['cargo', 'electric'] },
{ id: 'cargo-trike', name: 'Larsson Cargo trike', summary: 'A three-wheeled cargo bicycle with a lockable box.', maker: 'Larsson Bikes', category: 'cargo', price: 4195, rating: 4.3, inStock: false, tags: ['cargo', 'electric'] },
{ id: 'road-racer', name: 'Khoury Road racer', summary: 'A carbon road bicycle with electronic shifting.', maker: 'Khoury Composites', category: 'road', price: 4250, rating: 4.9, inStock: true, tags: ['road', 'carbon'] },
{ id: 'road-endurance', name: 'Khoury Endurance road bicycle', summary: 'A carbon road bicycle tuned for comfort over long distances.', maker: 'Khoury Composites', category: 'road', price: 2799, rating: 4.7, inStock: true, tags: ['road', 'carbon'] },
{ id: 'kids-first', name: 'Adams First ride bicycle', summary: 'A first bicycle for a child, with stabilisers that come off in one minute.', maker: 'Adams Assembly', category: 'kids', price: 189, rating: 4.3, inStock: true, tags: ['kids'] },
{ id: 'kids-junior', name: 'Adams Junior mountain bicycle', summary: 'A junior mountain bicycle with a suspension fork and six gears.', maker: 'Adams Assembly', category: 'kids', price: 329, rating: 4.1, inStock: true, tags: ['kids', 'aluminium'] },
])
const results = await narsil.query('bicycles', {
const page: QueryParams = {
term: 'bicycle',
filters: { fields: { inStock: { eq: true } } },
sort: { price: 'asc' },
group: {
fields: ['category'],
maxPerGroup: 2,
reduce: { initialValue: () => 0, reducer: (count) => Number(count) + 1 },
},
limit: 0,
})
document: false,
limit: 4,
}
const ids = (hits: Array<{ id: string }>): string[] => hits.map((hit) => hit.id)
const first = await narsil.query('bicycles', page)
const secondByOffset = await narsil.query('bicycles', { ...page, offset: 4 })
const secondByCursor = await narsil.query('bicycles', { ...page, searchAfter: first.cursor })
console.log(
JSON.stringify(
results.groups?.map((group) => ({ ...group.values, hits: group.hits.map((hit) => hit.id), counted: group.reduced })),
{
count: first.count,
first: ids(first.hits),
secondByOffset: ids(secondByOffset.hits),
secondByCursor: ids(secondByCursor.hits),
cursorAfterSecond: secondByCursor.cursor !== undefined,
},
null,
2
)
)
await narsil.shutdown(){
"count": 10,
"first": [
"kids-first",
"kids-junior",
"city-step",
"urban-commuter"
],
"secondByOffset": [
"folding-metro",
"folding-tourer",
"gravel-explorer",
"road-endurance"
],
"secondByCursor": [
"folding-metro",
"folding-tourer",
"gravel-explorer",
"road-endurance"
],
"cursorAfterSecond": true
}Offset and cursor reach the same four bicycles, while count still reports all ten matches. The last page comes back with no cursor at all, so stop once cursor is undefined. The engine binds a cursor to the query that produced it, so it fails the cursor with SEARCH_INVALID_CURSOR under a different term, filter, or sort, although it accepts a different limit. offset and limit together reach 10,000 hits at most, and past that the engine fails the request with SEARCH_RESULT_WINDOW_EXCEEDED, which leaves the cursor as the way to page deeper.
Browse an index in any order
listDocuments pages through an index without a search term, and it takes the same filters, sort, and document options that a query takes. Indexes and documents covers the listing in id order and the rules that hold for it while writes continue, while here you list the bicycles in stock from the most expensive down.
import { createNarsil } from '@delali/narsil'
const narsil = await createNarsil()
await narsil.createIndex('bicycles', {
schema: {
name: 'string',
summary: 'string',
maker: 'string:sortable',
category: 'enum',
price: 'number',
rating: 'number',
inStock: 'boolean',
tags: 'string[]',
},
})
await narsil.insertBatch('bicycles', [
{ id: 'urban-commuter', name: 'Gomes Urban commuter', summary: 'A lightweight aluminium commuter bicycle with hydraulic disc brakes.', maker: 'Gomes Cycles', category: 'city', price: 749, rating: 4.5, inStock: true, tags: ['commuter', 'aluminium'] },
{ id: 'city-step', name: 'Gomes Step-through city bicycle', summary: 'A step-through city bicycle with a chain guard and a hub dynamo.', maker: 'Gomes Cycles', category: 'city', price: 549, rating: 4.0, inStock: true, tags: ['commuter', 'steel'] },
{ id: 'gravel-explorer', name: 'Mori Gravel explorer', summary: 'A steel gravel bicycle built for long unpaved rides.', maker: 'Mori Works', category: 'gravel', price: 1299, rating: 4.8, inStock: true, tags: ['gravel', 'steel'] },
{ id: 'gravel-allroad', name: 'Mori Allroad gravel bicycle', summary: 'An aluminium gravel bicycle with mounts for three bottles.', maker: 'Mori Works', category: 'gravel', price: 999, rating: 4.4, inStock: false, tags: ['gravel', 'aluminium'] },
{ id: 'folding-metro', name: 'Obi Metro folding bicycle', summary: 'A folding bicycle that fits under a train seat.', maker: 'Obi Mobility', category: 'folding', price: 899, rating: 4.2, inStock: true, tags: ['folding', 'commuter'] },
{ id: 'folding-tourer', name: 'Obi Tourer folding bicycle', summary: 'A folding bicycle with a rear rack and eight gears.', maker: 'Obi Mobility', category: 'folding', price: 1150, rating: 4.1, inStock: true, tags: ['folding', 'aluminium'] },
{ id: 'cargo-hauler', name: 'Larsson Cargo hauler', summary: 'An electric cargo bicycle rated to carry two children.', maker: 'Larsson Bikes', category: 'cargo', price: 3499, rating: 4.6, inStock: true, tags: ['cargo', 'electric'] },
{ id: 'cargo-trike', name: 'Larsson Cargo trike', summary: 'A three-wheeled cargo bicycle with a lockable box.', maker: 'Larsson Bikes', category: 'cargo', price: 4195, rating: 4.3, inStock: false, tags: ['cargo', 'electric'] },
{ id: 'road-racer', name: 'Khoury Road racer', summary: 'A carbon road bicycle with electronic shifting.', maker: 'Khoury Composites', category: 'road', price: 4250, rating: 4.9, inStock: true, tags: ['road', 'carbon'] },
{ id: 'road-endurance', name: 'Khoury Endurance road bicycle', summary: 'A carbon road bicycle tuned for comfort over long distances.', maker: 'Khoury Composites', category: 'road', price: 2799, rating: 4.7, inStock: true, tags: ['road', 'carbon'] },
{ id: 'kids-first', name: 'Adams First ride bicycle', summary: 'A first bicycle for a child, with stabilisers that come off in one minute.', maker: 'Adams Assembly', category: 'kids', price: 189, rating: 4.3, inStock: true, tags: ['kids'] },
{ id: 'kids-junior', name: 'Adams Junior mountain bicycle', summary: 'A junior mountain bicycle with a suspension fork and six gears.', maker: 'Adams Assembly', category: 'kids', price: 329, rating: 4.1, inStock: true, tags: ['kids', 'aluminium'] },
])
const listing = await narsil.listDocuments('bicycles', {
filters: { fields: { inStock: { eq: true } } },
sort: { price: 'desc' },
document: { include: ['name', 'price'] },
limit: 3,
})
console.log(JSON.stringify(listing, (key, value) => (key === 'elapsed' ? undefined : value), 2))
await narsil.shutdown(){
"documents": [
{
"id": "road-racer",
"document": {
"name": "Khoury Road racer",
"price": 4250
}
},
{
"id": "cargo-hauler",
"document": {
"name": "Larsson Cargo hauler",
"price": 3499
}
},
{
"id": "road-endurance",
"document": {
"name": "Khoury Endurance road bicycle",
"price": 2799
}
}
],
"cursor": "eyJ2Ijo0LCJhIjoicm9hZC1lbmR1cmFuY2UiLCJrIjpbMjc5OV0sIm8iOiJbW1wicHJpY2VcIixcImRlc2NcIl1dIiwicSI6IjU3NzM1YTljIn0=",
"total": 10
}Ten bicycles pass the filter, which total reports, and the page holds the three most expensive of them with a cursor for the next three. The engine binds that cursor to the sort as well, so send it back with the same sort object. Where a listing leaves sort out, the engine returns to id order.
Pin a result in place
With pinned, the engine places a document at a fixed position in the results, counting from 0, which is how a shop puts a sponsored or editorial pick at the top of a search. In pinned.ts you pin the cargo hauler first on a search for gravel.
import { createNarsil } from '@delali/narsil'
const narsil = await createNarsil()
await narsil.createIndex('bicycles', {
schema: {
name: 'string',
summary: 'string',
maker: 'string:sortable',
category: 'enum',
price: 'number',
rating: 'number',
inStock: 'boolean',
tags: 'string[]',
},
})
await narsil.insertBatch('bicycles', [
{ id: 'urban-commuter', name: 'Gomes Urban commuter', summary: 'A lightweight aluminium commuter bicycle with hydraulic disc brakes.', maker: 'Gomes Cycles', category: 'city', price: 749, rating: 4.5, inStock: true, tags: ['commuter', 'aluminium'] },
{ id: 'city-step', name: 'Gomes Step-through city bicycle', summary: 'A step-through city bicycle with a chain guard and a hub dynamo.', maker: 'Gomes Cycles', category: 'city', price: 549, rating: 4.0, inStock: true, tags: ['commuter', 'steel'] },
{ id: 'gravel-explorer', name: 'Mori Gravel explorer', summary: 'A steel gravel bicycle built for long unpaved rides.', maker: 'Mori Works', category: 'gravel', price: 1299, rating: 4.8, inStock: true, tags: ['gravel', 'steel'] },
{ id: 'gravel-allroad', name: 'Mori Allroad gravel bicycle', summary: 'An aluminium gravel bicycle with mounts for three bottles.', maker: 'Mori Works', category: 'gravel', price: 999, rating: 4.4, inStock: false, tags: ['gravel', 'aluminium'] },
{ id: 'folding-metro', name: 'Obi Metro folding bicycle', summary: 'A folding bicycle that fits under a train seat.', maker: 'Obi Mobility', category: 'folding', price: 899, rating: 4.2, inStock: true, tags: ['folding', 'commuter'] },
{ id: 'folding-tourer', name: 'Obi Tourer folding bicycle', summary: 'A folding bicycle with a rear rack and eight gears.', maker: 'Obi Mobility', category: 'folding', price: 1150, rating: 4.1, inStock: true, tags: ['folding', 'aluminium'] },
{ id: 'cargo-hauler', name: 'Larsson Cargo hauler', summary: 'An electric cargo bicycle rated to carry two children.', maker: 'Larsson Bikes', category: 'cargo', price: 3499, rating: 4.6, inStock: true, tags: ['cargo', 'electric'] },
{ id: 'cargo-trike', name: 'Larsson Cargo trike', summary: 'A three-wheeled cargo bicycle with a lockable box.', maker: 'Larsson Bikes', category: 'cargo', price: 4195, rating: 4.3, inStock: false, tags: ['cargo', 'electric'] },
{ id: 'road-racer', name: 'Khoury Road racer', summary: 'A carbon road bicycle with electronic shifting.', maker: 'Khoury Composites', category: 'road', price: 4250, rating: 4.9, inStock: true, tags: ['road', 'carbon'] },
{ id: 'road-endurance', name: 'Khoury Endurance road bicycle', summary: 'A carbon road bicycle tuned for comfort over long distances.', maker: 'Khoury Composites', category: 'road', price: 2799, rating: 4.7, inStock: true, tags: ['road', 'carbon'] },
{ id: 'kids-first', name: 'Adams First ride bicycle', summary: 'A first bicycle for a child, with stabilisers that come off in one minute.', maker: 'Adams Assembly', category: 'kids', price: 189, rating: 4.3, inStock: true, tags: ['kids'] },
{ id: 'kids-junior', name: 'Adams Junior mountain bicycle', summary: 'A junior mountain bicycle with a suspension fork and six gears.', maker: 'Adams Assembly', category: 'kids', price: 329, rating: 4.1, inStock: true, tags: ['kids', 'aluminium'] },
])
const results = await narsil.query('bicycles', {
term: 'gravel',
filters: { fields: { inStock: { eq: true } } },
pinned: [{ docId: 'cargo-hauler', position: 0 }],
document: { include: ['name'] },
})
console.log(
JSON.stringify(
{ count: results.count, hits: results.hits },
(key, value) => (typeof value === 'number' ? Number(value.toFixed(2)) : value),
2
)
)
await narsil.shutdown(){
"count": 2,
"hits": [
{
"id": "cargo-hauler",
"score": 0,
"document": {
"name": "Larsson Cargo hauler"
}
},
{
"id": "gravel-explorer",
"score": 4.98,
"document": {
"name": "Mori Gravel explorer"
}
}
]
}The engine places the cargo hauler first with a score of 0, although the cargo hauler holds no word gravel, and the gravel explorer follows at 4.98. count reports 2, because the engine counts a pinned document along with the documents that the search matched, so a pager that computes its pages from count includes the pin. A pin at a position beyond the page stays off that page, and the engine skips every pin on a request that sets searchAfter.
Where to go next
- Filters and facets covers the filters that narrow the hits before you sort, group, or page them.
- Indexes and documents covers the listing in id order.
- Hybrid search covers the fused ranking, which takes no sort.