@incloodsolutions/node-toolkit
v0.0.20
Published
A toolkit containing utility, helper, config, interfaces for nodejs apps
Maintainers
Readme
@incloodsolutions/node-toolkit
Server-side helpers built on top of @incloodsolutions/toolkit: serverless
adapters for Express/NestJS, AWS SDK v3 wrappers, Mongoose helpers tuned for serverless,
crypto and hashing, environment-variable loading, request logging, and API-response
utilities.
For a one-line index of every export in this package (and the other toolkits), see
../docs/AI-INDEX.md. Every exported symbol also carries an inline TSDoc comment with parameter descriptions, default values, and examples.
Installation
npm install @incloodsolutions/node-toolkitRequires Node.js 22+. AWS SDK v3 clients, @codegenie/serverless-express, mongoose,
bcryptjs, crypto-js, class-validator, class-transformer, morgan, bwip-js, and
uuid are installed as dependencies. Every dependency is kept external (not bundled) so
the ESM build stays loadable — bundling mongoose would pull the MongoDB driver's dynamic
require() calls into the bundle and break ESM consumers.
express is not a dependency: it is only ever used as a type
(import type { Request } from 'express'), so it is erased from the build. A serverless
host (Nest, or @codegenie/serverless-express) brings its own Express.
Every subpath ships both formats (like the other toolkits): import resolves to ESM,
require to CommonJS, each with its own .d.ts. Every bundle is verified to load in a
real ESM runtime by test/esm-bundle.spec.ts.
Modular imports (tree-shaking)
There is no package-root import. import ... from '@incloodsolutions/node-toolkit'
does not resolve — you must import a specific subpath, so nothing can pull the whole
package (the AWS SDK, Mongoose, bwip-js, …) by accident. Each area is published as its
own subpath, down to the individual AWS service:
@incloodsolutions/node-toolkit/aws-sdk/s3 pulls only @aws-sdk/client-s3, not the
DynamoDB client or its Zod validation chain.
import { initLambdaFunctionHandler } from '@incloodsolutions/node-toolkit/aws-lambda';
import { initS3ClientWrapper } from '@incloodsolutions/node-toolkit/aws-sdk/s3';
import { initGcpFunctionHandler } from '@incloodsolutions/node-toolkit/gcp';
import { initMongooseConnection, initMongooseSchema } from '@incloodsolutions/node-toolkit/mongo';
import { encryptData, generateCustomUUID } from '@incloodsolutions/node-toolkit/utility';
import { initEnvironmentVariables } from '@incloodsolutions/node-toolkit/config';
import type { IBaseApiResult } from '@incloodsolutions/node-toolkit/interface';| Subpath | Contents |
| ------- | -------- |
| .../aws | everything AWS: Lambda adapter + all SDK wrappers |
| .../aws-lambda | initLambdaFunctionHandler, getCurrentLambdaInvocation |
| .../aws-cli | uploadToS3ViaCli (placeholder) |
| .../aws-sdk | all SDK wrappers (S3, SES, SNS, SSM, DynamoDB, EventBridge) |
| .../aws-sdk/s3 | initS3ClientWrapper |
| .../aws-sdk/ses | initSesClientWrapper |
| .../aws-sdk/sns | initSnsClientWrapper |
| .../aws-sdk/parameter | initSsmParameterClientWrapper |
| .../aws-sdk/dynamo-db | initDynamoDbClientWrapper |
| .../aws-sdk/event-bridge | initEventBridgeClientWrapper (placeholder) |
| .../gcp | initGcpFunctionHandler |
| .../mongo | initMongooseConnection, initMongooseSchema |
| .../utility | crypto/hashing, Lambda /tmp IO, IDs, API-response and logging helpers, class-validator helpers, Mongoose normalisers, QR/barcode |
| .../config | initEnvironmentVariables |
| .../interface | IBaseEnvironmentVariable, IBaseApiResult, IBaseMongoDocument, MongoIdType, SortOrderType |
Every subpath resolves ESM (import), CommonJS (require), and its own .d.ts.
What's inside
| Area | Exports |
| ---- | ------- |
| Serverless adapters | initLambdaFunctionHandler, getCurrentLambdaInvocation, initGcpFunctionHandler |
| AWS SDK wrappers | initS3ClientWrapper, initSesClientWrapper, initSnsClientWrapper, initSsmParameterClientWrapper, initDynamoDbClientWrapper |
| Config | initEnvironmentVariables |
| MongoDB / Mongoose | initMongooseConnection, initMongooseSchema |
| Crypto & hashing | encryptData, decryptData, hashWithBcrypt, validateHashWithBcrypt |
| Lambda filesystem | writeFileToLambda, readFileFromLambda, isLambdaEnvironment |
| IDs | generateCustomUUID, isValidUUID, isValidMongoId |
| HTTP / API | apiResult, returnApiResponse, returnApiOverview, getIpAddress, reqResLogger, encodeUrlComponent, decodeUrlComponent |
| Logging | initCustomLogger |
| Validation | validateDataWithClassValidator |
| Mongo data shaping | normalizeMongooseData, normalizeMongooseData_v2, sanitizeObject |
| Misc | generateQrBarcode, isNestApplication |
| Interfaces / types | IBaseEnvironmentVariable, IBaseApiResult, IBaseMongoDocument, INestAppInstance, MongoIdType, SortOrderType |
Not yet implemented: initEventBridgeClientWrapper (returns {}), uploadToS3ViaCli
(empty). BaseSchemaEntity exists in source but is not exported.
Usage
Run an Express or NestJS app on AWS Lambda
import { initLambdaFunctionHandler } from '@incloodsolutions/node-toolkit/aws-lambda';
import { app } from './app'; // an Express instance or a NestJS app instance
export const handler = (event: any, context: any, callback: any) =>
initLambdaFunctionHandler({ app, event, context, callback });getCurrentLambdaInvocation() returns the current { context, event } anywhere downstream.
The equivalent for Google Cloud Functions is initGcpFunctionHandler({ app, request, response }).
Load and validate environment variables (no dotenv)
import { initEnvironmentVariables } from '@incloodsolutions/node-toolkit/config';
export const env = initEnvironmentVariables(
{
NODE_ENV: { required: true, defaultValue: 'development' },
PORT: { defaultValue: 8080 },
MONGO_DATABASE_URL: { required: true },
},
{ enableDebug: true },
);A required variable with no value and no defaultValue throws a CustomException.
SSM Parameter Store
import { initSsmParameterClientWrapper } from '@incloodsolutions/node-toolkit/aws-sdk/parameter';
const ssm = initSsmParameterClientWrapper();
const dbPassword = await ssm.getParameter({ name: '/my-app/db-password' });
const { values, invalidParameterNames } = await ssm.getParameters({
names: ['/my-app/db-password', '/my-app/api-key'],
});
// Every parameter under a path, with pagination handled for you.
const config = await ssm.getParametersByPath({ path: '/my-app/' });SecureString parameters are decrypted by default (withDecryption: true).
DynamoDB document-client wrapper
import { initDynamoDbClientWrapper } from '@incloodsolutions/node-toolkit/aws-sdk/dynamo-db';
const users = initDynamoDbClientWrapper<UserRecord, 'emailIndex'>({
tableName: 'users',
schema: UserSchema, // a Zod object schema
compositePrimaryKeyOptions: { primaryKeyName: 'id', primaryKeyIdType: 'uuid' },
options: { timestamp: true, enableDebug: false },
});
await users.put({ data: { name: 'Ada', email: '[email protected]' } });
const { data, nextPageToken } = await users.query({
conditions: { email: '[email protected]' },
filter: {},
indexName: 'emailIndex',
returnAll: true,
});
await users.updateOne({ key: { id }, data: { name: 'Ada L.' } });
await users.delete({ key: { id } });The wrapper automatically escapes any of DynamoDB's ~573 reserved words (matched
case-insensitively) used as an attribute name in conditions, filter, searchTerms, or
updateOne's data, plus projection (select), contains search (searchTerms), and
automatic pagination when returnAll is set.
S3, SES, SNS
import { initS3ClientWrapper } from '@incloodsolutions/node-toolkit/aws-sdk/s3';
import { initSesClientWrapper } from '@incloodsolutions/node-toolkit/aws-sdk/ses';
import { initSnsClientWrapper } from '@incloodsolutions/node-toolkit/aws-sdk/sns';
const s3 = initS3ClientWrapper({ bucketName: 'assets' });
const url = await s3.generateSignedUrl({ fileName: 'a.png', commandType: 'read', expiresIn: 900 });
const ses = initSesClientWrapper({ sourceEmail: '[email protected]' });
await ses.sendEmail({ subject: 'Hi', message: { content: '<b>Hello</b>' }, receivers: ['[email protected]'] });
const sns = initSnsClientWrapper({ config: { region: 'us-east-1' } });
await sns.sendSms({ message: 'Code 1234', phoneNumber: '+15550000000' });Mongoose in serverless
import { initMongooseConnection, initMongooseSchema } from '@incloodsolutions/node-toolkit/mongo';
const { connection, closeConnection } = await initMongooseConnection({
options: { retries: 5, retryDelayMs: 5000, enableDebug: true },
});
const UserSchema = initMongooseSchema<UserModel>({ name: String, email: String });The connection is cached on global and reused across warm Lambda invocations.
Crypto, hashing, IDs
import {
encryptData, decryptData,
hashWithBcrypt, validateHashWithBcrypt,
generateCustomUUID,
} from '@incloodsolutions/node-toolkit/utility';
const token = encryptData({ data: { userId: 1 }, secret: process.env.SECRET, type: 'aes256' });
const payload = decryptData<{ userId: number }>({ hashedData: token, secret: process.env.SECRET });
const hash = await hashWithBcrypt('password', 10);
const ok = await validateHashWithBcrypt('password', hash);
const id = generateCustomUUID({ version: 7, asUpperCase: true });API responses and logging
import {
returnApiResponse, apiResult, returnApiOverview,
reqResLogger, initCustomLogger,
} from '@incloodsolutions/node-toolkit/utility';
app.use(reqResLogger({ formats: ['user-agent'] }));
app.get('/', (_req, res) => res.send(returnApiOverview({ name: 'My API', docsUrl: '/docs' })));
app.get('/users', (_req, res) => returnApiResponse(res, apiResult({ data: users }), 200));Normalising Mongoose documents for API output
import { normalizeMongooseData_v2 } from '@incloodsolutions/node-toolkit/utility';
const user = normalizeMongooseData_v2(await UserModel.findById(id));
// ObjectIds become strings recursively; `id` is added from `_id`.QR codes and barcodes
import { generateQrBarcode } from '@incloodsolutions/node-toolkit/utility';
const qr = await generateQrBarcode('https://inclood.io'); // data:image/png;base64,...
const barcode = await generateQrBarcode({ id: 42 }, { type: 'barcode' });Development
npm install
npm run build # tsup: one ESM + CJS + .d.ts bundle per subpath entry
npm run format # prettier --write (tabs, single quotes, trailing commas)
npm run lint # eslint --fix
npm test # vitest run — behaviour tests for every export
npm run test:watch # vitest in watch mode
npm run package # build, then npm pack a tarballTests live in test/ (utility / aws / lambda / platform). AWS SDK
clients are stubbed with aws-sdk-client-mock, @codegenie/serverless-express and
mongoose.connect are mocked, bwip-js and fs are mocked for the Lambda helpers —
nothing touches the network or a database. Each file's header comment lists the utilities
that are currently buggy.
The whole build is
tsup(dts: true), pinned totypescript@^6.tsconfig.jsonusesmoduleResolution: "bundler"withcustomConditions: ["node"]so packages that expose their types only through anodeexport condition (e.g.bwip-js) resolve.The published package is ESM-first (
"type": "module"). It has no root export and nomain/module/typesfields — every entry point is a subpath inexports, each wiring upimport(ESM),require(CommonJS), andtypes. Theindexbundle is still built as the test aggregation point but is not reachable by the package name.
License
MIT © Inclood Solutions
