snice 1.2.0 → 1.3.0

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 CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  A lightweight TypeScript framework for building web components with decorators and routing.
4
4
 
5
+ ## Quick Start
6
+
7
+ Create a new Snice app with one command:
8
+
9
+ ```bash
10
+ npx snice create-app my-app
11
+ cd my-app
12
+ npm run dev
13
+ ```
14
+
5
15
  ## Core Philosophy: Imperative, Not Reactive
6
16
 
7
17
  Snice takes an **imperative approach** to web components. Unlike reactive frameworks that automatically re-render when data changes, Snice components:
@@ -315,13 +325,16 @@ class AboutPage extends HTMLElement {
315
325
  }
316
326
  }
317
327
 
318
- // Page with URL parameter
319
- @page({ tag: 'user-page', routes: ['/users/:id'] })
328
+ // Page with URL parameter
329
+ import { property } from 'snice';
330
+
331
+ @page({ tag: 'user-page', routes: ['/users/:userId'] })
320
332
  class UserPage extends HTMLElement {
321
- id = ''; // Automatically set from URL
333
+ @property()
334
+ userId = '';
322
335
 
323
336
  html() {
324
- return `<h1>User ${this.id}</h1>`;
337
+ return `<h1>User ${this.userId}</h1>`;
325
338
  }
326
339
  }
327
340
 
package/bin/snice.js ADDED
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, join, resolve, basename } from 'path';
5
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, cpSync } from 'fs';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+
10
+ const args = process.argv.slice(2);
11
+ const command = args[0];
12
+
13
+ if (command === 'create-app') {
14
+ const projectPath = args[1] || '.';
15
+ createApp(projectPath);
16
+ } else {
17
+ console.log(`
18
+ Snice CLI
19
+
20
+ Usage:
21
+ snice create-app <project-name> Create a new Snice application
22
+ snice create-app . Initialize in current directory
23
+
24
+ Examples:
25
+ snice create-app my-app
26
+ npx snice create-app my-app
27
+ `);
28
+ }
29
+
30
+ function createApp(projectPath) {
31
+ const targetDir = resolve(process.cwd(), projectPath);
32
+ const projectName = projectPath === '.' ? basename(process.cwd()) : basename(targetDir);
33
+
34
+ console.log(`\nšŸš€ Creating Snice app in ${targetDir}...\n`);
35
+
36
+ // Check if directory exists and is empty
37
+ if (projectPath !== '.') {
38
+ if (existsSync(targetDir)) {
39
+ const files = readdirSync(targetDir);
40
+ if (files.length > 0 && !files.every(f => f.startsWith('.'))) {
41
+ console.error(`āŒ Directory ${targetDir} is not empty!`);
42
+ process.exit(1);
43
+ }
44
+ } else {
45
+ mkdirSync(targetDir, { recursive: true });
46
+ }
47
+ } else {
48
+ // Check current directory
49
+ const files = readdirSync(targetDir);
50
+ const hasNonDotFiles = files.some(f => !f.startsWith('.') && f !== 'node_modules');
51
+ if (hasNonDotFiles) {
52
+ console.error(`āŒ Current directory is not empty!`);
53
+ process.exit(1);
54
+ }
55
+ }
56
+
57
+ // Path to templates
58
+ const templateDir = join(__dirname, 'templates', 'base');
59
+
60
+ // Copy template files
61
+ copyTemplateFiles(templateDir, targetDir, projectName);
62
+
63
+ console.log(`\n✨ Project created successfully!\n`);
64
+ console.log('Next steps:');
65
+
66
+ if (projectPath !== '.') {
67
+ console.log(` cd ${projectPath}`);
68
+ }
69
+
70
+ console.log(' npm install');
71
+ console.log(' npm run dev\n');
72
+ console.log('Happy coding! šŸŽ‰\n');
73
+ }
74
+
75
+ function copyTemplateFiles(sourceDir, targetDir, projectName) {
76
+ const files = readdirSync(sourceDir, { withFileTypes: true });
77
+
78
+ for (const file of files) {
79
+ const sourcePath = join(sourceDir, file.name);
80
+ const targetPath = join(targetDir, file.name);
81
+
82
+ if (file.isDirectory()) {
83
+ // Create directory and recursively copy contents
84
+ if (!existsSync(targetPath)) {
85
+ mkdirSync(targetPath, { recursive: true });
86
+ }
87
+ copyTemplateFiles(sourcePath, targetPath, projectName);
88
+ } else {
89
+ // Read file, replace placeholders, and write to target
90
+ console.log(` Creating ${file.name}...`);
91
+
92
+ let content = readFileSync(sourcePath, 'utf8');
93
+
94
+ // Replace {{projectName}} placeholders
95
+ content = content.replace(/\{\{projectName\}\}/g, projectName);
96
+
97
+ writeFileSync(targetPath, content);
98
+ }
99
+ }
100
+ }
@@ -0,0 +1,33 @@
1
+ # {{projectName}}
2
+
3
+ A Snice application
4
+
5
+ ## Development
6
+
7
+ ```bash
8
+ npm run dev
9
+ ```
10
+
11
+ ## Build
12
+
13
+ ```bash
14
+ npm run build
15
+ ```
16
+
17
+ ## Preview Production Build
18
+
19
+ ```bash
20
+ npm run preview
21
+ ```
22
+
23
+ ## Project Structure
24
+
25
+ - `src/pages/` - Application pages/routes
26
+ - `src/components/` - Reusable components
27
+ - `src/controllers/` - Business logic controllers
28
+ - `src/styles/` - Global styles and shared styles
29
+ - `src/types/` - TypeScript interfaces and types
30
+ - `src/router.ts` - Router configuration
31
+ - `src/main.ts` - Application entry point
32
+
33
+ Built with [Snice](https://github.com/yourusername/snice)
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{projectName}}</title>
7
+ <link rel="icon" type="image/svg+xml" href="/vite.svg">
8
+ </head>
9
+ <body>
10
+ <div id="app"></div>
11
+ <script type="module" src="/src/main.ts"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "{{projectName}}",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc && vite build",
9
+ "type-check": "tsc --noEmit"
10
+ },
11
+ "dependencies": {
12
+ "snice": "^1.2.0"
13
+ },
14
+ "devDependencies": {
15
+ "@types/node": "^20.0.0",
16
+ "terser": "^5.24.0",
17
+ "typescript": "^5.3.3",
18
+ "vite": "^5.0.10"
19
+ }
20
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
@@ -0,0 +1,101 @@
1
+ import { element, property, query, on, dispatch } from 'snice';
2
+ import type { ICounterButton } from '../types/counter-button';
3
+
4
+ @element('counter-button')
5
+ export class CounterButton extends HTMLElement implements ICounterButton {
6
+ @property({ type: Number })
7
+ count = 0;
8
+
9
+ @query('.count')
10
+ countDisplay?: HTMLElement;
11
+
12
+ html() {
13
+ return /*html*/`
14
+ <div class="counter">
15
+ <button class="btn minus">-</button>
16
+ <span class="count">${this.count}</span>
17
+ <button class="btn plus">+</button>
18
+ </div>
19
+ `;
20
+ }
21
+
22
+ css() {
23
+ return /*css*/`
24
+ .counter {
25
+ display: inline-flex;
26
+ align-items: center;
27
+ gap: 1rem;
28
+ padding: 1rem;
29
+ background: white;
30
+ border-radius: 8px;
31
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
32
+ }
33
+
34
+ .count {
35
+ font-size: 1.5rem;
36
+ font-weight: bold;
37
+ min-width: 3rem;
38
+ text-align: center;
39
+ }
40
+
41
+ .btn {
42
+ width: 2rem;
43
+ height: 2rem;
44
+ border: 2px solid var(--primary-color);
45
+ background: white;
46
+ color: var(--primary-color);
47
+ border-radius: 4px;
48
+ cursor: pointer;
49
+ font-size: 1.2rem;
50
+ }
51
+
52
+ .btn:hover {
53
+ background: var(--primary-color);
54
+ color: white;
55
+ }
56
+ `;
57
+ }
58
+
59
+ // Imperative methods that controller can call
60
+ setCount(value: number) {
61
+ this.count = value;
62
+ this.updateDisplay();
63
+ }
64
+
65
+ @dispatch('count-changed')
66
+ increment() {
67
+ this.count++;
68
+ this.updateDisplay();
69
+ return { count: this.count };
70
+ }
71
+
72
+ @dispatch('count-changed')
73
+ decrement() {
74
+ this.count--;
75
+ this.updateDisplay();
76
+ return { count: this.count };
77
+ }
78
+
79
+ @dispatch('count-changed')
80
+ reset() {
81
+ this.count = 0;
82
+ this.updateDisplay();
83
+ return { count: this.count };
84
+ }
85
+
86
+ private updateDisplay() {
87
+ if (this.countDisplay) {
88
+ this.countDisplay.textContent = String(this.count);
89
+ }
90
+ }
91
+
92
+ @on('click', '.plus')
93
+ handlePlus() {
94
+ this.increment();
95
+ }
96
+
97
+ @on('click', '.minus')
98
+ handleMinus() {
99
+ this.decrement();
100
+ }
101
+ }
@@ -0,0 +1,24 @@
1
+ import { controller, on } from 'snice';
2
+ import type { ICounterButton } from '../types/counter-button';
3
+
4
+ @controller('counter')
5
+ export class CounterController {
6
+ element!: ICounterButton;
7
+
8
+ async attach() {
9
+ // Load saved count from localStorage
10
+ const saved = localStorage.getItem('counter-value');
11
+ if (saved) {
12
+ this.element.setCount(parseInt(saved));
13
+ }
14
+ }
15
+
16
+ async detach() {
17
+ // any clean up you need
18
+ }
19
+
20
+ @on('count-changed')
21
+ handleCountChanged(e: CustomEvent) {
22
+ localStorage.setItem('counter-value', String(e.detail.count));
23
+ }
24
+ }
@@ -0,0 +1,16 @@
1
+ import { initialize } from './router';
2
+ import './styles/global.css';
3
+
4
+ // Import components
5
+ import './components/counter-button';
6
+
7
+ // Import controllers
8
+ import './controllers/counter-controller';
9
+
10
+ // Import pages
11
+ import './pages/home-page';
12
+ import './pages/about-page';
13
+ import './pages/not-found-page';
14
+
15
+ // Initialize router
16
+ initialize();
@@ -0,0 +1,56 @@
1
+ import { page } from '../router';
2
+
3
+ @page({ tag: 'about-page', routes: ['/about'] })
4
+ export class AboutPage extends HTMLElement {
5
+ html() {
6
+ return /*html*/`
7
+ <div class="container">
8
+ <h1>About</h1>
9
+ <p>This app was built with Snice, a modern web components framework.</p>
10
+ <p>Version 1.0.0</p>
11
+
12
+ <div class="nav">
13
+ <a href="#/" class="btn">Back to Home</a>
14
+ </div>
15
+ </div>
16
+ `;
17
+ }
18
+
19
+ css() {
20
+ return /*css*/`
21
+ .container {
22
+ padding: 3rem;
23
+ max-width: 800px;
24
+ margin: 0 auto;
25
+ text-align: center;
26
+ }
27
+
28
+ h1 {
29
+ color: var(--primary-color);
30
+ margin-bottom: 1rem;
31
+ }
32
+
33
+ p {
34
+ color: var(--text-light);
35
+ margin-bottom: 1rem;
36
+ }
37
+
38
+ .nav {
39
+ margin-top: 3rem;
40
+ }
41
+
42
+ .btn {
43
+ display: inline-block;
44
+ padding: 0.75rem 1.5rem;
45
+ background: var(--primary-color);
46
+ color: white;
47
+ text-decoration: none;
48
+ border-radius: 6px;
49
+ }
50
+
51
+ .btn:hover {
52
+ background: var(--secondary-color);
53
+ }
54
+ `;
55
+ }
56
+ }
@@ -0,0 +1,77 @@
1
+ import { page } from '../router';
2
+
3
+ @page({ tag: 'home-page', routes: ['/'] })
4
+ export class HomePage extends HTMLElement {
5
+ html() {
6
+ return /*html*/`
7
+ <div class="container">
8
+ <h1>Welcome to {{projectName}}</h1>
9
+ <p>Built with Snice</p>
10
+
11
+ <div class="demo-section">
12
+ <h2>Interactive Counter Demo</h2>
13
+ <p>This counter persists its state using a controller:</p>
14
+ <counter-button controller="counter"></counter-button>
15
+ </div>
16
+
17
+ <div class="nav">
18
+ <a href="#/about" class="btn">About</a>
19
+ </div>
20
+ </div>
21
+ `;
22
+ }
23
+
24
+ css() {
25
+ return /*css*/`
26
+ .container {
27
+ padding: 3rem;
28
+ text-align: center;
29
+ max-width: 800px;
30
+ margin: 0 auto;
31
+ }
32
+
33
+ h1 {
34
+ color: var(--primary-color);
35
+ margin-bottom: 1rem;
36
+ font-size: 2.5rem;
37
+ }
38
+
39
+ h2 {
40
+ color: var(--primary-color);
41
+ margin-bottom: 1rem;
42
+ font-size: 1.5rem;
43
+ }
44
+
45
+ p {
46
+ color: var(--text-light);
47
+ margin-bottom: 2rem;
48
+ }
49
+
50
+ .demo-section {
51
+ margin: 3rem 0;
52
+ padding: 2rem;
53
+ background: rgba(255, 255, 255, 0.5);
54
+ border-radius: 12px;
55
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
56
+ }
57
+
58
+ .nav {
59
+ margin-top: 3rem;
60
+ }
61
+
62
+ .btn {
63
+ display: inline-block;
64
+ padding: 0.75rem 1.5rem;
65
+ background: var(--primary-color);
66
+ color: white;
67
+ text-decoration: none;
68
+ border-radius: 6px;
69
+ transition: background 0.3s ease;
70
+ }
71
+
72
+ .btn:hover {
73
+ background: var(--secondary-color);
74
+ }
75
+ `;
76
+ }
77
+ }
@@ -0,0 +1,52 @@
1
+ import { page } from '../router';
2
+
3
+ @page({ tag: 'not-found-page', routes: ['/404'] })
4
+ export class NotFoundPage extends HTMLElement {
5
+ html() {
6
+ return /*html*/`
7
+ <div class="container">
8
+ <h1>404</h1>
9
+ <p>Page not found</p>
10
+ <a href="#/" class="btn">Go Home</a>
11
+ </div>
12
+ `;
13
+ }
14
+
15
+ css() {
16
+ return /*css*/`
17
+ .container {
18
+ padding: 3rem;
19
+ text-align: center;
20
+ min-height: 100vh;
21
+ display: flex;
22
+ flex-direction: column;
23
+ justify-content: center;
24
+ align-items: center;
25
+ }
26
+
27
+ h1 {
28
+ font-size: 4rem;
29
+ color: var(--primary-color);
30
+ margin-bottom: 1rem;
31
+ }
32
+
33
+ p {
34
+ color: var(--text-light);
35
+ margin-bottom: 2rem;
36
+ }
37
+
38
+ .btn {
39
+ display: inline-block;
40
+ padding: 0.75rem 1.5rem;
41
+ background: var(--primary-color);
42
+ color: white;
43
+ text-decoration: none;
44
+ border-radius: 6px;
45
+ }
46
+
47
+ .btn:hover {
48
+ background: var(--secondary-color);
49
+ }
50
+ `;
51
+ }
52
+ }
@@ -0,0 +1,15 @@
1
+ import { Router } from 'snice';
2
+
3
+ const { page, initialize, navigate } = Router({
4
+ target: '#app',
5
+ routing_type: 'hash',
6
+ transition: {
7
+ mode: 'simultaneous',
8
+ outDuration: 200,
9
+ inDuration: 200,
10
+ out: 'opacity: 0; transform: translateX(-10px);',
11
+ in: 'opacity: 1; transform: translateX(0);'
12
+ }
13
+ });
14
+
15
+ export { page, initialize, navigate };
@@ -0,0 +1,27 @@
1
+ :root {
2
+ --primary-color: #667eea;
3
+ --secondary-color: #764ba2;
4
+ --text-color: #333;
5
+ --text-light: #666;
6
+ --bg-color: #f7f7f7;
7
+ --white: #ffffff;
8
+ --shadow: 0 2px 4px rgba(0,0,0,0.1);
9
+ --shadow-lg: 0 10px 30px rgba(0,0,0,0.15);
10
+ }
11
+
12
+ * {
13
+ margin: 0;
14
+ padding: 0;
15
+ box-sizing: border-box;
16
+ }
17
+
18
+ body {
19
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
20
+ color: var(--text-color);
21
+ background: var(--bg-color);
22
+ line-height: 1.6;
23
+ }
24
+
25
+ #app {
26
+ min-height: 100vh;
27
+ }
@@ -0,0 +1,82 @@
1
+ export const containerStyles = `
2
+ .container {
3
+ max-width: 1200px;
4
+ margin: 0 auto;
5
+ padding: 2rem;
6
+ }
7
+
8
+ .btn {
9
+ padding: 0.75rem 1.5rem;
10
+ border-radius: 6px;
11
+ text-decoration: none;
12
+ font-weight: 600;
13
+ transition: all 0.3s ease;
14
+ cursor: pointer;
15
+ border: none;
16
+ display: inline-block;
17
+ }
18
+
19
+ .btn-primary {
20
+ background: var(--primary-color);
21
+ color: white;
22
+ }
23
+
24
+ .btn-primary:hover {
25
+ background: var(--secondary-color);
26
+ transform: translateY(-2px);
27
+ }
28
+
29
+ .btn-secondary {
30
+ background: transparent;
31
+ color: var(--primary-color);
32
+ border: 2px solid var(--primary-color);
33
+ }
34
+
35
+ .btn-secondary:hover {
36
+ background: var(--primary-color);
37
+ color: white;
38
+ }
39
+ `;
40
+
41
+ export const navbarStyles = `
42
+ .navbar {
43
+ background: var(--white);
44
+ box-shadow: var(--shadow);
45
+ position: sticky;
46
+ top: 0;
47
+ z-index: 100;
48
+ }
49
+
50
+ .nav-container {
51
+ max-width: 1200px;
52
+ margin: 0 auto;
53
+ padding: 1rem 2rem;
54
+ display: flex;
55
+ justify-content: space-between;
56
+ align-items: center;
57
+ }
58
+
59
+ .nav-brand {
60
+ font-size: 1.5rem;
61
+ font-weight: 700;
62
+ color: var(--primary-color);
63
+ }
64
+
65
+ .nav-menu {
66
+ display: flex;
67
+ list-style: none;
68
+ gap: 2rem;
69
+ }
70
+
71
+ .nav-link {
72
+ color: var(--text-light);
73
+ text-decoration: none;
74
+ font-weight: 500;
75
+ transition: color 0.3s;
76
+ }
77
+
78
+ .nav-link:hover,
79
+ .nav-link.active {
80
+ color: var(--primary-color);
81
+ }
82
+ `;
@@ -0,0 +1,7 @@
1
+ export interface ICounterButton extends HTMLElement {
2
+ count: number;
3
+ setCount(count: number): void;
4
+ increment(): void;
5
+ decrement(): void;
6
+ reset(): void;
7
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "skipLibCheck": true,
7
+ "moduleResolution": "bundler",
8
+ "allowImportingTsExtensions": true,
9
+ "resolveJsonModule": true,
10
+ "isolatedModules": true,
11
+ "noEmit": true,
12
+ "strict": true,
13
+ "noUnusedLocals": true,
14
+ "noUnusedParameters": true,
15
+ "noFallthroughCasesInSwitch": true,
16
+ "experimentalDecorators": true,
17
+ "emitDecoratorMetadata": true,
18
+ "types": ["vite/client", "node"]
19
+ },
20
+ "include": ["src"]
21
+ }
@@ -0,0 +1,21 @@
1
+ import { defineConfig } from 'vite';
2
+
3
+ export default defineConfig({
4
+ build: {
5
+ target: 'es2015',
6
+ minify: 'terser',
7
+ cssMinify: true,
8
+ rollupOptions: {
9
+ output: {
10
+ manualChunks: {
11
+ vendor: ['snice']
12
+ }
13
+ }
14
+ },
15
+ sourcemap: true,
16
+ chunkSizeWarningLimit: 500
17
+ },
18
+ esbuild: {
19
+ drop: process.env.NODE_ENV === 'production' ? ['debugger'] : []
20
+ }
21
+ });
package/package.json CHANGED
@@ -1,13 +1,18 @@
1
1
  {
2
2
  "name": "snice",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "description": "A TypeScript library",
6
6
  "main": "src/index.ts",
7
7
  "module": "src/index.ts",
8
8
  "types": "src/index.ts",
9
+ "bin": {
10
+ "snice": "./bin/snice.js"
11
+ },
9
12
  "files": [
10
13
  "src",
14
+ "bin",
15
+ "templates",
11
16
  "!src/**/*.test.ts",
12
17
  "!src/**/*.spec.ts"
13
18
  ],
@@ -0,0 +1,8 @@
1
+ declare module 'route-parser' {
2
+ export default class Route {
3
+ constructor(spec: string);
4
+ match(path: string): Record<string, string> | false;
5
+ reverse(params?: Record<string, any>): string;
6
+ spec: string;
7
+ }
8
+ }