chomot 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ import { OpenAPIHono } from "@hono/zod-openapi";
2
+ type Variables = {
3
+ jwtPayload?: any;
4
+ user?: any;
5
+ };
6
+ export type App = OpenAPIHono<{
7
+ Variables: Variables;
8
+ }>;
9
+ export declare function createApp(): App;
10
+ export {};
@@ -0,0 +1,6 @@
1
+ import { OpenAPIHono } from "@hono/zod-openapi";
2
+ export function createApp() {
3
+ return new OpenAPIHono({
4
+ strict: false
5
+ });
6
+ }
@@ -0,0 +1,6 @@
1
+ export declare function createTokenFor(user: {
2
+ ID: string;
3
+ name: string;
4
+ role: unknown;
5
+ }): Promise<string>;
6
+ export declare function createOTP(): string;
@@ -0,0 +1,25 @@
1
+ import { getPrivateKey } from '@/crypto';
2
+ import { sign } from 'hono/jwt';
3
+ export async function createTokenFor(user) {
4
+ const privateKey = await getPrivateKey();
5
+ const payload = {
6
+ sub: user.ID,
7
+ name: user.name,
8
+ role: user.role,
9
+ exp: createTokenExpirationInMonths(6), // token expired in 6 months
10
+ };
11
+ return await sign(payload, Buffer.from(privateKey).toString("utf8"), "HS256");
12
+ }
13
+ function createTokenExpirationInMonths(months) {
14
+ const now = new Date();
15
+ now.setMonth(now.getMonth() + months);
16
+ return Math.floor(now.getTime() / 1000);
17
+ }
18
+ export function createOTP() {
19
+ const otp = Math.floor(Math.random() * 1000000);
20
+ // Mengubah angka OTP menjadi string.
21
+ // Menggunakan padStart(6, '0') untuk memastikan string selalu 6 digit.
22
+ // Jika angka kurang dari 6 digit (misal 9876), maka akan ditambahkan nol di depan (misal "009876").
23
+ const otpString = otp.toString().padStart(6, "0");
24
+ return otpString;
25
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./app.factory";
2
+ export * from "./auth.factory";
3
+ export * from "./response.factory";
4
+ export * from "./route.factory";
@@ -0,0 +1,4 @@
1
+ export * from "./app.factory";
2
+ export * from "./auth.factory";
3
+ export * from "./response.factory";
4
+ export * from "./route.factory";
@@ -0,0 +1,15 @@
1
+ import { z } from "@hono/zod-openapi";
2
+ export declare const createListResponseSchema: <DataSchema extends z.ZodTypeAny>(args: {
3
+ dataSchema: DataSchema;
4
+ }) => z.ZodObject<{
5
+ sortBy: z.ZodDefault<z.ZodString>;
6
+ sortDir: z.ZodDefault<z.ZodEnum<{
7
+ asc: "asc";
8
+ desc: "desc";
9
+ }>>;
10
+ cursor: z.ZodString;
11
+ data: z.ZodArray<DataSchema>;
12
+ }, z.core.$strip>;
13
+ export declare const createOkResponseSchema: () => z.ZodObject<{
14
+ msg: z.ZodString;
15
+ }, z.core.$strip>;
@@ -0,0 +1,12 @@
1
+ import { z } from "@hono/zod-openapi";
2
+ export const createListResponseSchema = (args) => z.object({
3
+ sortBy: z
4
+ .string()
5
+ .nonempty()
6
+ .default("createdAt")
7
+ .openapi({ examples: ["createdAt", "updatedAt"] }),
8
+ sortDir: z.enum(["asc", "desc"]).default("desc"),
9
+ cursor: z.string().datetime(),
10
+ data: z.array(args.dataSchema),
11
+ });
12
+ export const createOkResponseSchema = () => z.object({ msg: z.string().openapi({ example: "Query.OK" }) });
@@ -0,0 +1,6 @@
1
+ import { createRoute, RouteConfig } from "@hono/zod-openapi";
2
+ type CreateRouteConfig = Parameters<typeof createRoute>[0];
3
+ export declare function createSecureRoute(config: Omit<CreateRouteConfig, "request"> & {
4
+ request?: Omit<CreateRouteConfig["request"], "headers">;
5
+ }): RouteConfig;
6
+ export {};
@@ -0,0 +1,22 @@
1
+ import { createRoute, z } from "@hono/zod-openapi";
2
+ // Reusable auth header schema
3
+ const authHeader = z.object({
4
+ authorization: z
5
+ .string()
6
+ .startsWith("Bearer ")
7
+ .openapi({
8
+ description: "Bearer token",
9
+ example: !process.env?.ENV?.startsWith("prod") && process.env?.AUTH_TOKEN_TEST
10
+ ? `Bearer ${process.env?.AUTH_TOKEN_TEST}`
11
+ : "Bearer <your-token>",
12
+ }),
13
+ });
14
+ export function createSecureRoute(config) {
15
+ return createRoute({
16
+ ...config,
17
+ request: {
18
+ ...(config.request ?? {}),
19
+ headers: authHeader,
20
+ },
21
+ });
22
+ }
@@ -0,0 +1,2 @@
1
+ export declare function imitateFetch(imitatedResponses: Record<string, Response>): (input: string | URL, _init?: RequestInit) => Response;
2
+ export declare function setImitatedResponse(responseText: string): Response;
@@ -0,0 +1,15 @@
1
+ export function imitateFetch(imitatedResponses) {
2
+ return (input, _init) => {
3
+ const url = new URL(typeof input === "string" ? input : input.toString());
4
+ const res = imitatedResponses[url.href];
5
+ return (res ?? new Response(`{"msg":"Imitated URL Not Found"}`, { status: 404 }));
6
+ };
7
+ }
8
+ export function setImitatedResponse(responseText) {
9
+ return new Response(responseText.trim(), {
10
+ headers: {
11
+ "Content-Type": "application/json;charset=UTF-8",
12
+ },
13
+ status: 200,
14
+ });
15
+ }
package/dist/index.d.ts CHANGED
@@ -1 +1,4 @@
1
- export {};
1
+ export * from "./config";
2
+ export * from "./crypto";
3
+ export * from "./factories";
4
+ export * from "./pipes";
package/dist/index.js CHANGED
@@ -1 +1,4 @@
1
- export {};
1
+ export * from "./config";
2
+ export * from "./crypto";
3
+ export * from "./factories";
4
+ export * from "./pipes";
@@ -0,0 +1,2 @@
1
+ import { App } from '@/factories';
2
+ export declare function configureAppEssentialPipe(app: App, appName: string, appVersion: string): void;
@@ -0,0 +1,45 @@
1
+ import { rateLimiter } from 'hono-rate-limiter';
2
+ import { languageDetector } from "hono/language";
3
+ import { configureOpenapi } from './openapi.pipe';
4
+ import { configureAuthGuard } from './auth.pipe';
5
+ export function configureAppEssentialPipe(app, appName, appVersion) {
6
+ // LANGUAGE
7
+ configureLanguage(app);
8
+ // HELMET
9
+ configureHelmet(app);
10
+ // RATE LIMIT
11
+ configureRateLimit(app);
12
+ // OPENAPI
13
+ configureOpenapi(app, appName, appVersion);
14
+ // Auth Middleware
15
+ configureAuthGuard(app);
16
+ // Load unauthenticated modules
17
+ }
18
+ function configureHelmet(app) {
19
+ app.use("*", (c, next) => {
20
+ c.header("X-Content-Type-Options", "nosniff");
21
+ c.header("X-Frame-Options", "DENY");
22
+ c.header("X-XSS-Protection", "1; mode=block");
23
+ c.header("Referrer-Policy", "no-referrer");
24
+ return next();
25
+ });
26
+ }
27
+ function configureLanguage(app) {
28
+ app.use(languageDetector({
29
+ supportedLanguages: ["en", "id"],
30
+ fallbackLanguage: "en",
31
+ }));
32
+ }
33
+ function configureRateLimit(app) {
34
+ app.use("/api/*", rateLimiter({
35
+ windowMs: 60 * 1000, // 1 minute
36
+ limit: 60,
37
+ standardHeaders: true,
38
+ message: "Slowdown bud!",
39
+ keyGenerator: (c) => {
40
+ return (c.req.header("x-forwarded-for") ??
41
+ c.req.header("x-real-ip") ??
42
+ "unknown");
43
+ },
44
+ }));
45
+ }
@@ -0,0 +1,3 @@
1
+ import { App } from '@/factories';
2
+ export declare function configureAuthGuard(app: App): void;
3
+ export declare function authorize(allowed: boolean): void;
@@ -0,0 +1,37 @@
1
+ import { getPrivateKey } from '@/crypto';
2
+ import { verify } from "hono/jwt";
3
+ import { HTTPException } from "hono/http-exception";
4
+ export function configureAuthGuard(app) {
5
+ app.use("/api/*", async (c, next) => {
6
+ const authorizationHeader = c.req.header("Authorization");
7
+ if (authorizationHeader) {
8
+ const bearerToken = authorizationHeader.split(" ")[1];
9
+ if (bearerToken) {
10
+ try {
11
+ const payload = await verifyToken(bearerToken);
12
+ c.set("jwtPayload", payload);
13
+ }
14
+ catch (error) {
15
+ console.error(error);
16
+ throw new HTTPException(401, { message: "Invalid token" });
17
+ }
18
+ }
19
+ }
20
+ await next();
21
+ }, async (c, next) => {
22
+ const payload = c.get("jwtPayload");
23
+ if (!payload)
24
+ throw new HTTPException(401, { message: "Invalid payload" });
25
+ c.set("user", payload);
26
+ await next();
27
+ });
28
+ }
29
+ async function verifyToken(token) {
30
+ const privateKey = await getPrivateKey();
31
+ return await verify(token, Buffer.from(privateKey).toString("utf8"), "HS256");
32
+ }
33
+ export function authorize(allowed) {
34
+ if (!allowed) {
35
+ throw new HTTPException(401, { message: "Unauthorized" });
36
+ }
37
+ }
@@ -0,0 +1,11 @@
1
+ import { ContentfulStatusCode } from 'hono/utils/http-status';
2
+ export type DatabaseResult<T> = {
3
+ code: ContentfulStatusCode;
4
+ success: boolean;
5
+ message: string;
6
+ error?: {
7
+ message: string;
8
+ } | undefined;
9
+ data?: Awaited<T> | undefined;
10
+ };
11
+ export declare const DB: <T>(fn: () => Promise<T>) => Promise<DatabaseResult<T>>;
@@ -0,0 +1,31 @@
1
+ export const DB = async (fn) => {
2
+ try {
3
+ const data = await fn();
4
+ if (!data) {
5
+ return {
6
+ success: false,
7
+ message: "Query.RecordNotFound.",
8
+ code: 404,
9
+ data,
10
+ };
11
+ }
12
+ return { success: true, message: "Query.OK", code: 200, data };
13
+ }
14
+ catch (err) {
15
+ const maybeError = err;
16
+ if (maybeError?.cause?.message) {
17
+ return {
18
+ success: false,
19
+ code: 422,
20
+ message: "Unprocessable Entity",
21
+ error: { message: maybeError.cause.message },
22
+ };
23
+ }
24
+ return {
25
+ success: false,
26
+ code: 500,
27
+ message: "Server error",
28
+ error: { message: "Unhandled Exception" },
29
+ };
30
+ }
31
+ };
@@ -0,0 +1,5 @@
1
+ export * from "./app.pipe";
2
+ export * from "./auth.pipe";
3
+ export * from "./db.pipe";
4
+ export * from "./json.pipe";
5
+ export * from "./openapi.pipe";
@@ -0,0 +1,5 @@
1
+ export * from "./app.pipe";
2
+ export * from "./auth.pipe";
3
+ export * from "./db.pipe";
4
+ export * from "./json.pipe";
5
+ export * from "./openapi.pipe";
@@ -0,0 +1,9 @@
1
+ import { z } from '@hono/zod-openapi';
2
+ export declare function createJsonContent(content: z.ZodObject, description: string): {
3
+ content: {
4
+ "application/json": {
5
+ schema: z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>;
6
+ };
7
+ };
8
+ description: string;
9
+ };
@@ -0,0 +1,10 @@
1
+ export function createJsonContent(content, description) {
2
+ return {
3
+ content: {
4
+ "application/json": {
5
+ schema: content,
6
+ },
7
+ },
8
+ description,
9
+ };
10
+ }
@@ -0,0 +1,2 @@
1
+ import { App } from '@/factories';
2
+ export declare function configureOpenapi(app: App, title: string, version: string): void;
@@ -0,0 +1,11 @@
1
+ import { Scalar } from "@scalar/hono-api-reference";
2
+ export function configureOpenapi(app, title, version) {
3
+ if (!process.env.ENV?.startsWith("prod")) {
4
+ app
5
+ .doc("/openapi.json", {
6
+ openapi: "3.0.0",
7
+ info: { version, title },
8
+ })
9
+ .get("/docs", Scalar(() => ({ url: "/openapi.json", theme: "alternate" })));
10
+ }
11
+ }
package/package.json CHANGED
@@ -1,28 +1,33 @@
1
- {
2
- "name": "chomot",
3
- "version": "1.0.0",
4
- "description": "",
5
- "main": "./dist/index.js",
6
- "types": "./dist/index.d.ts",
7
- "scripts": {
8
- "build": "tsc",
9
- "prepublishOnly": "npm run build",
10
- "test": "vitest"
11
- },
12
- "files": [
13
- "dist"
14
- ],
15
- "keywords": [],
16
- "author": "smwgn1331",
17
- "license": "ISC",
18
- "devDependencies": {
19
- "@types/libsodium-wrappers-sumo": "^0.7.8",
20
- "@types/node": "^24.7.2",
21
- "tsx": "^4.20.6",
22
- "typescript": "^5.9.3",
23
- "vitest": "^3.2.4"
24
- },
25
- "dependencies": {
26
- "libsodium-wrappers-sumo": "^0.7.15"
27
- }
28
- }
1
+ {
2
+ "name": "chomot",
3
+ "version": "1.2.0",
4
+ "description": "",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "prepublishOnly": "npm run build",
10
+ "test": "vitest"
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "keywords": [],
16
+ "author": "smwgn1331",
17
+ "license": "ISC",
18
+ "devDependencies": {
19
+ "@types/libsodium-wrappers-sumo": "^0.7.8",
20
+ "@types/node": "^24.7.2",
21
+ "tsx": "^4.20.6",
22
+ "typescript": "^5.9.3",
23
+ "vitest": "^3.2.4"
24
+ },
25
+ "dependencies": {
26
+ "@hono/node-server": "^1.19.5",
27
+ "@hono/zod-openapi": "^1.1.3",
28
+ "@scalar/hono-api-reference": "^0.9.21",
29
+ "hono": "^4.9.12",
30
+ "hono-rate-limiter": "^0.4.2",
31
+ "libsodium-wrappers-sumo": "^0.7.15"
32
+ }
33
+ }