@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.
- package/README.md +620 -0
- package/dist/client/client.d.ts +8 -0
- package/dist/client/client.d.ts.map +1 -0
- package/dist/client/events.d.ts +4 -0
- package/dist/client/events.d.ts.map +1 -0
- package/dist/client/index.d.ts +6 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/navigation.svelte.d.ts +4 -0
- package/dist/client/navigation.svelte.d.ts.map +1 -0
- package/dist/client/page.svelte.d.ts +4 -0
- package/dist/client/page.svelte.d.ts.map +1 -0
- package/dist/client.cjs +2 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.mjs +605 -0
- package/dist/client.mjs.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/options/ServerOptions.d.ts +16 -0
- package/dist/options/ServerOptions.d.ts.map +1 -0
- package/dist/page.cjs +2 -0
- package/dist/page.cjs.map +1 -0
- package/dist/page.d.ts +2 -0
- package/dist/page.mjs +7 -0
- package/dist/page.mjs.map +1 -0
- package/dist/page.svelte-C4chAYK2.js +137 -0
- package/dist/page.svelte-C4chAYK2.js.map +1 -0
- package/dist/page.svelte-Dvj7306U.cjs +2 -0
- package/dist/page.svelte-Dvj7306U.cjs.map +1 -0
- package/dist/parser/IRoute.d.ts +31 -0
- package/dist/parser/IRoute.d.ts.map +1 -0
- package/dist/parser/openapi.d.ts +9 -0
- package/dist/parser/openapi.d.ts.map +1 -0
- package/dist/parser/path.d.ts +9 -0
- package/dist/parser/path.d.ts.map +1 -0
- package/dist/routing/base.d.ts +10 -0
- package/dist/routing/base.d.ts.map +1 -0
- package/dist/routing/component_loader/component_loader.d.ts +27 -0
- package/dist/routing/component_loader/component_loader.d.ts.map +1 -0
- package/dist/routing/component_loader/component_manager.d.ts +12 -0
- package/dist/routing/component_loader/component_manager.d.ts.map +1 -0
- package/dist/routing/html_render/html_render.d.ts +20 -0
- package/dist/routing/html_render/html_render.d.ts.map +1 -0
- package/dist/routing/manifest/base.d.ts +11 -0
- package/dist/routing/manifest/base.d.ts.map +1 -0
- package/dist/routing/manifest/store.svelte.d.ts +3 -0
- package/dist/routing/manifest/store.svelte.d.ts.map +1 -0
- package/dist/routing/page.d.ts +7 -0
- package/dist/routing/page.d.ts.map +1 -0
- package/dist/routing/server_adapter/api_adapter.d.ts +23 -0
- package/dist/routing/server_adapter/api_adapter.d.ts.map +1 -0
- package/dist/routing/server_adapter/express_server_adapter.d.ts +20 -0
- package/dist/routing/server_adapter/express_server_adapter.d.ts.map +1 -0
- package/dist/routing/server_adapter/middleware_adapter.d.ts +12 -0
- package/dist/routing/server_adapter/middleware_adapter.d.ts.map +1 -0
- package/dist/routing/server_adapter/server_adapter.d.ts +5 -0
- package/dist/routing/server_adapter/server_adapter.d.ts.map +1 -0
- package/dist/routing/shadow/shadow_route.d.ts +2 -0
- package/dist/routing/shadow/shadow_route.d.ts.map +1 -0
- package/dist/routing/url_parser.d.ts +12 -0
- package/dist/routing/url_parser.d.ts.map +1 -0
- package/dist/server/server.d.ts +5 -0
- package/dist/server/server.d.ts.map +1 -0
- package/dist/shared.cjs +2 -0
- package/dist/shared.cjs.map +1 -0
- package/dist/shared.d.ts +2 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.mjs +5 -0
- package/dist/shared.mjs.map +1 -0
- package/dist/types/Callback.d.ts +2 -0
- package/dist/types/Callback.d.ts.map +1 -0
- package/dist/types/FetchMiddleware.d.ts +21 -0
- package/dist/types/FetchMiddleware.d.ts.map +1 -0
- package/dist/vite-env.d.ts +19 -0
- package/dist-ssr/client/client.d.ts +8 -0
- package/dist-ssr/client/client.d.ts.map +1 -0
- package/dist-ssr/client/events.d.ts +4 -0
- package/dist-ssr/client/events.d.ts.map +1 -0
- package/dist-ssr/client/index.d.ts +6 -0
- package/dist-ssr/client/index.d.ts.map +1 -0
- package/dist-ssr/client/navigation.svelte.d.ts +4 -0
- package/dist-ssr/client/navigation.svelte.d.ts.map +1 -0
- package/dist-ssr/client/page.svelte.d.ts +4 -0
- package/dist-ssr/client/page.svelte.d.ts.map +1 -0
- package/dist-ssr/index.d.ts +3 -0
- package/dist-ssr/index.d.ts.map +1 -0
- package/dist-ssr/options/ServerOptions.d.ts +16 -0
- package/dist-ssr/options/ServerOptions.d.ts.map +1 -0
- package/dist-ssr/parser/IRoute.d.ts +31 -0
- package/dist-ssr/parser/IRoute.d.ts.map +1 -0
- package/dist-ssr/parser/openapi.d.ts +9 -0
- package/dist-ssr/parser/openapi.d.ts.map +1 -0
- package/dist-ssr/parser/path.d.ts +9 -0
- package/dist-ssr/parser/path.d.ts.map +1 -0
- package/dist-ssr/path-CyGuWUeq.cjs +68 -0
- package/dist-ssr/path-CyGuWUeq.cjs.map +1 -0
- package/dist-ssr/path-ODk1FhWY.js +69 -0
- package/dist-ssr/path-ODk1FhWY.js.map +1 -0
- package/dist-ssr/routing/base.d.ts +10 -0
- package/dist-ssr/routing/base.d.ts.map +1 -0
- package/dist-ssr/routing/component_loader/component_loader.d.ts +27 -0
- package/dist-ssr/routing/component_loader/component_loader.d.ts.map +1 -0
- package/dist-ssr/routing/component_loader/component_manager.d.ts +12 -0
- package/dist-ssr/routing/component_loader/component_manager.d.ts.map +1 -0
- package/dist-ssr/routing/html_render/html_render.d.ts +20 -0
- package/dist-ssr/routing/html_render/html_render.d.ts.map +1 -0
- package/dist-ssr/routing/manifest/base.d.ts +11 -0
- package/dist-ssr/routing/manifest/base.d.ts.map +1 -0
- package/dist-ssr/routing/manifest/store.svelte.d.ts +3 -0
- package/dist-ssr/routing/manifest/store.svelte.d.ts.map +1 -0
- package/dist-ssr/routing/page.d.ts +7 -0
- package/dist-ssr/routing/page.d.ts.map +1 -0
- package/dist-ssr/routing/server_adapter/api_adapter.d.ts +23 -0
- package/dist-ssr/routing/server_adapter/api_adapter.d.ts.map +1 -0
- package/dist-ssr/routing/server_adapter/express_server_adapter.d.ts +20 -0
- package/dist-ssr/routing/server_adapter/express_server_adapter.d.ts.map +1 -0
- package/dist-ssr/routing/server_adapter/middleware_adapter.d.ts +12 -0
- package/dist-ssr/routing/server_adapter/middleware_adapter.d.ts.map +1 -0
- package/dist-ssr/routing/server_adapter/server_adapter.d.ts +5 -0
- package/dist-ssr/routing/server_adapter/server_adapter.d.ts.map +1 -0
- package/dist-ssr/routing/shadow/shadow_route.d.ts +2 -0
- package/dist-ssr/routing/shadow/shadow_route.d.ts.map +1 -0
- package/dist-ssr/routing/url_parser.d.ts +12 -0
- package/dist-ssr/routing/url_parser.d.ts.map +1 -0
- package/dist-ssr/server/server.d.ts +5 -0
- package/dist-ssr/server/server.d.ts.map +1 -0
- package/dist-ssr/server.cjs +876 -0
- package/dist-ssr/server.cjs.map +1 -0
- package/dist-ssr/server.d.ts +2 -0
- package/dist-ssr/server.js +838 -0
- package/dist-ssr/server.js.map +1 -0
- package/dist-ssr/shared.cjs +31 -0
- package/dist-ssr/shared.cjs.map +1 -0
- package/dist-ssr/shared.d.ts +2 -0
- package/dist-ssr/shared.d.ts.map +1 -0
- package/dist-ssr/shared.js +31 -0
- package/dist-ssr/shared.js.map +1 -0
- package/dist-ssr/types/Callback.d.ts +2 -0
- package/dist-ssr/types/Callback.d.ts.map +1 -0
- package/dist-ssr/types/FetchMiddleware.d.ts +21 -0
- package/dist-ssr/types/FetchMiddleware.d.ts.map +1 -0
- package/dist-ssr/vite-env.d.ts +19 -0
- package/loaders/loader.mjs +20 -0
- package/package.json +121 -0
- package/schema.json +333 -0
package/README.md
ADDED
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
# @noego/forge
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@noego/forge)
|
|
4
|
+
[](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 @@
|
|
|
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 @@
|
|
|
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"}
|