PackagePackagetypeA library you install and call yourself.See every one →What this type is →independently maintainedindependently maintainedoriginPublished by one person or a small company outside SAP. Says nothing about quality; the readings say that.See every one →

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

Latestv1.0.1ReleasedLast pushLicenceMIT

Where it sits

Lives in
Provides
Input validationCAPInput validationcapabilityRejecting bad input before it reaches the database. While this can be done manually inside a handler, there are solutions in place to derive type, range, enum and mandatory checks from the model definitions.AuthorizationCAPAuthorizationcapabilityDeciding who may read or change what, at service, entity and instance level — role checks, forwarded user context, and restrictions evaluated as query filters rather than after the fact.
Flavors
Node.jsfrom its runtime
Requires
@sap/cds≥ 6.0.0@sap/cdsartifactThe peer range, as published.

Who is behind it

Eyüp BayçölEyüp BayçölPersonMaintains this
In the atlas: maintains 1, contributes to 1
@eyupbaycol
maintainer

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

Connections2

Install

npm add cap-toolkit

Derived from the package name. No recipe recorded; the README has the configuration.

README

quoted from eyupbaycol/cap-toolkit-core under MIT, read
eyupbaycol/cap-toolkit-core/README.mdOn GitHub

cap-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 handler
  • after(event, ...handlers) - Add after handler
  • requireAuth() - Authentication check
  • requireRole(...roles) - Role check
  • withAudit() - Audit fields
  • readOnly() - Read-only mode
  • withSoftDelete(field, keyField) - Soft delete
  • requireOwnership(field, keyField) - Ownership check
  • build() - Register handlers
Validator
  • required(fields) - Required field
  • email(field) - Email format
  • minLength(field, length) - Minimum length
  • maxLength(field, length) - Maximum length
  • pattern(field, regex, message) - Regex check
  • range(field, min, max) - Numeric range
  • unique(entity, field, keyField) - Uniqueness
  • url(field) - URL format
  • date(field) - Date format
  • futureDate(field) - Future date
  • pastDate(field) - Past date
  • positive(field) - Positive number
  • integer(field) - Integer
  • custom(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 ID
  • findAll(req, entity, options) - Get all
  • exists(req, entity, where) - Check existence
  • count(req, entity, where) - Count
  • findOne(req, entity, where) - Find one by condition
  • upsert(req, entity, data, uniqueFields, keyField) - Update or insert
  • insertMany(req, entity, records) - Bulk insert
  • updateMany(req, entity, data, where) - Bulk update
  • deleteMany(req, entity, ids, keyField) - Bulk delete
  • deleteWhere(req, entity, where) - Delete by condition
  • distinct(req, entity, field) - Distinct values
  • paginate(req, entity, page, pageSize, options) - Pagination
  • aggregate(req, entity, operation, field, where) - Aggregate

📄 License

MIT

🤝 Contributing

Pull requests are welcome!

📧 Contact

GitHub: eyupbaycol/cap-toolkit-core

MIT, read

History

Latest

v1.0.1

0downloads per week, npm
Weekly downloads for cap-toolkit26 complete calendar weeks from 2026-04-27 to 2026-10-25; the latest total is 0.27 Apr – 3 May: 24 May – 10 May: 411 May – 17 May: 218 May – 24 May: 325 May – 31 May: 41 Jun – 7 Jun: 68 Jun – 14 Jun: 515 Jun – 21 Jun: 322 Jun – 28 Jun: 429 Jun – 5 Jul: 56 Jul – 12 Jul: 313 Jul – 19 Jul: 220 Jul – 26 Jul: 127 Jul – 2 Aug: 13 Aug – 9 Aug: 210 Aug – 16 Aug: 217 Aug – 23 Aug: 124 Aug – 30 Aug: 731 Aug – 6 Sept: 47 Sept – 13 Sept: 214 Sept – 20 Sept: 321 Sept – 27 Sept: 328 Sept – 4 Oct: 15 Oct – 11 Oct: 012 Oct – 18 Oct: 119 Oct – 25 Oct: 0

Others providing13

Input validation
  1. abapDomCheckChecks field values against domain fixed values and check tablesfrom ABAP
  2. com.sap.cds:cds-services-bomMaven bill of materials pinning CAP Java module versionsfrom CAP
  3. abap-data-validatorValidation library for ABAP checking dates, emails and GUIDsfrom ABAP
  4. sap-cap-validator-pluginCAP plugin validating input against @Validator JSON Schema rulesfrom CAP
  5. capvalCustom messages and handler classes for CAP input validationfrom CAP
  6. cds-validateZod schemas as CAP validation middlewarefrom CAP
  7. @sap/cdsCAP Node.js runtimefrom CAP
Authorization
  1. SAP Authorization and Trust Management serviceOAuth server issuing JWTs and holding scopes for BTP appsfrom BTP/BAIP
  2. OAuth 2.0 on SAP BTPOAuth 2.0 grant flows for applications and APIs in Neo regionsfrom BTP/BAIP
  3. com.sap.cds:cds-services-bomMaven bill of materials pinning CAP Java module versionsfrom CAP
  4. @sap/ams-devCompiler from AMS DCL policies to DCN for local runs and testsfrom CAP
  5. @sap/amsAuthorization from DCL policies for Node.js apps on SAP Cloud Identityfrom CAP
  6. @sap/cdsCAP Node.js runtimefrom CAP
  7. sap-usi-authority-checkAuthority check helper class used across SchwarzIT's USI reportsfrom ABAP
  8. cds-entra-idMaps Microsoft Entra ID groups and app roles onto cds.User rolesfrom CAP

Details

Sources4
npm registrydiscovered here, keywords:cdschecked
npm registry, cap-toolkitversion, releases, installschecked
github.com/git+https://github.com/eyupbaycol/cap-toolkit-core.gittree, pushes, contributors, CIchecked
README.mdquoted, and what the entry declareschecked

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
runtimesnode
declared for>=6.0.0
latestv1.0.1
first release
releases and cadence1
discovered vianpm registry · keywords:cds
first seen in the atlas
ecosystem and domaincap, developer-experience
History in the atlas1
  1. First seen.
For your agent
Ref
artifact:eyupbaycol-cap-toolkit
JSON
/artifacts/eyupbaycol-cap-toolkit.json
MCP
Connecting an agent