olova 2.0.55 → 2.0.56

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 (84) hide show
  1. package/README.md +28 -288
  2. package/dist/chunk-23UAGQ6N.js +2208 -0
  3. package/dist/chunk-23UAGQ6N.js.map +1 -0
  4. package/dist/chunk-D7SIC5TC.js +367 -0
  5. package/dist/chunk-D7SIC5TC.js.map +1 -0
  6. package/dist/entry-server.cjs +2341 -0
  7. package/dist/entry-server.cjs.map +1 -0
  8. package/dist/entry-server.js +114 -0
  9. package/dist/entry-server.js.map +1 -0
  10. package/dist/entry-worker.cjs +2354 -0
  11. package/dist/entry-worker.cjs.map +1 -0
  12. package/dist/entry-worker.js +126 -0
  13. package/dist/entry-worker.js.map +1 -0
  14. package/dist/main.cjs +18 -0
  15. package/dist/main.cjs.map +1 -0
  16. package/dist/main.js +16 -0
  17. package/dist/main.js.map +1 -0
  18. package/dist/olova.cjs +1684 -0
  19. package/dist/olova.cjs.map +1 -0
  20. package/dist/olova.d.cts +72 -0
  21. package/dist/olova.d.ts +72 -0
  22. package/dist/olova.js +1325 -0
  23. package/dist/olova.js.map +1 -0
  24. package/dist/performance.cjs +386 -0
  25. package/dist/performance.cjs.map +1 -0
  26. package/dist/performance.js +3 -0
  27. package/dist/performance.js.map +1 -0
  28. package/dist/router.cjs +646 -0
  29. package/dist/router.cjs.map +1 -0
  30. package/dist/router.d.cts +113 -0
  31. package/dist/router.d.ts +113 -0
  32. package/dist/router.js +632 -0
  33. package/dist/router.js.map +1 -0
  34. package/main.tsx +76 -0
  35. package/olova.ts +619 -0
  36. package/package.json +42 -61
  37. package/src/entry-server.tsx +165 -0
  38. package/src/entry-worker.tsx +201 -0
  39. package/src/generator/index.ts +409 -0
  40. package/src/hydration/flight.ts +320 -0
  41. package/src/hydration/index.ts +12 -0
  42. package/src/hydration/types.ts +225 -0
  43. package/src/logger.ts +182 -0
  44. package/src/main.tsx +24 -0
  45. package/src/performance.ts +488 -0
  46. package/src/plugin/index.ts +204 -0
  47. package/src/router/ErrorBoundary.tsx +145 -0
  48. package/src/router/Link.tsx +117 -0
  49. package/src/router/OlovaRouter.tsx +354 -0
  50. package/src/router/Outlet.tsx +8 -0
  51. package/src/router/context.ts +117 -0
  52. package/src/router/index.ts +29 -0
  53. package/src/router/matching.ts +63 -0
  54. package/src/router/router.tsx +23 -0
  55. package/src/router/search-params.ts +29 -0
  56. package/src/scanner/index.ts +116 -0
  57. package/src/types/index.ts +191 -0
  58. package/src/utils/export.ts +85 -0
  59. package/src/utils/index.ts +4 -0
  60. package/src/utils/naming.ts +54 -0
  61. package/src/utils/path.ts +45 -0
  62. package/tsup.config.ts +35 -0
  63. package/CHANGELOG.md +0 -31
  64. package/LICENSE +0 -21
  65. package/dist/index.cjs +0 -883
  66. package/dist/index.cjs.map +0 -1
  67. package/dist/index.d.cts +0 -138
  68. package/dist/index.d.ts +0 -138
  69. package/dist/index.js +0 -832
  70. package/dist/index.js.map +0 -1
  71. package/dist/plugin.cjs +0 -927
  72. package/dist/plugin.cjs.map +0 -1
  73. package/dist/plugin.d.cts +0 -18
  74. package/dist/plugin.d.ts +0 -18
  75. package/dist/plugin.js +0 -894
  76. package/dist/plugin.js.map +0 -1
  77. package/dist/ssg.cjs +0 -637
  78. package/dist/ssg.cjs.map +0 -1
  79. package/dist/ssg.d.cts +0 -191
  80. package/dist/ssg.d.ts +0 -191
  81. package/dist/ssg.js +0 -585
  82. package/dist/ssg.js.map +0 -1
  83. package/dist/types-BT6YsBGO.d.cts +0 -143
  84. package/dist/types-BT6YsBGO.d.ts +0 -143
