@zerotal/arch 1.7.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.
Files changed (160) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +67 -0
  4. package/api-surface.md +283 -0
  5. package/docs/about.md +676 -0
  6. package/docs/admin/actions.md +183 -0
  7. package/docs/admin/auth.md +89 -0
  8. package/docs/admin/dashboard.md +155 -0
  9. package/docs/admin/extending-ui.md +229 -0
  10. package/docs/admin/extending.md +205 -0
  11. package/docs/admin/forms.md +155 -0
  12. package/docs/admin/index.md +144 -0
  13. package/docs/admin/operations.md +224 -0
  14. package/docs/admin/references.md +174 -0
  15. package/docs/admin/resources.md +132 -0
  16. package/docs/admin/structure.md +158 -0
  17. package/docs/admin/tables.md +185 -0
  18. package/docs/admin/testing.md +138 -0
  19. package/docs/ai.md +373 -0
  20. package/docs/application.md +435 -0
  21. package/docs/arch.md +263 -0
  22. package/docs/assets.md +755 -0
  23. package/docs/audit.md +367 -0
  24. package/docs/authentication.md +1000 -0
  25. package/docs/authorization.md +529 -0
  26. package/docs/broadcasting/channels.md +170 -0
  27. package/docs/broadcasting/client.md +108 -0
  28. package/docs/broadcasting/events.md +190 -0
  29. package/docs/broadcasting/index.md +163 -0
  30. package/docs/broadcasting/references.md +84 -0
  31. package/docs/broadcasting/testing.md +132 -0
  32. package/docs/cache.md +387 -0
  33. package/docs/carbon.md +830 -0
  34. package/docs/changelog.md +465 -0
  35. package/docs/client/auth.md +113 -0
  36. package/docs/client/errors.md +139 -0
  37. package/docs/client/files.md +118 -0
  38. package/docs/client/index.md +128 -0
  39. package/docs/client/references.md +58 -0
  40. package/docs/client/requests.md +131 -0
  41. package/docs/client/resilience.md +141 -0
  42. package/docs/client/testing.md +146 -0
  43. package/docs/commands.md +450 -0
  44. package/docs/components.md +2427 -0
  45. package/docs/config-system.md +258 -0
  46. package/docs/container.md +596 -0
  47. package/docs/context.md +579 -0
  48. package/docs/contributing.md +164 -0
  49. package/docs/controllers.md +354 -0
  50. package/docs/conventions.md +466 -0
  51. package/docs/cookies.md +101 -0
  52. package/docs/csrf.md +206 -0
  53. package/docs/database.md +476 -0
  54. package/docs/deployment.md +434 -0
  55. package/docs/devtools.md +1013 -0
  56. package/docs/email-verification.md +320 -0
  57. package/docs/encryption.md +380 -0
  58. package/docs/errors.md +317 -0
  59. package/docs/events.md +420 -0
  60. package/docs/flow/components.md +846 -0
  61. package/docs/flow/decorators.md +458 -0
  62. package/docs/flow/events.md +420 -0
  63. package/docs/flow/forms.md +551 -0
  64. package/docs/flow/index.md +568 -0
  65. package/docs/flow/layouts.md +793 -0
  66. package/docs/flow/lifecycle.md +336 -0
  67. package/docs/flow/pagination.md +402 -0
  68. package/docs/flow/performance.md +262 -0
  69. package/docs/flow/references.md +317 -0
  70. package/docs/flow/routing.md +550 -0
  71. package/docs/flow/testing.md +447 -0
  72. package/docs/getting-started.md +286 -0
  73. package/docs/health.md +269 -0
  74. package/docs/helpers.md +423 -0
  75. package/docs/i18n.md +414 -0
  76. package/docs/index.md +65 -0
  77. package/docs/inertia/build.md +95 -0
  78. package/docs/inertia/devtools.md +197 -0
  79. package/docs/inertia/index.md +267 -0
  80. package/docs/inertia/middleware.md +81 -0
  81. package/docs/inertia/props.md +507 -0
  82. package/docs/inertia/references.md +144 -0
  83. package/docs/inertia/rendering.md +189 -0
  84. package/docs/inertia/ssr.md +102 -0
  85. package/docs/inspirations.md +257 -0
  86. package/docs/lifecycle.md +331 -0
  87. package/docs/lock.md +454 -0
  88. package/docs/logger.md +517 -0
  89. package/docs/media.md +582 -0
  90. package/docs/middleware.md +509 -0
  91. package/docs/migrations.md +529 -0
  92. package/docs/monitor.md +377 -0
  93. package/docs/notifications.md +848 -0
  94. package/docs/orm/casts.md +365 -0
  95. package/docs/orm/factories.md +231 -0
  96. package/docs/orm/index.md +663 -0
  97. package/docs/orm/lifecycle.md +421 -0
  98. package/docs/orm/queries.md +692 -0
  99. package/docs/orm/relationships.md +440 -0
  100. package/docs/orm/serialization.md +251 -0
  101. package/docs/package-development.md +536 -0
  102. package/docs/pagination.md +340 -0
  103. package/docs/password-reset.md +335 -0
  104. package/docs/providers.md +447 -0
  105. package/docs/query-builder.md +465 -0
  106. package/docs/queue.md +549 -0
  107. package/docs/rate-limiting.md +253 -0
  108. package/docs/responses.md +336 -0
  109. package/docs/roles-and-2fa.md +418 -0
  110. package/docs/routing.md +1008 -0
  111. package/docs/scaffolding.md +335 -0
  112. package/docs/scheduler.md +533 -0
  113. package/docs/seeding.md +229 -0
  114. package/docs/session.md +451 -0
  115. package/docs/social.md +524 -0
  116. package/docs/storage.md +622 -0
  117. package/docs/structure.md +230 -0
  118. package/docs/support-policy.md +114 -0
  119. package/docs/telemetry.md +505 -0
  120. package/docs/tenancy.md +608 -0
  121. package/docs/testing/browser.md +153 -0
  122. package/docs/testing/console.md +130 -0
  123. package/docs/testing/database.md +238 -0
  124. package/docs/testing/flow-browser.md +216 -0
  125. package/docs/testing/http.md +441 -0
  126. package/docs/testing/index.md +240 -0
  127. package/docs/testing/mocking.md +410 -0
  128. package/docs/upgrade.md +192 -0
  129. package/docs/validator.md +549 -0
  130. package/docs/view.md +453 -0
  131. package/package.json +60 -0
  132. package/src/bin/mcp.ts +73 -0
  133. package/src/config.ts +63 -0
  134. package/src/errors.ts +27 -0
  135. package/src/index.ts +56 -0
  136. package/src/install/ArchInstallCommand.ts +189 -0
  137. package/src/install/detect.ts +98 -0
  138. package/src/install/guidelines.ts +290 -0
  139. package/src/install/markers.ts +77 -0
  140. package/src/install/mcpConfig.ts +81 -0
  141. package/src/mcp/index.ts +27 -0
  142. package/src/mcp/jsonrpc.ts +133 -0
  143. package/src/mcp/server.ts +307 -0
  144. package/src/mcp/stdio.ts +111 -0
  145. package/src/mcp/types.ts +146 -0
  146. package/src/probe/ArchProbeCommand.ts +48 -0
  147. package/src/probe/sentinel.ts +13 -0
  148. package/src/probe/topics.ts +291 -0
  149. package/src/provider/ArchProvider.ts +101 -0
  150. package/src/tools/_probe.ts +164 -0
  151. package/src/tools/apiSurface.ts +241 -0
  152. package/src/tools/appInfo.ts +88 -0
  153. package/src/tools/baselines.ts +210 -0
  154. package/src/tools/context.ts +37 -0
  155. package/src/tools/doctor.ts +93 -0
  156. package/src/tools/index.ts +50 -0
  157. package/src/tools/logs.ts +265 -0
  158. package/src/tools/routes.ts +133 -0
  159. package/src/tools/schema.ts +119 -0
  160. package/src/tools/searchDocs.ts +345 -0
