Turn a user's search sentence into filters your API already accepts.

You describe the filters your API supports. JevFilter reads the user's request against that schema and returns a typed, validated filter object. Your existing query code runs the search with your authorization checks, and JevFilter never writes SQL.

npm install jevfilter @typesafe-ai/sdk

v0.1, MIT, Node.js 20+, zero runtime dependencies

Understood
Asks first
Refused on purpose
search.ts prepare() never runs a search

waiting

These are real outputs from the library, with dates computed as of Wed 23 Sep 2026. Only ready carries filters you can execute.

  • ready
  • needs_clarification
  • unsupported
  • blocked
  • unavailable

01 how it works

Only one step uses a model

The model never writes a query. It picks from options built from your schema, and your code decides what runs.

  1. input · the user

    A sentence

    "urgent billing tickets from last week"

  2. code · deterministic

    Code finds dates & numbers

    "last week" → 2026-09-14 … 2026-09-21

  3. model · closed options only

    Jev chooses from closed options

    Which field? Which allowed value? Which phrase?

  4. code · deterministic

    JevFilter validates every answer

    Against the schema you defined.

  5. your code · your auth

    Your existing API executes it

    With your auth, inside the user's tenant.

02 boundaries

What JevFilter won't do

JevFilter doesn't claim to make AI safe. It guarantees only that the model never gets direct authority over your database.

  • Generate SQL, ORM code, or any other executable text.

  • Execute anything the model returns.

  • Expand the caller's permissions. Scope comes from your session, never from the filters.

  • Send the model your database credentials, your records, or your query code.

  • Guess which record you meant when a name matches several.

03 compare

What you get back

JevFilter returns a filter object that your existing API can already run.

Natural-language search approaches and what each returns
ApproachWhat you get back
Text-to-SQLA query string you have to trust
AI search engineA new index to keep all your data in
RAGA generated answer
Agent toolsA model that decides which tools to call
JevFilterA bounded, typed filter object for the API you already own

04 why jev

Why Jev instead of a hosted AI search service

Hosted AI search services generally ask you to copy or index your records into their system. They run the search on their side and return their results or a generated answer. That usually means keeping a second copy of your data in sync and re-creating your permission rules there.

JevFilter with Jev keeps your data where it is. Jev receives the user's search text and your filter schema: field names, descriptions, and allowed values. It never sees your records, your credentials, your query code, or the customer names your resolver finds. Your existing API and its authorization run the query.

A hosted AI search service, usually

your records
Copied or indexed into their system
leaves your server
Your records, kept in sync
runs the search
The service, on its side
permission rules
Re-created in the service
comes back
Their results or a generated answer

JevFilter with Jev

your records
Stay in your database
leaves your server
The search text and your filter schema
runs the search
Your existing API
permission rules
Your authorization, unchanged
comes back
A typed filter object from your schema
  1. bounded decisions

    Jev picks from your options

    Jev is built for bounded decisions. It picks from options you define and returns a probability for each, instead of generating free text. The probabilities are why JevFilter can say "I'm not sure, which one?", and an answer outside the options is rejected.

  2. cost

    About $0.0001 per search

    TypeSafe lists Jev at $0.042 per million input tokens and doesn't charge for output tokens. A demo search uses about 2.3k input tokens, so we estimate each search costs roughly $0.0001.

  3. speed

    About 300 to 520 ms

    In the live demo, searches took about 300 to 520 ms end to end, measured locally against the live API.

  4. no lock-in

    Swap the provider, keep the schema

    Jev is the launch provider. The FilterProvider interface lets you swap providers without changing your schema or your API.

Try the store demo

05 quick start

Three steps to a search box

It runs server-side on Node.js 20+ and ships as ESM. The core has no runtime dependencies; @typesafe-ai/sdk is only needed for the Jev provider.

  1. Describe the filters your API already supports

    tickets.search.ts
    import { defineSearch, enumField, booleanField, numberField, dateField, entityField } from "jevfilter";
    
    const tickets = defineSearch({
      resource: "tickets",
      fields: {
        status: enumField({ open: "still open, active", pending: null, closed: "resolved, done" }),
        priority: enumField({ low: null, medium: null, high: "urgent, critical, P1" }),
        category: enumField(["billing", "sales", "support"]),
        escalated: booleanField({ description: "escalated to a manager" }),
        amount: numberField({ unit: "USD" }),
        createdAt: dateField({ label: "created" }),
        customer: entityField({ resolve: (name, session) => findCustomers(name, session) }),
      },
    });

    "Urgent" maps to high only because the description says so. JevFilter takes business meaning from your schema and adds none of its own.

  2. Connect your existing API

    search.ts
    import { createNaturalFilter } from "jevfilter";
    import { jev } from "jevfilter/jev";
    
    const search = createNaturalFilter({
      schema: tickets,
      provider: jev(), // reads TYPESAFE_API_KEY
      authorize: (session) => session.canSearchTickets, // runs before any model call
      executor: (filters, session) => searchTickets(filters, session), // your code, your scope
      timeZone: "Asia/Kolkata",
    });
  3. Prepare, show, execute

    route.ts
    const result = await search.prepare(userText, { context: session }); // never runs a search
    
    if (result.status === "ready") {
      // show result.interpretation as chips, then:
      const page = await search.execute(result.filters, { context: session });
    }

    The filters are typed from your schema, so status is "open" | "pending" | "closed" | { not: … }.

06 keys

Bring your own Jev key

JevFilter never ships a key or proxies requests. Keys never appear in logs, results, or errors.

Powered by Jev from TypeSafe AI, the launch provider. Providers are pluggable.

provider.ts
jev()                                                  // TYPESAFE_API_KEY from the environment
jev({ apiKey: process.env.JEV_API_KEY })               // one key for your app
jev({ apiKey: (session) => session.workspace.jevKey }) // each customer brings their own key

07 tested against real Jev

live eval cases pass on jev-1.13.0
23/23
cross-tenant leaks
0
known failures, published in the suite with what should happen
4
unit tests, including a hostile provider that returns random and malicious answers
81

The confidence thresholds were tuned on this one small suite. To measure your own schema, copy evals/, point evals/run.ts at your createNaturalFilter setup, and write your own cases. Known failures today: sort words like "latest" are dropped, bare years aren't dates, OR between dates reads as AND, and a second customer name is dropped. See the eval cases

08 faq

Questions developers ask

Is this text-to-SQL?

No. JevFilter returns filter objects built from your schema. Your code runs the query.

Can the model see my data?

Jev receives the search text and your configured filter schema (field names, descriptions, and allowed values). Your records, credentials, and query code are never sent, so if your category names are sensitive, treat them like the search text. Entity candidate names aren't sent either. The model picks a phrase from the request, and your resolver looks it up within the user's scope.

What if the model is wrong?

Chips show users how their request was read, so they can check it and edit it. Ambiguity returns needs_clarification. If the model fails, times out, or answers outside the options it was given, you get unavailable, and nothing falls back to searching everything.

Can a prompt escape tenant scope?

Scope comes from your session, and the filters can't change it. execute() validates the filters again on every call and reruns authorize(context), and your executor applies tenant and user scope as an outer AND.

Do I have to write an authorize function?

Yes. createNaturalFilter throws if you leave out authorize, unless you pass allowUnauthenticated: true for data anyone may search. It runs before every model call and again before every execute.

Do I need Jev?

Jev is the launch provider. The FilterProvider interface lets you plug in any model that can answer closed multiple-choice questions, and the offline keywordProvider works for tests and demos.

What does it cost?

Each search makes one Jev call. A demo search uses about 2.3k input tokens. TypeSafe lists Jev at $0.042 per million input tokens and doesn't charge for output tokens, so we estimate a search costs about $0.0001. A repeat search served from the cache costs no tokens.

Can I cache results?

Yes. Wrap the provider with withCache and give it a scope, usually the tenant id, or shared: true for data anyone may search. Only Jev's answers are stored, keyed by the search text, the questions built from your schema, the scope and the model. authorize, validation, the customer lookup and your executor still run on every search, and dates like "last week" are recomputed on every search. In the store demo, a repeat search took 1 ms and 0 tokens instead of 602 ms and 2,829 tokens. memoryCache() ships with the package and lives in one process. To share the cache across servers, pass any store with get and set. The hosted demo uses Workers KV this way.

Should it run on every keystroke?

No. Each interpretation is a network call, and in the live demo one took 300 to 520 ms. Run it when the user presses Enter. Edits to chips or ordinary filter controls go straight to execute(), which doesn't call the model.

Which languages and limits?

v0.1 is English only, with one entity field per search. There's no OR across fields and no "open or pending" on a single field. Requests are capped at 500 characters.

What's the license?

MIT.

>_ try it

Type a sentence and watch it become a filter.

The store demo searches 640 made-up products and fills in its filter sidebar from your sentence. The helpdesk demo shows every step on the server, and you can try to break it.