Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions examples/express-hello.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* Express hello world endpoint using HipThrusTS
*
* Run: npx ts-node examples/express-hello.ts
* Test: curl -X POST http://localhost:3000/greet/Alice?shout=true -H "Content-Type: application/json" -d '{"greeting":"Hello"}'
*/
import express from 'express';
import { defineHandler, hipExpressHandlerFactory } from '../src';
import { HipError } from '../src/core';

const app = express();
app.use(express.json());

const greetHandler = defineHandler({
sanitizeParams: (params: any) => {
console.log(' [sanitizeParams] raw params:', params);
if (!params.name) throw new HipError(400, 'Name param is required');
return { name: params.name as string };
},

sanitizeQueryParams: (query: any) => {
console.log(' [sanitizeQueryParams] raw query:', query);
return { shout: query.shout === 'true' };
},

sanitizeBody: (body: any) => {
console.log(' [sanitizeBody] raw body:', body);
return { greeting: (body.greeting as string) || 'Hi' };
},

preAuthorize: ({ params, queryParams, body }) => {
console.log(' [preAuthorize] context:', { params, queryParams, body });
// Pretend we check an API key here
return { authorized: true };
},

attachData: async ({ params }) => {
console.log(' [attachData] looking up user:', params.name);
// Pretend DB lookup
const user = { id: '1', displayName: params.name, joined: '2024-01-01' };
return { user };
},

finalAuthorize: ({ user }) => {
console.log(' [finalAuthorize] user found:', user.displayName);
return true;
},

doWork: ({ user, body, queryParams }) => {
console.log(' [doWork] building greeting');
let message = `${body.greeting}, ${user.displayName}! You joined on ${user.joined}.`;
if (queryParams.shout) message = message.toUpperCase();
return { message };
},

respond: ({ message }) => {
console.log(' [respond] sending response');
return { unsafeResponse: { message, timestamp: new Date().toISOString() }, status: 200 };
},

sanitizeResponse: (response: any) => {
console.log(' [sanitizeResponse] filtering response');
return response;
},
});

app.post('/greet/:name', hipExpressHandlerFactory(greetHandler as any));

app.listen(4000, () => {
console.log('Express example running at http://localhost:4000');
console.log('Try: curl -X POST http://localhost:4000/greet/Alice?shout=true -H "Content-Type: application/json" -d \'{"greeting":"Hello"}\'');
});
60 changes: 60 additions & 0 deletions examples/fastify-hello.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Fastify hello world endpoint using HipThrusTS
*
* Run: npx ts-node examples/fastify-hello.ts
* Test: curl -X POST http://localhost:3002/greet/Charlie -H "Content-Type: application/json" -d '{"greeting":"Yo"}'
*/
import Fastify from 'fastify';
import { defineHandler } from '../src/adapter';
import { hipFastifyHandlerFactory } from '../src/fastify';
import { HipError } from '../src/core';

const app = Fastify();

const greetHandler = defineHandler({
sanitizeParams: (params: any) => {
console.log(' [sanitizeParams]', params);
if (!params.name) throw new HipError(400, 'Name required');
return { name: params.name as string };
},

sanitizeBody: (body: any) => {
console.log(' [sanitizeBody]', body);
return { greeting: (body.greeting as string) || 'Hi' };
},

preAuthorize: ({ params }) => {
console.log(' [preAuthorize] checking access for:', params.name);
if (params.name === 'admin') throw new HipError(403, 'Nice try');
return true;
},

attachData: async ({ params }) => {
console.log(' [attachData] fetching profile');
return { favoriteColor: params.name === 'Charlie' ? 'blue' : 'green' };
},

finalAuthorize: () => true,

doWork: ({ params, body, favoriteColor }) => {
console.log(' [doWork] composing response');
return {
message: `${body.greeting}, ${params.name}! Your favorite color is ${favoriteColor}.`,
};
},

respond: ({ message }) => ({
unsafeResponse: { message, framework: 'fastify' },
status: 200,
}),

sanitizeResponse: (r: any) => r,
});

app.post('/greet/:name', hipFastifyHandlerFactory(greetHandler as any));

app.listen({ port: 3002 }).then(() => {
console.log('Fastify example running at http://localhost:3002');
console.log('Try: curl -X POST http://localhost:3002/greet/Charlie -H "Content-Type: application/json" -d \'{"greeting":"Yo"}\'');
console.log('Try error: curl -X POST http://localhost:3002/greet/admin -H "Content-Type: application/json" -d \'{}\'');
});
57 changes: 57 additions & 0 deletions examples/hono-hello.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Hono hello world endpoint using HipThrusTS
*
* Run: npx ts-node examples/hono-hello.ts
* Test: curl -X POST http://localhost:3001/greet/Bob -H "Content-Type: application/json" -d '{"greeting":"Hey"}'
*/
import { Hono } from 'hono';
import { serve } from '@hono/node-server';
import { defineHandler } from '../src/adapter';
import { hipHonoHandlerFactory } from '../src/hono';
import { HipError } from '../src/core';

const app = new Hono();

