solidstep 0.1.0 → 0.1.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 (2) hide show
  1. package/README.md +536 -18
  2. package/package.json +7 -1
package/README.md CHANGED
@@ -1,18 +1,536 @@
1
- # @varlabs/create-solidstep
2
- Next Step SolidJS CLI for building web applications.
3
-
4
- ## Getting Started
5
-
6
- To get started with a new SolidJS project using the Next Step CLI, run the following command:
7
-
8
- ```bash
9
- npx @varlabs/create-solidstep@latest my-app
10
- ```
11
-
12
- or
13
-
14
- ```bash
15
- pnpm dlx @varlabs/create-solidstep@latest my-app
16
- ```
17
-
18
- Replace `my-app` with the name of your project or if you don't specify a name, it will create a project in the current directory.
1
+ # SolidStep
2
+
3
+ Next Solid Step towards a more performant web - A full-stack SolidJS framework for building modern web applications with file-based routing, SSR, and built-in security.
4
+
5
+ ## Features
6
+
7
+ - 🚀 **File-based Routing** - Automatic routing based on your file structure
8
+ - ⚡ **Server-Side Rendering (SSR)** - Fast initial page loads with full SSR support
9
+ - 🔄 **Data Loading** - Built-in loaders for efficient data fetching
10
+ - 🎨 **Layouts & Groups** - Nested layouts and parallel route groups
11
+ - 🛡️ **Security First** - Built-in CSP, CORS, CSRF, and cookie utilities
12
+ - 🎯 **Server Actions** - Type-safe server functions with automatic serialization
13
+ - ⚙️ **Middleware Support** - Request/response interceptors
14
+ - 📦 **Caching** - Built-in page-level caching
15
+ - 🔥 **Hot Module Replacement** - Fast development experience
16
+ - 📝 **TypeScript** - Full TypeScript support out of the box
17
+
18
+ ## Getting Started
19
+
20
+ ### Create a New Project
21
+
22
+ ```bash
23
+ [npx | yarn dlx | pnpm dlx | bunx] @varlabs/create-solidstep@latest my-app
24
+ cd my-app
25
+ [npm | yarn | pnpm | bun] install
26
+ [npm | yarn | pnpm | bun] run dev
27
+ ```
28
+
29
+ ### Special Files
30
+
31
+ - `page.tsx` - Page component
32
+ - `layout.tsx` - Layout wrapper
33
+ - `loading.tsx` - Loading state (Streaming - optional)
34
+ - `error.tsx` - Error boundary (optional)
35
+ - `not-found.tsx` - 404 page (root only - optional)
36
+ - `route.ts` - API route handler
37
+ - `middleware.ts` - Request middleware
38
+
39
+ **A route is defined by either the presence of a `page.tsx` or `route.ts` file in a directory.**
40
+
41
+ ### Configuration
42
+
43
+ Configure your app in `app.config.ts`:
44
+
45
+ ```tsx
46
+ import { defineConfig } from 'solidstep';
47
+
48
+ export default defineConfig({
49
+ server: {
50
+ port: 3000,
51
+ preset: 'node',
52
+ },
53
+ plugins: [
54
+ {
55
+ type: 'both',
56
+ plugin: myVitePlugin(),
57
+ },
58
+ ],
59
+ });
60
+ ```
61
+
62
+ ### Project Structure
63
+
64
+ ```
65
+ my-app/
66
+ ├── app/
67
+ │ ├── page.tsx # Home page (/)
68
+ │ ├── layout.tsx # Root layout
69
+ │ ├── middleware.ts # Request middleware
70
+ │ ├── about/
71
+ │ │ └── page.tsx # About page (/about)
72
+ │ ├── (admin)/
73
+ │ | └── dashboard/
74
+ │ | └── page.tsx # Group route (/dashboard)
75
+ │ └── blog/
76
+ │ ├── layout.tsx # Blog layout
77
+ │ ├── page.tsx # Blog index (/blog)
78
+ │ └── [slug]/
79
+ │ └── page.tsx # Dynamic route (/blog/:slug)
80
+ ├── public/
81
+ │ └── favicon.ico
82
+ ├── app.config.ts
83
+ └── package.json
84
+ ```
85
+
86
+ ## Core Concepts
87
+
88
+ ### Layouts
89
+
90
+ Wrap multiple pages with shared UI:
91
+
92
+ ```tsx
93
+ export default function BlogLayout(props: { children: any }) {
94
+ return (
95
+ <div>
96
+ <nav>Blog Navigation</nav>
97
+ {props.children()}
98
+ </div>
99
+ );
100
+ }
101
+ ```
102
+
103
+ ### Pages
104
+
105
+ Create a `page.tsx` file in any directory under `app/` to define a route:
106
+
107
+ ```tsx
108
+ export default function HomePage() {
109
+ return <h1>Welcome to SolidStep!</h1>;
110
+ }
111
+ ```
112
+
113
+ **Similar to NextJS, only content returned by a `page` or `route` is sent to the client**
114
+
115
+ ### Group Routes
116
+ Use parentheses to group routes without affecting the URL:
117
+
118
+ ```app/
119
+ ├── (admin)/
120
+ │ └── dashboard/
121
+ │ └── page.tsx // matches /dashboard
122
+ └── (user)/
123
+ └── profile/
124
+ └── page.tsx // matches /profile
125
+ ```
126
+
127
+ ### Dynamic Routes
128
+
129
+ Use square brackets for dynamic segments:
130
+
131
+ ```tsx
132
+ // app/blog/[slug]/page.tsx - matches /blog/my-post, /blog/another-post, etc.
133
+
134
+ export default function BlogPost(props: { routeParams: { slug: string } }) {
135
+ return <h1>Post: {props.routeParams.slug}</h1>;
136
+ }
137
+ ```
138
+
139
+ **Catch-all routes:**
140
+ ```tsx
141
+ // app/docs/[...path]/page.tsx - matches /docs/a, /docs/a/b, etc.
142
+ ```
143
+
144
+ **Catch-all routes (Optional):**
145
+ ```tsx
146
+ // app/docs/[[...path]]/page.tsx - matches /docs, /docs/a, /docs/a/b, etc.
147
+ ```
148
+
149
+ ### Parallel Routes (Groups)
150
+
151
+ Render multiple sections simultaneously:
152
+
153
+ ```
154
+ app/
155
+ ├── layout.tsx
156
+ ├── page.tsx
157
+ └── @graph1/
158
+ └── page.tsx
159
+ └── @graph2/
160
+ └── page.tsx
161
+ ```
162
+
163
+ ```tsx
164
+ export default function RootLayout(props: {
165
+ children: any;
166
+ slots: { graph1: any; graph2: any; };
167
+ }) {
168
+ return (
169
+ <div>
170
+ <main>
171
+ {props.children()}
172
+ <aside>
173
+ <div>{props.slots.graph1()}</div>
174
+ <div>{props.slots.graph2()}</div>
175
+ </aside>
176
+ </main>
177
+ </div>
178
+ );
179
+ }
180
+ ```
181
+
182
+ ### Data Loading
183
+
184
+ Use `defineLoader` to fetch data on the server:
185
+
186
+ ```tsx
187
+ import { defineLoader, type LoaderDataFromFunction } from 'solidstep/utils/loader';
188
+
189
+ export const loader = defineLoader(async (request) => {
190
+ const posts = await fetchPosts();
191
+ return { posts };
192
+ });
193
+
194
+ type LoaderData = LoaderDataFromFunction<typeof loader>;
195
+
196
+ export default function BlogPage(props: { loaderData: LoaderData }) {
197
+ return (
198
+ <ul>
199
+ <For each={props.loaderData.posts}>
200
+ {(post) => <li>{post.title}</li>}
201
+ </For>
202
+ </ul>
203
+ );
204
+ }
205
+ ```
206
+
207
+ ### Server Actions
208
+
209
+ Create type-safe server functions:
210
+
211
+ ```tsx
212
+ 'use server';
213
+
214
+ export const createPost = async (data: { title: string }) => {
215
+ await db.posts.create(data);
216
+ return { success: true };
217
+ };
218
+ ```
219
+
220
+ Call from client:
221
+
222
+ ```tsx
223
+ import { createPost } from './actions';
224
+
225
+ function CreatePostForm() {
226
+ const handleSubmit = async (e: Event) => {
227
+ e.preventDefault();
228
+ await createPost({ title: 'My Post' });
229
+ };
230
+
231
+ return <form onSubmit={handleSubmit}>...</form>;
232
+ }
233
+ ```
234
+
235
+ ### Metadata
236
+
237
+ Define metadata for SEO:
238
+
239
+ ```tsx
240
+ import type { Meta } from 'solidstep/utils/types';
241
+
242
+ export const generateMeta = async () => {
243
+ return {
244
+ title: {
245
+ type: 'title',
246
+ content: 'My Site',
247
+ attributes: {},
248
+ },
249
+ description: {
250
+ type: 'meta',
251
+ attributes: {
252
+ name: 'description',
253
+ content: 'My awesome site',
254
+ },
255
+ },
256
+ // manifest
257
+ manifest: {
258
+ type: 'link',
259
+ attributes: {
260
+ rel: 'manifest',
261
+ href: '/site.webmanifest',
262
+ },
263
+ },
264
+ // google fonts
265
+ 'google-font-link': {
266
+ type: 'link',
267
+ attributes: {
268
+ rel: 'preconnect',
269
+ href: 'https://fonts.googleapis.com'
270
+ }
271
+ },
272
+ 'gstatic-font-link': {
273
+ type: 'link',
274
+ attributes: {
275
+ rel: 'preconnect',
276
+ href: 'https://fonts.gstatic.com',
277
+ crossorigin: ''
278
+ }
279
+ },
280
+ 'inter-font': {
281
+ type: 'link',
282
+ attributes: {
283
+ rel: 'stylesheet',
284
+ href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap'
285
+ }
286
+ },
287
+ // external js
288
+ 'analytics-script': {
289
+ type: 'script',
290
+ attributes: {
291
+ src: 'analytics.js',
292
+ defer: true,
293
+ }
294
+ }
295
+ } satisfies Meta;
296
+ };
297
+ ```
298
+
299
+ ### Middleware
300
+
301
+ Intercept and modify requests:
302
+
303
+ ```tsx
304
+ import { eventHandler } from 'vinxi/http';
305
+
306
+ export default eventHandler((event) => {
307
+ event.locals = { user: getCurrentUser() };
308
+
309
+ // you can also modify request/response here
310
+ // also include cors, csrf, csp logic if needed
311
+ });
312
+ ```
313
+
314
+ ### Page Options
315
+
316
+ Configure page-level caching:
317
+
318
+ ```tsx
319
+ export const options = {
320
+ cache: {
321
+ ttl: 60000,
322
+ },
323
+ };
324
+ ```
325
+
326
+ ## API Routes
327
+
328
+ Create REST endpoints:
329
+
330
+ ```tsx
331
+ export async function GET(request: Request, { params }: any) {
332
+ const posts = await fetchPosts();
333
+ return new Response(JSON.stringify(posts), {
334
+ headers: { 'Content-Type': 'application/json' },
335
+ });
336
+ }
337
+
338
+ export async function POST(request: Request) {
339
+ const data = await request.json();
340
+ }
341
+ ```
342
+
343
+ ## Utilities
344
+
345
+ ### Cookies
346
+ ```tsx
347
+ import { getCookie, setCookie } from 'solidstep/utils/cookies';
348
+
349
+ export const loader = defineLoader(async () => {
350
+ const userData = await getCookie();
351
+
352
+ if (!userData) {
353
+ return [];
354
+ }
355
+
356
+ const userId = userData.id;
357
+
358
+ const { data, error } = await getDocumentsByUserId(userId);
359
+
360
+ if (error || !data) {
361
+ return [];
362
+ }
363
+
364
+ return data as Document[];
365
+ });
366
+
367
+ const action = async () => {
368
+ 'use server';
369
+
370
+ await setCookie('session', JSON.stringify({ id: 'user-id' }), { httpOnly: true, secure: true, maxAge: 3600 });
371
+
372
+ return { success: true };
373
+ };
374
+ ```
375
+
376
+ ### CORS
377
+ ```tsx
378
+ import { cors } from 'solidstep/utils/cors';
379
+
380
+ const trustedOrigins = ['https://example.com', 'https://another-example.com'];
381
+
382
+ const corsMiddleware = cors(trustedOrigins);
383
+
384
+ ...
385
+
386
+ const corsHeaders = corsMiddleware(origin, event.node.req.method === 'OPTIONS');
387
+
388
+ ...
389
+ ```
390
+
391
+ ### CSP
392
+ ```tsx
393
+ import { createBasePolicy, serializePolicy, withNonce } from '@varlabs/solidstep/utils/csp';
394
+
395
+ let cspPolicy = createBasePolicy();
396
+
397
+ ...
398
+
399
+ cspPolicy = withNonce(cspPolicy, nonce);
400
+
401
+ ...
402
+
403
+ event.response.headers.set('Content-Security-Policy', serializePolicy(cspPolicy));
404
+
405
+ ...
406
+ ```
407
+
408
+ ### CSRF Protection
409
+ ```tsx
410
+ import { csrf } from '@varlabs/solidstep/utils/csrf';
411
+
412
+ const trustedOrigins = ['https://example.com', 'https://another-example.com'];
413
+
414
+ const csrfMiddleware = csrf(trustedOrigins);
415
+
416
+ ...
417
+
418
+ const csrfResult = csrfMiddleware(
419
+ event.node.req.method,
420
+ requestUrl,
421
+ origin,
422
+ event.node.req.headers.referer
423
+ );
424
+
425
+ if (!csrfResult.success) {
426
+ event.node.res.statusCode = 403; // Forbidden
427
+ event.node.res.end(csrfResult.message);
428
+ return;
429
+ }
430
+ ```
431
+
432
+ ### Redirects
433
+ ```tsx
434
+ import { redirect } from 'solidstep/utils/redirect';
435
+
436
+ export const loader = defineLoader(async () => {
437
+ redirect('/login');
438
+ });
439
+
440
+ // or in client
441
+ export function MyComponent() {
442
+ const handleClick = () => {
443
+ redirect('/dashboard');
444
+ };
445
+
446
+ return <button onClick={handleClick}>Go to Dashboard</button>;
447
+ }
448
+ ```
449
+
450
+ ### Error Handling
451
+ ```tsx
452
+ // first define an error collection
453
+ import { createErrorFactory } from 'solidstep/utils/error-handler';
454
+
455
+ export const createError = createErrorFactory({
456
+ 'db-query-error': {
457
+ message: 'Something went wrong with the database query, not idea what',
458
+ severity: 'high',
459
+ action: (error) => {
460
+ console.error('Generic DB query error', error);
461
+ throw error;
462
+ },
463
+ },
464
+ 'auth-error': {
465
+ message: 'User authentication failed',
466
+ severity: 'high',
467
+ action: (error) => {
468
+ console.error('User authentication error', error);
469
+ throw error;
470
+ },
471
+ },
472
+ 'service-error': {
473
+ message:
474
+ 'Some service (external or internal that is interfacing with the app) failed',
475
+ severity: 'high',
476
+ action: (error) => {
477
+ console.error('Service error', error);
478
+ throw error;
479
+ },
480
+ },
481
+ });
482
+
483
+ // then use it in your loaders, actions or routes
484
+ export const loader = defineLoader(async () => {
485
+ const data = await tryCatch(fetchDataFromDB());
486
+ if (data.error) {
487
+ // handle the error using the defined error collection
488
+ createError('db-query-error').action();
489
+
490
+ // or overwrite the defaults
491
+ createError('db-query-error', {
492
+ // customize the error
493
+ message: data.error.message,
494
+ action: (error) => {
495
+ // just log it for example
496
+ console.error('Custom action for DB error', error);
497
+ },
498
+ severity: 'critical',
499
+ cause: data.error,
500
+ metadata: { query: 'SELECT * FROM users' },
501
+ }).action();
502
+
503
+ // defer the definition and the handling
504
+ const error = createError('db-query-error');
505
+ // some logic
506
+ error.action();
507
+
508
+ // or throw the error
509
+ const error = createError('db-query-error', {
510
+ cause: data.error,
511
+ });
512
+ throw error;
513
+ }
514
+ return data.result;
515
+ });
516
+ ```
517
+
518
+ ## Future Plans
519
+ - Support for dynamic site.webmanifest, robots.txt, sitemap.xml, manifest.json, and llms.txt
520
+ - Support loading and error pages for parallel routes
521
+ - Support deferring loaders
522
+ - Image/font optimizations
523
+ - Possible CSR/SPA, SSG, ISR, and PPR
524
+
525
+ ## License
526
+
527
+ MIT
528
+
529
+ ## Links
530
+
531
+ - [GitHub](https://github.com/HamzaKV/solidstep)
532
+ - [SolidJS Documentation](https://www.solidjs.com/)
533
+
534
+ ## Special Mentions
535
+ - Inspired by [Remix](https://remix.run/), [Next.js](https://nextjs.org/), and [TanStack](https://tanstack.com/)
536
+ - Built with [Vite](https://vitejs.dev/), [SolidJS](https://www.solidjs.com/), [Vinxi](https://github.com/nksaraf/vinxi) and [Seroval](https://github.com/lxsmnsyc/seroval)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solidstep",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Next Step SolidJS Framework for building web applications.",
5
5
  "type": "module",
6
6
  "author": "HamzaKV <hamzakv333@gmail.com>",
@@ -54,5 +54,11 @@
54
54
  "rimraf": "^6.0.1",
55
55
  "typescript": "^5.8.3",
56
56
  "vite-plugin-solid": "^2.11.7"
57
+ },
58
+ "engines": {
59
+ "node": ">=20"
60
+ },
61
+ "publishConfig": {
62
+ "access": "public"
57
63
  }
58
64
  }