@noego/forge 0.0.1

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 (145) hide show
  1. package/README.md +620 -0
  2. package/dist/client/client.d.ts +8 -0
  3. package/dist/client/client.d.ts.map +1 -0
  4. package/dist/client/events.d.ts +4 -0
  5. package/dist/client/events.d.ts.map +1 -0
  6. package/dist/client/index.d.ts +6 -0
  7. package/dist/client/index.d.ts.map +1 -0
  8. package/dist/client/navigation.svelte.d.ts +4 -0
  9. package/dist/client/navigation.svelte.d.ts.map +1 -0
  10. package/dist/client/page.svelte.d.ts +4 -0
  11. package/dist/client/page.svelte.d.ts.map +1 -0
  12. package/dist/client.cjs +2 -0
  13. package/dist/client.cjs.map +1 -0
  14. package/dist/client.d.ts +2 -0
  15. package/dist/client.mjs +605 -0
  16. package/dist/client.mjs.map +1 -0
  17. package/dist/index.d.ts +3 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/options/ServerOptions.d.ts +16 -0
  20. package/dist/options/ServerOptions.d.ts.map +1 -0
  21. package/dist/page.cjs +2 -0
  22. package/dist/page.cjs.map +1 -0
  23. package/dist/page.d.ts +2 -0
  24. package/dist/page.mjs +7 -0
  25. package/dist/page.mjs.map +1 -0
  26. package/dist/page.svelte-C4chAYK2.js +137 -0
  27. package/dist/page.svelte-C4chAYK2.js.map +1 -0
  28. package/dist/page.svelte-Dvj7306U.cjs +2 -0
  29. package/dist/page.svelte-Dvj7306U.cjs.map +1 -0
  30. package/dist/parser/IRoute.d.ts +31 -0
  31. package/dist/parser/IRoute.d.ts.map +1 -0
  32. package/dist/parser/openapi.d.ts +9 -0
  33. package/dist/parser/openapi.d.ts.map +1 -0
  34. package/dist/parser/path.d.ts +9 -0
  35. package/dist/parser/path.d.ts.map +1 -0
  36. package/dist/routing/base.d.ts +10 -0
  37. package/dist/routing/base.d.ts.map +1 -0
  38. package/dist/routing/component_loader/component_loader.d.ts +27 -0
  39. package/dist/routing/component_loader/component_loader.d.ts.map +1 -0
  40. package/dist/routing/component_loader/component_manager.d.ts +12 -0
  41. package/dist/routing/component_loader/component_manager.d.ts.map +1 -0
  42. package/dist/routing/html_render/html_render.d.ts +20 -0
  43. package/dist/routing/html_render/html_render.d.ts.map +1 -0
  44. package/dist/routing/manifest/base.d.ts +11 -0
  45. package/dist/routing/manifest/base.d.ts.map +1 -0
  46. package/dist/routing/manifest/store.svelte.d.ts +3 -0
  47. package/dist/routing/manifest/store.svelte.d.ts.map +1 -0
  48. package/dist/routing/page.d.ts +7 -0
  49. package/dist/routing/page.d.ts.map +1 -0
  50. package/dist/routing/server_adapter/api_adapter.d.ts +23 -0
  51. package/dist/routing/server_adapter/api_adapter.d.ts.map +1 -0
  52. package/dist/routing/server_adapter/express_server_adapter.d.ts +20 -0
  53. package/dist/routing/server_adapter/express_server_adapter.d.ts.map +1 -0
  54. package/dist/routing/server_adapter/middleware_adapter.d.ts +12 -0
  55. package/dist/routing/server_adapter/middleware_adapter.d.ts.map +1 -0
  56. package/dist/routing/server_adapter/server_adapter.d.ts +5 -0
  57. package/dist/routing/server_adapter/server_adapter.d.ts.map +1 -0
  58. package/dist/routing/shadow/shadow_route.d.ts +2 -0
  59. package/dist/routing/shadow/shadow_route.d.ts.map +1 -0
  60. package/dist/routing/url_parser.d.ts +12 -0
  61. package/dist/routing/url_parser.d.ts.map +1 -0
  62. package/dist/server/server.d.ts +5 -0
  63. package/dist/server/server.d.ts.map +1 -0
  64. package/dist/shared.cjs +2 -0
  65. package/dist/shared.cjs.map +1 -0
  66. package/dist/shared.d.ts +2 -0
  67. package/dist/shared.d.ts.map +1 -0
  68. package/dist/shared.mjs +5 -0
  69. package/dist/shared.mjs.map +1 -0
  70. package/dist/types/Callback.d.ts +2 -0
  71. package/dist/types/Callback.d.ts.map +1 -0
  72. package/dist/types/FetchMiddleware.d.ts +21 -0
  73. package/dist/types/FetchMiddleware.d.ts.map +1 -0
  74. package/dist/vite-env.d.ts +19 -0
  75. package/dist-ssr/client/client.d.ts +8 -0
  76. package/dist-ssr/client/client.d.ts.map +1 -0
  77. package/dist-ssr/client/events.d.ts +4 -0
  78. package/dist-ssr/client/events.d.ts.map +1 -0
  79. package/dist-ssr/client/index.d.ts +6 -0
  80. package/dist-ssr/client/index.d.ts.map +1 -0
  81. package/dist-ssr/client/navigation.svelte.d.ts +4 -0
  82. package/dist-ssr/client/navigation.svelte.d.ts.map +1 -0
  83. package/dist-ssr/client/page.svelte.d.ts +4 -0
  84. package/dist-ssr/client/page.svelte.d.ts.map +1 -0
  85. package/dist-ssr/index.d.ts +3 -0
  86. package/dist-ssr/index.d.ts.map +1 -0
  87. package/dist-ssr/options/ServerOptions.d.ts +16 -0
  88. package/dist-ssr/options/ServerOptions.d.ts.map +1 -0
  89. package/dist-ssr/parser/IRoute.d.ts +31 -0
  90. package/dist-ssr/parser/IRoute.d.ts.map +1 -0
  91. package/dist-ssr/parser/openapi.d.ts +9 -0
  92. package/dist-ssr/parser/openapi.d.ts.map +1 -0
  93. package/dist-ssr/parser/path.d.ts +9 -0
  94. package/dist-ssr/parser/path.d.ts.map +1 -0
  95. package/dist-ssr/path-CyGuWUeq.cjs +68 -0
  96. package/dist-ssr/path-CyGuWUeq.cjs.map +1 -0
  97. package/dist-ssr/path-ODk1FhWY.js +69 -0
  98. package/dist-ssr/path-ODk1FhWY.js.map +1 -0
  99. package/dist-ssr/routing/base.d.ts +10 -0
  100. package/dist-ssr/routing/base.d.ts.map +1 -0
  101. package/dist-ssr/routing/component_loader/component_loader.d.ts +27 -0
  102. package/dist-ssr/routing/component_loader/component_loader.d.ts.map +1 -0
  103. package/dist-ssr/routing/component_loader/component_manager.d.ts +12 -0
  104. package/dist-ssr/routing/component_loader/component_manager.d.ts.map +1 -0
  105. package/dist-ssr/routing/html_render/html_render.d.ts +20 -0
  106. package/dist-ssr/routing/html_render/html_render.d.ts.map +1 -0
  107. package/dist-ssr/routing/manifest/base.d.ts +11 -0
  108. package/dist-ssr/routing/manifest/base.d.ts.map +1 -0
  109. package/dist-ssr/routing/manifest/store.svelte.d.ts +3 -0
  110. package/dist-ssr/routing/manifest/store.svelte.d.ts.map +1 -0
  111. package/dist-ssr/routing/page.d.ts +7 -0
  112. package/dist-ssr/routing/page.d.ts.map +1 -0
  113. package/dist-ssr/routing/server_adapter/api_adapter.d.ts +23 -0
  114. package/dist-ssr/routing/server_adapter/api_adapter.d.ts.map +1 -0
  115. package/dist-ssr/routing/server_adapter/express_server_adapter.d.ts +20 -0
  116. package/dist-ssr/routing/server_adapter/express_server_adapter.d.ts.map +1 -0
  117. package/dist-ssr/routing/server_adapter/middleware_adapter.d.ts +12 -0
  118. package/dist-ssr/routing/server_adapter/middleware_adapter.d.ts.map +1 -0
  119. package/dist-ssr/routing/server_adapter/server_adapter.d.ts +5 -0
  120. package/dist-ssr/routing/server_adapter/server_adapter.d.ts.map +1 -0
  121. package/dist-ssr/routing/shadow/shadow_route.d.ts +2 -0
  122. package/dist-ssr/routing/shadow/shadow_route.d.ts.map +1 -0
  123. package/dist-ssr/routing/url_parser.d.ts +12 -0
  124. package/dist-ssr/routing/url_parser.d.ts.map +1 -0
  125. package/dist-ssr/server/server.d.ts +5 -0
  126. package/dist-ssr/server/server.d.ts.map +1 -0
  127. package/dist-ssr/server.cjs +876 -0
  128. package/dist-ssr/server.cjs.map +1 -0
  129. package/dist-ssr/server.d.ts +2 -0
  130. package/dist-ssr/server.js +838 -0
  131. package/dist-ssr/server.js.map +1 -0
  132. package/dist-ssr/shared.cjs +31 -0
  133. package/dist-ssr/shared.cjs.map +1 -0
  134. package/dist-ssr/shared.d.ts +2 -0
  135. package/dist-ssr/shared.d.ts.map +1 -0
  136. package/dist-ssr/shared.js +31 -0
  137. package/dist-ssr/shared.js.map +1 -0
  138. package/dist-ssr/types/Callback.d.ts +2 -0
  139. package/dist-ssr/types/Callback.d.ts.map +1 -0
  140. package/dist-ssr/types/FetchMiddleware.d.ts +21 -0
  141. package/dist-ssr/types/FetchMiddleware.d.ts.map +1 -0
  142. package/dist-ssr/vite-env.d.ts +19 -0
  143. package/loaders/loader.mjs +20 -0
  144. package/package.json +121 -0
  145. package/schema.json +333 -0
