Skip to content

Postr-Inc/Hapta

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

10 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ› οΈ Hapta

Hapta is a modular, scalable, and feature-rich backend framework designed to extend Pocketbase with authentication, schema validation, caching, and tenant-based service orchestration.

Designed to integrate easily into any modern Node.js backend β€” and purpose-built to unlock Pocketbase for production-scale deployments.


πŸ“¦ Installation

npm install hapta
# or
bun add hapta
# or
yarn add hapta

πŸš€ Key Features

  • βœ… Authentication via Clover or Pocketbase Directly Multi-strategy login (OAuth, password, OTP, MFA) with tenant role/clearance support.

  • πŸ” Context-Based Auth (ctx.principal) Automatically injects authenticated user state and metadata into each request.

  • πŸ“¦ Zod-Based Schema Validation Auto-validates route inputs with fully typed schema files.

  • ⚑ Smart Caching Layer Dynamic TTL, auto-invalidation, optimistic scaffolds, and memory-based caching.

  • 🧱 Modular Context System Unified Context class handles services, request metadata, auth, and responses.

  • πŸ—οΈ Batch Write Mode Queue DB changes, defer execution, and optimize complex flows.


πŸ“ Project Structure (Recommended)

/
  └── routes/
      └── auth/
          └── index.ts        # Example auth endpoint
  └── schemas/
      └── auth/
          └── index.ts        # Zod validation for auth route
 

🧠 Quick Start

🧩 Set up config

hapta-config.json

  • Note: Remove Clover_Tenant_ID if you want to handle login state yourself - disables clover authentication methods
{
    "port": 8080,
    "logLevel": "info",
    "origin":"http://localhost:8081",
    "AI_ENABLED": false,
    "Clover_Tenant_ID": "",
    "Clover_Secret":"",
    "Clover_Server_Url":"clover.postlyapp.com",
    "JWT_SECRET":"*",
    "DatabaseUrl":"http://localhost:8080",
    "ADMIN_EMAIL":"email@something.com",
    "ADMIN_PASSWORD":""
}

πŸ” Use the principal

if (ctx.principal.isAuthenticated) {
  const username = ctx.principal.username;
  const clearance = ctx.principal.highest_clearance;
}

βš™οΈ Usage Example

/routes/auth/index.ts

import Context from "hapta";
import { Database } from "hapta";

export default async function POST(ctx: Context, DB: Database) {
  const { type } = ctx.metadata.query as any;

  if (type === "oauth") {
    const result = await ctx.services.Authenticate({ type: "oauth", ...ctx.metadata.query });

    return result.isAuthenticated
      ? ctx.json(result)
      : ctx.json({ error: true, message: "OAuth failed" }, 401);

  } else if (type === "password") {
    const result = await ctx.services.Clover.Authenticate({
      type: "passwordAuth",
      ...ctx.metadata.json
    });

    return result.isAuthenticated
      ? ctx.json(result)
      : ctx.json({ error: true, message: "Invalid credentials" }, 400);
  }

  return ctx.json({ error: true, message: "Unsupported auth type" }, 400);
}

Then simply run

bun --hot run hapta

🧬 Database API

import { DatabaseService } from "hapta";

const DB = new DatabaseService(pocketbase, cacheHandler);

πŸ”Ή Get one (cached)

const record = await DB.get("users", "abc123");

πŸ”Ή List (cached + paginated)

await DB.list("posts", { page: 1, limit: 20 });

πŸ”Ή Create with scaffold

DB.setBatch(true);
await DB.create("comments", { body: "Hello!" }, true);
await DB.saveChanges();

🧰 Built-in Response Helpers

ctx.json({ success: true }, 200);
ctx.html("<h1>Welcome</h1>");
ctx.text("Hello world");

All responses include CORS headers out of the box:

Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type, Authorization

πŸ§ͺ Validation with Zod

// /schemas/auth/index.ts
import { z } from "zod";

export default z.object({
  emailOrUsername: z.string().min(3),
  password: z.string().min(6),
});

Hapta automatically loads and validates this schema for /auth requests before your handler runs.


πŸ”„ Batch Mode

DB.setBatch(true);
await DB.create("logs", { message: "Init" });
await DB.update("users", "abc", { active: false });
await DB.delete("sessions", "xyz");
await DB.saveChanges(); // executes all queued ops

πŸ“¦ Caching Details

Method Caches TTL Invalidation
get() βœ… dynamic on update, delete
list() βœ… dynamic on create, update, delete
create() βž– βž– invalidates all list caches
update() βž– βž– invalidates list + get cache
delete() βž– βž– invalidates list + get cache

🧱 Integrating with Express, Bun, or Custom Server

You can wire Hapta into any server environment:

const ctx = new Context();
// inject principal, services, metadata, etc.
ctx.metadata = {
  requestID: "xyz",
  timestamp: new Date(),
  json: await req.json(),
  headers: req.headers,
  ...
};

// call route handler
const res = await handler(ctx, DB);
return res;

🧾 License

MIT Β© Postr-Inc β€” Built for scale.


πŸ’¬ Questions?

  • Open an issue
  • PRs welcome
  • Built with ❀️ by the Postr-Inc team

About

A Modularized Feature Rich Scalable API Layer

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors