Observed since 20 Oct, 21:29 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
90dc-core
A package that contains utils and interfaces used to create 90dc
Shared utilities, models, and middleware for 90 Day Challenge microservices.
This version adds configuration validation and error handling middleware to improve code quality across all services.
npm install 90dc-core@latest zod
import { BaseConfigSchema, ConfigValidator } from '90dc-core';
import { z } from 'zod';
// Extend base config with service-specific settings
const ConfigSchema = BaseConfigSchema.extend({
GOOGLE_CLIENT_ID: z.string().min(1),
APPLE_TEAM_ID: z.string().min(1),
MOLLIE_API_KEY: z.string().min(1)
});
// Create validated config (fails fast on startup if invalid)
const config = new ConfigValidator(ConfigSchema);
// Use with type safety
const port = config.get('PORT'); // Type: number
const jwtSecret = config.get('JWT_SECRET'); // Type: string
const googleClientId = config.get('GOOGLE_CLIENT_ID'); // Type: string
import Koa from 'koa';
import { createErrorMiddleware } from '90dc-core';
const app = new Koa();
// Add error middleware FIRST
app.use(createErrorMiddleware({
exposeErrorDetails: config.isDevelopment()
}));
// Add your routes
app.use(router.routes());
import {
NotFoundError,
ValidationError,
ForbiddenError,
AuthenticationError
} from '90dc-core';
class UserController {
async getUser(ctx: Context) {
const user = await User.findByPk(ctx.params.id);
if (!user) {
throw new NotFoundError('User'); // Returns 404 with consistent format
}
ctx.body = { user };
}
async createUser(ctx: Context) {
const existing = await User.findOne({ where: { email } });
if (existing) {
throw new ConflictError('Email already registered'); // Returns 409
}
// ... create user
}
}
import { validateRequest } from '90dc-core';
import { z } from 'zod';
const CreateUserSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
firstName: z.string().min(1).max(100)
});
router.post('/users',
validateRequest({ body: CreateUserSchema }),
async (ctx) => {
// ctx.request.body is now validated and typed
const user = await User.create(ctx.request.body);
ctx.body = { user };
}
);
All errors return consistent JSON:
{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "User not found"
}
}
With details for validation errors:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": {
"validationErrors": [
{
"path": "email",
"message": "Invalid email",
"code": "invalid_string"
}
]
}
}
}
import {
ValidationError, // 400 - Invalid data
AuthenticationError, // 401 - Not authenticated
ForbiddenError, // 403 - No permission
NotFoundError, // 404 - Resource not found
ConflictError, // 409 - Resource conflict
UnprocessableEntityError,// 422 - Semantic error
RateLimitError, // 429 - Too many requests
InternalServerError, // 500 - Server error
ServiceUnavailableError, // 503 - Service down
DatabaseError, // 500 - Database issue
ExternalAPIError, // 502 - External API failed
} from '90dc-core';
Reusable schemas for common configurations:
import { CommonSchemas } from '90dc-core';
const ConfigSchema = BaseConfigSchema
.extend(CommonSchemas.redis.shape)
.extend(CommonSchemas.jwt.shape)
.extend(CommonSchemas.googleOAuth.shape);
Available common schemas:
CommonSchemas.database - PostgreSQL configurationCommonSchemas.redis - Redis configurationCommonSchemas.jwt - JWT authenticationCommonSchemas.googleOAuth - Google OAuthCommonSchemas.appleOAuth - Apple OAuthCommonSchemas.serviceUrls - Inter-service URLsCommonSchemas.featureFlags - Feature flagsSee USAGE_EXAMPLES.md for:
import {
ConfigValidator,
BaseConfigSchema,
CommonSchemas,
createConfig,
ConfigurationError
} from '90dc-core';
import {
AppError,
ValidationError,
AuthenticationError,
ForbiddenError,
NotFoundError,
ConflictError,
UnprocessableEntityError,
RateLimitError,
InternalServerError,
ServiceUnavailableError,
DatabaseError,
ExternalAPIError,
isAppError,
isOperationalError,
toAppError
} from '90dc-core';
import {
createErrorMiddleware,
validateRequest,
asyncHandler,
type ErrorMiddlewareConfig
} from '90dc-core';
import {
PersistedUser,
Program,
Workout,
Exercise,
Badge,
// ... many more
} from '90dc-core';
import {
AuthenticationUtil,
NotificationsUtil,
NotificationClient
} from '90dc-core';
Install dependencies:
npm install 90dc-core@latest zod
Add config validation:
import { BaseConfigSchema, ConfigValidator } from '90dc-core';
const config = new ConfigValidator(BaseConfigSchema);
Add error middleware:
import { createErrorMiddleware } from '90dc-core';
app.use(createErrorMiddleware());
Replace error handling:
// Before
if (!user) {
ctx.status = 404;
ctx.body = { message: 'Not found' };
return;
}
// After
import { NotFoundError } from '90dc-core';
if (!user) {
throw new NotFoundError('User');
}
Add request validation:
import { validateRequest } from '90dc-core';
router.post('/users',
validateRequest({ body: CreateUserSchema }),
handler
);
See USAGE_EXAMPLES.md for detailed migration guide.
# Install dependencies
npm install
# Build
npm run build
# Publish to npm
npm run publish:build
See CHANGELOG.md for version history.
ISC
This is part of the 90 Day Challenge microservices architecture. See the main refactor plan for contribution guidelines.
Download counts and popularity will appear here as the collector receives them.
| Package / target | Requirement | Relationship |
|---|---|---|
winstonEnvironment{
"local_name": "winston",
"alias": false,
"peer_metadata": null
} | ^3.8.2 | runtime |
sequelizeEnvironment{
"local_name": "sequelize",
"alias": false,
"peer_metadata": null
} | ^6.28.0 | runtime |
reflect-metadataEnvironment{
"local_name": "reflect-metadata",
"alias": false,
"peer_metadata": null
} | ^0.1.13 | runtime |
sequelize-typescriptEnvironment{
"local_name": "sequelize-typescript",
"alias": false,
"peer_metadata": null
} | ^2.1.5 | runtime |
jestEnvironment{
"local_name": "jest",
"alias": false,
"peer_metadata": null
} | ^29.3.1 | development |
tslintEnvironment{
"local_name": "tslint",
"alias": false,
"peer_metadata": null
} | ^6.1.3 | development |
ts-jestEnvironment{
"local_name": "ts-jest",
"alias": false,
"peer_metadata": null
} | ^29.0.5 | development |
ts-nodeEnvironment{
"local_name": "ts-node",
"alias": false,
"peer_metadata": null
} | ^10.9.1 | development |
prettierEnvironment{
"local_name": "prettier",
"alias": false,
"peer_metadata": null
} | ^2.8.3 | development |
typescriptEnvironment{
"local_name": "typescript",
"alias": false,
"peer_metadata": null
} | ^4.9.4 | development |
@types/jestEnvironment{
"local_name": "@types/jest",
"alias": false,
"peer_metadata": null
} | ^29.2.6 | development |
@types/nodeEnvironment{
"local_name": "@types/node",
"alias": false,
"peer_metadata": null
} | ^18.13.0 | development |
ts-node-devEnvironment{
"local_name": "ts-node-dev",
"alias": false,
"peer_metadata": null
} | ^2.0.0 | development |
@types/validatorEnvironment{
"local_name": "@types/validator",
"alias": false,
"peer_metadata": null
} | ^13.7.12 | development |
tslint-config-prettierEnvironment{
"local_name": "tslint-config-prettier",
"alias": false,
"peer_metadata": null
} | ^1.18.0 | development |
Source distribution
{
"shasum": "e9c626d75ff1c61a8aa632bc8ae8b3711828e090",
"tarball": "https://registry.npmjs.org/90dc-core/-/90dc-core-1.1.7.tgz",
"fileCount": 42,
"integrity": "sha512-QoAgjAl7HnquHwWS+k46/8n3l5FWmGXCQxpu0SF6xQcETS0Ia0/9YTmDRmS3pc6gEBUQerlePPsnaKZe9/XWng==",
"signatures": [
{
"sig": "MEUCIC/5/xgTqxaTgg+aBmEDtbc/Qp/syYKr8TVl4hWIl/9UAiEAu82yVzTxUlixwsfCb6pFHBROTaz+uT3h7z8oatairIo=",
"keyid": "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"
}
],
"unpackedSize": 42596,
"npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJj82kxACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrpUg//dVL13mvF78xw6WjMlabxDlqPk3DSxlntYcCQMKdFREaVzBDC\r\nLsHye8GZ+RICsOSyJ/ouGq6sTcaU1E/LV1YBH6mGJbcQhrw5m0vY/mIY3rWL\r\nHxLnZ9AwcLGezfgisebZ7JuLbhPDJSM+3/f4opPT1xW2cGTTsDdFfiBDZnba\r\nWAVW+43+nLFxmC96Ad7Gnh/25xi9to/j4fEmWZdefHgYCjeS8OcmreL1Em57\r\nsfS5ET2rgZExRDrBR8USHGwrIjzDOJk1RqUEOAUQWA3K7SxQqBvekDAIbn6Z\r\nYMx44kyXifDLUmQL/hDywE9rJzNlf0ZI1QkXCjmGjV78o2MRvFh/WSf4WB6L\r\nkZ2JCcuubyzsbppL2OScS9FazpFjtZdxNcvK+40TvImNht/3DY5LMWcf9mnh\r\nS0v6NUxtJQK3hG+L+/cjGIS6RyaT+l2OXhB1Pr94qv552ARcYBD/sgMPvEa1\r\nMfdBaCasfdIA/iieGo0jli5tEIofjpgZlMZt6E4tQG/Ylc/K31XrfVdzWBRC\r\nHUSYWNsejDCetcxP59oRxQ4xKvGI5K6rtQQFeLCALMfUv3BvBoJ3M1DujV6I\r\nOHk5taG8R7+mdoLjTJA1TOE6bvBxt+KvMjMv9sKh5hxV+gmBe2Y/7XCAq2mD\r\ndCOB8OyVvsItujTL5aJrCzqQYZ/UZvqeAVQ=\r\n=KOe5\r\n-----END PGP SIGNATURE-----\r\n"
}Observed since 20 Oct, 21:29 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package_version:f9474b8672daa4edecde9b15cb360c87ce23ffa4b341fe3bd61d7a089b91e4dc