package/README.md ADDED
@@ -0,0 +1,620 @@
1
+ # @noego/forge
2
+
3
+ [![NPM Version](https://img.shields.io/npm/v/@noego/forge.svg)](https://www.npmjs.com/package/@noego/forge)
4
+ [![License](https://img.shields.io/npm/l/@noego/forge.svg)](LICENSE)
5
+
6
+ **OpenAPI-Powered Svelte SSR Framework** - Build server-rendered Svelte applications with routes generated directly from your OpenAPI specification.
7
+
8
+ ## Overview
9
+
10
+ Forge is a framework that generates Svelte applications from OpenAPI definitions, combining server-side rendering (SSR) with client-side navigation.
11
+ You get **fast first paint & SEO** (SSR) *and* **instant page transitions** (client-side routing) without writing a single line of glue code.
12
+
13
+ 📝 **Just want to see it run?** Jump to the *TL;DR* section below and paste the two commands – you’ll have a working demo at `http://localhost:3000`.
14
+
15
+ ### Key Features
16
+
17
+ - **OpenAPI-Driven Development**: Define routes, views, and layouts directly in your OpenAPI spec
18
+ - **Server-Side Rendering**: Deliver fully rendered HTML on first load for SEO and performance
19
+ - **Client-Side Navigation**: Smooth, fast transitions after initial load
20
+ - **Nested Layouts**: Create complex page structures with multiple layout levels
21
+ - **Automatic Parameter Parsing**: Route parameters (like `/users/:id`) are parsed from OpenAPI paths
22
+ - **Data Loading**: Unified mechanism for data fetching in SSR and client-side
23
+ - **Vite Integration**: Modern, fast development with hot module reloading
24
+
25
+ ## Quick Start
26
+
27
+ ### TL;DR – Run the demo in two commands 🚀
28
+
29
+ ```bash
30
+ # 1. Install dependencies (Forge + peers)
31
+ npm install # or pnpm / yarn
32
+
33
+ # 2. Launch the development server (Express *and* Vite)
34
+ npm run dev # -> http://localhost:3000
35
+ ```
36
+
37
+ Vite is wired up as **middleware** inside Express (`middlewareMode: true`).
38
+ That means hot-module-reloading, static files, and SSR all live behind the
39
+ same port – you only ever need to open http://localhost:3000.
40
+
41
+ ### Installation
42
+
43
+ ```bash
44
+ npm i @noego/forge express svelte
45
+ ```
46
+
47
+ Forge is a thin wrapper around **Express** and **Svelte**. The command above installs all three runtime dependencies you need. For development, you'll also want Vite for hot-module-reloading:
48
+
49
+ ```bash
50
+ # development-only (HMR, build, …)
51
+ npm i -D vite
52
+ ```
53
+
54
+ The versions shown in the badge at the very top are always the ones that Forge
55
+ is tested against; newer minor / patch releases of Express and Svelte usually
56
+ work fine as well.
57
+
58
+ ### Basic Setup
59
+
60
+ ## Example Project Structure
61
+
62
+ Forge is completely agnostic about where you place your files – as long as the
63
+ paths in your **options object** are correct everything will “just work”. To make the
64
+ following snippets less abstract, here is one possible layout for a **full
65
+ stack** repository:
66
+
67
+ ```
68
+ .
69
+ ├─ frontend/ # Svelte + Forge source code
70
+ │ ├─ components/
71
+ │ ├─ openapi.yaml
72
+ │ ├─ client.ts # client hydration entry
73
+ │ └─ server.ts # Express SSR entry
74
+ ├─ backend/ # (optional) REST / database code
75
+ ├─ vite.config.js # shared by both dev & prod builds
76
+ ├─ forge.config.ts # (optional) central Forge options shared by client & server
77
+ └─ package.json # root-level scripts & deps
78
+ ```
79
+
80
+ Feel free to flatten or reorganise folders – if you move `components/` for
81
+ instance, simply update `component_dir` and the import paths keep resolving
82
+ correctly.
83
+
84
+ ### How Forge resolves paths
85
+
86
+ Forge only really needs three absolute paths to bootstrap your app:
87
+
88
+ 1. **`component_dir`** – look here for Svelte files referenced in the OpenAPI
89
+ schema (every `x-view` / `x-layout` is interpreted *relative* to this
90
+ directory).
91
+ 2. **`open_api_path`** – the location of your `openapi.yaml`.
92
+ 3. **`renderer`** – an HTML template that contains the placeholders
93
+ `{{{APP}}}`, `{{{HEAD}}}` and `{{{DATA}}}`.
94
+
95
+ All paths are resolved against **`process.cwd()`** (your *project root*):
96
+
97
+ * **Relative strings** – the preferred form. Pass `frontend/components` instead
98
+ of `./frontend/components` so that the value still resolves correctly when
99
+ your working directory changes (for instance when running tests).
100
+ * **Absolute strings starting with `/`** – treated as root-relative, e.g.
101
+ `/frontend/components` ➜ `path.join(process.cwd(), 'frontend/components')`.
102
+
103
+ Avoid the `./` prefix – while it works most of the time it breaks as soon as you
104
+ invoke Node from a different folder.
105
+
106
+ 1. **Configure OpenAPI Schema**
107
+
108
+ Forge extends the OpenAPI 3 spec with a couple of `x-*` vendor extensions. Two
109
+ conventions are important from day one:
110
+
111
+ * **Landing page must be explicit** – declare the `/` path manually, Forge will
112
+ not create a default route for you.
113
+ * **`x-layout` is always an array** – even when there is only one layout
114
+ component. That keeps the type consistent and makes it possible to add more
115
+ wrappers later without touching already deployed code.
116
+
117
+ Create `openapi.yaml` in your project root:
118
+
119
+ ```yaml
120
+ openapi: '3.0.3'
121
+ info:
122
+ title: My App
123
+ version: '1.0.0'
124
+
125
+ x-fallback-view: error/404.svelte # 404 page
126
+
127
+ paths:
128
+ /:
129
+ get:
130
+ summary: Home page
131
+ x-view: views/home.svelte
132
+ x-layout:
133
+ - layout/main.svelte
134
+
135
+ '/user/:id':
136
+ get:
137
+ summary: User profile
138
+ x-view: views/user_page.svelte
139
+ x-layout:
140
+ - layout/main.svelte
141
+ ```
142
+
143
+ 2. **Build (but don’t start) the Express server**
144
+
145
+ Keeping server construction and `listen()` separate makes automated testing a
146
+ lot easier because you can import the app without opening a TCP port.
147
+
148
+ ```ts
149
+ // server.ts – returns a fully configured Express application
150
+ import express from 'express';
151
+ import { createServer } from '@noego/forge/server';
152
+ import { options } from './forge.config';
153
+
154
+ export async function buildServer() {
155
+ const app = express();
156
+ await createServer(app, options);
157
+ return app;
158
+ }
159
+ ```
160
+
161
+ 3. **Start the server in dev / prod scripts**
162
+
163
+ Your actual entry file is now a tiny wrapper that calls `buildServer()` and then
164
+ `listen()`. Swap it out for a different bootstrapper in tests.
165
+
166
+ ```ts
167
+ // dev.ts – used by `npm run dev`
168
+ import { buildServer } from './server';
169
+
170
+ const PORT = process.env.PORT ?? 3000;
171
+
172
+ buildServer()
173
+ .then(app => app.listen(PORT))
174
+ .then(() => console.log(`🚀 http://localhost:${PORT}`))
175
+ .catch(err => {
176
+ console.error('Server failed to start', err);
177
+ process.exit(1);
178
+ });
179
+ ```
180
+
181
+ 4. **Create Client Initialization File**
182
+
183
+ ```typescript
184
+ // client.ts
185
+ import { createApp } from '@noego/forge/client';
186
+ import { options } from './forge.config';
187
+
188
+ document.addEventListener('DOMContentLoaded', () => {
189
+ createApp(document.getElementById('app'), options);
190
+ });
191
+ ```
192
+
193
+ 5. **Create HTML Template**
194
+
195
+ ```html
196
+ <!-- index.html -->
197
+ <!DOCTYPE html>
198
+ <html lang="en">
199
+ <head>
200
+ <meta charset="UTF-8">
201
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
202
+ <title>My Forge App</title>
203
+ <!-- 👇👇 **Important for Vite HMR during development** 👇👇 -->
204
+ <!-- This script connects the page to Vite's Hot-Module-Reloading client. -->
205
+ <!-- It MUST be included before your own client bundle when running the dev server. -->
206
+ <!-- Vite HMR client (served from project root) -->
207
+ <script type="module" src="/@vite/client"></script>
208
+ <style>{{{CSS}}}</style>
209
+ {{{HEAD}}}
210
+ </head>
211
+ <body>
212
+ <div id="app">{{{APP}}}</div>
213
+ <script>window.__INITIAL_DATA__ = {{{DATA}}};</script>
214
+ <!-- Application client entry (served from Vite dev server root) -->
215
+ <script type="module" src="/client.ts"></script>
216
+ </body>
217
+ </html>
218
+ ```
219
+
220
+ 6. **Provide Forge Options** (file name & language are totally up to you)
221
+
222
+ ```ts
223
+ // forge.config.ts (could also be .js / .mjs – only the exported object matters)
224
+ import type { ServerOptions } from '@noego/forge/options';
225
+
226
+ export const options: ServerOptions = {
227
+ component_dir: 'components',
228
+ renderer: 'index.html',
229
+ assets: {
230
+ '/assets': ['public']
231
+ },
232
+ open_api_path: 'openapi.yaml',
233
+ };
234
+ ```
235
+
236
+ 7. **Create Components**
237
+
238
+ ```html
239
+ <!-- components/layout/main.svelte -->
240
+ <script>
241
+ let { children } = $props();
242
+ </script>
243
+
244
+ <nav>
245
+ <a href="/">Home</a>
246
+ <a href="/user/1">User</a>
247
+ </nav>
248
+ <main>
249
+ {@render children()}
250
+ </main>
251
+
252
+ <!-- components/views/home.svelte -->
253
+ <h1>Welcome to my app!</h1>
254
+
255
+ <!-- components/views/user_page.svelte -->
256
+ <script>
257
+ export let params = {};
258
+ </script>
259
+
260
+ <h1>User {params.id}</h1>
261
+
262
+ <!-- components/error/404.svelte -->
263
+ <h1>Page not found</h1>
264
+ ```
265
+
266
+ 8. **Start Development Server**
267
+
268
+ Add to your package.json:
269
+ ```json
270
+ "scripts": {
271
+ "dev": "tsx dev.ts",
272
+ "watch": "tsx watch --ignore '**/*.svelte' dev.ts",
273
+ "typecheck": "tsc --noEmit && svelte-check"
274
+ }
275
+ ```
276
+
277
+ Run the server:
278
+ ```bash
279
+ npm run dev
280
+ ```
281
+
282
+ ## Available npm scripts
283
+
284
+ | Script | What it does |
285
+ |--------|--------------|
286
+ | `dev` | Express + Vite with HMR (default development mode) |
287
+ | `watch` | Restarts Express when server-side files change |
288
+ | `build` | Generates both client & SSR bundles (`vite build` twice) |
289
+ | `typecheck` | Runs `tsc` and `svelte-check` |
290
+
291
+ ## Further documentation 📚
292
+
293
+ The README is only the quick-start. For deeper topics have a look at the
294
+ markdown files under [`/docs`](./docs):
295
+
296
+ | Document | What you will find |
297
+ |----------|--------------------|
298
+ | **layout_system.md** | How Forge composes nested layouts and views, best-practices, common pitfalls, and design guidelines. |
299
+ | **load_functions.md** | How to fetch data safely on the server with `load()`, have it ready for SSR and client-side navigation, and access it via `$props()`. |
300
+ | **state_sharing.md** | Pattern for reactive global state with Svelte 5 context and `$state`, including multi-property examples. |
301
+ | **tailwind-layouts.md** | Design layouts with Tailwind CSS: global shells, nested wrappers, responsive tips. |
302
+ | **FETCH_MIDDLEWARE.md** | Client-side fetch middleware: header injection, redirect handling, debugging helpers, security notes. |
303
+
304
+ More docs will be added over time—keep an eye on the folder when upgrading.
305
+
306
+ 9. **Create a `vite.config.js` (if you don't already have one)**
307
+
308
+ Forge relies on Vite for both development (HMR) and production builds.
309
+ If your project was scaffolded from scratch you will need to add a minimal
310
+ `vite.config.js` file at the root of the repository so Vite knows how to handle
311
+ Svelte files:
312
+
313
+ ```js
314
+ // vite.config.js
315
+ import { defineConfig } from 'vite';
316
+ import { svelte } from '@sveltejs/vite-plugin-svelte';
317
+
318
+ export default defineConfig({
319
+ plugins: [svelte()],
320
+ });
321
+ ```
322
+
323
+ This can of course be extended with any additional Vite options that your
324
+ application requires – the only strict requirement is that the Svelte plugin is
325
+ present so that `*.svelte` files are compiled correctly.
326
+
327
+ ## Configuration Options
328
+
329
+ ### Server Options
330
+
331
+ | Option | Type | Default | Description |
332
+ |--------|------|---------|-------------|
333
+ | `development` | boolean | `process.env.NODE_ENV !== 'production'` | Development mode flag |
334
+ | `component_dir` | string | `/` | Directory containing Svelte components |
335
+ | `build_dir` | string | `dist_ssr` | Output directory for production builds |
336
+ | `renderer` | string \| IHTMLRender | `default` | HTML template path or renderer |
337
+ | `open_api_path` | string | `process.cwd() + '/openapi.yaml'` | Path to OpenAPI specification |
338
+ | `manifest_endpoint` | string | `/manifest.json` | Endpoint for manifest file |
339
+ | `assets` | Record<string, string[]> | `undefined` | Map of URL paths to directories for static assets |
340
+ | `viteOptions` | object | See code | Vite configuration options |
341
+
342
+ ### OpenAPI Extensions
343
+
344
+ Forge uses custom OpenAPI extensions to define your application structure:
345
+
346
+ | Extension | Description |
347
+ |-----------|-------------|
348
+ | `x-view` | Path to the Svelte component that handles the route |
349
+ | `x-layout` | Array of layout components that wrap the view (rendered outside-in) |
350
+ | `x-fallback-view` | Component to render for 404 errors |
351
+ | `x-fallback-layout` | Default layout when none is specified |
352
+
353
+ ## Advanced Usage
354
+
355
+ ### Fetch Middleware for Authentication
356
+
357
+ Forge provides a client-side fetch middleware system that allows you to automatically modify all fetch requests, eliminating the need to manually add authentication headers to every API call.
358
+
359
+ ```typescript
360
+ // client.ts
361
+ import { createApp, fetch } from '@noego/forge/client';
362
+ import { options } from './forge.config';
363
+
364
+ // Configure fetch middleware to automatically add authentication headers
365
+ fetch.configUpdate((url, init) => {
366
+ const token = localStorage.getItem('auth_token');
367
+ if (token) {
368
+ const headers = new Headers(init?.headers);
369
+ headers.set('Authorization', `Bearer ${token}`);
370
+ return {
371
+ ...init,
372
+ headers
373
+ };
374
+ }
375
+ return init;
376
+ });
377
+
378
+ document.addEventListener('DOMContentLoaded', () => {
379
+ createApp(document.getElementById('app'), options);
380
+ });
381
+ ```
382
+
383
+ With this middleware configured, all fetch calls in your application will automatically include the Authorization header:
384
+
385
+ ```typescript
386
+ // No need to manually add Authorization headers anymore
387
+ const response = await fetch('/api/contractor-types', {
388
+ method: 'POST',
389
+ headers: { 'Content-Type': 'application/json' },
390
+ body: JSON.stringify({ name: 'New Type' })
391
+ });
392
+ ```
393
+
394
+ The middleware function receives the same parameters as the native `fetch()` function and should return the modified `RequestInit` object. You can use this pattern for:
395
+
396
+ - Adding authentication tokens
397
+ - Setting default headers
398
+ - Logging requests
399
+ - Transforming request data
400
+
401
+ ### Nested Layouts
402
+
403
+ Forge supports nested layouts, rendered in the order specified:
404
+
405
+ ```yaml
406
+ paths:
407
+ '/product/:id':
408
+ get:
409
+ x-view: views/product_page.svelte # 👈 view component
410
+ x-layout:
411
+ - layout/main.svelte # outer layout
412
+ - layout/product_layout.svelte # inner (page-specific) layout
413
+ ```
414
+
415
+ The resulting component tree:
416
+ ```
417
+ main.svelte
418
+ └── product_layout.svelte
419
+ └── product_page.svelte (view)
420
+ ```
421
+
422
+ ### Static Assets
423
+
424
+ Configure multiple asset directories:
425
+
426
+ ```typescript
427
+ assets: {
428
+ '/assets': ['public'],
429
+ '/images': ['resources/images'],
430
+ '/docs': ['documentation']
431
+ }
432
+ ```
433
+
434
+ ⚠ **Important:** every directory listed in `assets` *must* exist at server
435
+ start-up, otherwise `express.static` will throw an error and Forge will refuse to
436
+ boot. If you need to commit an empty folder simply add a dummy
437
+ `public/.gitkeep` file so the directory makes it into version control.
438
+
439
+ ### Component Data Loading
440
+
441
+ Svelte components can export a **server-only** `load` function that returns the
442
+ initial data for the page.
443
+
444
+ Forge will:
445
+ 1. Invoke `load()` during SSR and embed the returned JSON into the HTML.
446
+ 2. On subsequent **client-side navigations** automatically issue an
447
+ `application/json` request to the same URL and pass the payload to the
448
+ destination component – you don’t have to write any extra code.
449
+
450
+ ```html
451
+ <script lang="ts">
452
+ export let data: any; // data returned by `load()`
453
+
454
+ /**
455
+ * `load()` is executed exactly once on the server.
456
+ * It receives a single argument – an object that describes the incoming
457
+ * request. Forge then serialises the returned value and re-hydrates it on
458
+ * the client (or performs an automatic JSON fetch when navigating
459
+ * client-side).
460
+ */
461
+ export async function load(request) {
462
+ const { params, query } = request;
463
+ // Fetch data however you like (Database, internal service, etc.)
464
+ return {
465
+ username: `User #${params.id}`,
466
+ debug: { query }
467
+ };
468
+ }
469
+ </script>
470
+
471
+ <h1>{data.username}</h1>
472
+ ```
473
+
474
+ The `request` object passed to `load()` (alias: `RequestData`) contains:
475
+
476
+ | Property | Description |
477
+ |----------|-------------|
478
+ | `url` | Full request URL. |
479
+ | `params` | Dynamic route params parsed from the URL. |
480
+ | `query` | Query-string parameters. |
481
+ | `headers`| Raw request headers. |
482
+ | `body` | Parsed request body (for POST/PUT …). |
483
+ | `context`| Mutable per-request bag you can populate in middleware. |
484
+
485
+ ## Building for Production
486
+
487
+ 1. **Add build scripts to package.json**:
488
+
489
+ ```json
490
+ "scripts": {
491
+ "build:client": "vite build",
492
+ "build:ssr": "SSR=true vite build --ssr",
493
+ "build": "npm run build:client && npm run build:ssr"
494
+ }
495
+ ```
496
+
497
+ 2. **Run build**:
498
+
499
+ ```bash
500
+ npm run build
501
+ ```
502
+
503
+ 3. **Start production server**:
504
+
505
+ ```bash
506
+ NODE_ENV=production node server.js
507
+ ```
508
+
509
+ ## Testing with Vitest / Jest
510
+
511
+ When you test Forge apps with **Vitest** or **Jest** you usually drive the
512
+ Express server via **supertest**:
513
+
514
+ ```ts
515
+ import request from 'supertest';
516
+ import { buildServer } from '../server';
517
+
518
+ const app = await buildServer();
519
+
520
+ it('renders the landing page', async () => {
521
+ const res = await request(app).get('/');
522
+ expect(res.status).toBe(200);
523
+ expect(res.text).toContain('<h1>Welcome');
524
+ });
525
+
526
+ afterAll(() => {
527
+ if (app.close) app.close();
528
+ });
529
+ ```
530
+
531
+ Two things to remember:
532
+
533
+ 1. The **HTML is streamed**, so the response body is available in `res.text`.
534
+ 2. Always close the server after long-running suites to release file & WS handles.
535
+
536
+ ## Troubleshooting
537
+
538
+ ### Common Issues
539
+
540
+ #### Components Not Loading
541
+
542
+ Ensure your `component_dir` matches the directory structure and is accessible relative to your project root.
543
+
544
+ #### SSR Hydration Errors
545
+
546
+ - Check for components that use browser-specific APIs without checking for `typeof window !== 'undefined'`
547
+ - Ensure components use the same data during SSR and client-side hydration
548
+
549
+ #### HMR not triggering inside WSL / Docker
550
+
551
+ File system events can behave differently inside virtualised environments. If
552
+ you do not see hot-reloading updates, set `watch: { usePolling: true }` in
553
+ `vite.config.js`. This project’s config already enables it – the note is here
554
+ so you know *why*.
555
+
556
+ #### "Unexpected token <" / Hydration mismatch warnings
557
+
558
+ Usually indicates that the HTML the server sent does not match the DOM the
559
+ client tries to hydrate. Double-check that any data returned from a
560
+ component’s `load()` function is identical on **both** sides.
561
+
562
+ #### Route Not Found
563
+
564
+ - Verify your OpenAPI paths are correctly defined
565
+ - Check that component file paths in `x-view` and `x-layout` are correct
566
+ - Ensure all referenced components exist
567
+
568
+ ## Stitch Integration (Modular YAML)
569
+
570
+ Forge automatically detects and builds stitch configurations for modular OpenAPI development. Instead of maintaining a single large `openapi.yaml`, you can split your API specification into multiple files.
571
+
572
+ ### Using Stitch Files
573
+
574
+ 1. **Create a stitch configuration** (`stitch.yaml`):
575
+
576
+ ```yaml
577
+ stitch:
578
+ - openapi/base.yaml
579
+ - openapi/routes/*.yaml
580
+ - openapi/components/*.yaml
581
+ ```
582
+
583
+ 2. **Pre-load YAML modules** in your client application:
584
+
585
+ ```typescript
586
+ // Client-side: Load YAML files into Vite's module cache
587
+ const yamlFiles = import.meta.glob('./openapi/**/*.yaml', { query: '?raw', import: 'default', eager: true });
588
+
589
+ await createApp(document.getElementById('app'), {
590
+ open_api_path: 'frontend/stitch.yaml', // Framework auto-detects and builds
591
+ component_dir: 'frontend/components/views'
592
+ });
593
+ ```
594
+
595
+ 3. **Framework auto-detection** – Forge automatically:
596
+ - Detects stitch files by checking for the `stitch` root property
597
+ - Builds the final OpenAPI configuration using `@noego/stitch/browser`
598
+ - Uses the cached YAML modules from your `import.meta.glob` call
599
+ - Falls back to regular OpenAPI processing for non-stitch files
600
+
601
+ ### Benefits
602
+
603
+ - **No manual build steps** – No need for `npm run stitch:frontend`
604
+ - **Modular development** – Split complex APIs into focused files
605
+ - **Hot reloading** – Vite automatically reloads when YAML files change
606
+ - **Backwards compatible** – Existing `openapi.yaml` files work unchanged
607
+
608
+ ### Requirements
609
+
610
+ - Client applications must call `import.meta.glob('./openapi/**/*.yaml', ...)` for Vite caching
611
+ - The glob pattern must be **static** and **top-level** for Vite's static analysis
612
+ - YAML files are loaded into the browser bundle but only once (no duplication)
613
+
614
+ ## Contributing
615
+
616
+ Contributions are welcome! Please feel free to submit a Pull Request.
617
+
618
+ ## License
619
+
620
+ ISC
@@ -0,0 +1,8 @@
1
+ import { ServerOptions } from '../options/ServerOptions';
2
+ import { FetchMiddlewareAPI } from '../types/FetchMiddleware';
3
+ export declare const FORGE_LOAD_SYMBOL: unique symbol;
4
+ export declare const fetch: FetchMiddlewareAPI & {
5
+ getMiddlewareCount(): number;
6
+ };
7
+ export declare function createApp<T extends HTMLElement = HTMLElement>(node: T, options: ServerOptions): Promise<void>;
8
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client/client.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,KAAK,EAAmB,kBAAkB,EAAsD,MAAM,0BAA0B,CAAC;AAGxI,eAAO,MAAM,iBAAiB,eAAgC,CAAC;AA4F/D,eAAO,MAAM,KAAK,EAAE,kBAAkB,GAAG;IAAE,kBAAkB,IAAI,MAAM,CAAA;CAwBtE,CAAC;AAIF,wBAAsB,SAAS,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAC/D,IAAI,EAAE,CAAC,EACP,OAAO,EAAE,aAAa,GACvB,OAAO,CAAC,IAAI,CAAC,CAkCf"}
@@ -0,0 +1,4 @@
1
+ import { IRoute } from '../parser/IRoute';
2
+ import { UrlMatcher } from '../routing/url_parser';
3
+ export declare function bootstrap_events(base_path: string, routes: IRoute[], matchers: UrlMatcher[]): void;
4
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/client/events.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAQxD,wBAAgB,gBAAgB,CAAC,SAAS,EAAC,MAAM,EAAC,MAAM,EAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,QA+JxF"}
@@ -0,0 +1,6 @@
1
+ export { createApp, fetch } from './client';
2
+ export { loadRoute, updateRoute } from './navigation.svelte';
3
+ export { bootstrap_events } from './events';
4
+ export { updateManifestStore, MANIFEST_STORE } from '../routing/manifest/store.svelte';
5
+ export { page, shadowUrl } from './page.svelte';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AACvF,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { IRoute } from '../parser/IRoute';
2
+ export declare function loadRoute(node: any, base_path: string, route: IRoute, combinedParams: any, urlParams: any, query: any): Promise<void>;
3
+ export declare function updateRoute(url: string, base_path: string, route: IRoute, params: any, urlParams: any, query: any, server_loading?: boolean, update?: boolean): Promise<void>;
4
+ //# sourceMappingURL=navigation.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigation.svelte.d.ts","sourceRoot":"","sources":["../../src/client/navigation.svelte.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAiC9C,wBAAsB,SAAS,CAAC,IAAI,EAAC,GAAG,EAAC,SAAS,EAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,SAAS,EAAC,GAAG,EAAE,KAAK,EAAC,GAAG,iBAwCtH;AAID,wBAAsB,WAAW,CAAC,GAAG,EAAC,MAAM,EAAC,SAAS,EAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAC,SAAS,EAAC,GAAG,EAAC,KAAK,EAAC,GAAG,EAAC,cAAc,GAAC,OAAe,EAAC,MAAM,UAAO,iBAgE7J"}
@@ -0,0 +1,4 @@
1
+ import { IPage } from '../routing/page';
2
+ export declare let page: IPage;
3
+ export declare function shadowUrl(pattern: string, url: string): any;
4
+ //# sourceMappingURL=page.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page.svelte.d.ts","sourceRoot":"","sources":["../../src/client/page.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAM7C,eAAO,IAAI,IAAI,OAKJ,CAAC;AAYZ,wBAAgB,SAAS,CAAC,OAAO,EAAC,MAAM,EAAE,GAAG,EAAE,MAAM,OAUpD"}