@@ -0,0 +1,189 @@
1
+ ---
2
+ title: Inertia Rendering Pages
3
+ description: Return a page from a controller, choose a component, and control the response.
4
+ ---
5
+
6
+ # Rendering pages
7
+
8
+ Every Inertia response goes through one helper: `inertia()`. This section covers how it
9
+ resolves components, what it returns for each request type, controller-less page
10
+ routes, and how redirects behave.
11
+
12
+ ## The inertia helper
13
+
14
+ ```ts
15
+ function inertia(component: PageName, props?: RenderProps): Promise<void>;
16
+ ```
17
+
18
+ Call `inertia()` from any controller action. It reads the active request from
19
+ `RequestContext` (via AsyncLocalStorage), so there is **no context argument** — and
20
+ it sets `ctx.response` as a side effect. It is **async** (it resolves lazy/deferred props), so it
21
+ returns `Promise<void>` — always `return inertia(...)` (or `await` it):
22
+
23
+ ```ts
24
+ // app/controllers/PostController.ts
25
+ import type { HttpContext } from "zerotal";
26
+ import { inertia } from "@zerotal/inertia";
27
+ import { Post } from "../models/Post.ts";
28
+
29
+ export class PostController {
30
+ async index(ctx: HttpContext): Promise<void> {
31
+ const posts = await Post.query()
32
+ .withScopes((s) => s.published())
33
+ .with("author")
34
+ .orderBy("published_at", "desc")
35
+ .paginate(10, Number(ctx.query("page", "1")));
36
+
37
+ return inertia("Posts/Index", { posts });
38
+ }
39
+
40
+ async show(ctx: HttpContext): Promise<void> {
41
+ const post = await Post.query()
42
+ .where("slug", ctx.params["slug"]!)
43
+ .with("author")
44
+ .with("comments")
45
+ .firstOrFail();
46
+
47
+ return inertia("Posts/Show", { post });
48
+ }
49
+ }
50
+ ```
51
+
52
+ `return inertia(...)` is idiomatic: the helper returns `Promise<void>`, so returning it ends the
53
+ action.
54
+
55
+ > **Warning** — `inertia()` takes `(component, props)` — _not_
56
+ > `inertia(ctx, component, props)`. The request is resolved from context
57
+ > automatically.
58
+
59
+ ### Controlling which props are sent, and when
60
+
61
+ Props can be more than plain values. Wrap them to make them lazy, optional, deferred, or mergeable —
62
+ the foundation for partial reloads, "load more" lists, and deferred content:
63
+
64
+ ```ts
65
+ // in a controller
66
+ import { inertia, optional, defer, merge } from "@zerotal/inertia";
67
+
68
+ return inertia("Users/Index", {
69
+ users: () => User.all(), // lazy — only evaluated when sent
70
+ roles: optional(() => Role.all()), // only on a partial reload that asks for it
71
+ stats: defer(() => computeStats()), // loaded after first paint
72
+ feed: merge(() => Post.paginate(15, page)), // appended on "load more"
73
+ });
74
+ ```
75
+
76
+ A unified `Inertia` facade exposes the full protocol API (`Inertia.render`, `Inertia.optional`,
77
+ `Inertia.defer`, `Inertia.merge`, `Inertia.share`, `Inertia.location`, …). See
78
+ [Data Props](/docs/inertia/props) for the full v3 feature set.
79
+
80
+ ## Component resolution
81
+
82
+ The component string maps to a file under your pages directory (`resources/js/pages/`
83
+ by default, configurable via `pagesDir`), with the framework extension appended
84
+ (`.tsx` for React, `.vue` for Vue):
85
+
86
+ | `inertia(...)` call | Component file |
87
+ | ----------------------------- | ----------------------------------------- |
88
+ | `inertia("Dashboard")` | `resources/js/pages/Dashboard.tsx` |
89
+ | `inertia("Posts/Index")` | `resources/js/pages/Posts/Index.tsx` |
90
+ | `inertia("Admin/Users/Edit")` | `resources/js/pages/Admin/Users/Edit.tsx` |
91
+
92
+ Component names are validated — a name containing `..` or a leading `/` is rejected
93
+ to prevent path traversal.
94
+
95
+ ### The name is checked at compile time
96
+
97
+ `inertia("Posts/Shwo", …)` does not compile: the name has to be a page in the
98
+ generated registry (`resources/js/pages.generated.ts`, rebuilt by
99
+ `bun zt inertia:build` and by `zt dev`). A renamed or misspelled page was a
100
+ runtime 500 before — the kind that reaches production because the route it lives
101
+ on is the one nobody clicked.
102
+
103
+ For a name that genuinely isn't known until runtime, `inertia.dynamic(name, props)`
104
+ takes any string and skips the check.
105
+
106
+ The props are checked too — see [Typed props](/docs/inertia/props#typed-props).
107
+
108
+ ### Props serialization
109
+
110
+ Props are JSON-serialized into the page object. **Pass plain data, not live ORM
111
+ models with unloaded relations** — eager-load what the page needs (`.with("author")`)
112
+ or map to a plain shape. Shared props (`auth.user`) are already reduced to scalars
113
+ for you; see [Shared Props](/docs/inertia/props).
114
+
115
+ ## First load vs. navigation
116
+
117
+ `inertia()` branches on the `X-Inertia` request header:
118
+
119
+ | Request | Response |
120
+ | ---------------------------------- | -------------------------------------------------------- |
121
+ | First load (no `X-Inertia` header) | Full HTML document with the page object JSON embedded |
122
+ | XHR navigation (`X-Inertia: true`) | JSON page object only (`Content-Type: application/json`) |
123
+
124
+ Both responses carry `Vary: X-Inertia` so browsers and CDNs cache the HTML and JSON
125
+ variants separately. The page object always includes the current `url` and asset
126
+ `version`.
127
+
128
+ ## Controller-less routes
129
+
130
+ For pages that need no controller logic (marketing pages, static dashboards), render
131
+ straight from the route with the `Router.inertia()` macro (added by the package):
132
+
133
+ ```ts
134
+ // routes/web.ts
135
+ import { Router } from "zerotal";
136
+
137
+ Router.inertia("/about", "About/Index"); // no props
138
+ Router.inertia("/home", "Home/Index", { greeting: "Hello" }); // static props
139
+ Router.inertia("/admin", "Admin/Dashboard", [AuthMiddleware]); // middleware shorthand
140
+ ```
141
+
142
+ The third argument is polymorphic: pass a **props object**, or pass a **middleware
143
+ array** directly as a shorthand. To use both, pass props third and middleware fourth:
144
+
145
+ ```ts
146
+ // routes/web.ts
147
+ Router.inertia("/admin", "Admin/Dashboard", { title: "Admin" }, [AuthMiddleware]);
148
+ ```
149
+
150
+ ## Redirects
151
+
152
+ After a non-GET action (a form POST/PUT/DELETE), redirect as usual — return a 302 and
153
+ [`InertiaMiddleware`](/docs/inertia/middleware) upgrades it to a **303** so the
154
+ browser issues a GET on the target instead of replaying the form:
155
+
156
+ ```ts
157
+ // in a controller
158
+ async store(ctx: HttpContext): Promise<void> {
159
+ const post = await Post.create(await ctx.body());
160
+ ctx.flash("success", "Post created.");
161
+ return ctx.redirect(`/posts/${post.slug}`); // 302 → 303, then renders Posts/Show
162
+ }
163
+ ```
164
+
165
+ Validation failures redirect back with errors in the session, which surface as the
166
+ `errors` shared prop on the re-rendered page — again, see
167
+ [Shared Props](/docs/inertia/props).
168
+
169
+ ### External redirects — Inertia.location
170
+
171
+ To send the browser to an external URL (or force a full-page visit), use `Inertia.location(url)`. On
172
+ an Inertia request it returns a `409` + `X-Inertia-Location` so the client does a `window.location`
173
+ visit; on a normal request it's a plain `302`:
174
+
175
+ ```ts
176
+ // in a controller
177
+ import { Inertia } from "@zerotal/inertia";
178
+
179
+ return Inertia.location("https://billing.stripe.com/session/abc");
180
+ ```
181
+
182
+ Redirects to a target with a URL fragment (`/page#section`) are automatically converted to a `409` +
183
+ `X-Inertia-Redirect` on Inertia requests so the fragment is preserved across the visit. See
184
+ [External & fragment redirects](/docs/inertia/props#external-fragment-redirects).
185
+
186
+ ## Next steps
187
+
188
+ - [Inertia overview](/docs/inertia) — the guide's front page and the rest of the sections.
189
+ - [Reference](/docs/inertia/references) — the full API surface in one table.
@@ -0,0 +1,102 @@
1
+ ---
2
+ title: Inertia Server-Side Rendering
3
+ description: Render the first paint on the server, and what changes when you do.
4
+ ---
5
+
6
+ # Server-side rendering
7
+
8
+ By default Inertia renders the first page on the client. Server-side rendering (SSR)
9
+ renders the initial HTML on the server instead — better Time-to-First-Byte and
10
+ crawlable content — while subsequent navigations keep using the fast JSON path.
11
+
12
+ Zerotal offers two approaches: the **`/__ssr` endpoint** (standard Inertia SSR) and
13
+ **streaming SSR** via `inertiaStream()`.
14
+
15
+ ## Which should I use?
16
+
17
+ - **Endpoint SSR** (`ssr: true`) — the standard Inertia SSR contract. Turn it on
18
+ globally and the Inertia client renders the first page through `POST /__ssr`. Use
19
+ this when you want crawlable, server-rendered HTML across the whole app with no
20
+ per-controller change.
21
+ - **Streaming SSR** (`inertiaStream()`) — swap `inertia()` for `inertiaStream()` in
22
+ the controllers whose initial document you want streamed for the fastest TTFB. Use
23
+ it selectively on heavy landing pages; everything else stays on `inertia()`.
24
+
25
+ ## Endpoint SSR
26
+
27
+ Enable the SSR endpoint in config:
28
+
29
+ ```ts
30
+ // config/inertia.ts
31
+ import { InertiaConfig } from "@zerotal/inertia";
32
+ import { env } from "zerotal";
33
+
34
+ export default InertiaConfig({
35
+ htmlTemplate: "./resources/app.html",
36
+ version: env("ASSET_VERSION", "1"),
37
+ ssr: true, // registers POST /__ssr — requires a server renderer
38
+ });
39
+ ```
40
+
41
+ When `ssr: true`, `InertiaProvider` registers `POST /__ssr`, which accepts
42
+ `{ component, props, url }` and returns `{ body, head }` — the same contract as the
43
+ Inertia Node SSR server. On a first-page load the server renders the component into
44
+ the template instead of shipping an empty root `<div>`; subsequent navigations use
45
+ the normal JSON path. Pages render with the framework they're authored in: React
46
+ `.tsx` via `react-dom/server`, or Vue `.vue` via `@inertiajs/vue3` +
47
+ `vue/server-renderer` — install the server renderer for the framework(s) your app uses.
48
+
49
+ ## Streaming SSR
50
+
51
+ `inertiaStream()` is a drop-in async alternative to `inertia()` that uses React 18's
52
+ `renderToReadableStream` to improve TTFB. Instead of buffering the whole render, it
53
+ streams the React output between the template's HTML prefix and suffix:
54
+
55
+ ```
56
+ │ HTML prefix (everything before <!-- @inertia -->) │ → browser starts parsing <head>
57
+ │ React component stream │ → above-the-fold content arrives early
58
+ │ <div> close + page JSON + HTML suffix │
59
+ ```
60
+
61
+ Swap `inertia()` → `inertiaStream()` and `await` it — nothing else changes:
62
+
63
+ ```ts
64
+ // app/controllers/PostController.ts
65
+ import { inertiaStream } from "@zerotal/inertia";
66
+
67
+ export class PostController {
68
+ async show(ctx: HttpContext): Promise<void> {
69
+ const post = await Post.findOrFail(ctx.params["id"]);
70
+ return inertiaStream("Posts/Show", { post });
71
+ }
72
+ }
73
+ ```
74
+
75
+ ### Requirements
76
+
77
+ - `react-dom/server` ≥ 18 (for `renderToReadableStream`)
78
+ - The HTML template must contain `<!-- @inertia -->`
79
+ - The page component must exist under your pages directory (`resources/js/pages/<component>.tsx`)
80
+
81
+ It throws if the template hasn't loaded, or if the component name contains path
82
+ traversal (`..` or a leading `/`).
83
+
84
+ ### inertia vs. inertiaStream
85
+
86
+ | | `inertia()` | `inertiaStream()` |
87
+ | -------------- | ---------------------- | ---------------------------------- |
88
+ | Return type | `Promise<void>` | `Promise<void>` |
89
+ | Rendering | Buffered HTML string | Streaming `renderToReadableStream` |
90
+ | Response body | Fully buffered string | Streaming `ReadableStream` |
91
+ | TTFB | After full render | After the prefix is flushed |
92
+ | XHR navigation | JSON (the normal path) | N/A — only the first-page document |
93
+
94
+ For XHR navigations (`X-Inertia: true`), keep using `inertia()` — streaming only
95
+ benefits the initial HTML document load.
96
+
97
+ > **Tip** — Stream the heaviest landing pages and leave everything else on `inertia()`.
98
+
99
+ ## Next steps
100
+
101
+ - [Inertia overview](/docs/inertia) — the guide's front page and the rest of the sections.
102
+ - [Reference](/docs/inertia/references) — the full API surface in one table.
@@ -0,0 +1,257 @@
1
+ ---
2
+ title: Inspirations & Attributions
3
+ description: The frameworks, runtimes, and tools that shaped Zerotal's design — what it borrowed, adapted, and where it diverged.
4
+ ---
5
+
6
+ # Inspirations & Attributions
7
+
8
+ Zerotal is built on the shoulders of giants. This page acknowledges the frameworks,
9
+ runtimes, and tools that shaped its design — what we borrowed, what we adapted, and
10
+ where we intentionally diverged.
11
+
12
+ Zerotal is a full-stack TypeScript framework built natively for the [Bun](https://bun.sh)
13
+ runtime, and to get there we drew on the best patterns from across the industry. The
14
+ influences below are grouped by the part of the stack they shaped.
15
+
16
+ ## The Runtime & Foundation
17
+
18
+ ### Bun
19
+
20
+ Bun is not just a runtime dependency — it is the reason Zerotal exists in the form it
21
+ does. Without Bun's native primitives there would be no coherent single-package story:
22
+
23
+ - **`Bun.SQL`** — the native SQLite/PostgreSQL client is the foundation of the ORM.
24
+ No `pg`, no `better-sqlite3`, no driver-abstraction overhead.
25
+ - **`Bun.serve`** — the HTTP server. No `http.createServer`, no Express under the hood.
26
+ - **`Bun.file` / `Bun.write`** — power the local storage driver and asset pipeline.
27
+ - **`Bun.markdown`** — renders this documentation site straight from the `docs/*.md`
28
+ source files.
29
+ - **`bun:test`** — the testing helpers (`TestApp`, `TestResponse`, factories) are built
30
+ directly on `bun:test`'s `describe` / `it` / `expect` primitives.
31
+ - **Bun plugin API** — `bun-plugin-tailwind` and the JS bundler used by `FlowProvider`
32
+ run inside Bun's native plugin lifecycle.
33
+
34
+ Where we diverged: Zerotal makes no attempt to also run on Node. Committing fully to one
35
+ runtime is what keeps the stack small — one SQL client, one server, one test runner —
36
+ instead of abstracting over several.
37
+
38
+ ### Temporal
39
+
40
+ The TC39 `Temporal` proposal directly underpins `Carbon`, Zerotal's date library. All
41
+ date arithmetic uses `Temporal.PlainDate`, `Temporal.ZonedDateTime`, and friends — never
42
+ the legacy `Date` object — and `Carbon` is built on Bun's bundled `Temporal`
43
+ implementation rather than wrapping `Date`.
44
+
45
+ Where we diverged: `Carbon` is a thin ergonomic layer over `Temporal`, not a Moment/Day.js
46
+ clone — it tracks the proposal toward stable rather than inventing its own date model.
47
+
48
+ ## Architectural Titans
49
+
50
+ ### Laravel
51
+
52
+ Laravel is the gold standard for developer-friendly full-stack frameworks. Its
53
+ influence on Zerotal is pervasive:
54
+
55
+ - **Active Record ORM** — `Model`, attribute decorators (`@column`, `@belongsTo`,
56
+ `@hasMany`, `@hasOne`), and the query builder API mirror Eloquent's ergonomics.
57
+ - **Service providers** — the `onRegister` / `onBooting` / `onBooted` lifecycle
58
+ (through to `onStarting` / `onStarted` / `onStopping` / `onStopped`) maps directly
59
+ to Laravel's provider boot cycle.
60
+ - **Artisan-style CLI** — the `zt` command runner and generator scaffolding
61
+ (`make:model`, `make:controller`, `migrate`, …) take heavy cues from Artisan.
62
+ - **Mail notifications** — `Notify.send(user, new WelcomeNotification())`, with the
63
+ email built fluently inside `toMail()` (`new MailMessage().subject(...).line(...)`),
64
+ is a conscious homage to Laravel's mail/notification API.
65
+ - **Facades** — `Notify`, `Cache`, `Storage`, `Queue`, `Auth`, and `Log` follow the
66
+ static-proxy pattern, resolving a container singleton on each call.
67
+ - **Gate / Policy authorization, password broker, and the `validate()` request flow**
68
+ all follow Laravel's shapes.
69
+
70
+ Where we diverged: Zerotal drops PHP's runtime magic (no dynamic proxies, no
71
+ `__get`/`__set` trickery) in favour of explicit TypeScript decorators and genuine
72
+ static typing throughout.
73
+
74
+ ### Rails
75
+
76
+ Before Laravel, Ruby on Rails established the patterns both frameworks inherit: the
77
+ **Active Record** model pattern itself, **convention over configuration**, and
78
+ **versioned, runnable migrations**. Zerotal's `Model.create()` / `Model.find()` surface
79
+ and its migration runner trace their lineage here.
80
+
81
+ Where we diverged: the same convention-over-configuration spirit, expressed through
82
+ explicit TypeScript types and decorators rather than Ruby metaprogramming — conventions
83
+ you can follow by reading types, not by memorising magic.
84
+
85
+ ## The TypeScript & Node Ecosystem
86
+
87
+ ### NestJS
88
+
89
+ NestJS demonstrated that a decorator-driven IoC container could feel natural in
90
+ TypeScript. Zerotal's dependency-injection story echoes it:
91
+
92
+ - **Container + decorators** — `@inject(...)` marks a class for auto-wiring,
93
+ declaring its constructor dependencies as explicit tokens the container
94
+ resolves in order.
95
+ - **Provider modules** — grouping bindings and boot logic into provider classes
96
+ parallels Nest's module/provider model.
97
+
98
+ Where we diverged: Zerotal uses standard **TC39 decorators** with no `reflect-metadata`
99
+ dependency — dependencies are declared explicitly as `@inject(...)` tokens rather than
100
+ inferred from constructor parameter types.
101
+
102
+ ### AdonisJS
103
+
104
+ AdonisJS proved that a Laravel-flavoured framework could thrive in the JavaScript
105
+ ecosystem. Several Zerotal conventions trace back to Adonis:
106
+
107
+ - **`HttpContext` as the single per-request object** — passing one rich context object
108
+ through the entire pipeline (rather than `req`/`res` pairs) was validated by Adonis.
109
+ - **IoC container token conventions** — string-keyed bindings (`'db'`, `'cache'`,
110
+ `'queue'`, `'events'`, `'log'`) alongside class and Symbol tokens follow the pattern
111
+ Adonis popularised.
112
+ - **Lucid-inspired query scopes** — named scopes (`Model.scope(...)`,
113
+ `Model.query().withScopes(...)`) and global scopes parallel Lucid's model scopes.
114
+
115
+ Where we diverged: Zerotal keeps views as typed TypeScript (template literals / JSX)
116
+ rather than adopting a dedicated template engine like Adonis's Edge — one less language
117
+ to learn, and views type-check against the data you pass them.
118
+
119
+ ### Koa
120
+
121
+ Zerotal's middleware pipeline is a Koa-style **onion model**, not a Laravel-style
122
+ before/after/terminate kernel:
123
+
124
+ - **`handle(ctx, next)`** — each middleware is a `Pipe` with a single `handle` method
125
+ that receives the `HttpContext` and a `next()` continuation, awaits downstream
126
+ middleware, and can run logic on the way out — exactly Koa's cascading model.
127
+ - **Short-circuiting** — a middleware that returns without calling `next()` stops the
128
+ chain, the same way Koa middleware can decline to continue.
129
+
130
+ Where we diverged: Koa is deliberately minimal — middleware and little else. Zerotal keeps
131
+ the onion model but ships a batteries-included stack on top of it, and the context is a
132
+ richly-typed `HttpContext` rather than a bare object. Post-response work (Laravel's
133
+ `terminate`) is handled via `ctx.afterResponse(...)` callbacks rather than a third phase.
134
+
135
+ ## Reactivity & The Frontend
136
+
137
+ ### Next.js
138
+
139
+ Next.js normalised several patterns that Zerotal adopted for its front-end story:
140
+
141
+ - **File-based routing** — a `routes/` directory with `GET`, `POST`, etc. named exports
142
+ mirrors the Next.js App Router file convention.
143
+ - **Co-located route handlers** — keeping data-fetching logic next to the route that
144
+ uses it (rather than in a separate controllers directory) is a Next.js-influenced
145
+ option.
146
+ - **`_layout.ts`** — a persistent shell that wraps every page in a section of the app
147
+ comes directly from Next.js layouts.
148
+
149
+ Where we diverged: file-based routing is _one option_, not the whole story — routes can
150
+ equally be registered explicitly with `Router.get(...)` — and everything runs
151
+ server-side on Bun, with no client bundler or React Server Components coupling.
152
+
153
+ ### Livewire / Alpine.js
154
+
155
+ Flow — Zerotal's first-party, server-driven reactive component system — draws from the
156
+ Livewire mental model:
157
+
158
+ - **Server-driven reactivity** — component state lives on the server; only diffs are
159
+ sent to the client.
160
+ - **`@locked` / `@expose` decorators** — the distinction between server-private and
161
+ client-accessible properties mirrors Livewire's `#[Locked]` and `wire:model` boundary.
162
+ - **SPA `navigate` transitions** — Livewire's `livewire:navigate` was the direct
163
+ reference point for Flow's fetch-and-swap page navigation.
164
+
165
+ Where we diverged: Flow is pure TypeScript with no PHP/Blade runtime — components are
166
+ typed classes, and the diffing/transport protocol is Zerotal's own.
167
+
168
+ ### Inertia.js
169
+
170
+ Zerotal's first-party Inertia adapter (`@zerotal/inertia`) is built on Inertia.js
171
+ itself. The adapter layer follows the same server-side adapter conventions established
172
+ by the official Laravel and Rails adapters.
173
+
174
+ Where we diverged: the adapter ships first-party and is versioned with the framework,
175
+ rather than living as a separately-maintained community package.
176
+
177
+ ### shadcn/ui
178
+
179
+ `@zerotal/flow-ui` follows the model shadcn/ui established, and the debt is worth naming
180
+ plainly:
181
+
182
+ - **You own the code** — `bun zt flow:add button` copies the source into your app rather
183
+ than adding a dependency you can only configure from the outside. `flow:add` is the
184
+ same idea as `npx shadcn add`, down to the registry manifest behind it.
185
+ - **Tokens over props** — re-theming happens by overriding CSS variables, not by
186
+ threading a theme object through every component. The default token names
187
+ (`background`, `card`, `primary`, `muted`, `accent`, `destructive`, `border`, `input`,
188
+ `ring`) are shadcn's, so a palette written for one drops into the other.
189
+ - **Styled wrappers over headless primitives** — shadcn styles Radix; flow-ui styles
190
+ Flow's own headless layer, which fills the same role.
191
+ - **The catalogue** — the component list was chosen by working through shadcn's and
192
+ asking which entries a Zerotal application would actually reach for.
193
+
194
+ Where we diverged: components render on the server and return HTML rather than React
195
+ elements, so there is no client component tree; the `Chart` component draws SVG on the
196
+ server instead of wrapping a charting library; and the AI-chat components have no
197
+ equivalent here.
198
+
199
+ ## Tooling, Data & Telemetry
200
+
201
+ ### Zod
202
+
203
+ The validator's API is shaped by Zod's fluent, inferable schemas:
204
+
205
+ - **Chained rule builders** — `r.string().min(3).max(255)`, `r.number().integer()`,
206
+ `r.array(r.string()).optional()` read like Zod schemas.
207
+ - **Type inference** — `Infer<>` derives the fully-typed result of a schema, so
208
+ validated data needs no manual casts — the same payoff as Zod's `z.infer`.
209
+
210
+ Where we diverged: Zerotal's validator is wired into the HTTP request lifecycle
211
+ (`FormRequest`, the `validate(ctx, …)` helper, automatic 422/redirect handling) rather
212
+ than being a standalone parsing library.
213
+
214
+ ### Monolog
215
+
216
+ Zerotal's logger borrows Monolog's channel architecture (by way of Laravel's logging):
217
+
218
+ - **Named channels with per-channel level thresholds** — `console`, `single`, `daily`,
219
+ `stack`, `null`, each filtering below its configured minimum level.
220
+ - **Stack channels** — fan a single entry out to several channels at once.
221
+ - **Daily rotation with retention** — date-stamped files pruned after `days`.
222
+
223
+ Where we diverged: the logger is a lean, five-driver reimplementation — there is no PHP
224
+ handler/processor ecosystem to port, just the channel model that earns its keep.
225
+
226
+ ### Laravel Telescope / Pulse
227
+
228
+ Zerotal's devtools (`@zerotal/devtools`) take their cue from Telescope and Pulse:
229
+
230
+ - **Per-request traces** — queries, cache operations, mail, jobs, and logs captured for
231
+ each request and streamed live to an in-browser dashboard.
232
+ - **Slow-query and N+1 detection** — surfaced automatically, the way Telescope flags
233
+ slow queries.
234
+
235
+ Where we diverged: rather than each domain package depending on devtools, everything is
236
+ captured through the synchronous **`FrameworkEvents`** instrumentation bus in
237
+ `@zerotal/core` — devtools (and the logger, and any metrics layer) simply subscribe, so
238
+ the domain packages never import their observers. See [Events](/docs/events).
239
+
240
+ ### Prisma / Drizzle
241
+
242
+ The class-based migration format (`export default class extends Migration` with `up()`
243
+ and `down()` methods) and the `migrate` / `migrate:fresh` CLI commands were shaped by
244
+ observing what Prisma and Drizzle got right.
245
+
246
+ Where we diverged: migrations stay as ordinary TypeScript you can read and run — no
247
+ schema DSL, no generated client step — keeping the database layer transparent rather
248
+ than hidden behind a code-generation pipeline.
249
+
250
+ If you see your work reflected here and feel it is misrepresented or missing, please
251
+ open an issue or pull request — attribution matters.
252
+
253
+ ## Next steps
254
+
255
+ - [Getting started](/docs/getting-started) — scaffold your first Zerotal app.
256
+ - [Application](/docs/application) — how the framework is wired together.
257
+ - [Contributing](/docs/contributing) — help shape the framework.