Queries in code

Selected databaseMongoDB

You describe what you want in natural language; the compiler turns each prompt into a real MongoDB query (find, aggregate, insert, update, delete) and generates code. At runtime you call MaskDatabase.prompt(promptText, params?) with the exact same string (or a mask- key from registery) and get the result. No AI calls at runtime.

For MongoDB you only need two things:define your models and then prompt (queries). Mask automatically determines which collections to use from your prompt — no manual wiring.

Query prompts compile to MongoDB operations

Use MaskDatabase.prompt('...') in code. The compiler turns prompts into find/aggregate/insert/update/delete for MongoDB.

const { MaskDatabase } = require('mask-databases');

const users = await MaskDatabase.prompt('list all users with name and email');
const user = await MaskDatabase.prompt('fetch user with id :userId', { userId });

Prompts registery (reusability)

Add a registery object inside overrideConfig in mask.compile.cjs (project root) with keys and full prompt text. In code use MaskDatabase.prompt('mask-key'). Change the prompt in one place and run node mask.compile.cjs again.

Format

Under registery, each key is an identifier, each value is the full prompt string (the same text the compiler and runtime use). Keys must not start with mask- — use plain names like userById, tasksWithAssignees. In code, use the mask- prefix: MaskDatabase.prompt('mask-userById', { userId }).

Example

// overrideConfig in mask.compile.cjs (keys without "mask-" prefix inside registery)
{
  "database": "mongodb",
  "dbModulePath": "src/db",
  "syncApiKey": "YOUR_PROJECT_API_KEY",
  "registery": {
    "listUsers": "list all users with name and email",
    "userById": "fetch user with id :userId",
    "tasksWithAssignees": "find all tasks with their assignees and assignee team details"
  }
}
// In code — use "mask-" + key when calling prompt()
const users = await MaskDatabase.prompt('mask-listUsers');
const user = await MaskDatabase.prompt('mask-userById', { userId: id });
const tasks = await MaskDatabase.prompt('mask-tasksWithAssignees');

Complex queries

Describe joins, filters, and ordering in natural language. Mask picks the right query type and uses your models for the right collections and fields.

// In registery or inline
'find all tasks with status open or in-progress, including assignee name and team name, ordered by dueDate'
'find all users that belong to team :teamId with name and email'

After compiling, run the same prompt string (or mask-key) at runtime to execute the generated query.

Insert, update, and delete

You can describe inserts, updates, and deletes in plain language. Pass the data (or id) as params. The generated code automatically uses only the relevant fields from the data you provide — any extra fields are ignored — and writes to the database in a safe way, so you get consistent, successful saves without manual field picking.

// Insert: describe what to insert, pass the data
await MaskDatabase.prompt('insert a new user with name, email and status', { name: 'Jane', email: 'jane@example.com', status: 'active' });

// Update: describe what to update and by what, pass id and fields
await MaskDatabase.prompt('update user with id :userId set name and email', { userId: id, name: 'Jane Doe', email: 'jane.doe@example.com' });

// Delete: describe what to remove, pass id
await MaskDatabase.prompt('delete task with id :taskId', { taskId });

Relevant fields are taken from your model schema; the rest of the payload is ignored. No need to strip or whitelist fields yourself.

Driver and query options (MongoDB & Mongoose)

For native MongoDB and Mongoose, describe extra behavior in your prompt at compile time. The compiler stores driverOptions on the query — the same kind of options you would pass to the underlying driver call (for example findOneAndUpdate, insertOne, find, aggregate). Typical shapes include { upsert: true }, { returnDocument: 'after' } (full document after update), { collation: { locale: 'en', strength: 2 } }, or arrayFilters where the operation supports them. With Mongoose, update-return behavior lines up with options such as { new: true } on findOneAndUpdate.

After compiling, call MaskDatabase.prompt(promptText, params?) as usual. Use MaskDatabase.getQueryForPrompt(...) to inspect the compiled spec; the readable output may include a // driverOptions: ... line when defaults exist.

// Example prompts — the compiler turns these into driverOptions like the examples above
'insert a new user with name and email and return the created document'
'update user by id :userId set name and return the updated document'
'find users whose name matches :name using case-insensitive collation'

