instaserve 0.1.24 → 1.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 CHANGED
@@ -1,47 +1,145 @@
1
- # instaserve
2
- Instant web stack
1
+ <div align="center">
2
+ <h1 style="color: #3b82f6; margin: 10px 0 5px;">Instaserve</h1>
3
+ <p style="color: #6b7280; margin: 0;">Instant web stack for Node.js</p>
4
+ </div>
3
5
 
4
- In any folder:
6
+ ## Usage
5
7
 
6
- > npx instaserve
7
- Starts a server in the current directory
8
- Create a public folder and add files for static file serving
9
- Use a routes.mjs or one will be created automatically
8
+ <div style="background: white; padding: 15px; border-radius: 6px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); margin: 15px 0;">
9
+ <pre style="margin: 0;"><code>npx instaserve [options]</code></pre>
10
+ </div>
10
11
 
11
- > npm run deno (deno)
12
- Starts a deno server using routes.mjs and static serving
12
+ ## Options
13
13
 
14
- > npm run bun (bun)
15
- Starts a bun server
14
+ <div style="display: flex; gap: 8px; margin: 8px 0;">
15
+ <code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">-port &lt;number&gt;</code>
16
+ <span>Port to listen on (default: 3000)</span>
17
+ </div>
16
18
 
17
- > port=8080 npx instaserve
18
- Use custom port and routes file
19
+ <div style="display: flex; gap: 8px; margin: 8px 0;">
20
+ <code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">-ip &lt;address&gt;</code>
21
+ <span>IP address to bind to (default: 127.0.0.1)</span>
22
+ </div>
19
23
 
20
- ###Script usage
21
- ````
22
- import serve from 'instaserve'
23
- serve({
24
+ <div style="display: flex; gap: 8px; margin: 8px 0;">
25
+ <code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">-public &lt;path&gt;</code>
26
+ <span>Public directory path (default: ./public)</span>
27
+ </div>
24
28
 
25
- // routes prefixed with "_" run on every request
29
+ <div style="display: flex; gap: 8px; margin: 8px 0;">
30
+ <code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">-api &lt;file&gt;</code>
31
+ <span>Path to routes file (default: ./routes.js)</span>
32
+ </div>
26
33
 
27
- _log: (r, s) => console.log(r.method, r.url),
28
- _example: (r, s) => console.log('returning a falsy value (above) will stop processing'),
34
+ <div style="display: flex; gap: 8px; margin: 8px 0;">
35
+ <code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">-secure</code>
36
+ <span>Enable HTTPS (requires cert.pem and key.pem - run ./generate-certs.sh)</span>
37
+ </div>
29
38
 
30
- api: (r, s, body) => s.end('an api response'),
39
+ <div style="display: flex; gap: 8px; margin: 8px 0;">
40
+ <code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">-help</code>
41
+ <span>Show help message</span>
42
+ </div>
31
43
 
32
- }, port) // port is optional (3000)
33
- ````
44
+ ## HTTPS Support
34
45
 
35
- ###Routes.mjs file example - data is request body + query string
36
- ````
46
+ Instaserve supports HTTPS with self-signed certificates. To enable HTTPS:
47
+
48
+ 1. **Generate certificates:**
49
+ ```bash
50
+ ./generate-certs.sh
51
+ ```
52
+ This creates `cert.pem` and `key.pem` files and adds them to your system's trust store.
53
+
54
+ 2. **Run with HTTPS:**
55
+ ```bash
56
+ npx instaserve -secure
57
+ ```
58
+
59
+ The certificate generation script:
60
+ - Creates a self-signed certificate valid for 365 days
61
+ - Automatically adds the certificate to your system trust store (macOS/Linux)
62
+ - Prevents browser security warnings
63
+
64
+ ## Routes
65
+
66
+ The routes file (`routes.js` by default) defines your API endpoints. Each route is a function that handles requests to a specific URL path.
67
+
68
+ ### Basic Route Example
69
+
70
+ ```javascript
37
71
  export default {
38
- _debug: ({ method, url }, s, data) => !console.log(method, url, data),
39
- _example: (r, s, data) => console.log('returning a truthy value (above) will stop the chain'),
40
- api: (r, s, data) => s.end('an example api response')
72
+ // Handle GET /hello
73
+ hello: (req, res, data) => {
74
+ return { message: 'Hello World' }
75
+ }
41
76
  }
42
- ````
77
+ ```
78
+
79
+ ### Special Routes (Middleware)
80
+
81
+ Routes starting with `_` are middleware functions that run on **every request** before the main route handler. They are useful for:
82
+
83
+ - Logging requests
84
+ - Authentication
85
+ - Request modification
86
+ - Response headers
87
+
88
+ Middleware functions can:
89
+ - Return `false` to continue to the next middleware or main route
90
+ - Return a truthy value to stop processing and use that as the response
91
+ - Modify the request or response objects
92
+
93
+ #### Middleware Example
43
94
 
