File size: 4,699 Bytes
4554903 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | # TypeScript Patterns for Multiverse Campus
## Express Route Pattern (with auth)
```typescript
import { Router } from 'express';
import { requireAuth, requireAdmin } from '../middleware/auth';
const router = Router();
// All routes require auth unless explicitly public
router.use(requireAuth);
router.get('/resource', async (req, res) => {
try {
const result = await service.getResource(req.user.studentId);
res.json({ success: true, data: result });
} catch (err) {
res.status(500).json({ success: false, error: err.message });
}
});
// Admin routes
router.put('/resource/:id', requireAdmin, async (req, res) => { ... });
```
## Zustand Store Pattern
```typescript
import { create } from 'zustand';
interface ResourceStore {
items: Resource[];
loading: boolean;
error: string | null;
fetch: () => Promise<void>;
update: (id: string, data: Partial<Resource>) => void;
}
export const useResourceStore = create<ResourceStore>((set, get) => ({
items: [],
loading: false,
error: null,
fetch: async () => {
set({ loading: true, error: null });
try {
const res = await api.get('/resource');
set({ items: res.data, loading: false });
} catch (err) {
set({ error: err.message, loading: false });
}
},
update: (id, data) => {
set(state => ({
items: state.items.map(item =>
item.id === id ? { ...item, ...data } : item
),
}));
},
}));
```
## Socket.IO Handler Pattern (server, PM2 cluster-safe)
```typescript
export function registerHandlers(io: Server, socket: AuthenticatedSocket) {
socket.on('resource:action', async (payload, callback) => {
try {
// Validate payload
const { resourceId } = validatePayload(payload);
// Perform action
const result = await service.performAction(socket.userId, resourceId);
// Broadcast to room (Redis adapter handles cross-process)
io.to(`resource:${resourceId}`).emit('resource:updated', result);
// Acknowledge to sender
callback({ success: true, data: result });
} catch (err) {
callback({ success: false, error: err.message });
}
});
}
```
## Database Transaction Pattern (pg-boss safe)
```typescript
import { getPool } from '../db';
async function transferGems(fromId: string, toId: string, amount: number) {
const pool = getPool();
const client = await pool.connect();
try {
await client.query('BEGIN');
// Debit
const debit = await client.query(
'UPDATE students SET gems = gems - $1 WHERE id = $2 AND gems >= $1 RETURNING gems',
[amount, fromId]
);
if (debit.rowCount === 0) throw new Error('Insufficient gems');
// Credit
await client.query(
'UPDATE students SET gems = gems + $1 WHERE id = $2',
[amount, toId]
);
await client.query('COMMIT');
} catch (err) {
await client.query('ROLLBACK');
throw err;
} finally {
client.release();
}
}
```
## Migration Pattern (additive only)
```typescript
// migrations/YYYYMMDDHHMMSS_add_feature_column.ts
import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> {
await knex.schema.alterTable('students', table => {
table.timestamp('last_login_at').nullable().defaultTo(null);
table.index('last_login_at'); // Only if frequently queried
});
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable('students', table => {
table.dropColumn('last_login_at');
});
}
// NOTE: down() exists for dev rollback only.
// In production, prefer a NEW migration that deprecates rather than drops.
```
## Error Handling Pattern
```typescript
// Custom error classes for typed error handling
class AppError extends Error {
constructor(
message: string,
public statusCode: number = 500,
public code: string = 'INTERNAL_ERROR'
) {
super(message);
}
}
class NotFoundError extends AppError {
constructor(resource: string, id: string) {
super(`${resource} ${id} not found`, 404, 'NOT_FOUND');
}
}
class AuthorizationError extends AppError {
constructor(action: string) {
super(`Not authorized to ${action}`, 403, 'FORBIDDEN');
}
}
// Express error middleware
function errorHandler(err: Error, req: Request, res: Response, next: NextFunction) {
if (err instanceof AppError) {
return res.status(err.statusCode).json({
success: false,
error: { code: err.code, message: err.message },
});
}
// Unknown errors — log and return generic
console.error('Unhandled error:', err);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: 'Something went wrong' } });
}
```
|