@lolyjs/core 0.2.0-alpha.1 → 0.2.0-alpha.10

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/LICENCE.md ADDED
@@ -0,0 +1,9 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 LolyJS.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -6,13 +6,29 @@
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/@lolyjs/core?style=flat-square)](https://www.npmjs.com/package/@lolyjs/core)
8
8
  [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg?style=flat-square)](https://opensource.org/licenses/ISC)
9
+ ![Downloads](https://img.shields.io/npm/dm/@lolyjs/core)
10
+ <br>
11
+ [![Alpha](https://img.shields.io/badge/status-alpha-red.svg)](https://github.com/MenvielleValen/loly-framework)
12
+ [![Expermiental](https://img.shields.io/badge/phase-experimental-black.svg)](https://github.com/MenvielleValen/loly-framework)
9
13
 
10
- *Built with React 19, Express, Rspack, Socket.IO, and TypeScript*
14
+ _Built with React 19, Express, Rspack, Socket.IO, and TypeScript_
11
15
 
12
16
  </div>
13
17
 
14
18
  ---
15
19
 
20
+ ## Getting Started
21
+
22
+ Create a new Loly application in seconds:
23
+
24
+ ```bash
25
+ npx create-loly-app mi-app
26
+ ```
27
+
28
+ This will create a new project with all the necessary files and dependencies. For more information about the CLI, visit the [@lolyjs/cli package](https://www.npmjs.com/package/@lolyjs/cli).
29
+
30
+ ---
31
+
16
32
  ## Overview
17
33
 
18
34
  Loly is a full-stack React framework that combines the simplicity of file-based routing with powerful server-side rendering, static site generation, and unique features like native WebSocket support and route-level middlewares.
@@ -55,7 +71,7 @@ import type { ServerLoader } from "@lolyjs/core";
55
71
 
56
72
  export const getServerSideProps: ServerLoader = async (ctx) => {
57
73
  const data = await fetchData();
58
-
74
+
59
75
  return {
60
76
  props: { data },
61
77
  metadata: {
@@ -131,6 +147,7 @@ socket.emit("message", { text: "Hello!" });
131
147
  ```
132
148
 
133
149
  **Key Benefits:**
150
+
134
151
  - Automatic namespace creation from file structure
135
152
  - Same routing pattern as pages and APIs
136
153
  - Built-in broadcasting helpers (`emit`, `broadcast`, `emitTo`, `emitToClient`)
@@ -196,6 +213,7 @@ export async function GET(ctx: ApiContext) {
196
213
  ```
197
214
 
198
215
  **Key Benefits:**
216
+
199
217
  - Middlewares execute before loaders/handlers
200
218
  - Share data via `ctx.locals`
201
219
  - Method-specific middlewares for APIs
@@ -205,11 +223,11 @@ export async function GET(ctx: ApiContext) {
205
223
 
206
224
  Routes are automatically created from your file structure:
207
225
 
208
- | File Path | Route |
209
- |-----------|-------|
210
- | `app/page.tsx` | `/` |
211
- | `app/about/page.tsx` | `/about` |
212
- | `app/blog/[slug]/page.tsx` | `/blog/:slug` |
226
+ | File Path | Route |
227
+ | ----------------------------- | --------------------- |
228
+ | `app/page.tsx` | `/` |
229
+ | `app/about/page.tsx` | `/about` |
230
+ | `app/blog/[slug]/page.tsx` | `/blog/:slug` |
213
231
  | `app/post/[...path]/page.tsx` | `/post/*` (catch-all) |
214
232
 
215
233
  **Nested Layouts:**
@@ -265,7 +283,7 @@ export const dynamic = "force-static" as const;
265
283
 
266
284
  export const generateStaticParams: GenerateStaticParams = async () => {
267
285
  const posts = await getAllPosts();
268
- return posts.map(post => ({ slug: post.slug }));
286
+ return posts.map((post) => ({ slug: post.slug }));
269
287
  };
270
288
 
271
289
  export const getServerSideProps: ServerLoader = async (ctx) => {
@@ -282,11 +300,11 @@ import { useState, useEffect } from "react";
282
300
 
283
301
  export default function Dashboard() {
284
302
  const [data, setData] = useState(null);
285
-
303
+
286
304
  useEffect(() => {
287
305
  fetchData().then(setData);
288
306
  }, []);
289
-
307
+
290
308
  return <div>{data}</div>;
291
309
  }
292
310
  ```
@@ -427,10 +445,10 @@ import type { ServerLoader } from "@lolyjs/core";
427
445
 
428
446
  export const getServerSideProps: ServerLoader = async (ctx) => {
429
447
  const { req, res, params, pathname, locals } = ctx;
430
-
448
+
431
449
  // Fetch data
432
450
  const data = await fetchData();
433
-
451
+
434
452
  // Redirect
435
453
  return {
436
454
  redirect: {
@@ -438,10 +456,10 @@ export const getServerSideProps: ServerLoader = async (ctx) => {
438
456
  permanent: true,
439
457
  },
440
458
  };
441
-
459
+
442
460
  // Not found
443
461
  return { notFound: true };
444
-
462
+
445
463
  // Return props
446
464
  return {
447
465
  props: { data },
@@ -487,13 +505,13 @@ export const events = [
487
505
  name: "custom-event",
488
506
  handler: (ctx: WssContext) => {
489
507
  const { socket, data, actions } = ctx;
490
-
508
+
491
509
  // Emit to all clients
492
510
  actions.emit("response", { message: "Hello" });
493
-
511
+
494
512
  // Broadcast to all except sender
495
513
  actions.broadcast("notification", data);
496
-
514
+
497
515
  // Emit to specific socket
498
516
  actions.emitTo(socketId, "private", data);
499
517
  },
@@ -509,11 +527,11 @@ import { revalidate } from "@lolyjs/core/client-cache";
509
527
 
510
528
  export default function Page() {
511
529
  const { params, props } = usePageProps();
512
-
530
+
513
531
  const handleRefresh = async () => {
514
532
  await revalidate(); // Refresh current page data
515
533
  };
516
-
534
+
517
535
  return <div>{/* Your UI */}</div>;
518
536
  }
519
537
  ```
@@ -582,9 +600,7 @@ import { ServerConfig } from "@lolyjs/core";
582
600
  export const config = (env: string): ServerConfig => {
583
601
  return {
584
602
  bodyLimit: "1mb",
585
- corsOrigin: env === "production"
586
- ? ["https://yourdomain.com"]
587
- : "*",
603
+ corsOrigin: env === "production" ? ["https://yourdomain.com"] : "*",
588
604
  rateLimit: {
589
605
  windowMs: 15 * 60 * 1000,
590
606
  max: 1000,
@@ -610,10 +626,10 @@ export async function init({
610
626
  }) {
611
627
  // Initialize database connection
612
628
  await connectToDatabase();
613
-
629
+
614
630
  // Setup external services
615
631
  await setupExternalServices();
616
-
632
+
617
633
  // Any other initialization logic
618
634
  console.log("Server initialized successfully");
619
635
  }
@@ -665,6 +681,7 @@ npm run build
665
681
  ```
666
682
 
667
683
  This generates:
684
+
668
685
  - Client bundle (`.loly/client`)
669
686
  - Static pages if using SSG (`.loly/ssg`)
670
687
  - Server code (`.loly/server`)
@@ -702,18 +719,14 @@ import { validate, safeValidate, ValidationError } from "@lolyjs/core";
702
719
 
703
720
  // Security
704
721
  import { sanitizeString, sanitizeObject } from "@lolyjs/core";
705
- import {
722
+ import {
706
723
  createRateLimiter,
707
724
  defaultRateLimiter,
708
- strictRateLimiter
725
+ strictRateLimiter,
709
726
  } from "@lolyjs/core";
710
727
 
711
728
  // Logging
712
- import {
713
- logger,
714
- createModuleLogger,
715
- getRequestLogger
716
- } from "@lolyjs/core";
729
+ import { logger, createModuleLogger, getRequestLogger } from "@lolyjs/core";
717
730
 
718
731
  // Client
719
732
  import { Link } from "@lolyjs/core/components";
@@ -1,4 +1,5 @@
1
1
  declare const WINDOW_DATA_KEY = "__FW_DATA__";
2
+ declare const ROUTER_DATA_KEY = "__LOLY_ROUTER_DATA__";
2
3
 
3
4
  type InitialData = {
4
5
  pathname: string;
@@ -12,9 +13,15 @@ type InitialData = {
12
13
  error?: boolean;
13
14
  theme?: string;
14
15
  };
16
+ type RouterData = {
17
+ pathname: string;
18
+ params: Record<string, string>;
19
+ searchParams: Record<string, unknown>;
20
+ };
15
21
  declare global {
16
22
  interface Window {
17
23
  [WINDOW_DATA_KEY]?: InitialData;
24
+ [ROUTER_DATA_KEY]?: RouterData;
18
25
  }
19
26
  }
20
27
  type ClientLoadedComponents = {
@@ -1,4 +1,5 @@
1
1
  declare const WINDOW_DATA_KEY = "__FW_DATA__";
2
+ declare const ROUTER_DATA_KEY = "__LOLY_ROUTER_DATA__";
2
3
 
3
4
  type InitialData = {
4
5
  pathname: string;
@@ -12,9 +13,15 @@ type InitialData = {
12
13
  error?: boolean;
13
14
  theme?: string;
14
15
  };
16
+ type RouterData = {
17
+ pathname: string;
18
+ params: Record<string, string>;
19
+ searchParams: Record<string, unknown>;
20
+ };
15
21
  declare global {
16
22
  interface Window {
17
23
  [WINDOW_DATA_KEY]?: InitialData;
24
+ [ROUTER_DATA_KEY]?: RouterData;
18
25
  }
19
26
  }
20
27
  type ClientLoadedComponents = {
package/dist/cli.cjs CHANGED
@@ -47,17 +47,19 @@ __export(globals_exports, {
47
47
  NOT_FOUND_FILE_PREFIX: () => NOT_FOUND_FILE_PREFIX,
48
48
  NOT_FOUND_PATTERN: () => NOT_FOUND_PATTERN,
49
49
  PAGE_FILE_NAME: () => PAGE_FILE_NAME,
50
+ ROUTER_DATA_KEY: () => ROUTER_DATA_KEY,
50
51
  STATIC_PATH: () => STATIC_PATH,
51
52
  STYLE_FILE_NAME: () => STYLE_FILE_NAME,
52
53
  WINDOW_DATA_KEY: () => WINDOW_DATA_KEY
53
54
  });
54
- var BUILD_FOLDER_NAME, STYLE_FILE_NAME, WINDOW_DATA_KEY, APP_CONTAINER_ID, STATIC_PATH, NOT_FOUND_PATTERN, ERROR_PATTERN, NOT_FOUND_CHUNK_KEY, ERROR_CHUNK_KEY, NOT_FOUND_FILE_PREFIX, ERROR_FILE_PREFIX, PAGE_FILE_NAME, LAYOUT_FILE_NAME, FAVICON_PATH, CLIENT_CSS_PATH, CLIENT_JS_PATH, ASSETS_BASE_DIR;
55
+ var BUILD_FOLDER_NAME, STYLE_FILE_NAME, WINDOW_DATA_KEY, ROUTER_DATA_KEY, APP_CONTAINER_ID, STATIC_PATH, NOT_FOUND_PATTERN, ERROR_PATTERN, NOT_FOUND_CHUNK_KEY, ERROR_CHUNK_KEY, NOT_FOUND_FILE_PREFIX, ERROR_FILE_PREFIX, PAGE_FILE_NAME, LAYOUT_FILE_NAME, FAVICON_PATH, CLIENT_CSS_PATH, CLIENT_JS_PATH, ASSETS_BASE_DIR;
55
56
  var init_globals = __esm({
56
57
  "constants/globals.ts"() {
57
58
  "use strict";
58
59
  BUILD_FOLDER_NAME = ".loly";
59
60
  STYLE_FILE_NAME = "styles.css";
60
61
  WINDOW_DATA_KEY = "__FW_DATA__";
62
+ ROUTER_DATA_KEY = "__LOLY_ROUTER_DATA__";
61
63
  APP_CONTAINER_ID = "__app";
62
64
  STATIC_PATH = "/static";
63
65
  NOT_FOUND_PATTERN = "/not-found";
@@ -1490,6 +1492,7 @@ function createDocumentTree(options) {
1490
1492
  const {
1491
1493
  appTree,
1492
1494
  initialData,
1495
+ routerData,
1493
1496
  meta,
1494
1497
  titleFallback,
1495
1498
  descriptionFallback,
@@ -1527,6 +1530,9 @@ function createDocumentTree(options) {
1527
1530
  ...initialData,
1528
1531
  theme
1529
1532
  });
1533
+ const routerSerialized = JSON.stringify({
1534
+ ...routerData
1535
+ });
1530
1536
  const documentTree = import_react.default.createElement(
1531
1537
  "html",
1532
1538
  { lang },
@@ -1574,6 +1580,12 @@ function createDocumentTree(options) {
1574
1580
  dangerouslySetInnerHTML: {
1575
1581
  __html: `window.${WINDOW_DATA_KEY} = ${serialized};`
1576
1582
  }
1583
+ }),
1584
+ import_react.default.createElement("script", {
1585
+ nonce,
1586
+ dangerouslySetInnerHTML: {
1587
+ __html: `window.${ROUTER_DATA_KEY} = ${routerSerialized};`
1588
+ }
1577
1589
  })
1578
1590
  );
1579
1591
  return documentTree;
@@ -1596,6 +1608,15 @@ function buildInitialData(urlPath, params, loaderResult) {
1596
1608
  };
1597
1609
  }
1598
1610
 
1611
+ // modules/rendering/routerData/index.ts
1612
+ var buildRouterData = (req) => {
1613
+ return {
1614
+ pathname: req.path,
1615
+ params: req.params,
1616
+ searchParams: req.query
1617
+ };
1618
+ };
1619
+
1599
1620
  // modules/build/ssg/renderer.ts
1600
1621
  init_globals();
1601
1622
  async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params) {
@@ -1649,10 +1670,12 @@ async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params)
1649
1670
  return;
1650
1671
  }
1651
1672
  const initialData = buildInitialData(urlPath, params, loaderResult);
1673
+ const routerData = buildRouterData(req);
1652
1674
  const appTree = buildAppTree(route, params, initialData.props);
1653
1675
  const documentTree = createDocumentTree({
1654
1676
  appTree,
1655
1677
  initialData,
1678
+ routerData,
1656
1679
  meta: loaderResult.metadata,
1657
1680
  titleFallback: "My Framework Dev",
1658
1681
  descriptionFallback: "Static page generated by @lolyjs/core.",
@@ -4454,6 +4477,7 @@ async function handlePageRequestInternal(options) {
4454
4477
  }
4455
4478
  }
4456
4479
  const matched = matchRoute(routes, urlPath);
4480
+ const routerData = buildRouterData(req);
4457
4481
  if (!matched) {
4458
4482
  if (notFoundPage) {
4459
4483
  const ctx2 = {
@@ -4474,6 +4498,7 @@ async function handlePageRequestInternal(options) {
4474
4498
  const documentTree2 = createDocumentTree({
4475
4499
  appTree: appTree2,
4476
4500
  initialData: initialData2,
4501
+ routerData,
4477
4502
  meta: loaderResult2.metadata ?? null,
4478
4503
  titleFallback: "Not found",
4479
4504
  descriptionFallback: "Loly demo",
@@ -4581,6 +4606,7 @@ async function handlePageRequestInternal(options) {
4581
4606
  const documentTree = createDocumentTree({
4582
4607
  appTree,
4583
4608
  initialData,
4609
+ routerData,
4584
4610
  meta: loaderResult.metadata,
4585
4611
  titleFallback: "Loly framework",
4586
4612
  descriptionFallback: "Loly demo",
@@ -4638,6 +4664,7 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
4638
4664
  loaderResult.theme = theme;
4639
4665
  }
4640
4666
  const initialData = buildInitialData(req.path, { error: String(error) }, loaderResult);
4667
+ const routerData = buildRouterData(req);
4641
4668
  initialData.error = true;
4642
4669
  if (isDataReq) {
4643
4670
  res.statusCode = 500;
@@ -4668,6 +4695,7 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
4668
4695
  const documentTree = createDocumentTree({
4669
4696
  appTree,
4670
4697
  initialData,
4698
+ routerData,
4671
4699
  meta: loaderResult.metadata ?? null,
4672
4700
  titleFallback: "Error",
4673
4701
  descriptionFallback: "An error occurred",
@@ -5093,7 +5121,7 @@ async function startProdServer(options = {}) {
5093
5121
  var import_client5 = require("react-dom/client");
5094
5122
 
5095
5123
  // modules/runtime/client/AppShell.tsx
5096
- var import_react2 = require("react");
5124
+ var import_react3 = require("react");
5097
5125
 
5098
5126
  // modules/runtime/client/RouterView.tsx
5099
5127
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -5122,6 +5150,10 @@ var dataCache = cacheStore.data;
5122
5150
  var pathIndex = cacheStore.index;
5123
5151
  var lru = cacheStore.lru;
5124
5152
 
5153
+ // modules/runtime/client/RouterContext.tsx
5154
+ var import_react2 = require("react");
5155
+ var RouterContext = (0, import_react2.createContext)(null);
5156
+
5125
5157
  // modules/runtime/client/AppShell.tsx
5126
5158
  var import_jsx_runtime2 = require("react/jsx-runtime");
5127
5159