A TypeScript-first toolkit providing decorators and utilities to streamline SvelteKit development with enhanced type safety and developer experience.
Deckit provides powerful decorators for authentication, authorization, data validation, and utilities to reduce boilerplate code while maintaining type safety and improving developer experience in SvelteKit applications.
Typical SvelteKit Action (verbose & repetitive):
// src/routes/users/[id]/+page.server.ts
import { error, fail } from '@sveltejs/kit';
import { z } from 'zod';
const updateUserSchema = z.object({
name: z.string().min(1),
email: z.string().email()
});
export const actions = {
update: async ({ request, locals, params }) => {
// 1. Check authentication
if (!locals.user) {
throw error(401, 'Unauthorized');
}
// 2. Check permissions
if (locals.user.role !== 'admin' && locals.user.id !== params.id) {
throw error(403, 'Forbidden');
}
// 3. Check if resource exists
const user = await getUserById(params.id);
if (!user) {
throw error(404, 'User not found');
}
// 4. Validate form data
const formData = await request.formData();
const result = updateUserSchema.safeParse(Object.fromEntries(formData));
if (!result.success) {
return fail(422, {
errors: result.error.flatten().fieldErrors
});
}
// 5. Finally, the actual business logic
return await updateUser(params.id, result.data);
}
};
With Deckit Decorators (clean & declarative):
// src/routes/users/[id]/UserController.ts
import { Check, ZodValidate } from 'deckit';
import { z } from 'zod';
// move you app specific decorators to lib folder to reuse them
const Auth = Check((e) => e.locals.user ? true : error(401));
const OwnerOrAdmin = Check((e) =>
e.locals.user?.role === 'admin' || e.locals.user?.id === e.params.id ? true : error(403)
);
const UserExists = Check(async (e) => {
const user = await getUserById(e.params.id);
return user ? true : error(404, 'User not found');
});
const updateUserSchema = z.object({
name: z.string().min(1),
email: z.string().email()
});
export class UserController {
@Auth
@OwnerOrAdmin
@UserExists
@ZodValidate(updateUserSchema)
async update(event: RequestEvent) {
// All checks passed, validated data available
const { name, email } = event.locals.validated;
return await updateUser(event.params.id, { name, email });
}
}
npm install deckit
# or
pnpm add deckit
Ensure your tsconfig.json
includes:
{
"compilerOptions": {
"experimentalDecorators": true,
...
}
}
Create custom check decorators for different use cases:
import { Check } from 'deckit';
import { error } from '@sveltejs/kit';
// Create a reusable Auth decorator
const Auth = Check((event) => {
if (event.locals.user) {
return true;
}
error(401, 'Unauthorized');
});
// Create a role-based permission decorator
const RequireAdmin = Check((event) => {
if (event.locals.user?.role === 'admin') {
return true;
}
error(403, 'Admin access required');
});
// Method-level authentication
class UserController {
@Auth
async profile(event: RequestEvent) {
return { user: event.locals.user };
}
@RequireAdmin
async deleteUser(event: RequestEvent) {
// Only admins can access this
return { success: true };
}
}
// Class-level authentication (protects all public methods)
@Auth
class ProtectedController {
async createUser() { /* protected */ }
async deleteUser() { /* protected */ }
_privateMethod() { /* not protected */ }
}
import { z } from 'zod';
import { ZodValidate } from 'deckit';
const userSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
age: z.string().transform(val => parseInt(val))
});
type ValidatedEvent = RequestEvent & {
locals: RequestEvent['locals'] & {
validated: z.infer<typeof userSchema>;
}
};
class UserController {
@ZodValidate(userSchema)
async createUser(event: ValidatedEvent) {
// event.locals.validated is fully typed and validated
const { name, email, age } = event.locals.validated;
return { success: true };
}
// With custom failure handling
@ZodValidate(userSchema, (event, error) => {
console.log('Validation failed:', error.errors);
})
async updateUser(event: ValidatedEvent) {
// Your logic here
}
}
import { Check, ZodValidate } from 'deckit';
const Auth = Check((event) => {
if (event.locals.user) return true;
error(401, 'Unauthorized');
});
class SecureController {
@Auth // runs first
@ZodValidate(adminSchema) // runs after
async sensitiveOperation(event: ValidatedEvent) {
// Both authenticated AND validated
return { data: event.locals.validated };
}
}
Generate controller scaffolds using the built-in CLI:
use --force
to overwrite existing files
use --name MyName
to set the class's name
# creates a controller as src/routes/dashboard/users/UserController.ts using template.
npx deckit make:controller dashboard/users/UserController --force
Check(checkFn?: (event: RequestEvent) => true | void | any)
Creates a decorator that can be applied to methods or classes to perform custom checks.
Parameters:
checkFn
(optional): Function to execute for the check. Should return true
for success, or any other value to halt execution and return that value as if it's returns from main method.Usage:
_
)Features:
true
Common Patterns:
// Authentication
const Auth = Check((event) => {
if (event.locals.user) return true;
error(401);
});
// Role-based access
const RequireRole = (role: string) => Check((event) => {
if (event.locals.user?.role === role) return true;
error(403, `${role} access required`);
});
// Custom business logic
const OwnerOnly = Check((event) => {
const userId = event.params?.id;
if (event.locals.user?.id === userId) return true;
error(403, 'Resource owner access required');
});
ZodValidate(schema: ZodSchema, onFailure?: (event: RequestEvent, error: ZodError) => void)
Creates a decorator that validates FormData against a Zod schema.
Parameters:
schema
: Zod schema for validationonFailure
(optional): Callback function called on validation failureBehavior:
event.request
event.locals.validated
to parsed data (success) or null
(failure)fail(422, { message: 'Validation failed.', errors: [...] })
on validation failureonFailure
callback with the event and error detailsFormData Handling:
formDataToObject
formDataToObject<T>(formData: FormData): T
Utility function to convert FormData to typed objects:
import { formDataToObject } from 'deckit';
const formData = new FormData();
formData.append('name', 'John');
formData.append('tags', 'tag1');
formData.append('tags', 'tag2');
const obj = formDataToObject(formData);
// Result: { name: 'John', tags: ['tag1', 'tag2'] }
true
error()
helper for proper HTTP responsesfail(422, { message, errors })
response on validation failureonFailure
callback with full error detailsconsole.log('Validation result:', result.error)
event.locals.validated = null
on failureconst Auth = Check((event) => {
if (event.locals.user) return true;
error(401);
});
const RequirePlan = (plan: string) => Check((event) => {
if (event.locals.user?.plan === plan) return true;
error(402, `${plan} plan required`);
});
@Auth
class PremiumController {
@RequirePlan('premium')
async premiumFeature(event: RequestEvent) {
return { feature: 'premium content' };
}
}
export const EnsureResourceExists = (paramName: string, service: {find: (id: string) => Promise<object | null>}) => {
return Check(async (e) => {
const resourceId = e.params[paramName];
if (!resourceId) {
error(404);
}
const resource = await service.find(resourceId);
if (!resource) {
error(404);
}
e.locals.resource = resource;
return true;
});
};
// usage:
class TestController {
@EnsureResourceExists('id', new ResourceService())
async ...
}
We welcome contributions! Please see our contributing guidelines and ensure all tests pass:
npm test
MIT License - see LICENSE file for details.