mongo-query-dsl
v1.0.1
Published
A domain-specific language (DSL) for traversing and querying MongoDB documents with dynamic lookups
Downloads
188
Maintainers
Readme
MongoDB Query DSL
A domain-specific language (DSL) for traversing and querying MongoDB documents with dynamic lookups. Supports both JSON-based queries and a custom string syntax.
Features
- Dual Query Syntax: Use JSON for programmatic queries or a human-readable string DSL
- Dynamic Value Resolution: Reference document fields dynamically with
:fieldNamesyntax - Enhanced GET Operations: Use field aliases (
field as alias), default fallbacks, and single-field extraction withgetOne - Flexible Output Formats: Get just values, full documents, or detailed execution traces
- WHERE Conditions: Filter documents with AND conditions and multiple value types
- Type-Safe: Written in TypeScript with strict mode enabled
- Error Handling: Custom error classes for better debugging
Installation
npm install mongo-query-dslQuick Start
import { NoQL } from "mongo-query-dsl";
// Connect to MongoDB
const noql = await NoQL.connect("mongodb://localhost:27017");
// Execute a simple DSL query
const result = await noql.executeDSL(`
FROM users IN myDb WHERE _id = "user_123"
-> GET name, email
`);
console.log(result);
// Close connection
await noql.close();String DSL Syntax
Basic Structure
[WITH output: "value" | "document" | "trace"]
FROM collection IN database WHERE field = "value" [AND field2 = value2]
-> LOOKUP collection IN database BY field [WHERE condition]
-> GET field1, field2, ...Example
WITH output: "trace"
FROM documents IN myDb WHERE _id = "doc_123"
-> LOOKUP users IN myDb BY userId
-> GET name, email, role
-> LOOKUP :role IN permissions BY roleId
-> GET permissionsJSON Query Syntax
Structure
{
output?: "value" | "document" | "trace",
start: {
collection: string,
db: string,
where: { field: value, ... }
},
steps: [
{ type: "lookup", collection: string, db: string, by: string, where?: {...} },
{ type: "get", fields: string[], default?: Record<string, any> },
{ type: "getOne", fields: string }
]
}Example
const query = {
output: "value",
start: {
collection: "Sales",
db: "smartTadbeer",
where: { _id: "693553b149c75fdf39495ead" },
},
steps: [
{
type: "get",
fields: ["clientId", "candidateId as candidateRef"],
default: { gender: "Female" },
},
{
type: "lookup",
collection: "Candidate",
db: "smartTadbeer",
by: "candidateRef",
where: { gender: ":gender" },
},
{
type: "getOne",
fields: "fullNameEnglish",
},
],
};
const result = await noql.executeJson(query);GET and GETONE Notes
get.fieldssupports aliases withfield as alias.get.fieldscan also reference previous documents using:fieldName.get.defaultprovides fallback values and is merged with extracted fields.getOne.fieldsextracts a single field as the direct result value.
Dynamic Value Resolution
Use the colon prefix (:fieldName) to reference values from the current document context:
// If current document has { ownerType: "users", ownerSource: "mainDb" }
// Then :ownerType resolves to "users" and :ownerSource resolves to "mainDb"
const query = `
FROM documents IN myDb WHERE _id = "doc_123"
-> LOOKUP metadata IN myDb BY metadataId
-> GET ownerId, ownerType, ownerSource
-> LOOKUP :ownerType IN :ownerSource BY ownerId
-> GET name
`;Output Formats
"value" (default)
Returns only the requested field(s):
{ success: true, result: { name: "John", email: "[email protected]" } }"document"
Returns the entire final document:
{
success: true,
result: { _id: "123", name: "John", email: "[email protected]", ... }
}"trace"
Returns result with full execution trace for debugging:
{
success: true,
result: { name: "John" },
trace: [
{
stepNumber: 0,
stepType: "start",
collection: "users",
db: "myDb",
query: { _id: "user_123" },
documentFound: { _id: "user_123", name: "John", ... }
},
{
stepNumber: 1,
stepType: "get",
fieldsExtracted: { name: "John" }
}
]
}API Reference
NoQL Class
static async connect(connectionString: string): Promise<NoQL>
Connects to MongoDB and returns a NoQL instance.
const noql = await NoQL.connect("mongodb://localhost:27017");async executeJson(query: JsonQuery): Promise<QueryResult>
Executes a JSON query.
const result = await noql.executeJson({
start: { collection: "users", db: "myDb", where: { _id: "123" } },
steps: [{ type: "get", fields: ["name"] }],
});async executeDSL(dsl: string): Promise<QueryResult>
Parses and executes a DSL string query.
const result = await noql.executeDSL(
'FROM users IN myDb WHERE _id = "123" -> GET name',
);parseDSL(dsl: string): JsonQuery
Parses a DSL string into JsonQuery format without executing.
const query = noql.parseDSL('FROM users IN myDb WHERE _id = "123" -> GET name');async close(): Promise<void>
Closes the database connection.
await noql.close();Error Handling
DocumentNotFoundError
Thrown when a document is not found during query execution.
try {
await noql.executeDSL('FROM users IN myDb WHERE _id = "nonexistent"');
} catch (error) {
if (error instanceof DocumentNotFoundError) {
console.log(`Document not found in ${error.collection}`);
}
}ParseError
Thrown when DSL syntax is invalid.
try {
noql.parseDSL("INVALID SYNTAX");
} catch (error) {
if (error instanceof ParseError) {
console.log(`Parse error: ${error.message}`);
}
}ResolutionError
Thrown when a dynamic field reference cannot be resolved.
try {
// If :ownerType field doesn't exist in the document
await noql.executeDSL(
'FROM docs IN myDb WHERE _id = "123" -> LOOKUP :ownerType IN myDb BY ownerId',
);
} catch (error) {
if (error instanceof ResolutionError) {
console.log(`Cannot resolve field: ${error.fieldName}`);
}
}Value Types in WHERE Conditions
The DSL supports multiple value types:
WHERE stringField = "text" # String
WHERE numberField = 42 # Number
WHERE boolField = true # Boolean
WHERE nullField = null # Null
WHERE dynamicField = :fieldName # Dynamic referenceExamples
See the examples/usage.ts file for comprehensive examples.
Use Cases
- Data Traversal: Navigate through related documents across collections
- Dynamic Lookups: Resolve collection and database names at runtime
- Audit Trails: Use trace mode to debug complex queries
- Data Aggregation: Collect data from multiple related documents
- Reporting: Build reports from interconnected data
TypeScript Support
The package is written in TypeScript and provides full type definitions:
import {
NoQL,
JsonQuery,
QueryResult,
DocumentNotFoundError,
} from "mongo-query-dsl";
const query: JsonQuery = {
start: { collection: "users", db: "myDb", where: { _id: "123" } },
steps: [],
};
const result: QueryResult = await noql.executeJson(query);Requirements
- Node.js 16+
- MongoDB 4.0+
- TypeScript 5.0+ (for development)
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
