cap-toolkit
A utility bundle for CAP handlers, with a chainable HandlerBuilder, role checks, validation rules and query helpers, collecting boilerplate that otherwise repeats across projects into one dependency.
Collects the boilerplate that accumulates across projects into one dependency — new HandlerBuilder(srv, 'Books').requireAuth() in place of the same three blocks written again.
Facts
Where it sits
Who is behind it
In the atlas: maintains 1, contributes to 1@eyupbaycolmaintainer
Before choosing this
Conditions
PracticalFunctional boundary
It is a bundle rather than a plugin with one job, and a single release from March 2026 — depending on it for the query helpers brings the auth and validation layers too.
What it declares
Every artifact
- Type
- Package
- Origin
- Independently maintained
- Runtime
- Node.js
As a package
- Declared for
- >=6.0.0
- Where it runs
- does not say
- Distributed through
- npm
Connections
Deciding who may read or change what, down to instance level
Rejecting bad input before it reaches a handler
Download
Install
npm add cap-toolkitDerived from the package name. No recipe recorded; the README has the configuration.
README
quoted from eyupbaycol/cap-toolkit-core under MIT, readcap-toolkit
🚀 Eliminate repetitive code in your CAP projects!
I've consolidated the boilerplate code you constantly write in SAP CAP projects into a single package. Ready-to-use utilities for handlers, authentication, authorization, validation, and query operations.
✨ Features
- ✅ Handler Builder - Easily create Before/After handlers
- ✅ Auth & Authorization - Role-based access control
- ✅ Validation - 15+ different validation rules
- ✅ Query Helpers - Helpers for frequently used queries
- ✅ TypeScript - Full type support
📦 Installation
npm install cap-toolkit
🚀 Quick Start
Basic Usage
const { HandlerBuilder, Validator, AuthManager } = require('cap-toolkit');
module.exports = (srv) => {
// Set up all handlers at once
new HandlerBuilder(srv, 'Books')
.requireAuth() // Authentication check
.requireRole('Admin', 'Editor') // Role check
.withAudit() // Auto-fill audit fields
.before('CREATE', Validator.required(['title', 'author']))
.before('CREATE', Validator.email('contactEmail'))
.build();
};
📚 Detailed Usage
1️⃣ Handler Builder
The most powerful feature! Create handlers in a chaining fashion with Fluent API.
const { HandlerBuilder, Validator } = require('cap-toolkit');
new HandlerBuilder(srv, 'Products')
.requireAuth() // Authentication
.requireRole('User', 'Admin') // Authorization
.withAudit() // Auto-fill createdBy, modifiedBy
.before('CREATE', [
Validator.required(['name', 'price']),
Validator.minLength('name', 3),
Validator.range('price', 0, 1000000)
])
.after('CREATE', async (result, req) => {
console.log('New product added:', result);
})
.build();
Other Handler Builder Features:
// Read-only entity
builder.readOnly();
// Soft delete
builder.withSoftDelete('isDeleted');
builder.withSoftDelete('isDeleted', 'bookId'); // Custom key field
// Ownership check
builder.requireOwnership('createdBy');
builder.requireOwnership('createdBy', 'bookId'); // Custom key field
// Rate limiting
builder.withRateLimit(10, 60000); // 10 req/min
2️⃣ Authentication & Authorization
const { AuthManager } = require('cap-toolkit');
// In service handler
srv.before('CREATE', 'Orders', AuthManager.requireAuth);
srv.before('DELETE', 'Orders', AuthManager.requireRole('Admin'));
// Ownership check
srv.before('UPDATE', 'Posts', async (req) => {
await AuthManager.checkOwnership(req, 'Posts', 'authorId');
});
// Ownership check with custom key field
srv.before('UPDATE', 'Posts', async (req) => {
await AuthManager.checkOwnership(req, 'Posts', 'authorId', 'postId');
});
// All roles required
srv.before('*', 'SensitiveData',
AuthManager.requireAll('Admin', 'SecurityOfficer')
);
// Custom authorization
srv.before('approve', AuthManager.custom((req, user) => {
return user.department === 'Finance' || user.roles.includes('Manager');
}));
3️⃣ Validation (15+ Rules)
const { Validator } = require('cap-toolkit');
new HandlerBuilder(srv, 'Users')
.before('CREATE', [
Validator.required(['email', 'name', 'password']),
Validator.email('email'),
Validator.minLength('password', 8),
Validator.maxLength('name', 100),
Validator.pattern('phone', /^\d{10}$/, 'Phone must be 10 digits'),
Validator.unique('Users', 'email'), // default key: ID
// Validator.unique('Users', 'email', 'userId'), // custom key field
Validator.url('website'),
Validator.positive('age'),
Validator.integer('age'),
Validator.range('age', 18, 100),
Validator.futureDate('startDate'),
Validator.pastDate('birthDate')
])
.build();
// Custom validation
const customValidator = Validator.custom((data) => {
if (data.password !== data.confirmPassword) {
return 'Passwords do not match';
}
});
// Combine validators
const combinedValidator = Validator.combine(
Validator.required('email'),
Validator.email('email'),
Validator.unique('Users', 'email')
);
4️⃣ Query Helper
All QueryHelper methods require a request context (req) as the first parameter.
const { QueryHelper } = require('cap-toolkit');
// Find by ID
const book = await QueryHelper.findById(req, 'Books', bookId);
const book2 = await QueryHelper.findById(req, 'Books', bookId, 'bookId'); // custom key field
// Get all records (filtering, sorting, limit)
const books = await QueryHelper.findAll(req, 'Books', {
where: { category: 'Fiction' },
orderBy: ['title', 'author'],
limit: 10,
offset: 20
});
// Check if exists
const exists = await QueryHelper.exists(req, 'Books', { isbn: '123456' });
// Count
const count = await QueryHelper.count(req, 'Books', { category: 'Fiction' });
// Upsert (update or insert)
await QueryHelper.upsert(req, 'Books', bookData, ['isbn']);
await QueryHelper.upsert(req, 'Books', bookData, ['isbn'], 'bookId'); // custom key field
// Bulk operations
await QueryHelper.insertMany(req, 'Books', [book1, book2, book3]);
await QueryHelper.updateMany(req, 'Books', { status: 'archived' }, { year: { '<': 2020 } });
await QueryHelper.deleteMany(req, 'Books', [1, 2, 3, 4, 5]);
await QueryHelper.deleteMany(req, 'Books', ['b1', 'b2'], 'bookId'); // custom key field
// Distinct values
const categories = await QueryHelper.distinct(req, 'Books', 'category');
// Pagination
const result = await QueryHelper.paginate(req, 'Books', 2, 20, {
where: { category: 'Fiction' },
orderBy: 'title'
});
// { data, total, page, pageSize, totalPages, hasNext, hasPrev }
// Aggregate functions
const avgPrice = await QueryHelper.aggregate(req, 'Books', 'avg', 'price');
const totalStock = await QueryHelper.aggregate(req, 'Products', 'sum', 'stock');
🎯 Complete Example
const cds = require('@sap/cds');
const {
HandlerBuilder,
Validator,
AuthManager,
QueryHelper
} = require('cap-toolkit');
module.exports = cds.service.impl(async function() {
const { Books, Authors } = this.entities;
// Complete handler setup for Books entity
new HandlerBuilder(this, 'Books')
.requireAuth()
.requireRole('User', 'Admin')
.withAudit()
.before('CREATE', [
Validator.required(['title', 'authorId', 'isbn']),
Validator.minLength('title', 3),
Validator.unique('Books', 'isbn'),
async (req) => {
const authorExists = await QueryHelper.exists(req, 'Authors', {
ID: req.data.authorId
});
if (!authorExists) {
req.reject(403, 'Related record not found');
}
}
])
.after('CREATE', async (result, req) => {
// Increment author's book count
await QueryHelper.updateMany(req, 'Authors',
{ bookCount: { '+=': 1 } },
{ ID: req.data.authorId }
);
})
.before('DELETE', AuthManager.requireRole('Admin'))
.build();
// Custom action - Book search
this.on('searchBooks', async (req) => {
const { search, category, minPrice, maxPrice } = req.data;
let query = SELECT.from('Books');
// Pagination
const result = await QueryHelper.paginate(req, 'Books',
req.data.page || 1,
req.data.pageSize || 20,
{ orderBy: 'title' }
);
return result;
});
});
🛠️ API Reference
HandlerBuilder
before(event, ...handlers)- Add before handlerafter(event, ...handlers)- Add after handlerrequireAuth()- Authentication checkrequireRole(...roles)- Role checkwithAudit()- Audit fieldsreadOnly()- Read-only modewithSoftDelete(field, keyField)- Soft deleterequireOwnership(field, keyField)- Ownership checkbuild()- Register handlers
Validator
required(fields)- Required fieldemail(field)- Email formatminLength(field, length)- Minimum lengthmaxLength(field, length)- Maximum lengthpattern(field, regex, message)- Regex checkrange(field, min, max)- Numeric rangeunique(entity, field, keyField)- Uniquenessurl(field)- URL formatdate(field)- Date formatfutureDate(field)- Future datepastDate(field)- Past datepositive(field)- Positive numberinteger(field)- Integercustom(fn)- Custom validation
QueryHelper
All methods require req (transaction context) as first parameter: req, req.tx, or cds.tx.
findById(req, entity, id, keyField)- Find by IDfindAll(req, entity, options)- Get allexists(req, entity, where)- Check existencecount(req, entity, where)- CountfindOne(req, entity, where)- Find one by conditionupsert(req, entity, data, uniqueFields, keyField)- Update or insertinsertMany(req, entity, records)- Bulk insertupdateMany(req, entity, data, where)- Bulk updatedeleteMany(req, entity, ids, keyField)- Bulk deletedeleteWhere(req, entity, where)- Delete by conditiondistinct(req, entity, field)- Distinct valuespaginate(req, entity, page, pageSize, options)- Paginationaggregate(req, entity, operation, field, where)- Aggregate
📄 License
MIT
🤝 Contributing
Pull requests are welcome!
📧 Contact
GitHub: eyupbaycol/cap-toolkit-core
History
v1.0.1
Others providing
- abapDomCheckabapDomCheckChecks a structure's field values against ABAP domain fixed values and check tables outside the GUI, such as during a data import, answering with a rejected flag and the messages behind it.independent, abapChecks field values against domain fixed values and check tablesfrom ABAP
- com.sap.cds:cds-services-bomcom.sap.cds:cds-services-bomThe Maven bill of materials for CAP Java, pinning every com.sap.cds module in a release, from the API and core runtime to the Spring Boot starters and the CQN engine, to the same version.sap official, capMaven bill of materials pinning CAP Java module versionsfrom CAP
- abap-data-validatorabap-data-validatorA validation library for ABAP that checks values such as dates, emails, URLs, JSON and GUIDs against type rules through one interface, or a whole internal table against a set of rules.independent, abapValidation library for ABAP checking dates, emails and GUIDsfrom ABAP
- sap-cap-validator-pluginsap-cap-validator-pluginA CAP plugin that checks incoming data against JSON Schema rules written as @Validator annotations on model elements, evaluated by Ajv, with no handler to register.independent, capCAP plugin validating input against @Validator JSON Schema rulesfrom CAP
- capvalcapvalReplaces the message a failed input validation annotation returns, and adds @validation.handler, which names a class that runs a check of the developer's own.independent, capCustom messages and handler classes for CAP input validationfrom CAP
- cds-validatecds-validateA package that runs Zod schemas as CAP validation middleware, so one schema replaces the hand-written checks at the top of a handler and failures come back as OData 422 errors per field.independent, capZod schemas as CAP validation middlewarefrom CAP
- @sap/cds@sap/cdsThe CAP Node.js runtime, which compiles the CDS model, serves the services declared in it, answers generic requests before any handler runs, and loads every cds-plugin.sap official, capCAP Node.js runtimefrom CAP
- SAP Authorization and Trust Management serviceSAP Authorization and Trust Management serviceThe OAuth server behind a BTP application: issues the JWT the app checks, holds the scopes and role collections it checks against, and brokers trust to an identity provider. Bound, never installed.sap official, btpOAuth server issuing JWTs and holding scopes for BTP appsfrom BTP/BAIP
- OAuth 2.0 on SAP BTPOAuth 2.0 on SAP BTPOAuth 2.0 protection for applications and APIs on SAP BTP, listed in Neo regions: the authorization code grant for web applications and the client credentials grant for API and IoT scenarios.sap official, btpOAuth 2.0 grant flows for applications and APIs in Neo regionsfrom BTP/BAIP
- com.sap.cds:cds-services-bomcom.sap.cds:cds-services-bomThe Maven bill of materials for CAP Java, pinning every com.sap.cds module in a release, from the API and core runtime to the Spring Boot starters and the CQN engine, to the same version.sap official, capMaven bill of materials pinning CAP Java module versionsfrom CAP
- @sap/ams-dev@sap/ams-devCompiles the DCL authorization policies of SAP's Authorization Management Service to DCN, so an AMS-secured application runs and is tested on a laptop.sap official, capCompiler from AMS DCL policies to DCN for local runs and testsfrom CAP
- @sap/ams@sap/amsDecides what a signed-in user may do in a Node.js application that authenticates through SAP Cloud Identity Services, from policies written in DCL.sap official, capAuthorization from DCL policies for Node.js apps on SAP Cloud Identityfrom CAP
- @sap/cds@sap/cdsThe CAP Node.js runtime, which compiles the CDS model, serves the services declared in it, answers generic requests before any handler runs, and loads every cds-plugin.sap official, capCAP Node.js runtimefrom CAP
- sap-usi-authority-checksap-usi-authority-checkA helper class, /USI/CL_AUTH, that checks whether the current user is authorized for the running transaction, mandatory for SchwarzIT's USI reports under their internal development guidelines.independent, abapAuthority check helper class used across SchwarzIT's USI reportsfrom ABAP
- cds-entra-idcds-entra-idA CAP plugin that maps Microsoft Entra ID groups and app roles onto cds.User roles, so @requires and @restrict keep working for a CAP app hosted on Azure App Services.independent, capMaps Microsoft Entra ID groups and app roles onto cds.User rolesfrom CAP
Details
Sources
Sources are graph entities: the same page may back several entries, and an unreachable source flags every row that rests on it.
Compatibility and provenance
History in the atlas
- First seen.
For your agent
- Ref
artifact:eyupbaycol-cap-toolkit- JSON
- /artifacts/eyupbaycol-cap-toolkit.json
- MCP
- Connecting an agent