44
- ###Helpers
45
- ````
46
- saveJSON(url, file, fetch_options)
47
- ````
95
+ ```javascript
96
+ export default {
97
+ // Log every request
98
+ _log: (req, res, data) => {
99
+ console.log(`${req.method} ${req.url}`)
100
+ return false // Continue processing
101
+ },
102
+
103
+ // Block unauthorized requests
104
+ _auth: (req, res, data) => {
105
+ if (!data.token) {
106
+ res.writeHead(401)
107
+ return 'Unauthorized'
108
+ }
109
+ return false // Continue if authorized
110
+ }
111
+ }
112
+ ```
113
+
114
+ ### Route Parameters
115
+
116
+ Each route function receives:
117
+ - `req` - The HTTP request object
118
+ - `res` - The HTTP response object
119
+ - `data` - Combined data from:
120
+ - POST body (if JSON)
121
+ - URL query parameters
122
+ - Form data
123
+
124
+ ### Example Routes File
125
+
126
+ ```javascript
127
+ // routes.js
128
+ export default {
129
+ // Middleware example
130
+ _debug: (req, res, data) => {
131
+ console.log('Request:', req.url)
132
+ return false // Continue to next route
133
+ },
134
+
135
+ // API endpoint
136
+ api: (req, res, data) => {
137
+ return { status: 'ok', data }
138
+ },
139
+
140
+ // Error handling
141
+ testerror: () => {
142
+ throw new Error('Test error')
143
+ }
144
+ }
145
+ ```
@@ -0,0 +1,59 @@
1
+ #!/bin/bash
2
+
3
+ echo "Generating self-signed certificates for Instaserve..."
4
+
5
+ # Create openssl config file
6
+ cat > openssl.conf << EOF
7
+ [req]
8
+ distinguished_name = req_distinguished_name
9
+ req_extensions = v3_req
10
+ prompt = no
11
+
12
+ [req_distinguished_name]
13
+ C = US
14
+ ST = State
15
+ L = City
16
+ O = Instaserve
17
+ OU = Development
18
+ CN = localhost
19
+
20
+ [v3_req]
21
+ basicConstraints = CA:FALSE
22
+ keyUsage = nonRepudiation, digitalSignature, keyEncipherment
23
+ extendedKeyUsage = serverAuth, clientAuth
24
+ subjectAltName = @alt_names
25
+
26
+ [alt_names]
27
+ DNS.1 = localhost
28
+ DNS.2 = *.localhost
29
+ IP.1 = 127.0.0.1
30
+ IP.2 = ::1
31
+ EOF
32
+
33
+ # Generate private key
34
+ openssl genrsa -out key.pem 2048
35
+
36
+ # Generate certificate with config
37
+ openssl req -new -x509 -key key.pem -out cert.pem -days 365 -config openssl.conf -extensions v3_req
38
+
39
+ # Clean up config
40
+ rm openssl.conf
41
+
42
+ echo "Certificates generated: cert.pem and key.pem"
43
+
44
+ # Add to system trust store (macOS)
45
+ if [[ "$OSTYPE" == "darwin"* ]]; then
46
+ echo "Adding certificate to macOS trust store..."
47
+ sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain cert.pem
48
+ echo "Certificate added to macOS trust store"
49
+ elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
50
+ echo "Adding certificate to Linux trust store..."
51
+ sudo cp cert.pem /usr/local/share/ca-certificates/instaserve.crt
52
+ sudo update-ca-certificates
53
+ echo "Certificate added to Linux trust store"
54
+ else
55
+ echo "Please manually add cert.pem to your system's trust store"
56
+ fi
57
+
58
+ echo "HTTPS certificates ready! Use 'npx instaserve -secure' to enable HTTPS"
59
+ echo "Note: You may still see browser warnings for self-signed certificates"
package/instaserve ADDED
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+
3
+ import chalk from 'chalk'
4
+ import fs from 'node:fs'
5
+
6
+ console.log(chalk.cyan('\nInstaserve - Instant Web Stack\n'))
7
+ console.log(chalk.yellow('Usage:'))
8
+ console.log(chalk.green(' npx instaserve [options]\n'))
9
+ console.log(chalk.yellow('Options:'))
10
+ console.log(chalk.green(' -port <number>') + ' Port to listen on (default: 3000)')
11
+ console.log(chalk.green(' -ip <address>') + ' IP address to bind to (default: 127.0.0.1)')
12
+ console.log(chalk.green(' -public <path>') + ' Public directory path (default: ./public)')
13
+ console.log(chalk.green(' -api <file>') + ' Path to routes file (default: ./routes.js)')
14
+ console.log(chalk.green(' -secure') + ' Enable HTTPS (requires cert.pem and key.pem: run generate-certs.sh)')
15
+ console.log(chalk.green(' -help') + ' Show this help message\n')
16
+
17
+ if (process.argv.includes('-help')) {
18
+ process.exit(0)
19
+ }
20
+
21
+ import server from './module.mjs'
22
+
23
+ const args = process.argv.slice(2)
24
+ const params = {}
25
+ for (let i = 0; i < args.length; i++) {
26
+ const arg = args[i]
27
+ if (arg.startsWith('-')) {
28
+ const key = arg.slice(1)
29
+ const nextArg = args[i + 1]
30
+ if (nextArg && !nextArg.startsWith('-')) {
31
+ params[key] = nextArg
32
+ i++ // Skip the next argument since we used it
33
+ } else {
34
+ params[key] = true // Boolean flag
35
+ }
36
+ }
37
+ }
38
+
39
+ // Load routes file
40
+ let routes = {}
41
+ const routesFile = params.api || './routes.js'
42
+ if (fs.existsSync(routesFile)) {
43
+ try {
44
+ const imported = await import(routesFile)
45
+ routes = imported.default || imported
46
+ } catch (e) {
47
+ console.log(chalk.yellow(`Warning: Could not load routes file ${routesFile}: ${e.message}`))
48
+ }
49
+ }
50
+
51
+ server(routes)
package/module.mjs CHANGED
@@ -1,31 +1,55 @@
1
1
  import http from 'node:http'