const greetHandler = defineHandler({
sanitizeParams: (params: any) => {
console.log(' [sanitizeParams]', params);
if (!params.name) throw new HipError(400, 'Name required');
return { name: params.name as string };
},

sanitizeBody: (body: any) => {
console.log(' [sanitizeBody]', body);
return { greeting: (body.greeting as string) || 'Hi' };
},

preAuthorize: ({ params, body }) => {
console.log(' [preAuthorize] name:', params.name);
return { role: 'user' };
},

attachData: async ({ params }) => {
console.log(' [attachData] loading data for:', params.name);
return { emoji: params.name === 'Bob' ? '👋' : '🎉' };
},

finalAuthorize: () => true,

doWork: ({ params, body, emoji }) => {
console.log(' [doWork] building message');
return { message: `${body.greeting} ${params.name}! ${emoji}` };
},

respond: ({ message }) => ({
unsafeResponse: { message },
status: 200,
}),

sanitizeResponse: (r: any) => r,
});

app.post('/greet/:name', hipHonoHandlerFactory(greetHandler as any));

serve({ fetch: app.fetch, port: 3001 }, () => {
console.log('Hono example running at http://localhost:3001');
console.log('Try: curl -X POST http://localhost:3001/greet/Bob -H "Content-Type: application/json" -d \'{"greeting":"Hey"}\'');
});
71 changes: 71 additions & 0 deletions examples/next-hello.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* Next.js App Router hello world endpoint using HipThrusTS
*
* This file shows what a Next.js route.ts would look like.
* (Can't run standalone — needs Next.js server. This is for reference.)
*
* File would be at: app/api/greet/[name]/route.ts
*/
import { defineHandler } from '../src/adapter';
import { hipNextHandlerFactory } from '../src/next';
import { HipError } from '../src/core';

const greetHandler = defineHandler({
initPreContext: (unsafe: any) => {
console.log(' [initPreContext] raw request URL:', unsafe.req?.url);
return { requestedAt: new Date().toISOString() };
},

sanitizeParams: (params: any) => {
console.log(' [sanitizeParams] raw params:', params);
if (!params.name) throw new HipError(400, 'Name param is required');
return { name: params.name as string };
},

sanitizeBody: (body: any) => {
console.log(' [sanitizeBody] raw body:', body);
return { greeting: (body.greeting as string) || 'Hi' };
},

preAuthorize: ({ params, body, preContext }) => {
console.log(' [preAuthorize] name:', params.name, 'greeting:', body.greeting);
// In real app: check Clerk auth here
return { clerkUserId: 'user-123' };
},

attachData: async ({ clerkUserId, params }) => {
console.log(' [attachData] fetching user for:', clerkUserId);
// In real app: Payload CMS lookup
const user = { id: clerkUserId, displayName: params.name };
return { user };
},

finalAuthorize: ({ user }) => {
console.log(' [finalAuthorize] user:', user.displayName);
return true;
},

doWork: ({ user, body }) => {
console.log(' [doWork] building greeting');
const message = `${body.greeting}, ${user.displayName}!`;
return { message };
},

respond: ({ message, preContext }) => {
console.log(' [respond] done');
return {
unsafeResponse: { message, requestedAt: preContext.requestedAt },
status: 200,
};
},

sanitizeResponse: (response: any) => response,
});

// This is what you'd export in route.ts:
export const POST = hipNextHandlerFactory(greetHandler as any, {
gatherContext: async (req) => {
// In real app: const { userId } = await auth();
return { clerkUserId: 'user-from-clerk' };
},
});
23 changes: 19 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,43 @@
},
"license": "MIT",
"peerDependencies": {
"@hapi/boom": "^9.1.4",
"express": "^5.1.0",
"fastify": "^5",
"hono": "^4",
"json-mask": "^0.3.8",
"mongoose": "^5.9.4",
"next": ">=14",
"zod": "^4.2.1"
},
"peerDependenciesMeta": {
"express": { "optional": true },
"fastify": { "optional": true },
"hono": { "optional": true },
"json-mask": { "optional": true },
"mongoose": { "optional": true },
"next": { "optional": true },
"zod": { "optional": true }
},
"devDependencies": {
"@hapi/boom": "^9.1.4",
"@hono/node-server": "^2.0.1",
"@istanbuljs/nyc-config-typescript": "^0.1.3",
"@types/boom": "^7.3.5",
"@types/chai": "^4.2.3",
"@types/chai-as-promised": "^7.1.2",
"@types/express": "^5.0.6",
"@types/mocha": "^5.2.7",
"@types/mongoose": "^5.5.23",
"@types/node": "^8.10.53",
"@types/node": "^22.19.17",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"coveralls": "^3.0.6",
"express": "^5.1.0",
"fastify": "^5.8.5",
"hono": "^4.12.17",
"husky": "^1.3.1",
"json-mask": "^0.3.8",
"mocha": "^6.2.1",
"mongoose": "^5.9.4",
"next": "^16.2.4",
"nyc": "^14.1.1",
"prettier": "^1.18.2",
"pretty-quick": "^1.10.0",
Expand All @@ -59,7 +72,9 @@
"tslint": "5.20.0",
"tslint-config-prettier": "^1.18.0",
"tslint-eslint-rules": "*5.4.0",
"tsx": "^4.21.0",
"typescript": "^5.9.3",
"vitest": "^4.1.5",
"zod": "^4.2.1"
},
"prettier": {
Expand Down
Loading