riser-bob 0.0.0-stage → 1.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zen Allaham — RiserBob
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,96 @@
1
- # Temporary Holding Version
1
+ <h1>
2
+ <img src="https://res.cloudinary.com/dgagbheuj/image/upload/v1792601028/riserbob/covers/keps4sy2lumzdx0ho23i.png" alt="RiserBob Logo" width="48" height="48" align="absmiddle" />
3
+ RiserBob
4
+ </h1>
2
5
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
6
+ A fun and fast framework powered by Bun and TypeScript, bringing simplicity and a fresh perspective to backend development.
7
+ Stay in the Sponge Zone!
8
+
9
+ > RiserBob is distributed under the [MIT License](#license).
10
+
11
+ ![RiserBob](https://res.cloudinary.com/dgagbheuj/image/upload/v1792927501/qlflwqvq6xqgxnbn0puk.png)
12
+
13
+ ## Features
14
+
15
+ - **Modular Structure** — Clean, reusable modules that keep your codebase easy to maintain.
16
+ - **Ready-to-Use Middleware** — Protect routes and handle common request workflows out of the box.
17
+ - **Blazing-Fast Performance** — Built for Bun and TypeScript, with a lightweight HTTP core.
18
+ - **Built-in Validation** — Validate incoming data with a simple, developer-friendly API.
19
+ - **Integrated CLI** — Generate whole modules in seconds.
20
+ - **Fun Sponge Zone** — Playful, SpongeBob-inspired messages and errors that make debugging enjoyable.
21
+
22
+ ## Installation
23
+
24
+ Requires [Bun](https://bun.sh) 1.3 or newer. There are two ways to start.
25
+
26
+ ### 1. Starter template (recommended)
27
+ ```bash
28
+ bun create riser-bob my-app
29
+ ```
30
+
31
+ The best choice if you want a ready-made, organized template to start your project with. It creates `src/index.ts`:
32
+
33
+ ```ts
34
+ import { RiserBob } from "riser-bob";
35
+ import { UsersModule } from "./modules/users/users.module";
36
+
37
+ async function bootstrap() {
38
+ const bob = new RiserBob();
39
+ bob.spongGet("/", async (spongReq, spongRes) => {
40
+ return spongRes.json({
41
+ message: "Welcome to Bikini Bottom! 🍍",
42
+ });
43
+ });
44
+ new UsersModule().register(bob);
45
+ bob.spongListen(4000, () => {
46
+ console.log("🧽 RiserBob is running at http://localhost:4000 🍍");
47
+ });
48
+ }
49
+
50
+ bootstrap().catch((error: unknown) => {
51
+ console.error("[RiserBob] Failed to start application:", error);
52
+ process.exit(1);
53
+ });
54
+ ```
55
+
56
+ Then the terminal shows:
57
+
58
+ ```text
59
+ 🧽 RiserBob — Project created successfully!
60
+ 📁 Location: /path/to/my-app
61
+
62
+ Get started:
63
+ $ cd "my-app"
64
+ $ bun install
65
+ $ bun run dev
66
+ 🍍 Happy coding in the Sponge Zone!
67
+ ```
68
+
69
+ ### 2. Start from scratch
70
+ RiserBob doesn't force anything on you. Run `bun init`, then `bun add riser-bob`. Just remember that you'll need to build the server entry file yourself (like the one above).
71
+
72
+ ## Simple API, Powerful Structure
73
+
74
+ Every feature is a module of three focused files: a **controller** (the logic), a **router** (paths mapped to handlers) and a **module** that wires them together, so one call registers the whole feature: `new UsersModule().register(bob)`.
75
+
76
+ ## CLI
77
+
78
+ Generate a complete module (`users.controller.ts`, `users.route.ts`, `users.module.ts`) with one command:
79
+
80
+ ```bash
81
+ bun run riser-bob g module users # `generate` also works
82
+ ```
83
+ ## Learn more
84
+
85
+ Docs and examples: **[riserbob.vercel.app](https://riserbob.vercel.app)**. Contributions are welcome ([CONTRIBUTING.md](CONTRIBUTING.md), [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)); report vulnerabilities privately ([SECURITY.md](SECURITY.md)).
86
+
87
+ ## License
88
+ RiserBob is released under the **MIT License**. See [LICENSE](LICENSE). Copyright (c) 2026 Zen Allaham — RiserBob
89
+
90
+ <p align="center">
91
+ <img
92
+ src="https://res.cloudinary.com/dgagbheuj/image/upload/v1792769341/ob3ejwt32h6dtfhxg1kh.png"
93
+ alt="RiserBob — Build APIs. Stay in the Sponge Zone."
94
+ width="100%"
95
+ />
96
+ </p>
@@ -0,0 +1,24 @@
1
+ import type { SpongHandler } from '../types/spong-handler';
2
+ import type { SpongRequest } from '../types/spong-request';
3
+ import type { SpongMiddleware } from '../types/spong-middleware';
4
+ import type { RiserBobOptions } from '../types/riser-bob-options';
5
+ import { Server as SocketIOServer } from 'socket.io';
6
+ import type { ServerOptions } from 'socket.io';
7
+ export declare class RiserBob {
8
+ private readonly router;
9
+ private readonly middlewares;
10
+ private readonly errorMessages;
11
+ private socketIO?;
12
+ constructor(options?: RiserBobOptions);
13
+ private register;
14
+ spongGet(path: string, handler: SpongHandler, ...middlewares: SpongMiddleware<SpongRequest>[]): this;
15
+ spongPost(path: string, handler: SpongHandler, ...middlewares: SpongMiddleware<SpongRequest>[]): this;
16
+ spongPut(path: string, handler: SpongHandler, ...middlewares: SpongMiddleware<SpongRequest>[]): this;
17
+ spongPatch(path: string, handler: SpongHandler, ...middlewares: SpongMiddleware<SpongRequest>[]): this;
18
+ spongDelete(path: string, handler: SpongHandler, ...middlewares: SpongMiddleware<SpongRequest>[]): this;
19
+ spongHead(path: string, handler: SpongHandler, ...middlewares: SpongMiddleware<SpongRequest>[]): this;
20
+ spongOptions(path: string, handler: SpongHandler, ...middlewares: SpongMiddleware<SpongRequest>[]): this;
21
+ spongUse(middleware: SpongMiddleware<SpongRequest>): this;
22
+ spongSocketIO(options?: Partial<ServerOptions>): SocketIOServer;
23
+ spongListen(port?: number, callback?: () => void): Bun.Server<import("@socket.io/bun-engine").WebSocketData>;
24
+ }
package/dist/cli.js ADDED
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+ import{mkdir as m,writeFile as y,unlink as R}from"fs/promises";import s from"path";function g(o){return o.split(/[-_\s]+/).filter(Boolean).map((e)=>e[0].toUpperCase()+e.slice(1).toLowerCase()).join("")}function C(o){return o.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase().replace(/[_\s]+/g,"-")}function x(o){if(o.endsWith("ies")&&o.length>3)return`${o.slice(0,-3)}y`;if(o.endsWith("s")&&!o.endsWith("ss"))return o.slice(0,-1);return o}function v(o){return/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(o)}async function f(o){if(!v(o))throw Error('Invalid module name. Use letters, numbers, "-" or "_", starting with a letter.');let e=C(o),r=g(e),p=x(e),i=g(p),a=s.resolve(process.cwd(),"src","modules",e),c={[`${e}.controller.ts`]:`import type { SpongHandler } from "riser-bob";
4
+
5
+ export class ${r}Controller {
6
+ readonly create: SpongHandler = async (spongReq, spongRes) => {
7
+ // TODO: Implement creation logic.
8
+ };
9
+
10
+ readonly findAll: SpongHandler = async (spongReq, spongRes) => {
11
+ // TODO: Implement fetching all records.
12
+ };
13
+
14
+ readonly findOne: SpongHandler = async (spongReq, spongRes) => {
15
+ // TODO: Implement fetching a record by ID.
16
+ };
17
+
18
+ readonly update: SpongHandler = async (spongReq, spongRes) => {
19
+ // TODO: Implement updating a record.
20
+ };
21
+
22
+ readonly delete: SpongHandler = async (spongReq, spongRes) => {
23
+ // TODO: Implement deleting a record.
24
+ };
25
+
26
+ verifyToken(token: string) {
27
+ // TODO: Implement token verification.
28
+ return null;
29
+ }
30
+ }
31
+ `,[`${e}.route.ts`]:`import type { RiserBob } from "riser-bob";
32
+ import { AuthMiddleware } from "riser-bob";
33
+ import { ${r}Controller } from "./${e}.controller";
34
+
35
+ export class ${r}Router {
36
+ constructor(
37
+ private readonly bob: RiserBob,
38
+ private readonly controller: ${r}Controller,
39
+ private readonly authMiddleware: AuthMiddleware,
40
+ ) {}
41
+
42
+ register(): void {
43
+ // Public routes
44
+ this.bob.spongGet("/api/${e}", this.controller.findAll);
45
+ this.bob.spongGet("/api/${e}/:id", this.controller.findOne);
46
+
47
+ // Protected routes
48
+ this.bob.spongPost("/api/${e}",
49
+ this.controller.create,
50
+ this.authMiddleware.middleware,
51
+ );
52
+
53
+ this.bob.spongPut(
54
+ "/api/${e}/:id",
55
+ this.controller.update,
56
+ this.authMiddleware.middleware,
57
+ );
58
+
59
+ this.bob.spongDelete(
60
+ "/api/${e}/:id",
61
+ this.controller.delete,
62
+ this.authMiddleware.middleware,
63
+ );
64
+ }
65
+ }
66
+ `,[`${e}.module.ts`]:`import type { RiserBob } from "riser-bob";
67
+ import { AuthMiddleware } from "riser-bob";
68
+ import { ${r}Controller } from "./${e}.controller";
69
+ import { ${r}Router } from "./${e}.route";
70
+
71
+ export class ${r}Module {
72
+ register(bob: RiserBob): void {
73
+ const controller = new ${r}Controller();
74
+
75
+ const authMiddleware = new AuthMiddleware((token) =>
76
+ controller.verifyToken(token),
77
+ );
78
+
79
+ const router = new ${r}Router(
80
+ bob,
81
+ controller,
82
+ authMiddleware,
83
+ );
84
+
85
+ router.register();
86
+ }
87
+ }
88
+ `,[`schemas/${p}.schema.ts`]:`export class ${i}Schema {}
89
+ `,["rules/create.ts"]:`import type { ValidationSchema } from "riser-bob";
90
+
91
+ export const Create${i}Rule = {} satisfies ValidationSchema;
92
+ `,["rules/update.ts"]:`import { Create${i}Rule } from "./create";
93
+
94
+ export const Update${i}Rule = Create${i}Rule;
95
+ `,[`tests/${e}.controller.test.ts`]:`import { describe, expect, test } from "bun:test";
96
+ import { ${r}Controller } from "../${e}.controller";
97
+
98
+ describe("${r}Controller", () => {
99
+ const controller = new ${r}Controller();
100
+
101
+ test("should be defined", () => {
102
+ expect(controller).toBeDefined();
103
+ });
104
+
105
+ test("should expose all route handlers", () => {
106
+ expect(typeof controller.create).toBe("function");
107
+ expect(typeof controller.findAll).toBe("function");
108
+ expect(typeof controller.findOne).toBe("function");
109
+ expect(typeof controller.update).toBe("function");
110
+ expect(typeof controller.delete).toBe("function");
111
+ });
112
+ });
113
+ `};await m(a,{recursive:!0});for(let t of Object.keys(c)){let n=s.join(a,t);if(await Bun.file(n).exists())throw Error(`File "${s.relative(process.cwd(),n)}" already exists.`)}let u=[];try{for(let[t,n]of Object.entries(c)){let d=s.join(a,t);await m(s.dirname(d),{recursive:!0}),await y(d,n,{flag:"wx"}),u.push(d)}}catch(t){throw await Promise.all(u.map((n)=>R(n).catch(()=>{}))),t}console.log(""),console.log(`\uD83E\uDDFD Module "${e}" generated successfully!`),console.log(""),console.log(`\uD83D\uDCC1 ${s.relative(process.cwd(),a)}`),console.log("");for(let t of Object.keys(c))console.log(` \u2514\u2500\u2500 ${t}`)}var l=process.argv.slice(2),[h,O,b]=l;function w(){console.log(`
114
+ \uD83E\uDDFD RiserBob CLI
115
+
116
+ Usage:
117
+ riserbob g module <name>
118
+ riserbob generate module <name>
119
+ riserbob --help
120
+
121
+ Commands:
122
+ g, generate Generate a component
123
+ --help Show this help message
124
+ `)}async function B(){if(l.length===0||l.includes("--help")||l.includes("-h")){w();return}if(l.includes("--version")||l.includes("-v")){console.log("RiserBob CLI"),console.log("Version is provided by the installed package.");return}if((h==="g"||h==="generate")&&O==="module"&&b){await f(b);return}console.error("\uD83E\uDDFD Oh, barnacles! Unknown command."),w(),process.exitCode=1}B().catch((o)=>{let e=o instanceof Error?o.message:"Something went wrong.";console.error(`\uD83C\uDF4D RiserBob CLI: ${e}`),process.exitCode=1});
@@ -0,0 +1,3 @@
1
+ export declare class ErrorHandler {
2
+ static handle(error: unknown): Response;
3
+ }
@@ -0,0 +1,14 @@
1
+ export declare class ErrorMessages {
2
+ private readonly funnyMode;
3
+ constructor(funnyMode?: boolean);
4
+ invalidPath(path: string): string;
5
+ handlerDidNotReturnResponse(): string;
6
+ requestFailed(): string;
7
+ nextCalledMultipleTimes(): string;
8
+ duplicateRoute(key: string): string;
9
+ methodNotAllowed(method: string, path: string, allowed: string[]): string;
10
+ routeNotFound(method: string, path: string): string;
11
+ handlerResponseMissing(method: string, path: string, received: string): string;
12
+ requestHandlingFailed(): string;
13
+ invalidValidationSchema(field: string): string;
14
+ }
@@ -0,0 +1,4 @@
1
+ export declare class HttpError extends Error {
2
+ readonly status: number;
3
+ constructor(status: number, message: string);
4
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Checks whether a request uses JSON as its body format.
3
+ */
4
+ export declare const isJsonContentType: (contentType: string | null) => boolean;
5
+ /**
6
+ * Throws 415 when the request body is not declared as JSON.
7
+ */
8
+ export declare const requireJsonContentType: (contentType: string | null) => void;
9
+ /**
10
+ * Checks whether an Accept header permits a given response media type.
11
+ *
12
+ * Supports exact types, type wildcards, and q= quality values.
13
+ * A missing Accept header means any response type is acceptable.
14
+ */
15
+ export declare const acceptsMediaType: (acceptHeader: string | null, responseType: string) => boolean;
16
+ /**
17
+ * Throws 406 when the client does not accept the response media type.
18
+ */
19
+ export declare const requireAccepts: (acceptHeader: string | null, responseType: string) => void;
@@ -0,0 +1,2 @@
1
+ export declare const DEFAULT_MAX_BODY_SIZE: number;
2
+ export declare const readRequestBody: (request: Request, maxBodySize?: number) => Promise<Uint8Array>;
@@ -0,0 +1,2 @@
1
+ import type { SpongResponse } from '../../types/spong-response';
2
+ export declare function RiserBobResponse(): SpongResponse;
@@ -0,0 +1,65 @@
1
+ export declare const StatusCode: {
2
+ readonly Continue: 100;
3
+ readonly SwitchingProtocols: 101;
4
+ readonly Processing: 102;
5
+ readonly EarlyHints: 103;
6
+ readonly OK: 200;
7
+ readonly Created: 201;
8
+ readonly Accepted: 202;
9
+ readonly NonAuthoritativeInfo: 203;
10
+ readonly NoContent: 204;
11
+ readonly ResetContent: 205;
12
+ readonly PartialContent: 206;
13
+ readonly MultiStatus: 207;
14
+ readonly AlreadyReported: 208;
15
+ readonly IMUsed: 226;
16
+ readonly MultipleChoices: 300;
17
+ readonly MovedPermanently: 301;
18
+ readonly Found: 302;
19
+ readonly SeeOther: 303;
20
+ readonly NotModified: 304;
21
+ readonly UseProxy: 305;
22
+ readonly TemporaryRedirect: 307;
23
+ readonly PermanentRedirect: 308;
24
+ readonly BadRequest: 400;
25
+ readonly Unauthorized: 401;
26
+ readonly PaymentRequired: 402;
27
+ readonly Forbidden: 403;
28
+ readonly NotFound: 404;
29
+ readonly MethodNotAllowed: 405;
30
+ readonly NotAcceptable: 406;
31
+ readonly ProxyAuthenticationRequired: 407;
32
+ readonly RequestTimeout: 408;
33
+ readonly Conflict: 409;
34
+ readonly Gone: 410;
35
+ readonly LengthRequired: 411;
36
+ readonly PreconditionFailed: 412;
37
+ readonly ContentTooLarge: 413;
38
+ readonly URITooLong: 414;
39
+ readonly UnsupportedMediaType: 415;
40
+ readonly RangeNotSatisfiable: 416;
41
+ readonly ExpectationFailed: 417;
42
+ readonly ImATeapot: 418;
43
+ readonly MisdirectedRequest: 421;
44
+ readonly UnprocessableContent: 422;
45
+ readonly Locked: 423;
46
+ readonly FailedDependency: 424;
47
+ readonly TooEarly: 425;
48
+ readonly UpgradeRequired: 426;
49
+ readonly PreconditionRequired: 428;
50
+ readonly TooManyRequests: 429;
51
+ readonly RequestHeaderFieldsTooLarge: 431;
52
+ readonly UnavailableForLegalReasons: 451;
53
+ readonly InternalServerError: 500;
54
+ readonly NotImplemented: 501;
55
+ readonly BadGateway: 502;
56
+ readonly ServiceUnavailable: 503;
57
+ readonly GatewayTimeout: 504;
58
+ readonly HTTPVersionNotSupported: 505;
59
+ readonly VariantAlsoNegotiates: 506;
60
+ readonly InsufficientStorage: 507;
61
+ readonly LoopDetected: 508;
62
+ readonly NotExtended: 510;
63
+ readonly NetworkAuthenticationRequired: 511;
64
+ };
65
+ export type StatusCode = (typeof StatusCode)[keyof typeof StatusCode];
@@ -0,0 +1,15 @@
1
+ export { RiserBob } from './application/riser-bob';
2
+ export { HttpError } from './errors/http-error';
3
+ export { AuthMiddleware } from './middleware/auth-middleware';
4
+ export { corsMiddleware } from './middleware/cors-middleware';
5
+ export { loggerMiddleware } from './middleware/logger-middleware';
6
+ export { MiddlewarePipeline } from './middleware/middleware-pipeline';
7
+ export type { SpongAuth } from './types/spong-auth';
8
+ export type { SpongHandler } from './types/spong-handler';
9
+ export type { SpongMethod } from './types/spong-method';
10
+ export type { SpongMiddleware } from './types/spong-middleware';
11
+ export type { SpongRequest } from './types/spong-request';
12
+ export type { SpongResponse } from './types/spong-response';
13
+ export type { SpongNext } from './types/spong-middleware';
14
+ export type { ValidationSchema } from './validation/validator';
15
+ export { Validator } from './validation/validator';