In theory, each prompt lines up with driver-level behavior like this:

  • Insert … return the created document — the same outcome as inserting (e.g. insertOne) and then reading back the saved row by id, with compiled driverOptions reflecting that you want the full document after write (not only insertedId).
  • Update … return the updated document — the same idea as findOneAndUpdate with { returnDocument: 'after' } on the native MongoDB driver, or { new: true } on Mongoose, so you get the document after the change.
  • Find … case-insensitive collation — a find on users with your :name filter, plus options such as { collation: { locale: 'en', strength: 2 } } so string matching follows case-insensitive collation rules (exact filter shape is still decided at compile time).

Change the prompt text and recompile when you need different behavior. See Compiling and the Models page for Mongoose defines.

Inspecting a query (debugging)

Use MaskDatabase.getQueryForPrompt('mask-key') (or the exact prompt string) to see the generated query without running it. This is for debugging: you can check that a prompt produced the correct query before using it in production, or troubleshoot when a query doesn't behave as expected. It returns an object with hash, spec, and readable when the prompt is compiled, or null if the prompt is not found.

const info = MaskDatabase.getQueryForPrompt('mask-userById');

if (info) {
  console.log(info.readable);
  // e.g. "db.collection('users').find({ _id: ':userId' }).toArray()"
  console.log(info.spec);
  // { type: 'find', collection: 'users', filter: { _id: ':userId' }, ... }
  console.log(info.hash);
  // "a1b2c3d4"
}

For model debugging, see getModelForPrompt on the Models page.

After adding or changing prompts

Run node mask.compile.cjs, then mask-sync-push if you use sync. See Compiling and Sync.

Optional legacy app mode: use exact query code

If your prompt already contains a real query, you can pass it directly. Mask treats SQL/Cypher/query-spec input as direct query code and preserves the same query semantics in the compiled output.

  • SQL: paste the exact SQL you want to keep.
  • Neo4j: paste the exact Cypher query.
  • MongoDB / Mongoose: provide explicit query/filter/update/aggregation spec text when you need exact behavior.

Use this for old running apps where behavior must stay stable. After migration, you can still move to natural-language prompts where appropriate.

Migration examples: old query code to MaskDatabase.prompt

// BEFORE (repository file)
const sql = `
  SELECT id, email, status
  FROM users
  WHERE team_id = :teamId AND status = 'active'
  ORDER BY created_at DESC
  LIMIT :limit OFFSET :offset
`;
const rows = await db.query(sql, { teamId, limit, offset });
// AFTER (Mask usage in service/repository — exact query text in prompt)
const rows = await MaskDatabase.prompt(
  `SELECT id, email, status
  FROM users
  WHERE team_id = :teamId AND status = 'active'
  ORDER BY created_at DESC
  LIMIT :limit OFFSET :offset`,
  { teamId, limit, offset }
);
// BEFORE
const users = await User.find(
  { teamId, status: 'active' },
  { email: 1, name: 1, _id: 0 }
).sort({ createdAt: -1 }).limit(20);
// AFTER (exact Mongo/Mongoose query-spec text in prompt)
const users = await MaskDatabase.prompt(
  `User.find(
  { teamId, status: 'active' },
  { email: 1, name: 1, _id: 0 }
).sort({ createdAt: -1 }).limit(20);`,
  { teamId }
);
// BEFORE
const cypher = `
  MATCH (u:User)-[:MEMBER_OF]->(t:Team)
  WHERE t.id = $teamId
  RETURN u.id AS id, u.email AS email
  ORDER BY u.createdAt DESC
`;
const result = await session.run(cypher, { teamId });
// AFTER (exact cypher text in prompt)
const result = await MaskDatabase.prompt(
  `MATCH (u:User)-[:MEMBER_OF]->(t:Team)
  WHERE t.id = $teamId
  RETURN u.id AS id, u.email AS email
  ORDER BY u.createdAt DESC`,
  { teamId }
);

Keep the old query semantics first. After production parity is verified, you can refactor prompts into shorter natural-language versions if needed.