@kithinji/orca 1.0.14 → 1.0.16

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,541 @@
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.16-blue" alt="Orca on npm" />
16
+ </a>
17
+ </p>
18
+
19
+ <p align="center">
20
+ <a href="https://github.com/kithinjibrian/pod">
21
+ <img src="https://img.shields.io/badge/POD CLI Tool-blue" alt="Pod on github" />
22
+ </a>
23
+ </p>
24
+
25
+ <hr>
26
+
27
+ ## The Problem
28
+
29
+ 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_.
30
+
31
+ 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.
32
+
33
+ **Orca solves this.**
34
+
35
+ ## What is Orca?
36
+
37
+ 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.
38
+
39
+ 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.
40
+
41
+ ### The Core Innovation
42
+
43
+ Instead of forcing you to choose between server rendering or client interactivity, Orca gives you **both** with two simple directives:
44
+
45
+ ```tsx
46
+ // Server-rendered by default - fast, works without JavaScript
47
+ @Component()
48
+ export class ProductList {
49
+ constructor(private products: ProductService) {}
50
+
51
+ async build() {
52
+ const items = await this.products.getAll();
53
+ return (
54
+ <div>
55
+ {items.map((item) => (
56
+ <ProductCard key={item.id} product={item} />
57
+ ))}
58
+ </div>
59
+ );
60
+ }
61
+ }
62
+ ```
63
+
64
+ ```tsx
65
+ // "use interactive" creates islands of interactivity
66
+ "use interactive";
67
+ import { Component, signal } from "@kithinji/orca";
68
+
69
+ @Component()
70
+ export class AddToCartButton {
71
+ constructor(private cart: CartService) {}
72
+
73
+ props!: { productId: number };
74
+ private adding = signal(false);
75
+
76
+ build() {
77
+ return (
78
+ <button
79
+ disabled={this.adding.value}
80
+ onClick={async () => {
81
+ this.adding.value = true;
82
+ await this.cart.addItem(this.props.productId);
83
+ this.adding.value = false;
84
+ }}
85
+ >
86
+ {this.adding.value ? "Adding..." : "Add to Cart"}
87
+ </button>
88
+ );
89
+ }
90
+ }
91
+ ```
92
+
93
+ ```tsx
94
+ // "use public" creates type-safe API endpoints automatically
95
+ "use public";
96
+ import { Injectable } from "@kithinji/orca";
97
+
98
+ @Injectable()
99
+ export class CartService {
100
+ public async addItem(productId: number) {
101
+ // This code runs on the server
102
+ // But can be called from the client like a local function
103
+ const item = await this.db.products.findUnique({ where: { productId } });
104
+ return this.db.cart.create({ data: { productId, quantity: 1 } });
105
+ }
106
+
107
+ public async getTotal() {
108
+ return this.db.cart.aggregate({ _sum: { price: true } });
109
+ }
110
+ }
111
+ ```
112
+
113
+ When you call `this.cart.addItem()` from a client component, Orca automatically:
114
+
115
+ 1. Generates a `/CartService/addItem` API endpoint
116
+ 2. Validates the input on the server
117
+ 3. Executes your server-side logic
118
+ 4. Returns the typed response
119
+
120
+ **You never write `fetch()` calls. The types never drift.**
121
+
122
+ ## Key Features
123
+
124
+ - **Islands Architecture**: Server-render by default, add interactivity only where needed with `"use interactive"`
125
+ - **Type-Safe APIs**: Mark services with `"use public"` to auto-generate typed API endpoints. No manual fetch calls
126
+ - **Dependency Injection**: Services, controllers, and components work together with clean separation of concerns
127
+ - **Stack-Based Navigation**: Push components onto a navigation stack instead of wrestling with file-based routing
128
+ - **Shared TypeScript Types**: Your types never drift because they're literally the same types across client and server
129
+ - **Decorator-Based APIs**: Express your intent clearly with TypeScript decorators
130
+ - **Built-in Validation**: Request validation using schemas like Zod
131
+ - **Modular Architecture**: Organize code by feature, not by technical layer
132
+
133
+ ## Why Not...?
134
+
135
+ ### "Why not separate frontend/backend repos?"
136
+
137
+ 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.
138
+
139
+ ### "Why not Next.js?"
140
+
141
+ 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.
142
+
143
+ Orca gives you a proper backend with controllers, services, and dependency injection, plus UI rendering that doesn't feel like an afterthought.
144
+
145
+ ### "Why not HTMX?"
146
+
147
+ 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.
148
+
149
+ 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.
150
+
151
+ ## Getting Started
152
+
153
+ ### Prerequisites
154
+
155
+ - Node.js v20.9 or above
156
+ - A text editor (VS Code recommended)
157
+
158
+ ### Installation
159
+
160
+ Install the Orca CLI globally:
161
+
162
+ ```bash
163
+ npm install -g @kithinji/pod
164
+ ```
165
+
166
+ ### Create Your First Project
167
+
168
+ ```bash
169
+ pod new my-app
170
+ cd my-app
171
+ npm run dev
172
+ ```
173
+
174
+ Visit `http://localhost:8080` to see your application running. 🎉
175
+
176
+ ## Project Structure
177
+
178
+ ```
179
+ src/
180
+ ├── features/
181
+ │ ├── user/
182
+ │ │ ├── user.controller.ts # HTTP endpoints (optional)
183
+ │ │ ├── user.service.ts # Business logic
184
+ │ │ ├── pages/ # UI page components
185
+ │ │ │ ├── user-profile.page.tsx
186
+ │ │ │ └── user-login.page.tsx
187
+ │ │ ├── components/ # UI components
188
+ │ │ │ └── avatar.component.tsx
189
+ │ │ └── user.module.ts # Module definition
190
+ │ └── product/
191
+ │ ├── product.service.ts
192
+ │ ├── components/
193
+ │ └── product.module.ts
194
+ └── app.module.ts # Root module
195
+ ```
196
+
197
+ ## Core Concepts
198
+
199
+ ### 1. Services (Business Logic)
200
+
201
+ Services contain your application's business logic. They're injectable classes that can be used anywhere in your app.
202
+
203
+ ```typescript
204
+ // product.service.ts
205
+ "use public"; // Makes this service callable from the client
206
+ import { Injectable, Signature } from "@kithinji/orca";
207
+ import { z } from "zod";
208
+
209
+ const GetProductSchema = z.object({ id: z.number() });
210
+ const ProductSchema = z.object({
211
+ id: z.number(),
212
+ name: z.string(),
213
+ price: z.number(),
214
+ });
215
+
216
+ @Injectable()
217
+ export class ProductService {
218
+ constructor(private db: DatabaseService) {}
219
+
220
+ @Signature(GetProductSchema, ProductSchema) // used in generating validation logic automatically
221
+ public async getProduct(id: number) {
222
+ return this.db.products.findUnique({ where: { id } });
223
+ }
224
+
225
+ public async listProducts() {
226
+ return this.db.products.findMany();
227
+ }
228
+ }
229
+ ```
230
+
231
+ ### 2. Components (UI Layer)
232
+
233
+ Components are class-based and return JSX from a `build()` method. They're server-rendered by default.
234
+
235
+ **Server Component (async data fetching):**
236
+
237
+ ```tsx
238
+ // product-list.component.tsx
239
+ import { Component } from "@kithinji/orca";
240
+
241
+ @Component()
242
+ export class ProductList {
243
+ constructor(private products: ProductService) {}
244
+
245
+ async build() {
246
+ const items = await this.products.listProducts();
247
+
248
+ return (
249
+ <div>
250
+ <h1>Products</h1>
251
+ {items.map((item) => (
252
+ <div key={item.id}>
253
+ <h3>{item.name}</h3>
254
+ <p>${item.price}</p>
255
+ <AddToCartButton productId={item.id} />
256
+ </div>
257
+ ))}
258
+ </div>
259
+ );
260
+ }
261
+ }
262
+ ```
263
+
264
+ **Interactive Component (client-side reactivity):**
265
+
266
+ ```tsx
267
+ // add-to-cart-button.component.tsx
268
+ "use interactive";
269
+ import { Component, signal } from "@kithinji/orca";
270
+
271
+ @Component()
272
+ export class AddToCartButton {
273
+ constructor(private cart: CartService) {}
274
+
275
+ props!: { productId: number };
276
+ private adding = signal(false);
277
+
278
+ build() {
279
+ return (
280
+ <button
281
+ onClick={async () => {
282
+ this.adding.value = true;
283
+ await this.cart.addItem(this.props.productId);
284
+ this.adding.value = false;
285
+ }}
286
+ >
287
+ {this.adding.value ? "Adding..." : "Add to Cart"}
288
+ </button>
289
+ );
290
+ }
291
+ }
292
+ ```
293
+
294
+ ### 3. Controllers (HTTP Layer)
295
+
296
+ Controllers handle HTTP requests and define REST endpoints. You can write them manually or let Orca generate them automatically for `"use public"` services.
297
+
298
+ **Manual Controller:**
299
+
300
+ ```typescript
301
+ import { Controller, Get, Post, Body, Param } from "@kithinji/orca";
302
+
303
+ @Controller("/products")
304
+ export class ProductController {
305
+ constructor(private products: ProductService) {}
306
+
307
+ @Get()
308
+ async findAll() {
309
+ return this.products.listProducts();
310
+ }
311
+
312
+ @Get("/:id")
313
+ async findOne(@Param("id") id: number) {
314
+ return this.products.getProduct(id);
315
+ }
316
+
317
+ @Post()
318
+ async create(@Body() data: CreateProductDto) {
319
+ return this.products.create(data);
320
+ }
321
+ }
322
+ ```
323
+
324
+ **Auto-Generated Controller:**
325
+
326
+ When you mark a service with `"use public"`, Orca automatically creates endpoints:
327
+
328
+ - `POST /ProductService/getProduct`
329
+ - `GET /ProductService/listProducts`
330
+
331
+ No controller code needed - just mark the service and call methods from your client components.
332
+
333
+ ### 4. Modules (Organization)
334
+
335
+ Modules group related functionality together and manage dependencies.
336
+
337
+ ```typescript
338
+ // product.module.ts
339
+ import { Module } from "@kithinji/orca";
340
+ import { ProductController } from "./product.controller";
341
+ import { ProductService } from "./product.service";
342
+ import { ProductList, AddToCartButton } from "./components";
343
+
344
+ @Module({
345
+ imports: [DatabaseModule], // Modules we depend on
346
+ controllers: [ProductController], // HTTP endpoints
347
+ providers: [ProductService], // Services
348
+ declarations: [ProductList, AddToCartButton], // UI components
349
+ exports: [ProductService, ProductList], // What others can use
350
+ })
351
+ export class ProductModule {}
352
+ ```
353
+
354
+ ### 5. Navigation (Stack-Based Routing)
355
+
356
+ Instead of file-based routing, Orca uses a navigation stack. Push components onto the stack to navigate.
357
+
358
+ ```tsx
359
+ import { Navigate } from "@kithinji/orca";
360
+
361
+ @Component()
362
+ export class HomePage {
363
+ constructor(private navigate: Navigate) {}
364
+
365
+ build() {
366
+ return (
367
+ <div>
368
+ <h1>Welcome</h1>
369
+ <button onClick={() => this.navigate.push(<UserProfile userId={1} />)}>
370
+ View Profile
371
+ </button>
372
+ </div>
373
+ );
374
+ }
375
+ }
376
+ ```
377
+
378
+ Navigation methods:
379
+
380
+ - `push()` - Add a new page on top
381
+ - `pop()` - Go back to previous page
382
+ - `replace()` - Replace current page
383
+ - `popToRoot()` - Clear stack and return to root
384
+ - `canPop()` - Check if back navigation is possible
385
+
386
+ ## The Magic: How It All Works Together
387
+
388
+ Here's a complete feature showing the full stack:
389
+
390
+ ```typescript
391
+ // cart.service.ts
392
+ "use public";
393
+ @Injectable()
394
+ export class CartService {
395
+ constructor(private db: DatabaseService) {}
396
+
397
+ public async addItem(productId: number, quantity: number = 1) {
398
+ const product = await this.db.products.findUnique({ where: { productId } });
399
+ return this.db.cart.create({
400
+ data: { productId, quantity, price: product.price },
401
+ });
402
+ }
403
+
404
+ public async getCart() {
405
+ return this.db.cart.findMany();
406
+ }
407
+ }
408
+
409
+ // cart-page.component.tsx
410
+ @Component()
411
+ export class CartPage {
412
+ constructor(private cart: CartService) {}
413
+
414
+ async build() {
415
+ const items = await this.cart.getCart();
416
+ return (
417
+ <div>
418
+ <h1>Your Cart</h1>
419
+ {items.map((item) => (
420
+ <CartItem key={item.id} item={item} />
421
+ ))}
422
+ </div>
423
+ );
424
+ }
425
+ }
426
+
427
+ // add-to-cart-button.component.tsx
428
+ ("use interactive");
429
+
430
+ @Component()
431
+ export class AddToCartButton {
432
+ constructor(private cart: CartService, private navigate: Navigate) {}
433
+
434
+ props!: { productId: number };
435
+
436
+ build() {
437
+ return (
438
+ <button
439
+ onClick={async () => {
440
+ await this.cart.addItem(this.props.productId);
441
+ this.navigate.push(<CartPage />);
442
+ }}
443
+ >
444
+ Add to Cart
445
+ </button>
446
+ );
447
+ }
448
+ }
449
+ ```
450
+
451
+ **What happens here:**
452
+
453
+ 1. `CartService` is marked `"use public"`, so Orca generates API endpoints
454
+ 2. `CartPage` (server component) calls `cart.getCart()` on the server during initial render
455
+ 3. `AddToCartButton` (interactive component) calls `cart.addItem()` from the browser
456
+ 4. Orca automatically converts that call into `fetch('/CartService/addItem', ...)`
457
+ 5. Types are preserved end-to-end; TypeScript catches errors everywhere
458
+ 6. Navigation happens by pushing a component, not a URL string
459
+
460
+ ## Is Orca for You?
461
+
462
+ **Orca is great if you:**
463
+
464
+ - Are a solo developer or small team
465
+ - Want to ship features without maintaining two separate projects
466
+ - Value clear architecture and separation of concerns
467
+ - Need a real API that can grow (mobile apps, CLIs, integrations)
468
+ - Are tired of context switching between frontend and backend mental models
469
+ - Build internal tools, dashboards, or admin panels
470
+ - You're building highly interactive web apps
471
+
472
+ **Orca might not be for you if:**
473
+
474
+ - You have a large team with dedicated frontend/backend developers
475
+ - You prefer the frontend/backend split and it works for your workflow
476
+ - You need a purely client-side SPA framework
477
+ - You're building a marketing site or content-heavy blog (use Astro or Next.js)
478
+ - Your team is already deeply invested in another stack
479
+
480
+ 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.
481
+
482
+ ## Philosophy
483
+
484
+ 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.
485
+
486
+ Orca rejects the artificial divide. Instead:
487
+
488
+ - **One codebase**: Write features end-to-end in one place
489
+ - **Islands of interactivity**: Server-render by default, add JavaScript only where needed
490
+ - **Type-safe APIs**: No manual fetch calls, no drifting types
491
+ - **Clear architecture**: DI, decorators, and modules keep large apps maintainable
492
+ - **Stack-based navigation**: Route by pushing components, not by folder structure
493
+
494
+ 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.
495
+
496
+ ## Documentation
497
+
498
+ Full guides, API references, and examples at **[orca.dafifi.net](https://orca.dafifi.net/)**
499
+
500
+ Topics covered:
501
+
502
+ - Components and JSX
503
+ - Dependency Injection
504
+ - Modules and Providers
505
+ - Controllers and Routing
506
+ - The `"use interactive"` directive
507
+ - The `"use public"` directive
508
+ - Signals and Reactivity
509
+ - Observables
510
+ - Navigation Stack
511
+ - Validation with Zod
512
+
513
+ ## Roadmap
514
+
515
+ - [ ] Database integrations (TypeORM, Prisma)
516
+ - [ ] Authentication & authorization modules
517
+ - [ ] WebSocket support
518
+ - [ ] GraphQL adapter
519
+ - [ ] CLI scaffolding improvements
520
+ - [ ] File upload handling
521
+ - [ ] Background jobs / task queues
522
+
523
+ ## Contributing
524
+
525
+ Contributions are welcome! Please read our contributing guidelines before submitting PRs.
526
+
527
+ ## Stay in Touch
528
+
529
+ - **Author**: [Kithinji Brian](https://www.linkedin.com/in/kithinjibrian/)
530
+ - **Website**: [orca.dafifi.net](https://orca.dafifi.net/)
531
+ - **NPM**: [@kithinji/orca](https://www.npmjs.com/package/@kithinji/orca)
532
+
533
+ ## License
534
+
535
+ MIT
536
+
537
+ ---
538
+
539
+ <p align="center">
540
+ Made in Nairobi with ❤️ for developers who just want to ship
541
+ </p>
@@ -81,10 +81,12 @@ var kithinjiorca = (() => {
81
81
  SIGNATURE_METADATA_KEY: () => SIGNATURE_METADATA_KEY,
82
82
  Signature: () => Signature,
83
83
  assert$: () => assert$,
84
+ batch: () => batch,
84
85
  browser: () => browser_exports,
85
86
  collectAllProvidersFromNode: () => collectAllProvidersFromNode,
86
87
  computed: () => computed,
87
88
  createComponent: () => createComponent,
89
+ createRoot: () => createRoot,
88
90
  effect: () => effect,
89
91
  from: () => from,
90
92
  getCurrentInjector: () => getCurrentInjector,
@@ -109,7 +111,8 @@ var kithinjiorca = (() => {
109
111
  style: () => style,
110
112
  symbolValueReplacer: () => symbolValueReplacer,
111
113
  symbolValueReviver: () => symbolValueReviver,
112
- toSignal: () => toSignal
114
+ toSignal: () => toSignal,
115
+ untracked: () => untracked
113
116
  });
114
117
  var import_reflect_metadata = __require("reflect-metadata");
115
118
 
@@ -171,8 +174,10 @@ var kithinjiorca = (() => {
171
174
  StringRenderer: () => StringRenderer,
172
175
  VNode: () => VNode,
173
176
  assert$: () => assert$,
177
+ batch: () => batch,
174
178
  collectAllProvidersFromNode: () => collectAllProvidersFromNode,
175
179
  computed: () => computed,
180
+ createRoot: () => createRoot,
176
181
  effect: () => effect,
177
182
  from: () => from,
178
183
  getCurrentInjector: () => getCurrentInjector,
@@ -194,7 +199,8 @@ var kithinjiorca = (() => {
194
199
  store: () => store,
195
200
  symbolValueReplacer: () => symbolValueReplacer,
196
201
  symbolValueReviver: () => symbolValueReviver,
197
- toSignal: () => toSignal
202
+ toSignal: () => toSignal,
203
+ untracked: () => untracked
198
204
  });
199
205
 
200
206
  // src/shared/store.ts
@@ -1220,11 +1226,17 @@ var kithinjiorca = (() => {
1220
1226
 
1221
1227
  // src/shared/signal/signal.ts
1222
1228
  var currentEffect = null;
1229
+ var batchDepth = 0;
1230
+ var pendingEffects = /* @__PURE__ */ new Set();
1223
1231
  function signal(initialValue) {
1224
1232
  let value = initialValue;
1225
1233
  const subscribers = /* @__PURE__ */ new Set();
1226
1234
  const notify = () => {
1227
- subscribers.forEach((sub) => sub());
1235
+ if (batchDepth > 0) {
1236
+ subscribers.forEach((sub) => pendingEffects.add(sub));
1237
+ } else {
1238
+ subscribers.forEach((sub) => sub.execute());
1239
+ }
1228
1240
  };
1229
1241
  return {
1230
1242
  _isSignal: true,
@@ -1232,6 +1244,7 @@ var kithinjiorca = (() => {
1232
1244
  get value() {
1233
1245
  if (currentEffect) {
1234
1246
  subscribers.add(currentEffect);
1247
+ currentEffect.deps.add(subscribers);
1235
1248
  }
1236
1249
  return value;
1237
1250
  },
@@ -1244,29 +1257,98 @@ var kithinjiorca = (() => {
1244
1257
  };
1245
1258
  }
1246
1259
  function effect(fn) {
1247
- const execute = () => {
1248
- currentEffect = execute;
1260
+ const deps = /* @__PURE__ */ new Set();
1261
+ let cleanupFn;
1262
+ const runner = () => {
1263
+ runner.execute();
1264
+ };
1265
+ runner.deps = deps;
1266
+ runner.execute = () => {
1267
+ if (cleanupFn) {
1268
+ cleanupFn();
1269
+ cleanupFn = void 0;
1270
+ }
1271
+ deps.forEach((subs) => subs.delete(runner));
1272
+ deps.clear();
1273
+ const prevEffect = currentEffect;
1274
+ currentEffect = runner;
1249
1275
  try {
1250
- fn();
1276
+ const result = fn();
1277
+ if (typeof result === "function") {
1278
+ cleanupFn = result;
1279
+ }
1251
1280
  } finally {
1252
- currentEffect = null;
1281
+ currentEffect = prevEffect;
1253
1282
  }
1254
1283
  };
1255
- execute();
1256
- return () => {
1257
- currentEffect = null;
1284
+ runner.cleanup = () => {
1285
+ if (cleanupFn) {
1286
+ cleanupFn();
1287
+ cleanupFn = void 0;
1288
+ }
1289
+ deps.forEach((subs) => subs.delete(runner));
1290
+ deps.clear();
1258
1291
  };
1292
+ runner.cleanupFn = cleanupFn;
1293
+ runner.execute();
1294
+ return () => runner.cleanup();
1259
1295
  }
1260
1296
  function computed(fn) {
1261
1297
  const sig = signal(fn());
1262
- effect(() => {
1298
+ let cleanup;
1299
+ cleanup = effect(() => {
1263
1300
  sig.value = fn();
1264
1301
  });
1302
+ const originalSignal = sig;
1303
+ const dispose = cleanup;
1304
+ originalSignal.dispose = dispose;
1265
1305
  return sig;
1266
1306
  }
1307
+ function batch(fn) {
1308
+ batchDepth++;
1309
+ try {
1310
+ fn();
1311
+ } finally {
1312
+ batchDepth--;
1313
+ if (batchDepth === 0) {
1314
+ const effects = Array.from(pendingEffects);
1315
+ pendingEffects.clear();
1316
+ effects.forEach((effect2) => effect2.execute());
1317
+ }
1318
+ }
1319
+ }
1267
1320
  function isSignal(value) {
1268
1321
  return value && value._isSignal === true;
1269
1322
  }
1323
+ function createRoot(fn) {
1324
+ const cleanups = [];
1325
+ const dispose = () => {
1326
+ cleanups.forEach((cleanup) => cleanup());
1327
+ cleanups.length = 0;
1328
+ };
1329
+ const prevEffect = currentEffect;
1330
+ const originalEffect = effect;
1331
+ globalThis.effect = (fn2) => {
1332
+ const cleanup = originalEffect(fn2);
1333
+ cleanups.push(cleanup);
1334
+ return cleanup;
1335
+ };
1336
+ try {
1337
+ return fn(dispose);
1338
+ } finally {
1339
+ globalThis.effect = originalEffect;
1340
+ currentEffect = prevEffect;
1341
+ }
1342
+ }
1343
+ function untracked(fn) {
1344
+ const prevEffect = currentEffect;
1345
+ currentEffect = null;
1346
+ try {
1347
+ return fn();
1348
+ } finally {
1349
+ currentEffect = prevEffect;
1350
+ }
1351
+ }
1270
1352
 
1271
1353
  // src/shared/observable/observable.ts
1272
1354
  var Observable = class {