@kithinji/orca 1.0.13 → 1.0.15

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/README.md ADDED
@@ -0,0 +1,535 @@
1
+ <h1 align="center">Orca</h1>
2
+ <p align="center">
3
+ <img src="https://i.postimg.cc/Wpq8CWvV/orca.png" alt="orca-logo" width="150px"/>
4
+ <br>
5
+ <em>One codebase. One deployment.
6
+ <br> Build full-stack applications without the frontend/backend divide.</em>
7
+ <br>
8
+ </p>
9
+ <p align="center">
10
+ <a href="https://orca.dafifi.net/"><strong>orca.dafifi.net</strong></a>
11
+ <br>
12
+ </p>
13
+ <p align="center">
14
+ <a href="https://www.npmjs.com/package/@kithinji/orca">
15
+ <img src="https://img.shields.io/badge/NPM_package-v1.0.14-blue" alt="Orca on npm" />
16
+ </a>
17
+ </p>
18
+
19
+ <hr>
20
+
21
+ ## The Problem
22
+
23
+ Building modern web apps means maintaining two separate projects: a frontend repo and a backend repo. Two package.jsons. Two deployment pipelines. Two sets of bugs for what should be _one feature_.
24
+
25
+ The cognitive overhead is exhausting. You're not just switching files, you're switching mental models. Frontend brain, backend brain. Component lifecycle, request lifecycle. By the time you've wired everything together, you've spent more time _connecting_ things than building them.
26
+
27
+ **Orca solves this.**
28
+
29
+ ## What is Orca?
30
+
31
+ Orca is a full-stack TypeScript framework that lets you build your entire application - API, business logic, and UI - in a single codebase with shared types, unified architecture, and zero artificial boundaries.
32
+
33
+ Inspired by NestJS and Angular, Orca brings structure and convention to full-stack development using dependency injection, event systems, and a novel approach to client-server separation.
34
+
35
+ ### The Core Innovation
36
+
37
+ Instead of forcing you to choose between server rendering or client interactivity, Orca gives you **both** with two simple directives:
38
+
39
+ ```tsx
40
+ // Server-rendered by default - fast, works without JavaScript
41
+ @Component()
42
+ export class ProductList {
43
+ constructor(private products: ProductService) {}
44
+
45
+ async build() {
46
+ const items = await this.products.getAll();
47
+ return (
48
+ <div>
49
+ {items.map((item) => (
50
+ <ProductCard key={item.id} product={item} />
51
+ ))}
52
+ </div>
53
+ );
54
+ }
55
+ }
56
+ ```
57
+
58
+ ```tsx
59
+ // "use interactive" creates islands of interactivity
60
+ "use interactive";
61
+ import { Component, signal } from "@kithinji/orca";
62
+
63
+ @Component()
64
+ export class AddToCartButton {
65
+ constructor(private cart: CartService) {}
66
+
67
+ props!: { productId: number };
68
+ private adding = signal(false);
69
+
70
+ build() {
71
+ return (
72
+ <button
73
+ disabled={this.adding.value}
74
+ onClick={async () => {
75
+ this.adding.value = true;
76
+ await this.cart.addItem(this.props.productId);
77
+ this.adding.value = false;
78
+ }}
79
+ >
80
+ {this.adding.value ? "Adding..." : "Add to Cart"}
81
+ </button>
82
+ );
83
+ }
84
+ }
85
+ ```
86
+
87
+ ```tsx
88
+ // "use public" creates type-safe API endpoints automatically
89
+ "use public";
90
+ import { Injectable } from "@kithinji/orca";
91
+
92
+ @Injectable()
93
+ export class CartService {
94
+ public async addItem(productId: number) {
95
+ // This code runs on the server
96
+ // But can be called from the client like a local function
97
+ const item = await this.db.products.findUnique({ where: { productId } });
98
+ return this.db.cart.create({ data: { productId, quantity: 1 } });
99
+ }
100
+
101
+ public async getTotal() {
102
+ return this.db.cart.aggregate({ _sum: { price: true } });
103
+ }
104
+ }
105
+ ```
106
+
107
+ When you call `this.cart.addItem()` from a client component, Orca automatically:
108
+
109
+ 1. Generates a `/CartService/addItem` API endpoint
110
+ 2. Validates the input on the server
111
+ 3. Executes your server-side logic
112
+ 4. Returns the typed response
113
+
114
+ **You never write `fetch()` calls. The types never drift.**
115
+
116
+ ## Key Features
117
+
118
+ - **Islands Architecture**: Server-render by default, add interactivity only where needed with `"use interactive"`
119
+ - **Type-Safe APIs**: Mark services with `"use public"` to auto-generate typed API endpoints. No manual fetch calls
120
+ - **Dependency Injection**: Services, controllers, and components work together with clean separation of concerns
121
+ - **Stack-Based Navigation**: Push components onto a navigation stack instead of wrestling with file-based routing
122
+ - **Shared TypeScript Types**: Your types never drift because they're literally the same types across client and server
123
+ - **Decorator-Based APIs**: Express your intent clearly with TypeScript decorators
124
+ - **Built-in Validation**: Request validation using schemas like Zod
125
+ - **Modular Architecture**: Organize code by feature, not by technical layer
126
+
127
+ ## Why Not...?
128
+
129
+ ### "Why not separate frontend/backend repos?"
130
+
131
+ Because maintaining two repos for one app doubles your work. Two sets of types that drift. Two deployment pipelines. Constant context switching between mental models. If you're a solo dev or small team, you're spending more time wiring things together than building features.
132
+
133
+ ### "Why not Next.js?"
134
+
135
+ Next.js is excellent at rendering UI fast. But the moment you try to build a real backend with Server Actions, the lines blur in uncomfortable ways. Validation logic bleeds into components. Database queries live next to UI code. It's optimized for frontend-first development, not full-stack architecture.
136
+
137
+ Orca gives you a proper backend with controllers, services, and dependency injection, plus UI rendering that doesn't feel like an afterthought.
138
+
139
+ ### "Why not HTMX?"
140
+
141
+ HTMX is elegant for server-rendered apps. But endpoints that return HTML lock you in. What happens when you need a mobile app? A CLI? A webhook consumer? You either build a second JSON API or parse HTML on the client.
142
+
143
+ Orca's API returns JSON by default. The UI rendering is a layer on top, not a replacement. You're never painted into a corner.
144
+
145
+ ## Getting Started
146
+
147
+ ### Prerequisites
148
+
149
+ - Node.js v20.9 or above
150
+ - A text editor (VS Code recommended)
151
+
152
+ ### Installation
153
+
154
+ Install the Orca CLI globally:
155
+
156
+ ```bash
157
+ npm install -g @kithinji/pod
158
+ ```
159
+
160
+ ### Create Your First Project
161
+
162
+ ```bash
163
+ pod new my-app
164
+ cd my-app
165
+ npm run dev
166
+ ```
167
+
168
+ Visit `http://localhost:8080` to see your application running. 🎉
169
+
170
+ ## Project Structure
171
+
172
+ ```
173
+ src/
174
+ ├── features/
175
+ │ ├── user/
176
+ │ │ ├── user.controller.ts # HTTP endpoints (optional)
177
+ │ │ ├── user.service.ts # Business logic
178
+ │ │ ├── pages/ # UI page components
179
+ │ │ │ ├── user-profile.page.tsx
180
+ │ │ │ └── user-login.page.tsx
181
+ │ │ ├── components/ # UI components
182
+ │ │ │ └── avatar.component.tsx
183
+ │ │ └── user.module.ts # Module definition
184
+ │ └── product/
185
+ │ ├── product.service.ts
186
+ │ ├── components/
187
+ │ └── product.module.ts
188
+ └── app.module.ts # Root module
189
+ ```
190
+
191
+ ## Core Concepts
192
+
193
+ ### 1. Services (Business Logic)
194
+
195
+ Services contain your application's business logic. They're injectable classes that can be used anywhere in your app.
196
+
197
+ ```typescript
198
+ // product.service.ts
199
+ "use public"; // Makes this service callable from the client
200
+ import { Injectable, Signature } from "@kithinji/orca";
201
+ import { z } from "zod";
202
+
203
+ const GetProductSchema = z.object({ id: z.number() });
204
+ const ProductSchema = z.object({
205
+ id: z.number(),
206
+ name: z.string(),
207
+ price: z.number(),
208
+ });
209
+
210
+ @Injectable()
211
+ export class ProductService {
212
+ constructor(private db: DatabaseService) {}
213
+
214
+ @Signature(GetProductSchema, ProductSchema) // used in generating validation logic automatically
215
+ public async getProduct(id: number) {
216
+ return this.db.products.findUnique({ where: { id } });
217
+ }
218
+
219
+ public async listProducts() {
220
+ return this.db.products.findMany();
221
+ }
222
+ }
223
+ ```
224
+
225
+ ### 2. Components (UI Layer)
226
+
227
+ Components are class-based and return JSX from a `build()` method. They're server-rendered by default.
228
+
229
+ **Server Component (async data fetching):**
230
+
231
+ ```tsx
232
+ // product-list.component.tsx
233
+ import { Component } from "@kithinji/orca";
234
+
235
+ @Component()
236
+ export class ProductList {
237
+ constructor(private products: ProductService) {}
238
+
239
+ async build() {
240
+ const items = await this.products.listProducts();
241
+
242
+ return (
243
+ <div>
244
+ <h1>Products</h1>
245
+ {items.map((item) => (
246
+ <div key={item.id}>
247
+ <h3>{item.name}</h3>
248
+ <p>${item.price}</p>
249
+ <AddToCartButton productId={item.id} />
250
+ </div>
251
+ ))}
252
+ </div>
253
+ );
254
+ }
255
+ }
256
+ ```
257
+
258
+ **Interactive Component (client-side reactivity):**
259
+
260
+ ```tsx
261
+ // add-to-cart-button.component.tsx
262
+ "use interactive";
263
+ import { Component, signal } from "@kithinji/orca";
264
+
265
+ @Component()
266
+ export class AddToCartButton {
267
+ constructor(private cart: CartService) {}
268
+
269
+ props!: { productId: number };
270
+ private adding = signal(false);
271
+
272
+ build() {
273
+ return (
274
+ <button
275
+ onClick={async () => {
276
+ this.adding.value = true;
277
+ await this.cart.addItem(this.props.productId);
278
+ this.adding.value = false;
279
+ }}
280
+ >
281
+ {this.adding.value ? "Adding..." : "Add to Cart"}
282
+ </button>
283
+ );
284
+ }
285
+ }
286
+ ```
287
+
288
+ ### 3. Controllers (HTTP Layer)
289
+
290
+ Controllers handle HTTP requests and define REST endpoints. You can write them manually or let Orca generate them automatically for `"use public"` services.
291
+
292
+ **Manual Controller:**
293
+
294
+ ```typescript
295
+ import { Controller, Get, Post, Body, Param } from "@kithinji/orca";
296
+
297
+ @Controller("/products")
298
+ export class ProductController {
299
+ constructor(private products: ProductService) {}
300
+
301
+ @Get()
302
+ async findAll() {
303
+ return this.products.listProducts();
304
+ }
305
+
306
+ @Get("/:id")
307
+ async findOne(@Param("id") id: number) {
308
+ return this.products.getProduct(id);
309
+ }
310
+
311
+ @Post()
312
+ async create(@Body() data: CreateProductDto) {
313
+ return this.products.create(data);
314
+ }
315
+ }
316
+ ```
317
+
318
+ **Auto-Generated Controller:**
319
+
320
+ When you mark a service with `"use public"`, Orca automatically creates endpoints:
321
+
322
+ - `POST /ProductService/getProduct`
323
+ - `GET /ProductService/listProducts`
324
+
325
+ No controller code needed - just mark the service and call methods from your client components.
326
+
327
+ ### 4. Modules (Organization)
328
+
329
+ Modules group related functionality together and manage dependencies.
330
+
331
+ ```typescript
332
+ // product.module.ts
333
+ import { Module } from "@kithinji/orca";
334
+ import { ProductController } from "./product.controller";
335
+ import { ProductService } from "./product.service";
336
+ import { ProductList, AddToCartButton } from "./components";
337
+
338
+ @Module({
339
+ imports: [DatabaseModule], // Modules we depend on
340
+ controllers: [ProductController], // HTTP endpoints
341
+ providers: [ProductService], // Services
342
+ declarations: [ProductList, AddToCartButton], // UI components
343
+ exports: [ProductService, ProductList], // What others can use
344
+ })
345
+ export class ProductModule {}
346
+ ```
347
+
348
+ ### 5. Navigation (Stack-Based Routing)
349
+
350
+ Instead of file-based routing, Orca uses a navigation stack. Push components onto the stack to navigate.
351
+
352
+ ```tsx
353
+ import { Navigate } from "@kithinji/orca";
354
+
355
+ @Component()
356
+ export class HomePage {
357
+ constructor(private navigate: Navigate) {}
358
+
359
+ build() {
360
+ return (
361
+ <div>
362
+ <h1>Welcome</h1>
363
+ <button onClick={() => this.navigate.push(<UserProfile userId={1} />)}>
364
+ View Profile
365
+ </button>
366
+ </div>
367
+ );
368
+ }
369
+ }
370
+ ```
371
+
372
+ Navigation methods:
373
+
374
+ - `push()` - Add a new page on top
375
+ - `pop()` - Go back to previous page
376
+ - `replace()` - Replace current page
377
+ - `popToRoot()` - Clear stack and return to root
378
+ - `canPop()` - Check if back navigation is possible
379
+
380
+ ## The Magic: How It All Works Together
381
+
382
+ Here's a complete feature showing the full stack:
383
+
384
+ ```typescript
385
+ // cart.service.ts
386
+ "use public";
387
+ @Injectable()
388
+ export class CartService {
389
+ constructor(private db: DatabaseService) {}
390
+
391
+ public async addItem(productId: number, quantity: number = 1) {
392
+ const product = await this.db.products.findUnique({ where: { productId } });
393
+ return this.db.cart.create({
394
+ data: { productId, quantity, price: product.price },
395
+ });
396
+ }
397
+
398
+ public async getCart() {
399
+ return this.db.cart.findMany();
400
+ }
401
+ }
402
+
403
+ // cart-page.component.tsx
404
+ @Component()
405
+ export class CartPage {
406
+ constructor(private cart: CartService) {}
407
+
408
+ async build() {
409
+ const items = await this.cart.getCart();
410
+ return (
411
+ <div>
412
+ <h1>Your Cart</h1>
413
+ {items.map((item) => (
414
+ <CartItem key={item.id} item={item} />
415
+ ))}
416
+ </div>
417
+ );
418
+ }
419
+ }
420
+
421
+ // add-to-cart-button.component.tsx
422
+ ("use interactive");
423
+
424
+ @Component()
425
+ export class AddToCartButton {
426
+ constructor(private cart: CartService, private navigate: Navigate) {}
427
+
428
+ props!: { productId: number };
429
+
430
+ build() {
431
+ return (
432
+ <button
433
+ onClick={async () => {
434
+ await this.cart.addItem(this.props.productId);
435
+ this.navigate.push(<CartPage />);
436
+ }}
437
+ >
438
+ Add to Cart
439
+ </button>
440
+ );
441
+ }
442
+ }
443
+ ```
444
+
445
+ **What happens here:**
446
+
447
+ 1. `CartService` is marked `"use public"`, so Orca generates API endpoints
448
+ 2. `CartPage` (server component) calls `cart.getCart()` on the server during initial render
449
+ 3. `AddToCartButton` (interactive component) calls `cart.addItem()` from the browser
450
+ 4. Orca automatically converts that call into `fetch('/CartService/addItem', ...)`
451
+ 5. Types are preserved end-to-end; TypeScript catches errors everywhere
452
+ 6. Navigation happens by pushing a component, not a URL string
453
+
454
+ ## Is Orca for You?
455
+
456
+ **Orca is great if you:**
457
+
458
+ - Are a solo developer or small team
459
+ - Want to ship features without maintaining two separate projects
460
+ - Value clear architecture and separation of concerns
461
+ - Need a real API that can grow (mobile apps, CLIs, integrations)
462
+ - Are tired of context switching between frontend and backend mental models
463
+ - Build internal tools, dashboards, or admin panels
464
+ - You're building highly interactive web apps
465
+
466
+ **Orca might not be for you if:**
467
+
468
+ - You have a large team with dedicated frontend/backend developers
469
+ - You prefer the frontend/backend split and it works for your workflow
470
+ - You need a purely client-side SPA framework
471
+ - You're building a marketing site or content-heavy blog (use Astro or Next.js)
472
+ - Your team is already deeply invested in another stack
473
+
474
+ This isn't a crusade. If the traditional split works for you, keep doing what works. But if you've ever thought "there has to be a simpler way", Orca is that way.
475
+
476
+ ## Philosophy
477
+
478
+ With the rise of React, Vue, and Angular, web development split into two worlds: frontend and backend. For large teams, that makes sense. But for solo devs and small teams, it doubles the work.
479
+
480
+ Orca rejects the artificial divide. Instead:
481
+
482
+ - **One codebase**: Write features end-to-end in one place
483
+ - **Islands of interactivity**: Server-render by default, add JavaScript only where needed
484
+ - **Type-safe APIs**: No manual fetch calls, no drifting types
485
+ - **Clear architecture**: DI, decorators, and modules keep large apps maintainable
486
+ - **Stack-based navigation**: Route by pushing components, not by folder structure
487
+
488
+ This is opinionated software. It has rules, structure, and conventions. That's the point. The rules exist to free you from decision fatigue so you can focus on building.
489
+
490
+ ## Documentation
491
+
492
+ Full guides, API references, and examples at **[orca.dafifi.net](https://orca.dafifi.net/)**
493
+
494
+ Topics covered:
495
+
496
+ - Components and JSX
497
+ - Dependency Injection
498
+ - Modules and Providers
499
+ - Controllers and Routing
500
+ - The `"use interactive"` directive
501
+ - The `"use public"` directive
502
+ - Signals and Reactivity
503
+ - Observables
504
+ - Navigation Stack
505
+ - Validation with Zod
506
+
507
+ ## Roadmap
508
+
509
+ - [ ] Database integrations (TypeORM, Prisma)
510
+ - [ ] Authentication & authorization modules
511
+ - [ ] WebSocket support
512
+ - [ ] GraphQL adapter
513
+ - [ ] CLI scaffolding improvements
514
+ - [ ] File upload handling
515
+ - [ ] Background jobs / task queues
516
+
517
+ ## Contributing
518
+
519
+ Contributions are welcome! Please read our contributing guidelines before submitting PRs.
520
+
521
+ ## Stay in Touch
522
+
523
+ - **Author**: [Kithinji Brian](https://www.linkedin.com/in/kithinjibrian/)
524
+ - **Website**: [orca.dafifi.net](https://orca.dafifi.net/)
525
+ - **NPM**: [@kithinji/orca](https://www.npmjs.com/package/@kithinji/orca)
526
+
527
+ ## License
528
+
529
+ MIT
530
+
531
+ ---
532
+
533
+ <p align="center">
534
+ Made in Nairobi with ❤️ for developers who just want to ship
535
+ </p>
@@ -1373,7 +1373,7 @@ var kithinjiorca = (() => {
1373
1373
  const subst = obs.subscribe((val) => {
1374
1374
  sig.value = val;
1375
1375
  });
1376
- instance.__drop = [...instance.__drop ?? [], () => subst.unsubscribe()];
1376
+ instance.__cleanup = [...instance.__cleanup ?? [], () => subst.unsubscribe()];
1377
1377
  return sig;
1378
1378
  }
1379
1379
  function isObservable(value) {