2
+ import https from 'node:https'
2
3
  import fs from 'node:fs'
3
4
 
4
- global.saveJSON = async (url, filename, options) => {
5
- const f = await fetch(url, options)
6
- const j = await f.json()
7
- fs.writeFileSync(filename, JSON.stringify(j, null, 2))
5
+ const args = process.argv.slice(2)
6
+ const params = {}
7
+ for (let i = 0; i < args.length; i++) {
8
+ const arg = args[i]
9
+ if (arg.startsWith('-')) {
10
+ const key = arg.slice(1)
11
+ const nextArg = args[i + 1]
12
+ if (nextArg && !nextArg.startsWith('-')) {
13
+ params[key] = nextArg
14
+ i++ // Skip the next argument since we used it
15
+ } else {
16
+ params[key] = true // Boolean flag
17
+ }
18
+ }
8
19
  }
9
20
 
10
- const {debug, port, ip} = process.env
11
-
12
21
  function public_file(r, s) {
13
22
  if (r.url == '/') r.url = '/index.html'
14
- const fn = `./public${r.url.replace('..', '')}`
23
+ const fn = `${params.public || './public'}${r.url.replace(/\.\./g, '')}`
15
24
  if (fs.existsSync(fn)) {
16
- if (fn.match(/.js$/)) s.writeHead(200, { 'Content-Type': 'application/javascript' })
17
- return fs.readFileSync(fn, 'utf-8')
25
+ const content = fs.readFileSync(fn, 'utf-8')
26
+ if (fn.match(/.js$/)) {
27
+ s.writeHead(200, { 'Content-Type': 'application/javascript' })
28
+ } else {
29
+ s.writeHead(200)
30
+ }
31
+ s.end(content)
32
+ return true // Indicate file was served
18
33
  }
34
+ return false // Indicate no file was served
19
35
  }
20
36
 
21
- export default function (routes, port = 3000, ip = '127.0.0.1') {
22
- const server = http.createServer(async (r, s) => {
37
+ export default async function (routes, port = params.port || 3000, ip = params.ip || '127.0.0.1') {
38
+ const publicDir = params.public || './public'
39
+ if (publicDir.includes('..')) {
40
+ throw new Error('Public directory path cannot contain ".."')
41
+ }
42
+ if (!fs.existsSync(publicDir)) {
43
+ throw new Error(`Public directory "${publicDir}" does not exist`)
44
+ }
45
+
46
+ const requestHandler = async (r, s) => {
23
47
  let sdata = '', rrurl = r.url || ''
48
+ let responseSent = false
49
+
24
50
  r.on('data', (s) => sdata += s.toString().trim())
25
51
  r.on('end', (x) => {
26
52
  try {
27
- if (debug) console.log(`routes: "${JSON.stringify(routes)}"`)
28
-
29
53
  // Compose data object
30
54
  const data = sdata ? JSON.parse(sdata) : {}
31
55
  const qs = rrurl.split('?')
@@ -36,29 +60,72 @@ export default function (routes, port = 3000, ip = '127.0.0.1') {
36
60
 
37
61
  const midware = Object.keys(routes)
38
62
  .filter((k) => k.startsWith('_'))
39
- .find((k) => routes[k](r, s, data))
63
+ .find((k) => {
64
+ const result = routes[k](r, s, data)
65
+ if (result && !responseSent) {
66
+ responseSent = true
67
+ s.end(typeof result === 'string' ? result : JSON.stringify(result))
68
+ }
69
+ return result
70
+ })
40
71
 
41
72
  // Response closed by middleware
42
- if(s.finished) return
73
+ if(responseSent || s.writableEnded) return
43
74
 
44
- const fc = public_file(r, s)
45
- if(fc) return s.end(fc)
75
+ // Try to serve public file
76
+ if(public_file(r, s)) {
77
+ responseSent = true
78
+ return
79
+ }
46
80
 
47
81
  const url = rrurl.split('/')[1].split('?')[0]
48
82
  if (routes[url]) {
49
83
  const resp = routes[url](r, s, data)
50
- if(debug) console.log(`route: ${url}, returned: ${JSON.stringify(resp)}`)
51
- return s.end(typeof resp === 'string' ? resp:JSON.stringify(resp))
84
+ if (!responseSent && !s.writableEnded) {
85
+ responseSent = true
86
+ s.end(typeof resp === 'string' ? resp:JSON.stringify(resp))
87
+ }
88
+ return
89
+ }
90
+
91
+ if (!responseSent && !s.writableEnded) {
92
+ responseSent = true
93
+ s.writeHead(404);
94
+ s.end();
52
95
  }
53
- throw Error(r.url + ' not found')
54
96
  } catch (e) {
55
97
  console.error(e.stack)
56
- s.writeHead(500).end()
98
+ if (!responseSent && !s.writableEnded) {
99
+ responseSent = true
100
+ s.writeHead(500).end()
101
+ }
57
102
  }
58
103
  })
59
- }).listen(port || 3000, ip || '')
104
+ }
105
+
106
+ let server
107
+ if (params.secure) {
108
+ const certPath = './cert.pem'
109
+ const keyPath = './key.pem'
110
+
111
+ if (!fs.existsSync(certPath) || !fs.existsSync(keyPath)) {
112
+ throw new Error('Certificate files not found. Run ./generate-certs.sh first.')
113
+ }
114
+
115
+ const options = {
116
+ cert: fs.readFileSync(certPath),
117
+ key: fs.readFileSync(keyPath)
118
+ }
119
+
120
+ server = https.createServer(options, requestHandler)
121
+ } else {
122
+ server = http.createServer(requestHandler)
123
+ }
124
+
125
+ server.listen(port || 3000, ip || '')
60
126
 
61
- console.log(`started on: ${(process.env.ip || ip)}:${(process.env.port || port)}, using routes: ${Object.keys(routes)}`)
127
+ const protocol = params.secure ? 'https' : 'http'
128
+ console.log(`started on: ${protocol}://${(process.env.ip || ip)}:${(process.env.port || port)}, public: ${publicDir}, ${Object.keys(routes).length > 0 ? `using routes: ${Object.keys(routes)}` : 'not using routes'}`)
62
129
 
63
130
  return {
64
131
  routes: routes,
@@ -66,5 +133,4 @@ export default function (routes, port = 3000, ip = '127.0.0.1') {
66
133
  server: server,
67
134
  stop: () => { server.close(); return true }
68
135
  }
69
-
70
- }
136
+ }
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "instaserve",
3
- "version": "0.1.24",
3
+ "version": "1.0.1",
4
4
  "description": "Instant web stack",
5
5
  "main": "module.mjs",
6
- "bin": "./server.mjs",
6
+ "bin": "./instaserve",
7
7
  "scripts": {
8
- "start": "node server.mjs",
9
- "deno": "deno run --allow-env --unstable --allow-net --allow-read deno/server.js",
10
- "bun": "bun run bun/http.js",
11
- "test": "node --no-warnings test.mjs"
8
+ "start": "node instaserve"
9
+ },
10
+ "type": "module",
11
+ "dependencies": {
12
+ "chalk": "^5.4.1"
12
13
  }
13
14
  }
package/public/index.html CHANGED
@@ -1,13 +1,209 @@
1
1
  <!DOCTYPE html>
2
- <link rel="icon" href="data:;base64,iVBORw0KGgo=">
3
- <script src="enigmatic.js"></script>
4
-
5
- <script>
6
- // navigator.serviceWorker.register('sw.js')
7
- </script>
8
- <link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css'>
9
-
10
- <div class='bg-yellow-200'>hello</div>
11
- <script>
12
- document.write(enigmatic.version)
13
- </script>
2
+ <html>
3
+ <head>
4
+ <title>Instaserve</title>
5
+ <style>
6
+ :root {
7
+ --primary: #3b82f6;
8
+ --primary-dark: #2563eb;
9
+ --text: #1f2937;
10
+ --text-light: #6b7280;
11
+ --bg: #f9fafb;
12
+ }
13
+ body {
14
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
15
+ line-height: 1.5;
16
+ max-width: 800px;
17
+ margin: 0 auto;
18
+ padding: 20px 15px;
19
+ color: var(--text);
20
+ background: var(--bg);
21
+ }
22
+ .header {
23
+ text-align: center;
24
+ margin-bottom: 20px;
25
+ }
26
+ .orb {
27
+ width: 60px;
28
+ height: 60px;
29
+ background: radial-gradient(circle at 30% 30%, #60a5fa, var(--primary));
30
+ border-radius: 50%;
31
+ margin: 0 auto 10px;
32
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
33
+ }
34
+ h1 {
35
+ color: var(--primary);
36
+ font-size: 2rem;
37
+ margin: 0;
38
+ }
39
+ .subtitle {
40
+ color: var(--text-light);
41
+ font-size: 1rem;
42
+ margin: 5px 0;
43
+ }
44
+ h2 {
45
+ color: var(--text);
46
+ font-size: 1.3rem;
47
+ margin: 20px 0 10px;
48
+ padding-bottom: 5px;
49
+ border-bottom: 2px solid var(--primary);
50
+ }
51
+ h3 {
52
+ color: var(--text);
53
+ font-size: 1.1rem;
54
+ margin: 15px 0 8px;
55
+ }
56
+ code {
57
+ background: #f3f4f6;
58
+ padding: 2px 4px;
59
+ border-radius: 4px;
60
+ font-family: 'SF Mono', Menlo, monospace;
61
+ color: var(--primary-dark);
62
+ }
63
+ pre {
64
+ background: #f3f4f6;
65
+ padding: 10px;
66
+ border-radius: 6px;
67
+ overflow-x: auto;
68
+ border: 1px solid #e5e7eb;
69
+ margin: 10px 0;
70
+ font-size: 0.9rem;
71
+ }
72
+ .option {
73
+ margin: 8px 0;
74
+ padding-left: 0;
75
+ position: relative;
76
+ display: flex;
77
+ gap: 8px;
78
+ }
79
+ .option:before {
80
+ display: none;
81
+ }
82
+ .option code {
83
+ flex-shrink: 0;
84
+ }
85
+ ul {
86
+ padding-left: 15px;
87
+ margin: 8px 0;
88
+ }
89
+ li {
90
+ margin: 5px 0;
91
+ }
92
+ .usage {
93
+ background: white;
94
+ padding: 15px;
95
+ border-radius: 6px;
96
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
97
+ margin: 15px 0;
98
+ }
99
+ p {
100
+ margin: 8px 0;
101
+ }
102
+ </style>
103
+ </head>
104
+ <body>
105
+ <div class="header">
106
+ <div class="orb"></div>
107
+ <h1>Instaserve</h1>
108
+ <p class="subtitle">Instant web stack for Node.js</p>
109
+ </div>
110
+
111
+ <div class="usage">
112
+ <h2>Usage</h2>
113
+ <pre><code>npx instaserve [options]</code></pre>
114
+ </div>
115
+
116
+ <h2>Options</h2>
117
+ <div class="option"><code>-port &lt;number&gt;</code> - Port to listen on (default: 3000)</div>
118
+ <div class="option"><code>-ip &lt;address&gt;</code> - IP address to bind to (default: 127.0.0.1)</div>
119
+ <div class="option"><code>-public &lt;path&gt;</code> - Public directory path (default: ./public)</div>
120
+ <div class="option"><code>-api &lt;file&gt;</code> - Path to routes file (default: ./routes.js)</div>
121
+ <div class="option"><code>-secure</code> - Enable HTTPS (requires cert.pem and key.pem - run ./generate-certs.sh)</div>
122
+ <div class="option"><code>-help</code> - Show help message</div>
123
+
124
+ <h2>HTTPS Support</h2>
125
+ <p>Instaserve supports HTTPS with self-signed certificates. To enable HTTPS:</p>
126
+ <ol>
127
+ <li><strong>Generate certificates:</strong> <code>./generate-certs.sh</code></li>
128
+ <li><strong>Run with HTTPS:</strong> <code>npx instaserve -secure</code></li>
129
+ </ol>
130
+ <p>The certificate generation script creates trusted certificates that won't show browser warnings.</p>
131
+
132
+ <h2>Routes</h2>
133
+ <p>The routes file (<code>routes.js</code> by default) defines your API endpoints. Each route is a function that handles requests to a specific URL path.</p>
134
+
135
+ <h3>Basic Route Example</h3>
136
+ <pre><code>export default {
137
+ // Handle GET /hello
138
+ hello: (req, res, data) => {
139
+ return { message: 'Hello World' }
140
+ }
141
+ }</code></pre>
142
+
143
+ <h3>Special Routes (Middleware)</h3>
144
+ <p>Routes starting with <code>_</code> are middleware functions that run on <strong>every request</strong> before the main route handler. They are useful for:</p>
145
+ <ul>
146
+ <li>Logging requests</li>
147
+ <li>Authentication</li>
148
+ <li>Request modification</li>
149
+ <li>Response headers</li>
150
+ </ul>
151
+ <p>Middleware functions can:</p>
152
+ <ul>
153
+ <li>Return <code>false</code> to continue to the next middleware or main route</li>
154
+ <li>Return a truthy value to stop processing and use that as the response</li>
155
+ <li>Modify the request or response objects</li>
156
+ </ul>
157
+
158
+ <h4>Middleware Example</h4>
159
+ <pre><code>export default {
160
+ // Log every request
161
+ _log: (req, res, data) => {
162
+ console.log(`${req.method} ${req.url}`)
163
+ return false // Continue processing
164
+ },
165
+
166
+ // Block unauthorized requests
167
+ _auth: (req, res, data) => {
168
+ if (!data.token) {
169
+ res.writeHead(401)
170
+ return 'Unauthorized'
171
+ }
172
+ return false // Continue if authorized
173
+ }
174
+ }</code></pre>
175
+
176
+ <h3>Route Parameters</h3>
177
+ <p>Each route function receives:</p>
178
+ <ul>
179
+ <li><code>req</code> - The HTTP request object</li>
180
+ <li><code>res</code> - The HTTP response object</li>
181
+ <li><code>data</code> - Combined data from:
182
+ <ul>
183
+ <li>POST body (if JSON)</li>
184
+ <li>URL query parameters</li>
185
+ <li>Form data</li>
186
+ </ul>
187
+ </li>
188
+ </ul>
189
+
190
+ <h3>Example Routes File</h3>
191
+ <pre><code>export default {
192
+ // Middleware example
193
+ _debug: (req, res, data) => {
194
+ console.log('Request:', req.url)
195
+ return false // Continue to next route
196
+ },
197
+
198
+ // API endpoint
199
+ api: (req, res, data) => {
200
+ return { status: 'ok', data }
201
+ },
202
+
203
+ // Error handling
204
+ testerror: () => {
205
+ throw new Error('Test error')
206
+ }
207
+ }</code></pre>
208
+ </body>
209
+ </html>
package/routes.js ADDED
@@ -0,0 +1,54 @@
1
+ /*
2
+ * This file defines the API routes and middleware for the Instaserve web server.
3
+ *
4
+ * The file exports a default object containing route handlers and middleware functions.
5
+ *
6
+ * Middleware functions (prefixed with _):
7
+ * - _debug: Logs request method, URL and data to console
8
+ * - _returnfalsy: Example middleware that returns true to stop request processing
9
+ * - _example: Demonstrates middleware behavior with console log
10
+ * - _end: Example of early response termination (commented out)
11
+ *
12
+ * Route handlers:
13
+ * - api: Returns a string response with the request data
14
+ * - testerror: Throws an error for testing error handling
15
+ * - testdata: Example route that returns the context parameter
16
+ *
17
+ * Each route handler receives:
18
+ * - req: HTTP request object
19
+ * - s: HTTP response object
20
+ * - data: Combined request data (POST body, query params, form data)
21
+ */
22
+
23
+ export default {
24
+ // Middleware functions (prefixed with _) run on every request
25
+ // Return false to continue processing, or a value to use as response
26
+
27
+ // Example: Log all requests
28
+ _log: (req, res, data) => {
29
+ console.log(`${req.method} ${req.url}`)
30
+ return false // Continue to next middleware or route
31
+ },
32
+
33
+ /**** Example: Basic authentication
34
+ _auth: (req, res, data) => {
35
+ if (!data.token) {
36
+ res.writeHead(401)
37
+ return 'Unauthorized'
38
+ }
39
+ return false // Continue if authorized
40
+ }, */
41
+
42
+ // Regular route handlers
43
+ hello: (req, res, data) => {
44
+ return { message: 'Hello World' }
45
+ },
46
+
47
+ _debug: ({method, url}, s, data) => { console.log(method, url, data) },
48
+ _example: (r, s) => console.log('in routes.mjs, returning a truthy value (above) will stop the chain'),
49
+ // _returnfalsy: (r, s) => { return true }, // Commented out - interferes with static file serving
50
+
51
+ api: (r, s, data) => 'an example api response, data:' + JSON.stringify(data),
52
+ testerror: () => { throw new Error('this from testerror') },
53
+ testdata: (r, s, c, d) => c
54
+ }
@@ -1,9 +0,0 @@
1
- # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/alpine/.devcontainer/base.Dockerfile
2
-
3
- # [Choice] Alpine version: 3.16, 3.15, 3.14, 3.13
4
- ARG VARIANT="3.16"
5
- FROM mcr.microsoft.com/vscode/devcontainers/base:0-alpine-${VARIANT}
6
-
7
- # ** [Optional] Uncomment this section to install additional packages. **
8
- # RUN apk update \
9
- # && apk add --no-cache <your-package-list-here>
@@ -1,22 +0,0 @@
1
- // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
2
- // https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/alpine
3
- {
4
- "name": "Alpine-NodeJS",
5
- "build": {
6
- "dockerfile": "Dockerfile",
7
- // Update 'VARIANT' to pick an Alpine version: 3.13, 3.14, 3.15, 3.16
8
- "args": { "VARIANT": "3.16" }
9
- },
10
-
11
- // Use 'forwardPorts' to make a list of ports inside the container available locally.
12
- // "forwardPorts": [],
13
-
14
- // Use 'postCreateCommand' to run commands after the container is created.
15
- // "postCreateCommand": "uname -a",
16
-
17
- // Replace when using a ptrace-based debugger like C++, Go, and Rust
18
- // "runArgs": [ "--init", "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ],
19
-
20
- // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
21
- "remoteUser": "vscode"
22
- }
package/bun/http.js DELETED
@@ -1,50 +0,0 @@
1
- import {existsSync} from 'fs'
2
- import { pathToFileURL } from 'node:url'
3
- import { resolve } from 'node:path'
4
- const routesfile = resolve(process.env.routes || '../routes.mjs')
5
- const routesurl = pathToFileURL(routesfile).href
6
-
7
- const routes = (await import(routesurl)).default
8
- const port = process.env.port || 3000
9
-
10
- class s {
11
- end(s) {
12
- this.resp = s
13
- }
14
- }
15
-
16
- Bun.serve({
17
- port: port,
18
- async fetch(r) {
19
-
20
- let url = new URL(r.url).pathname
21
- const data = await r.text()
22
-
23
- const ru = {method: r.method, url: url}
24
- const rs = new s()
25
-
26
- const midware = Object.keys(routes)
27
- .filter(k => k.startsWith('_'))
28
- .find(k => routes[k](ru, rs, data))
29
-
30
- // Routes.mjs
31
- if(routes[url]) {
32
- const f = routes[url](ru, rs, data)
33
- return new Response(rs.resp)
34
- }
35
-
36
- // Static
37
- const fn = (url == '/') ? `public/index.html` : `public/${url}`
38
- if (existsSync(fn))
39
- return new Response(Bun.file(fn))
40
-
41
- return new Response('', { status: 404 })
42
-
43
- },
44
- error(e) {
45
- console.error(e)
46
- return new Response('', { status: 404 })
47
- }
48
- })
49
-
50
- console.log(`Started on: ${port}, using routes: ${Object.keys(routes)}`)
package/deno/server.js DELETED
@@ -1,39 +0,0 @@
1
- import { existsSync } from "https://deno.land/std/fs/mod.ts";
2
-
3
- const routesfile = Deno.env.get('routes') || '../routes.mjs'
4
- const port = Deno.env.get('port') || 3000
5
- const routes = (await import(routesfile)).default
6
-
7
- class s {
8
- end(s) {
9
- this.resp = s
10
- }
11
- }
12
-
13
- Deno.serve(async (r) => {
14
-
15
- const data = await r.text()
16
- let url = new URL(r.url).pathname
17
- const ru = { method: r.method, url: url }
18
- const rs = new s()
19
-
20
- const midware = Object.keys(routes)
21
- .filter((k) => k.startsWith('_'))
22
- .find((k) => routes[k](ru, rs, data))
23
-
24
- // Routes.mjs
25
- if (routes[url]) {
26
- const f = routes[url](ru, rs, data)
27
- return new Response(rs.resp)
28
- }
29
-
30
- // Static
31
- const fn = (url == '/') ? `public/index.html` : `public/${url}`
32
- if (existsSync(fn))
33
- return new Response(await Deno.readTextFile(fn), { headers: { 'Content-Type': 'text/html' } })
34
-
35
- return new Response('', { status: 404 })
36
-
37
- }, {port: port})
38
-
39
- console.log(`routes: ${Object.keys(routes)}`)
@@ -1,150 +0,0 @@
1
- const w = {}, d = document
2
-
3
- // helpers
4
-
5
- w.$ = d.querySelector.bind(d)
6
- w.$$ = d.querySelectorAll.bind(d)
7
- w.loadJS = (src) => {
8
- return new Promise((r, j) => {
9
- if ($(`script[src="${src}"]`)) return r(true)
10
- const s = d.createElement('script')
11
- s.src = src
12
- s.addEventListener('load', r)
13
- d.head.appendChild(s)
14
- })
15
- }
16
- w.loadCSS = (src) => {
17
- return new Promise((r, j) => {
18
- const s = document.createElement('link')
19
- s.rel = 'stylesheet'
20
- s.href = src
21
- s.addEventListener('load', r)
22
- d.head.appendChild(s)
23
- })
24
- }
25
- w.wait = (ms) => new Promise((r) => setTimeout(r, ms))
26
- w.ready = async () => {
27
- return new Promise((r) => {
28
- if (document.readyState === 'complete') r(true)
29
- document.onreadystatechange = () => {
30
- if (document.readyState === 'complete') r()
31
- }
32
- })
33
- }
34
- w.child = (type = 'div', html = '') => {
35
- const e = d.createElement(type)
36
- e.innerHTML = html
37
- d.body.appendChild(e)
38
- return e
39
- }
40
-
41
- // Custom element
42
-
43
- w.element = (name, { onMount = x => x, beforeData = x => x, style, template = '' }) => {
44
- customElements.define(name, class extends HTMLElement {
45
- async connectedCallback(props) {
46
- await onMount()
47
- if (style) {
48
- const s = document.createElement('style')
49
- s.innerHTML = `${name} {${style}}`
50
- d.body.appendChild(s)
51
- }
52
- this.template = template
53
- if (!this.template.match('{'))
54
- this.innerHTML = this.template
55
- }
56
- set(o) {
57
- this.innerHTML = ''
58
- o = beforeData(o)
59
- if (!Array.isArray(o)) o = [o]
60
- const m = new Function('o', 'return `' + this.template + '`')
61
- o.map((i) => (this.innerHTML += m(o)))
62
- }
63
- })
64
- }
65
-
66
- if (window.components) {
67
- for (let name in window.components)
68
- w.element(name, window.components[name])
69
- }
70
-
71
- // Data
72
-
73
- w.state = new Proxy(
74
- {}, {
75
- set: (obj, prop, value) => {
76
- let ret = []
77
- for (const e of $$(`[data*=${prop}]`)) {
78
- console.log(['setting e', e.tagName, e.id, value])
79
- e.set(value)
80
- }
81
- obj[prop] = value
82
- },
83
- get: (obj, prop, receiver) => {
84
- if (prop == '_state') return obj
85
- return obj[prop]
86
- },
87
- }
88
- )
89
-
90
- w.dataEvent = (x) => console.log(`dataevent: ${x}`)
91
-
92
- w.fetchJSON = async (url, key) => {
93
- const j = await (await fetch(url)).json()
94
- if (key) state[key] = j
95
- dataEvent(j)
96
- return j
97
- }
98
-
99
- w.streamJSON = async (url, key) => {
100
- const ev = new EventSource(url)
101
- ev.onmessage = (ev) => {
102
- const j = JSON.parse(ev.data)
103
- if (key) state[key] = j
104
- dataEvent(j)
105
- return j
106
- }
107
- }
108
-
109
- // State changes
110
-
111
- w.trackStateChanges = () =>
112
- (w.dataEvent = (o) =>
113
- localStorage.set(new Date().toISOString(), JSON.stringify(o)))
114
- w.untrackStateChanges = () =>
115
- (w.dataEvent = (o) => console.log('dataevent:', o))
116
-
117
- // Startup
118
-
119
- w.start = async () => {
120
- await w.ready();
121
- [...$$('div')].map((e) => {
122
- if (!e.id)
123
- e.id = Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 3)
124
- e.pr = {};
125
- [...e.attributes].map((a) => (e.pr[a.name] = a.value))
126
- if (e.pr.fetch) e.fetch = fetchJSON.bind(null, e.pr.fetch, e.id)
127
- if ('immediate' in e.pr) e.fetch()
128
- if (e.pr.stream) e.stream = streamJSON.bind(null, e.pr.stream, e.id)
129
- if (e.pr.data) {
130
- if (e.innerHTML && e.innerHTML.includes('{')) {
131
- e.template = e.innerHTML.replaceAll('{', '${')
132
- e.innerHTML = ''
133
- }
134
- e.set = (o) => {
135
- e.innerHTML = ''
136
- if (!Array.isArray(o)) o = [o]
137
- const m = new Function('o', 'return `' + e.template + '`')
138
- o.map((i) => (e.innerHTML += m(i)))
139
- }
140
- }
141
- })
142
- }
143
-
144
- w.enigmatic = { version: '2022-03-05 0.10.2' }
145
- Object.assign(window, w);
146
-
147
- (async () => {
148
- await w.start()
149
- if (window.main) window.main()
150
- })()
package/public/sw.js DELETED
@@ -1,18 +0,0 @@
1
- self.addEventListener('fetch', event => {
2
- const cache = await caches.open('e')
3
- const req = event.request
4
-
5
- event.respondWith(async () => {
6
- if(!req.url.match(/\.js$|\.css$/))
7
- return fetch(req)
8
-
9
- const response = await cache.match(req)
10
- if(response) return response
11
- response = await fetch(req)
12
-
13
- if(response.status == 200)
14
- cache.put(req, response)
15
-
16
- return response
17
- })
18
- })
package/public/test.html DELETED
@@ -1 +0,0 @@
1
- ok
package/routes.mjs DELETED
@@ -1,13 +0,0 @@
1
- import url from 'node:url'
2
-
3
- export default {
4
- /*_testerror: ()=>console.testerrors(),*/
5
- _debug: ({method, url}, s, data) => { console.log(method, url, data) },
6
- _returnfalsy: (r, s) => { return true },
7
- _example: (r, s) => console.log('returning a truthy value (above) will stop the chain'),
8
- //_end: (r, s) => s.end('ended early'),
9
-
10
- api: (r, s, data) => 'an example api response, data:' + JSON.stringify(data),
11
- testerror: () => { throw new Error('this from testerror') },
12
- testdata: (r, s, c, d) => c
13
- }
package/server.mjs DELETED
@@ -1,22 +0,0 @@
1
- #!/usr/local/bin/node
2
-
3
- // > npx instaserve
4
- // > port=8080 npx instaserve
5
-
6
- import server from './module.mjs'
7
- import { pathToFileURL } from 'node:url'
8
- import fs from 'node:fs'
9
- const [npx, instaserve, cmd] = process.argv
10
- const {port, ip} = process.env
11
-
12
- if (cmd === 'create' && !fs.existsSync('routes.mjs')) {
13
- fs.writeFileSync('routes.mjs', `export default {
14
- _debug: ({method, url}, s) => !console.log(method, url),
15
- _example: (r, s) => console.log('returning a falsy value (above) will stop the chain'),
16
- api: (r, s) => 'an example api response'
17
- }`)
18
- }
19
-
20
- const routesurl = pathToFileURL('routes.mjs').href
21
- const routes = (await import(routesurl)).default
22
- server(routes, Number(port||3000), ip)
package/test.mjs DELETED
@@ -1,48 +0,0 @@
1
- import serve from './module.mjs'
2
- import { get, te, tde } from '../instax/module.mjs'
3
-
4
- const port = 8080
5
- const server = serve({
6
- api: (r, s) => 'Hello!',
7
- api2: (r, s, data) => JSON.stringify(data)
8
- }, port)
9
- te(server.port, port)
10
-
11
- // Routes
12
- const resp = await get('http://localhost:8080/api')
13
- te(resp, 'Hello!')
14
- const resp2 = await get('http://localhost:8080/api2', {method: 'POST', body: JSON.stringify({a:1})})
15
- tde(resp2, {a:1})
16
-
17
- // Public
18
- const testhtml = await get('http://localhost:8080/test.html')
19
- te(testhtml, 'ok')
20
- te(server.stop(), true)
21
-
22
- // Test route returned values
23
- const db = {}
24
- const server2 = serve({
25
- _: ({url}) => console.log(url),
26
- __: ({headers: {host}, method, url}) => console.log(host, method, url),
27
- str: () => 'ok',
28
- obj: x => ({a: 'ok'}),
29
- undef: () => undefined,
30
- testerror: () => { throw new Error('this from testerror')}
31
- }, 8085)
32
- te(server2.port, 8085)
33
- te(server2.routes.str(), 'ok')
34
-
35
- const return_str = await get('http://localhost:8085/str')
36
- te(return_str, 'ok')
37
-
38
- const return_obj = await get('http://localhost:8085/obj')
39
- te(return_obj.a, 'ok')
40
-
41
- const return_undefined = await get('http://localhost:8085/undef')
42
- te(return_undefined, '')
43
-
44
- const test_error = await get('http://localhost:8085/testerror')
45
- te(test_error.error, 'this from testerror')
46
-
47
- server2.stop()
48
- console.log('tests complete')