@@ -0,0 +1,116 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import type { ErrorEntry, LayoutEntry, LoadingEntry, MiddlewareEntry, NotFoundEntry, RouteEntry, ScanResult } from '../types';
4
+ import { isRouteGroup, pathToRoute } from '../utils';
5
+
6
+ const RESERVED_NAMES = new Set([
7
+ 'index', 'layout', 'loading', 'error', '404', 'middleware',
8
+ 'App', 'main', 'route.tree',
9
+ ]);
10
+
11
+ function scanDirectory(
12
+ dir: string,
13
+ rootDir: string,
14
+ extensions: string[],
15
+ routes: RouteEntry[],
16
+ notFoundPages: NotFoundEntry[],
17
+ layouts: LayoutEntry[],
18
+ loadingPages: LoadingEntry[],
19
+ errorPages: ErrorEntry[],
20
+ middlewares: MiddlewareEntry[],
21
+ isRoot = false
22
+ ) {
23
+ if (!fs.existsSync(dir)) return;
24
+
25
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
26
+
27
+ for (const entry of entries) {
28
+ const fullPath = path.join(dir, entry.name);
29
+
30
+ if (entry.isDirectory()) {
31
+ if (entry.name === 'node_modules' || entry.name === 'assets' || entry.name.startsWith('_')) continue;
32
+ scanDirectory(fullPath, rootDir, extensions, routes, notFoundPages, layouts, loadingPages, errorPages, middlewares, false);
33
+ } else if (entry.isFile()) {
34
+ const ext = path.extname(entry.name);
35
+ const baseName = path.basename(entry.name, ext);
36
+
37
+ if (baseName === 'layout' && extensions.includes(ext)) {
38
+ const relativePath = path.relative(rootDir, dir);
39
+ const { routePath } = pathToRoute(relativePath, path.sep);
40
+ layouts.push({
41
+ path: isRoot ? '/' : routePath,
42
+ filePath: fullPath
43
+ });
44
+ }
45
+ else if (baseName === 'loading' && extensions.includes(ext)) {
46
+ const relativePath = path.relative(rootDir, dir);
47
+ const { routePath } = pathToRoute(relativePath, path.sep);
48
+ loadingPages.push({
49
+ path: isRoot ? '/' : routePath,
50
+ filePath: fullPath
51
+ });
52
+ }
53
+ else if (baseName === 'error' && extensions.includes(ext)) {
54
+ const relativePath = path.relative(rootDir, dir);
55
+ const { routePath } = pathToRoute(relativePath, path.sep);
56
+ errorPages.push({
57
+ path: isRoot ? '/' : routePath,
58
+ filePath: fullPath
59
+ });
60
+ }
61
+ else if (baseName === 'middleware' && extensions.includes(ext)) {
62
+ const relativePath = path.relative(rootDir, dir);
63
+ const { routePath } = pathToRoute(relativePath, path.sep);
64
+ middlewares.push({
65
+ path: isRoot ? '/' : routePath,
66
+ filePath: fullPath
67
+ });
68
+ }
69
+ else if (baseName === '404' && extensions.includes(ext)) {
70
+ const relativeParts = path.relative(rootDir, dir).split(path.sep).filter(Boolean);
71
+ const filteredParts = relativeParts.filter(p => !isRouteGroup(p));
72
+ const pathPrefix = isRoot ? '' : '/' + filteredParts.join('/');
73
+ notFoundPages.push({ pathPrefix: pathPrefix || '', filePath: fullPath });
74
+ } else if (isRoot && baseName === 'App' && extensions.includes(ext)) {
75
+ routes.push({ path: '/', filePath: fullPath, isDynamic: false, params: [] });
76
+ } else if (!isRoot && baseName === 'index' && extensions.includes(ext)) {
77
+ const relativePath = path.relative(rootDir, path.dirname(fullPath));
78
+ const { routePath, params } = pathToRoute(relativePath, path.sep);
79
+ routes.push({ path: routePath, filePath: fullPath, isDynamic: params.length > 0, params });
80
+ } else if (
81
+ !RESERVED_NAMES.has(baseName) &&
82
+ !baseName.endsWith('.d') &&
83
+ !baseName.startsWith('_') &&
84
+ extensions.includes(ext)
85
+ ) {
86
+ const relativePath = path.relative(rootDir, fullPath);
87
+ const relativePathNoExt = relativePath.substring(0, relativePath.length - ext.length);
88
+ const { routePath, params } = pathToRoute(relativePathNoExt, path.sep);
89
+ routes.push({ path: routePath, filePath: fullPath, isDynamic: params.length > 0, params });
90
+ }
91
+ }
92
+ }
93
+ }
94
+
95
+ export function scanRoutes(rootDir: string, extensions: string[]): ScanResult {
96
+ const routes: RouteEntry[] = [];
97
+ const notFoundPages: NotFoundEntry[] = [];
98
+ const layouts: LayoutEntry[] = [];
99
+ const loadingPages: LoadingEntry[] = [];
100
+ const errorPages: ErrorEntry[] = [];
101
+ const middlewares: MiddlewareEntry[] = [];
102
+ const absoluteRoot = path.isAbsolute(rootDir) ? rootDir : path.resolve(rootDir);
103
+
104
+ if (!fs.existsSync(absoluteRoot)) {
105
+ throw new Error(`Olova Router: Root directory does not exist: ${absoluteRoot}`);
106
+ }
107
+
108
+ scanDirectory(absoluteRoot, absoluteRoot, extensions, routes, notFoundPages, layouts, loadingPages, errorPages, middlewares, true);
109
+ routes.sort((a, b) => (a.isDynamic !== b.isDynamic ? (a.isDynamic ? 1 : -1) : a.path.localeCompare(b.path)));
110
+ notFoundPages.sort((a, b) => b.pathPrefix.length - a.pathPrefix.length);
111
+ layouts.sort((a, b) => a.path.length - b.path.length);
112
+ loadingPages.sort((a, b) => b.path.length - a.path.length);
113
+ errorPages.sort((a, b) => b.path.length - a.path.length);
114
+ middlewares.sort((a, b) => a.path.length - b.path.length);
115
+ return { routes, notFoundPages, layouts, loadingPages, errorPages, middlewares };
116
+ }
@@ -0,0 +1,191 @@
1
+ import type { ComponentType, ReactNode } from 'react';
2
+ export type { PluginOption, ResolvedConfig } from 'vite';
3
+
4
+ export interface RouteEntry {
5
+ path: string;
6
+ filePath: string;
7
+ isDynamic: boolean;
8
+ params: string[];
9
+ }
10
+
11
+ export interface ScannerOptions {
12
+ rootDir: string;
13
+ extensions: string[];
14
+ }
15
+
16
+ export interface Metadata {
17
+ title?: string;
18
+ description?: string;
19
+ keywords?: string[];
20
+ openGraph?: {
21
+ title?: string;
22
+ description?: string;
23
+ image?: string;
24
+ url?: string;
25
+ type?: string;
26
+ };
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ [key: string]: any;
29
+ }
30
+
31
+ export interface RouteConfig {
32
+ path: string;
33
+ component: string;
34
+ params?: string[];
35
+ metadata?: Metadata;
36
+ }
37
+
38
+ export interface GeneratorOptions {
39
+ routes: RouteEntry[];
40
+ basePath: string;
41
+ }
42
+
43
+ export interface OlovaRouterOptions {
44
+ rootDir?: string;
45
+ extensions?: string[];
46
+ packageName?: string;
47
+ }
48
+
49
+ export interface DynamicSegmentResult {
50
+ isDynamic: boolean;
51
+ paramName: string | null;
52
+ }
53
+
54
+ export interface NotFoundEntry {
55
+ pathPrefix: string;
56
+ filePath: string;
57
+ }
58
+
59
+ export interface NotFoundPageConfig {
60
+ pathPrefix: string;
61
+ component: ComponentType;
62
+ metadata?: Metadata;
63
+ }
64
+
65
+ export interface LayoutEntry {
66
+ path: string;
67
+ filePath: string;
68
+ }
69
+
70
+ export interface LoadingEntry {
71
+ path: string;
72
+ filePath: string;
73
+ }
74
+
75
+ export interface ErrorEntry {
76
+ path: string;
77
+ filePath: string;
78
+ }
79
+
80
+ export interface MiddlewareEntry {
81
+ path: string;
82
+ filePath: string;
83
+ }
84
+
85
+ export interface ScanResult {
86
+ routes: RouteEntry[];
87
+ notFoundPages: NotFoundEntry[];
88
+ layouts: LayoutEntry[];
89
+ loadingPages: LoadingEntry[];
90
+ errorPages: ErrorEntry[];
91
+ middlewares: MiddlewareEntry[];
92
+ }
93
+
94
+ export interface RouteWithExport extends RouteConfig {
95
+ hasDefault: boolean;
96
+ namedExport: string | null;
97
+ hasMetadata: boolean;
98
+ metadataSource?: string;
99
+ hasRoute: boolean;
100
+ hasGetStaticPaths: boolean;
101
+ hasLoader: boolean;
102
+ }
103
+
104
+ export interface NotFoundWithExport extends NotFoundEntry {
105
+ hasDefault: boolean;
106
+ namedExport: string | null;
107
+ hasMetadata: boolean;
108
+ }
109
+
110
+ export interface LayoutWithExport extends LayoutEntry {
111
+ hasDefault: boolean;
112
+ namedExport: string | null;
113
+ hasMetadata: boolean;
114
+ }
115
+
116
+ export interface LoadingWithExport extends LoadingEntry {
117
+ hasDefault: boolean;
118
+ namedExport: string | null;
119
+ }
120
+
121
+ export interface ErrorWithExport extends ErrorEntry {
122
+ hasDefault: boolean;
123
+ namedExport: string | null;
124
+ }
125
+
126
+ export interface MiddlewareWithExport extends MiddlewareEntry {
127
+ hasDefault: boolean;
128
+ namedExport: string | null;
129
+ }
130
+
131
+ export interface RouteErrorProps {
132
+ error: Error;
133
+ reset: () => void;
134
+ }
135
+
136
+ export interface LoaderContext {
137
+ request: Request;
138
+ params: Record<string, string>;
139
+ }
140
+
141
+ export interface Route {
142
+ id?: string;
143
+ path: string;
144
+ component?: ComponentType;
145
+ metadata?: Metadata;
146
+ loading?: ComponentType;
147
+ error?: ComponentType<RouteErrorProps>;
148
+ filePath?: string;
149
+ params?: string[];
150
+ loader?: (ctx: LoaderContext) => Promise<Record<string, unknown>>;
151
+ }
152
+
153
+ export interface LayoutRoute {
154
+ path: string;
155
+ layout?: ComponentType;
156
+ children: Route[];
157
+ metadata?: Metadata;
158
+ }
159
+
160
+ export type SearchParams = Record<string, string | string[]>;
161
+
162
+ export interface SetSearchParamsOptions {
163
+ replace?: boolean;
164
+ merge?: boolean;
165
+ }
166
+
167
+ export interface NavigateOptions {
168
+ scroll?: boolean;
169
+ replace?: boolean;
170
+ }
171
+
172
+ export type NavigationListener = (from: string, to: string) => void | boolean;
173
+
174
+ export interface RouterContextType {
175
+ currentPath: string;
176
+ params: Record<string, string>;
177
+ searchParams: SearchParams;
178
+ navigate: (path: string, options?: NavigateOptions) => void;
179
+ push: (path: string, options?: NavigateOptions) => void;
180
+ replace: (path: string, options?: NavigateOptions) => void;
181
+ back: () => void;
182
+ forward: () => void;
183
+ refresh: () => void;
184
+ setSearchParams: (params: Record<string, string | string[] | null>, options?: SetSearchParamsOptions) => void;
185
+ isNavigating: boolean;
186
+ prefetch: (path: string) => void;
187
+ }
188
+
189
+ export interface OutletContextType {
190
+ content: ReactNode;
191
+ }
@@ -0,0 +1,85 @@
1
+ import fs from 'fs';
2
+
3
+ function extractBalancedBraces(content: string, startIndex: number): string | null {
4
+ if (content[startIndex] !== '{') return null;
5
+ let depth = 0;
6
+ for (let i = startIndex; i < content.length; i++) {
7
+ if (content[i] === '{') depth++;
8
+ else if (content[i] === '}') {
9
+ depth--;
10
+ if (depth === 0) return content.substring(startIndex, i + 1);
11
+ }
12
+ }
13
+ return null;
14
+ }
15
+
16
+ function extractMetadataSource(content: string): string | undefined {
17
+ const match = content.match(/export\s+(?:const|let|var)\s+metadata\s*(?::\s*\w+\s*)?=\s*/);
18
+ if (!match || match.index === undefined) return undefined;
19
+ const afterEquals = match.index + match[0].length;
20
+ return extractBalancedBraces(content, afterEquals) || undefined;
21
+ }
22
+
23
+ export function detectExportType(filePath: string): {
24
+ hasDefault: boolean;
25
+ namedExport: string | null;
26
+ hasMetadata: boolean;
27
+ metadataSource?: string;
28
+ hasRoute: boolean;
29
+ hasGetStaticPaths: boolean;
30
+ hasLoader: boolean;
31
+ } {
32
+ try {
33
+ const content = fs.readFileSync(filePath, 'utf-8');
34
+
35
+ const hasMetadata = /export\s+(?:const|let|var)\s+metadata\s*/.test(content) ||
36
+ /export\s*\{\s*metadata\s*\}/.test(content);
37
+
38
+ const hasRoute = /export\s+(?:const|let|var)\s+route\s*=/.test(content) ||
39
+ /export\s*\{\s*route\s*\}/.test(content);
40
+
41
+ const hasGetStaticPaths = /export\s+(?:async\s+)?function\s+getStaticPaths/.test(content) ||
42
+ /export\s+(?:const|let|var)\s+getStaticPaths\s*=/.test(content) ||
43
+ /export\s*\{[^}]*getStaticPaths[^}]*\}/.test(content);
44
+
45
+ const hasLoader = /export\s+(?:async\s+)?function\s+loader/.test(content) ||
46
+ /export\s+(?:const|let|var)\s+loader\s*=/.test(content) ||
47
+ /export\s*\{[^}]*loader[^}]*\}/.test(content);
48
+
49
+ // Extract metadata source for inlining (works for any file type with object literal metadata)
50
+ const metadataSource = extractMetadataSource(content);
51
+
52
+ if (filePath.toLowerCase().endsWith('.mdx')) {
53
+ return {
54
+ hasDefault: true,
55
+ namedExport: null,
56
+ hasMetadata: !!metadataSource,
57
+ hasRoute: false,
58
+ hasGetStaticPaths: false,
59
+ hasLoader: false,
60
+ metadataSource
61
+ };
62
+ }
63
+
64
+ if (/export\s+default\s+/.test(content)) {
65
+ return { hasDefault: true, namedExport: null, hasMetadata, metadataSource, hasRoute, hasGetStaticPaths, hasLoader };
66
+ }
67
+
68
+ const namedMatch = content.match(/export\s+(?:const|function|class)\s+(\w+)/);
69
+ if (namedMatch) {
70
+ return { hasDefault: false, namedExport: namedMatch[1], hasMetadata, metadataSource, hasRoute, hasGetStaticPaths, hasLoader };
71
+ }
72
+
73
+ const exportMatch = content.match(/export\s*\{\s*(\w+)(?:\s+as\s+default)?\s*\}/);
74
+ if (exportMatch) {
75
+ if (content.includes('as default')) {
76
+ return { hasDefault: true, namedExport: null, hasMetadata, metadataSource, hasRoute, hasGetStaticPaths, hasLoader };
77
+ }
78
+ return { hasDefault: false, namedExport: exportMatch[1], hasMetadata, metadataSource, hasRoute, hasGetStaticPaths, hasLoader };
79
+ }
80
+
81
+ return { hasDefault: false, namedExport: null, hasMetadata, metadataSource, hasRoute, hasGetStaticPaths, hasLoader };
82
+ } catch {
83
+ return { hasDefault: true, namedExport: null, hasMetadata: false, hasRoute: false, hasGetStaticPaths: false, hasLoader: false };
84
+ }
85
+ }
@@ -0,0 +1,4 @@
1
+ export { detectExportType } from './export';
2
+ export { getRouteName } from './naming';
3
+ export { isRouteGroup, parseDynamicSegment, pathToRoute } from './path';
4
+
@@ -0,0 +1,54 @@
1
+ export function getRouteName(path: string): string {
2
+ if (path === '/' || path === '') return 'Root';
3
+
4
+ // Remove leading/trailing slashes and split
5
+ const segments = path.replace(/^\/|\/$/g, '').split(/[\\/]/);
6
+
7
+ const nameSegments = segments.map(segment => {
8
+ let cleanSegment = segment;
9
+
10
+ // Handle dynamic segments :id -> Id
11
+ if (cleanSegment.startsWith(':')) {
12
+ cleanSegment = cleanSegment.slice(1);
13
+ }
14
+
15
+ // Handle bracket syntax [id] -> Id, [...slug] -> Slug
16
+ if (cleanSegment.startsWith('[') && cleanSegment.endsWith(']')) {
17
+ cleanSegment = cleanSegment.slice(1, -1);
18
+ if (cleanSegment.startsWith('...')) {
19
+ cleanSegment = cleanSegment.slice(3);
20
+ }
21
+ }
22
+
23
+ // Handle catch-all * -> All
24
+ if (cleanSegment === '*') {
25
+ return 'All';
26
+ }
27
+
28
+ // Skip 'index' segment for cleaner names
29
+ if (cleanSegment.toLowerCase() === 'index') {
30
+ return '';
31
+ }
32
+
33
+ // Handle route groups (auth) -> Auth or ignore? Usually people ignore path but name might want it?
34
+ // User request is about "main folder name". (auth) is usually invisible.
35
+ // Let's strip parens but keep name for uniqueness? Or just strip?
36
+ // scanner often treats (group) as transparent for path, but for Naming it might be good to include or exclude.
37
+ // Let's include it cleaned up to ensure Uniqueness (e.g. /app/(dashboard)/layout vs /app/(marketing)/layout).
38
+ if (cleanSegment.startsWith('(') && cleanSegment.endsWith(')')) {
39
+ cleanSegment = cleanSegment.slice(1, -1);
40
+ }
41
+
42
+ return capitalize(cleanSegment);
43
+ });
44
+
45
+ const finalName = nameSegments.join('');
46
+ if (/^\d/.test(finalName)) {
47
+ return 'Page' + finalName;
48
+ }
49
+ return finalName;
50
+ }
51
+
52
+ function capitalize(str: string): string {
53
+ return str.charAt(0).toUpperCase() + str.slice(1).replace(/[^a-zA-Z0-9]/g, '');
54
+ }
@@ -0,0 +1,45 @@
1
+ import type { DynamicSegmentResult } from '../types';
2
+
3
+ export function parseDynamicSegment(segment: string): DynamicSegmentResult & { isCatchAll: boolean } {
4
+ if (segment.match(/^\[\.\.\.(.+)\]$/)) {
5
+ const paramName = segment.match(/^\[\.\.\.(.+)\]$/)?.[1] || 'slug';
6
+ return { isDynamic: true, paramName, isCatchAll: true };
7
+ }
8
+
9
+ const bracketMatch = segment.match(/^\[(.+)\]$/);
10
+ if (bracketMatch) {
11
+ return { isDynamic: true, paramName: bracketMatch[1], isCatchAll: false };
12
+ }
13
+
14
+
15
+
16
+ return { isDynamic: false, paramName: null, isCatchAll: false };
17
+ }
18
+
19
+ export function isRouteGroup(segment: string): boolean {
20
+ return /^\(.+\)$/.test(segment);
21
+ }
22
+
23
+ export function pathToRoute(relativePath: string, sep: string) {
24
+ const params: string[] = [];
25
+ let hasCatchAll = false;
26
+ const segments = relativePath.split(sep).filter(Boolean);
27
+
28
+ const routeSegments = segments
29
+ .filter(segment => !isRouteGroup(segment))
30
+ .map(segment => {
31
+ const { isDynamic, paramName, isCatchAll } = parseDynamicSegment(segment);
32
+ if (isDynamic && paramName) {
33
+ params.push(paramName);
34
+ if (isCatchAll) {
35
+ hasCatchAll = true;
36
+ return `*`;
37
+ }
38
+ return `:${paramName}`;
39
+ }
40
+ return segment;
41
+ });
42
+
43
+ const routePath = '/' + routeSegments.join('/');
44
+ return { routePath: routePath === '/' ? '/' : routePath, params, hasCatchAll };
45
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,35 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: {
5
+ 'olova': 'olova.ts',
6
+ 'main': 'src/main.tsx',
7
+ 'entry-server': 'src/entry-server.tsx',
8
+ 'entry-worker': 'src/entry-worker.tsx',
9
+ 'performance': 'src/performance.ts',
10
+ 'router': 'src/router/router.tsx',
11
+ },
12
+ format: ['cjs', 'esm'],
13
+ dts: {
14
+ entry: {
15
+ 'olova': 'olova.ts',
16
+ 'router': 'src/router/router.tsx',
17
+ }
18
+ },
19
+ clean: true,
20
+ shims: true,
21
+ platform: 'node',
22
+ external: [
23
+ 'vite',
24
+ 'react',
25
+ 'react-dom',
26
+ 'react-dom/client',
27
+ 'react-dom/server',
28
+ 'virtual:olova-app', // External - resolved at runtime by plugin
29
+ '/src/index.css' // External - resolved by Vite in user project
30
+ ],
31
+ treeshake: true,
32
+ sourcemap: true,
33
+ minify: false,
34
+ target: 'node18',
35
+ });
package/CHANGELOG.md DELETED
@@ -1,31 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [0.1.0] - 2026-01-08
9
-
10
- ### Added
11
-
12
- - Initial release of Olova
13
- - File-based routing with Next.js App Router conventions
14
- - `OlovaRouter` component for rendering routes
15
- - Vite plugin for automatic route generation
16
- - React hooks: `usePathname`, `useParams`, `useSearchParams`, `useRouter`
17
- - `Link` and `NavLink` components for client-side navigation
18
- - Error boundaries with `error.tsx` support
19
- - Loading states with `loading.tsx` support
20
- - 404 handling with `not-found.tsx` support
21
- - Dynamic routes with `[slug]` syntax
22
- - Catch-all routes with `[...slug]` syntax
23
- - Route groups with `(group)` syntax
24
- - Nested layouts support
25
- - TypeScript support with full type definitions
26
- - CLI for build commands
27
-
28
- ### Known Limitations
29
-
30
- - SSG pre-rendering is not yet fully implemented
31
- - Parallel routes are experimental
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 sera
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.