update
This commit is contained in:
@@ -0,0 +1,546 @@
|
||||
# bare-dgram - UDP Datagrams
|
||||
|
||||
## Overview
|
||||
|
||||
bare-dgram provides UDP (User Datagram Protocol) support for JavaScript. It enables connectionless, unreliable datagram communication for use cases where speed is prioritized over reliability.
|
||||
|
||||
### Key Features
|
||||
|
||||
- **UDP sockets**: Send/receive datagrams
|
||||
- **Connectionless**: No persistent connections
|
||||
- **Multicast**: Support for multicast groups
|
||||
- **Built on UDX**: High-performance UDP implementation
|
||||
- **Node.js compatible**: Similar to dgram module
|
||||
|
||||
### Use Cases
|
||||
|
||||
- **DNS queries**: Domain name resolution
|
||||
- **Streaming media**: Real-time audio/video
|
||||
- **Gaming**: Fast game state updates
|
||||
- **IoT**: Device communication
|
||||
- **Broadcasting**: Discovery protocols
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install bare-dgram
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### UDP Server
|
||||
|
||||
```js
|
||||
const dgram = require('bare-dgram')
|
||||
|
||||
const server = dgram.createSocket('udp4')
|
||||
|
||||
server.on('message', (msg, rinfo) => {
|
||||
console.log(`Received: ${msg} from ${rinfo.address}:${rinfo.port}`)
|
||||
|
||||
// Echo back
|
||||
server.send(msg, rinfo.port, rinfo.address)
|
||||
})
|
||||
|
||||
server.bind(41234, () => {
|
||||
console.log('UDP server listening')
|
||||
})
|
||||
```
|
||||
|
||||
### UDP Client
|
||||
|
||||
```js
|
||||
const dgram = require('bare-dgram')
|
||||
|
||||
const client = dgram.createSocket('udp4')
|
||||
const message = Buffer.from('Hello UDP!')
|
||||
|
||||
client.send(message, 41234, 'localhost', (err) => {
|
||||
if (err) console.error(err)
|
||||
client.close()
|
||||
})
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Socket Creation
|
||||
|
||||
#### `dgram.createSocket(type[, callback])`
|
||||
|
||||
Create UDP socket.
|
||||
|
||||
**Parameters:**
|
||||
- `type` (string): 'udp4' or 'udp6'
|
||||
- `callback` (function): Message handler
|
||||
|
||||
**Returns:** Socket
|
||||
|
||||
#### `dgram.createSocket(options[, callback])`
|
||||
|
||||
Create with options.
|
||||
|
||||
**Options:**
|
||||
- `type`: 'udp4' or 'udp6'
|
||||
- `reuseAddr`: Allow address reuse
|
||||
|
||||
### Socket Methods
|
||||
|
||||
#### `socket.bind([port][, address][, callback])`
|
||||
|
||||
Bind to address.
|
||||
|
||||
#### `socket.send(msg[, offset, length], port, address[, callback])`
|
||||
|
||||
Send datagram.
|
||||
|
||||
**Example:**
|
||||
```js
|
||||
socket.send(Buffer.from('Hello'), 0, 5, 8080, 'localhost')
|
||||
```
|
||||
|
||||
#### `socket.close([callback])`
|
||||
|
||||
Close socket.
|
||||
|
||||
#### `socket.address()`
|
||||
|
||||
Get address info.
|
||||
|
||||
**Returns:** `{ address, family, port }`
|
||||
|
||||
#### `socket.setBroadcast(flag)`
|
||||
|
||||
Enable/disable broadcast.
|
||||
|
||||
#### `socket.setMulticastTTL(ttl)`
|
||||
|
||||
Set multicast TTL.
|
||||
|
||||
#### `socket.addMembership(multicastAddress[, multicastInterface])`
|
||||
|
||||
Join multicast group.
|
||||
|
||||
#### `socket.dropMembership(multicastAddress[, multicastInterface])`
|
||||
|
||||
Leave multicast group.
|
||||
|
||||
### Events
|
||||
|
||||
- `message` - Datagram received `(msg, rinfo)`
|
||||
- `listening` - Socket bound
|
||||
- `close` - Socket closed
|
||||
- `error` - Error occurred
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Example 1: DNS Client
|
||||
|
||||
```js
|
||||
const dgram = require('bare-dgram')
|
||||
|
||||
// Simple DNS query
|
||||
function dnsQuery(domain, dnsServer = '8.8.8.8') {
|
||||
const socket = dgram.createSocket('udp4')
|
||||
|
||||
// Build DNS query (simplified)
|
||||
const query = buildDNSQuery(domain)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
socket.on('message', (response) => {
|
||||
socket.close()
|
||||
resolve(parseDNSResponse(response))
|
||||
})
|
||||
|
||||
socket.on('error', reject)
|
||||
|
||||
socket.send(query, 53, dnsServer, (err) => {
|
||||
if (err) reject(err)
|
||||
})
|
||||
|
||||
// Timeout
|
||||
setTimeout(() => {
|
||||
socket.close()
|
||||
reject(new Error('DNS timeout'))
|
||||
}, 5000)
|
||||
})
|
||||
}
|
||||
|
||||
function buildDNSQuery(domain) {
|
||||
// Simplified DNS query construction
|
||||
const buf = Buffer.alloc(512)
|
||||
let offset = 0
|
||||
|
||||
// Transaction ID
|
||||
buf.writeUInt16BE(Math.floor(Math.random() * 65535), offset)
|
||||
offset += 2
|
||||
|
||||
// Flags (standard query)
|
||||
buf.writeUInt16BE(0x0100, offset)
|
||||
offset += 2
|
||||
|
||||
// Questions: 1
|
||||
buf.writeUInt16BE(1, offset)
|
||||
offset += 2
|
||||
|
||||
// Other counts: 0
|
||||
buf.writeUInt32BE(0, offset)
|
||||
offset += 4
|
||||
buf.writeUInt32BE(0, offset)
|
||||
offset += 4
|
||||
|
||||
// Question: domain
|
||||
for (const part of domain.split('.')) {
|
||||
buf.writeUInt8(part.length, offset++)
|
||||
buf.write(part, offset)
|
||||
offset += part.length
|
||||
}
|
||||
buf.writeUInt8(0, offset++) // End of name
|
||||
|
||||
// Type A, Class IN
|
||||
buf.writeUInt16BE(1, offset)
|
||||
offset += 2
|
||||
buf.writeUInt16BE(1, offset)
|
||||
offset += 2
|
||||
|
||||
return buf.slice(0, offset)
|
||||
}
|
||||
|
||||
function parseDNSResponse(buf) {
|
||||
// Simplified parsing
|
||||
const answers = []
|
||||
// ... parsing logic
|
||||
return answers
|
||||
}
|
||||
|
||||
// Usage
|
||||
dnsQuery('example.com').then(console.log).catch(console.error)
|
||||
```
|
||||
|
||||
### Example 2: Multicast Discovery
|
||||
|
||||
```js
|
||||
const dgram = require('bare-dgram')
|
||||
|
||||
const MULTICAST_ADDR = '239.255.255.250'
|
||||
const MULTICAST_PORT = 1900
|
||||
|
||||
class DiscoveryService {
|
||||
constructor(serviceId) {
|
||||
this.serviceId = serviceId
|
||||
this.peers = new Map()
|
||||
}
|
||||
|
||||
start() {
|
||||
this.socket = dgram.createSocket('udp4')
|
||||
|
||||
this.socket.on('message', (msg, rinfo) => {
|
||||
try {
|
||||
const data = JSON.parse(msg)
|
||||
this.handleMessage(data, rinfo)
|
||||
} catch (err) {
|
||||
// Not our protocol
|
||||
}
|
||||
})
|
||||
|
||||
this.socket.on('listening', () => {
|
||||
const addr = this.socket.address()
|
||||
console.log(`Discovery listening on ${addr.address}:${addr.port}`)
|
||||
|
||||
// Join multicast group
|
||||
this.socket.addMembership(MULTICAST_ADDR)
|
||||
this.socket.setMulticastTTL(128)
|
||||
|
||||
// Announce ourselves
|
||||
this.announce()
|
||||
|
||||
// Periodic announce
|
||||
this.interval = setInterval(() => this.announce(), 5000)
|
||||
})
|
||||
|
||||
this.socket.bind(MULTICAST_PORT)
|
||||
}
|
||||
|
||||
announce() {
|
||||
const msg = Buffer.from(JSON.stringify({
|
||||
type: 'announce',
|
||||
id: this.serviceId,
|
||||
timestamp: Date.now()
|
||||
}))
|
||||
|
||||
this.socket.send(msg, MULTICAST_PORT, MULTICAST_ADDR)
|
||||
}
|
||||
|
||||
handleMessage(data, rinfo) {
|
||||
if (data.type === 'announce' && data.id !== this.serviceId) {
|
||||
console.log(`Found peer: ${data.id} at ${rinfo.address}`)
|
||||
this.peers.set(data.id, {
|
||||
address: rinfo.address,
|
||||
port: rinfo.port,
|
||||
lastSeen: Date.now()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
clearInterval(this.interval)
|
||||
this.socket.close()
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const discovery = new DiscoveryService('my-service-' + Date.now())
|
||||
discovery.start()
|
||||
|
||||
setTimeout(() => discovery.stop(), 60000)
|
||||
```
|
||||
|
||||
### Example 3: Game State Server
|
||||
|
||||
```js
|
||||
const dgram = require('bare-dgram')
|
||||
|
||||
class GameServer {
|
||||
constructor(port) {
|
||||
this.port = port
|
||||
this.clients = new Map()
|
||||
this.gameState = {}
|
||||
}
|
||||
|
||||
start() {
|
||||
this.socket = dgram.createSocket('udp4')
|
||||
|
||||
this.socket.on('message', (msg, rinfo) => {
|
||||
const clientId = `${rinfo.address}:${rinfo.port}`
|
||||
|
||||
// Update client activity
|
||||
this.clients.set(clientId, {
|
||||
address: rinfo.address,
|
||||
port: rinfo.port,
|
||||
lastPing: Date.now()
|
||||
})
|
||||
|
||||
// Handle input
|
||||
try {
|
||||
const input = JSON.parse(msg)
|
||||
this.handleInput(clientId, input)
|
||||
} catch (err) {
|
||||
// Invalid input
|
||||
}
|
||||
})
|
||||
|
||||
this.socket.bind(this.port, () => {
|
||||
console.log(`Game server on port ${this.port}`)
|
||||
})
|
||||
|
||||
// Broadcast game state
|
||||
this.broadcastInterval = setInterval(() => {
|
||||
this.broadcastState()
|
||||
}, 1000 / 60) // 60 FPS
|
||||
|
||||
// Clean up inactive clients
|
||||
this.cleanupInterval = setInterval(() => {
|
||||
this.cleanupClients()
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
handleInput(clientId, input) {
|
||||
// Update game state based on input
|
||||
if (!this.gameState[clientId]) {
|
||||
this.gameState[clientId] = { x: 0, y: 0 }
|
||||
}
|
||||
|
||||
if (input.up) this.gameState[clientId].y -= 5
|
||||
if (input.down) this.gameState[clientId].y += 5
|
||||
if (input.left) this.gameState[clientId].x -= 5
|
||||
if (input.right) this.gameState[clientId].x += 5
|
||||
}
|
||||
|
||||
broadcastState() {
|
||||
const state = JSON.stringify({
|
||||
type: 'state',
|
||||
timestamp: Date.now(),
|
||||
players: this.gameState
|
||||
})
|
||||
|
||||
const msg = Buffer.from(state)
|
||||
|
||||
for (const [clientId, client] of this.clients) {
|
||||
this.socket.send(msg, client.port, client.address, (err) => {
|
||||
if (err) console.error(`Failed to send to ${clientId}:`, err.message)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
cleanupClients() {
|
||||
const now = Date.now()
|
||||
for (const [clientId, client] of this.clients) {
|
||||
if (now - client.lastPing > 10000) {
|
||||
console.log(`Removing inactive client: ${clientId}`)
|
||||
this.clients.delete(clientId)
|
||||
delete this.gameState[clientId]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
clearInterval(this.broadcastInterval)
|
||||
clearInterval(this.cleanupInterval)
|
||||
this.socket.close()
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const server = new GameServer(7777)
|
||||
server.start()
|
||||
```
|
||||
|
||||
### Example 4: Reliable UDP
|
||||
|
||||
```js
|
||||
const dgram = require('bare-dgram')
|
||||
|
||||
// Simple reliability layer on top of UDP
|
||||
class ReliableUDP {
|
||||
constructor(port) {
|
||||
this.port = port
|
||||
this.sequence = 0
|
||||
this.acknowledged = new Map()
|
||||
this.pending = new Map()
|
||||
this.socket = null
|
||||
}
|
||||
|
||||
start() {
|
||||
this.socket = dgram.createSocket('udp4')
|
||||
|
||||
this.socket.on('message', (msg, rinfo) => {
|
||||
try {
|
||||
const packet = JSON.parse(msg)
|
||||
this.handlePacket(packet, rinfo)
|
||||
} catch (err) {
|
||||
console.error('Invalid packet:', err)
|
||||
}
|
||||
})
|
||||
|
||||
this.socket.bind(this.port)
|
||||
|
||||
// Retransmit unacknowledged packets
|
||||
this.retransmitInterval = setInterval(() => {
|
||||
this.retransmit()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
send(data, port, address) {
|
||||
this.sequence++
|
||||
const packet = {
|
||||
seq: this.sequence,
|
||||
data: data,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
|
||||
const key = `${address}:${port}`
|
||||
if (!this.pending.has(key)) {
|
||||
this.pending.set(key, [])
|
||||
}
|
||||
this.pending.get(key).push(packet)
|
||||
|
||||
this.socket.send(Buffer.from(JSON.stringify(packet)), port, address)
|
||||
}
|
||||
|
||||
handlePacket(packet, rinfo) {
|
||||
const key = `${rinfo.address}:${rinfo.port}`
|
||||
|
||||
if (packet.ack) {
|
||||
// Remove acknowledged packets
|
||||
const pending = this.pending.get(key) || []
|
||||
const index = pending.findIndex(p => p.seq === packet.ack)
|
||||
if (index > -1) {
|
||||
pending.splice(index, 1)
|
||||
}
|
||||
} else {
|
||||
// Send ACK
|
||||
const ack = { ack: packet.seq }
|
||||
this.socket.send(Buffer.from(JSON.stringify(ack)), rinfo.port, rinfo.address)
|
||||
|
||||
// Process data (if not duplicate)
|
||||
console.log(`Received: ${packet.data}`)
|
||||
}
|
||||
}
|
||||
|
||||
retransmit() {
|
||||
const now = Date.now()
|
||||
|
||||
for (const [key, packets] of this.pending) {
|
||||
const [address, port] = key.split(':')
|
||||
|
||||
for (const packet of packets) {
|
||||
if (now - packet.timestamp > 1000) {
|
||||
packet.timestamp = now
|
||||
this.socket.send(
|
||||
Buffer.from(JSON.stringify(packet)),
|
||||
parseInt(port),
|
||||
address
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
clearInterval(this.retransmitInterval)
|
||||
this.socket.close()
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const reliable = new ReliableUDP(12345)
|
||||
reliable.start()
|
||||
|
||||
reliable.send('Hello!', 12346, 'localhost')
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Handle Packet Loss
|
||||
|
||||
```js
|
||||
// UDP is unreliable - expect packet loss
|
||||
socket.on('message', (msg) => {
|
||||
try {
|
||||
const data = JSON.parse(msg)
|
||||
// Process data
|
||||
} catch (err) {
|
||||
// Corrupted packet - ignore or log
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Limit Packet Size
|
||||
|
||||
```js
|
||||
// Stay under MTU to avoid fragmentation
|
||||
const MAX_SIZE = 1400 // Safe for most networks
|
||||
if (message.length > MAX_SIZE) {
|
||||
// Split into multiple packets
|
||||
}
|
||||
```
|
||||
|
||||
### Use Timeouts
|
||||
|
||||
```js
|
||||
const timeout = setTimeout(() => {
|
||||
socket.close()
|
||||
reject(new Error('Timeout'))
|
||||
}, 5000)
|
||||
|
||||
socket.on('message', (msg) => {
|
||||
clearTimeout(timeout)
|
||||
resolve(msg)
|
||||
})
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
|
||||
---
|
||||
**Module Type**: Runtime/Networking | **Ecosystem Role**: UDP | **Dependencies**: udx-native
|
||||
@@ -0,0 +1,223 @@
|
||||
# bare-dns - Domain Name Resolution
|
||||
|
||||
## Overview
|
||||
|
||||
bare-dns provides domain name resolution (DNS) capabilities for JavaScript. It enables looking up IP addresses from hostnames and other DNS operations.
|
||||
|
||||
### Key Features
|
||||
|
||||
- **DNS lookup**: Resolve hostnames to IPs
|
||||
- **IPv4/IPv6**: Support for both address families
|
||||
- **Asynchronous**: Promise and callback APIs
|
||||
- **Caching**: Built-in DNS caching
|
||||
|
||||
### Use Cases
|
||||
|
||||
- **Network connections**: Resolve hosts before connecting
|
||||
- **Service discovery**: Find service endpoints
|
||||
- **Email validation**: MX record lookups
|
||||
- **Network diagnostics**: DNS troubleshooting
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install bare-dns
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Lookup
|
||||
|
||||
```js
|
||||
const dns = require('bare-dns')
|
||||
|
||||
// Promise API
|
||||
const { address, family } = await dns.lookup('github.com')
|
||||
console.log(`IP: ${address}, Family: IPv${family}`)
|
||||
|
||||
// Callback API
|
||||
dns.lookup('example.com', (err, address, family) => {
|
||||
if (err) throw err
|
||||
console.log(`IP: ${address}, Family: IPv${family}`)
|
||||
})
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```js
|
||||
const dns = require('bare-dns')
|
||||
|
||||
// Force IPv4
|
||||
const result = await dns.lookup('google.com', { family: 4 })
|
||||
|
||||
// Force IPv6
|
||||
const result = await dns.lookup('google.com', { family: 6 })
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### dns.lookup(hostname[, options])
|
||||
|
||||
Resolve hostname to IP address.
|
||||
|
||||
**Parameters:**
|
||||
- `hostname` (string): Domain to lookup
|
||||
- `options` (object):
|
||||
- `family` (number): 4 for IPv4, 6 for IPv6, 0 for either (default)
|
||||
|
||||
**Returns:** Promise<{ address, family }>
|
||||
|
||||
**Callback Signature:**
|
||||
```js
|
||||
dns.lookup(hostname, (err, address, family) => {
|
||||
// address: IP address string
|
||||
// family: 4 or 6
|
||||
})
|
||||
```
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Example 1: Connection Helper
|
||||
|
||||
```js
|
||||
const dns = require('bare-dns')
|
||||
const net = require('bare-net')
|
||||
|
||||
async function connectToHost(hostname, port) {
|
||||
// Resolve hostname first
|
||||
const { address } = await dns.lookup(hostname)
|
||||
|
||||
// Connect to resolved IP
|
||||
return new Promise((resolve, reject) => {
|
||||
const socket = net.createConnection(port, address, () => {
|
||||
resolve(socket)
|
||||
})
|
||||
|
||||
socket.on('error', reject)
|
||||
})
|
||||
}
|
||||
|
||||
// Usage
|
||||
const conn = await connectToHost('api.example.com', 8080)
|
||||
conn.write('Hello')
|
||||
```
|
||||
|
||||
### Example 2: DNS Cache
|
||||
|
||||
```js
|
||||
const dns = require('bare-dns')
|
||||
|
||||
class DNSCache {
|
||||
constructor(ttl = 300000) { // 5 min default
|
||||
this.cache = new Map()
|
||||
this.ttl = ttl
|
||||
}
|
||||
|
||||
async lookup(hostname, options = {}) {
|
||||
const key = `${hostname}:${options.family || 0}`
|
||||
const cached = this.cache.get(key)
|
||||
|
||||
if (cached && Date.now() - cached.time < this.ttl) {
|
||||
return cached.result
|
||||
}
|
||||
|
||||
const result = await dns.lookup(hostname, options)
|
||||
this.cache.set(key, { result, time: Date.now() })
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.cache.clear()
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const cache = new DNSCache()
|
||||
|
||||
const result1 = await cache.lookup('github.com')
|
||||
const result2 = await cache.lookup('github.com') // From cache
|
||||
```
|
||||
|
||||
### Example 3: Multi-DNS Resolver
|
||||
|
||||
```js
|
||||
const dns = require('bare-dns')
|
||||
|
||||
class MultiResolver {
|
||||
async resolveAll(hostname) {
|
||||
const results = {
|
||||
ipv4: null,
|
||||
ipv6: null
|
||||
}
|
||||
|
||||
// Try IPv4
|
||||
try {
|
||||
results.ipv4 = await dns.lookup(hostname, { family: 4 })
|
||||
} catch (err) {
|
||||
console.log(`No IPv4 for ${hostname}`)
|
||||
}
|
||||
|
||||
// Try IPv6
|
||||
try {
|
||||
results.ipv6 = await dns.lookup(hostname, { family: 6 })
|
||||
} catch (err) {
|
||||
console.log(`No IPv6 for ${hostname}`)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
async resolveFastest(hostname) {
|
||||
const promises = [
|
||||
dns.lookup(hostname, { family: 4 }).then(r => ({ ...r, family: 4 })),
|
||||
dns.lookup(hostname, { family: 6 }).then(r => ({ ...r, family: 6 }))
|
||||
]
|
||||
|
||||
// Return first successful
|
||||
return Promise.race(promises)
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const resolver = new MultiResolver()
|
||||
const all = await resolver.resolveAll('google.com')
|
||||
console.log(all)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Always Handle Failures
|
||||
|
||||
```js
|
||||
try {
|
||||
const { address } = await dns.lookup('example.com')
|
||||
} catch (err) {
|
||||
console.error('DNS lookup failed:', err.message)
|
||||
// Use fallback or cached value
|
||||
}
|
||||
```
|
||||
|
||||
### Cache Results
|
||||
|
||||
```js
|
||||
// DNS lookups can be slow - cache results
|
||||
const cache = new Map()
|
||||
|
||||
async function cachedLookup(hostname) {
|
||||
if (cache.has(hostname)) {
|
||||
return cache.get(hostname)
|
||||
}
|
||||
|
||||
const result = await dns.lookup(hostname)
|
||||
cache.set(hostname, result)
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
|
||||
---
|
||||
**Module Type**: Runtime/Networking | **Ecosystem Role**: DNS Resolution | **Dependencies**: None
|
||||
@@ -0,0 +1,459 @@
|
||||
# bare-net - TCP and IPC Networking
|
||||
|
||||
## Overview
|
||||
|
||||
bare-net provides TCP and IPC (Inter-Process Communication) servers and clients for JavaScript. It enables network communication with a Node.js-compatible API.
|
||||
|
||||
### Key Features
|
||||
|
||||
- **TCP sockets**: Create TCP servers and clients
|
||||
- **IPC support**: Unix domain sockets and Windows named pipes
|
||||
- **Stream-based**: Built on bare-stream for composability
|
||||
- **Node.js compatible**: Familiar net API
|
||||
|
||||
### Use Cases
|
||||
|
||||
- **Network servers**: HTTP servers, game servers, etc.
|
||||
- **Client connections**: Connect to remote services
|
||||
- **IPC**: Communicate between local processes
|
||||
- **Protocols**: Build custom network protocols
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install bare-net
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### TCP Server
|
||||
|
||||
```js
|
||||
const net = require('bare-net')
|
||||
|
||||
const server = net.createServer((socket) => {
|
||||
console.log('Client connected')
|
||||
|
||||
socket.on('data', (data) => {
|
||||
console.log('Received:', data.toString())
|
||||
socket.write('Echo: ' + data)
|
||||
})
|
||||
|
||||
socket.on('close', () => {
|
||||
console.log('Client disconnected')
|
||||
})
|
||||
})
|
||||
|
||||
server.listen(8080, () => {
|
||||
console.log('Server listening on port 8080')
|
||||
})
|
||||
```
|
||||
|
||||
### TCP Client
|
||||
|
||||
```js
|
||||
const net = require('bare-net')
|
||||
|
||||
const client = net.createConnection(8080, 'localhost', () => {
|
||||
console.log('Connected to server')
|
||||
client.write('Hello, Server!')
|
||||
})
|
||||
|
||||
client.on('data', (data) => {
|
||||
console.log('Server says:', data.toString())
|
||||
})
|
||||
|
||||
client.on('close', () => {
|
||||
console.log('Connection closed')
|
||||
})
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Server
|
||||
|
||||
#### `net.createServer([options][, connectionListener])`
|
||||
|
||||
Create TCP server.
|
||||
|
||||
**Parameters:**
|
||||
- `options` (object): Server options
|
||||
- `connectionListener` (function): Connection callback
|
||||
|
||||
**Example:**
|
||||
```js
|
||||
const server = net.createServer((socket) => {
|
||||
// Handle connection
|
||||
})
|
||||
```
|
||||
|
||||
#### `server.listen(port[, host][, callback])`
|
||||
|
||||
Start listening.
|
||||
|
||||
**Example:**
|
||||
```js
|
||||
server.listen(8080, '0.0.0.0', () => {
|
||||
console.log('Server ready')
|
||||
})
|
||||
```
|
||||
|
||||
#### `server.address()`
|
||||
|
||||
Get server address.
|
||||
|
||||
**Returns:** `{ port, family, address }`
|
||||
|
||||
#### `server.close([callback])`
|
||||
|
||||
Stop server.
|
||||
|
||||
### Socket
|
||||
|
||||
#### `net.createConnection(port[, host][, connectListener])`
|
||||
|
||||
Create client connection.
|
||||
|
||||
**Example:**
|
||||
```js
|
||||
const socket = net.createConnection(8080, 'localhost')
|
||||
```
|
||||
|
||||
#### `net.createConnection(options[, connectListener])`
|
||||
|
||||
Create connection with options.
|
||||
|
||||
**Options:**
|
||||
- `port`: Target port
|
||||
- `host`: Target host
|
||||
- `localAddress`: Local address to bind
|
||||
- `localPort`: Local port
|
||||
|
||||
#### `socket.write(data[, encoding][, callback])`
|
||||
|
||||
Send data.
|
||||
|
||||
#### `socket.end([data][, encoding])`
|
||||
|
||||
Close connection.
|
||||
|
||||
#### `socket.setTimeout(timeout[, callback])`
|
||||
|
||||
Set timeout.
|
||||
|
||||
#### `socket.setKeepAlive([enable][, initialDelay])`
|
||||
|
||||
Enable keepalive.
|
||||
|
||||
#### `socket.address()`
|
||||
|
||||
Get local address.
|
||||
|
||||
#### `socket.remoteAddress`
|
||||
|
||||
Get remote address.
|
||||
|
||||
#### `socket.remotePort`
|
||||
|
||||
Get remote port.
|
||||
|
||||
#### Events
|
||||
|
||||
- `connect` - Connection established
|
||||
- `data` - Data received
|
||||
- `end` - Remote closed
|
||||
- `close` - Socket closed
|
||||
- `error` - Error occurred
|
||||
- `timeout` - Timeout occurred
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Example 1: Simple Echo Server
|
||||
|
||||
```js
|
||||
const net = require('bare-net')
|
||||
|
||||
const server = net.createServer((socket) => {
|
||||
const clientInfo = `${socket.remoteAddress}:${socket.remotePort}`
|
||||
console.log(`Client ${clientInfo} connected`)
|
||||
|
||||
socket.on('data', (data) => {
|
||||
console.log(`Received from ${clientInfo}:`, data.toString())
|
||||
socket.write(data) // Echo back
|
||||
})
|
||||
|
||||
socket.on('close', () => {
|
||||
console.log(`Client ${clientInfo} disconnected`)
|
||||
})
|
||||
|
||||
socket.on('error', (err) => {
|
||||
console.error(`Socket error from ${clientInfo}:`, err.message)
|
||||
})
|
||||
})
|
||||
|
||||
server.listen(3000, () => {
|
||||
const addr = server.address()
|
||||
console.log(`Echo server listening on ${addr.address}:${addr.port}`)
|
||||
})
|
||||
|
||||
// Graceful shutdown
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\nShutting down...')
|
||||
server.close(() => {
|
||||
process.exit(0)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Example 2: Connection Pool
|
||||
|
||||
```js
|
||||
const net = require('bare-net')
|
||||
|
||||
class ConnectionPool {
|
||||
constructor(host, port, maxConnections = 10) {
|
||||
this.host = host
|
||||
this.port = port
|
||||
this.maxConnections = maxConnections
|
||||
this.pool = []
|
||||
this.waiting = []
|
||||
}
|
||||
|
||||
async getConnection() {
|
||||
// Return existing available connection
|
||||
const available = this.pool.find(c => !c.busy)
|
||||
if (available) {
|
||||
available.busy = true
|
||||
return available
|
||||
}
|
||||
|
||||
// Create new if under limit
|
||||
if (this.pool.length < this.maxConnections) {
|
||||
const conn = await this._createConnection()
|
||||
conn.busy = true
|
||||
this.pool.push(conn)
|
||||
return conn
|
||||
}
|
||||
|
||||
// Wait for available connection
|
||||
return new Promise((resolve) => {
|
||||
this.waiting.push(resolve)
|
||||
})
|
||||
}
|
||||
|
||||
_createConnection() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const socket = net.createConnection(this.port, this.host, () => {
|
||||
socket.busy = false
|
||||
resolve(socket)
|
||||
})
|
||||
|
||||
socket.on('error', reject)
|
||||
|
||||
socket.on('close', () => {
|
||||
const idx = this.pool.indexOf(socket)
|
||||
if (idx > -1) this.pool.splice(idx, 1)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
releaseConnection(conn) {
|
||||
conn.busy = false
|
||||
|
||||
// Give to waiting client
|
||||
if (this.waiting.length > 0) {
|
||||
const next = this.waiting.shift()
|
||||
conn.busy = true
|
||||
next(conn)
|
||||
}
|
||||
}
|
||||
|
||||
closeAll() {
|
||||
for (const conn of this.pool) {
|
||||
conn.end()
|
||||
}
|
||||
this.pool = []
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const pool = new ConnectionPool('localhost', 3000)
|
||||
|
||||
const conn = await pool.getConnection()
|
||||
conn.write('Request data')
|
||||
conn.once('data', (response) => {
|
||||
console.log('Response:', response.toString())
|
||||
pool.releaseConnection(conn)
|
||||
})
|
||||
```
|
||||
|
||||
### Example 3: IPC Server
|
||||
|
||||
```js
|
||||
const net = require('bare-net')
|
||||
const path = require('bare-path')
|
||||
|
||||
const socketPath = process.platform === 'win32'
|
||||
? '\\.\pipe\my-service'
|
||||
: '/tmp/my-service.sock'
|
||||
|
||||
// Clean up old socket
|
||||
if (process.platform !== 'win32') {
|
||||
try {
|
||||
require('bare-fs').unlinkSync(socketPath)
|
||||
} catch (err) {
|
||||
// May not exist
|
||||
}
|
||||
}
|
||||
|
||||
const server = net.createServer((socket) => {
|
||||
console.log('IPC client connected')
|
||||
|
||||
socket.on('data', (data) => {
|
||||
try {
|
||||
const request = JSON.parse(data)
|
||||
console.log('Request:', request)
|
||||
|
||||
// Process request
|
||||
const response = { id: request.id, result: 'processed' }
|
||||
socket.write(JSON.stringify(response))
|
||||
} catch (err) {
|
||||
socket.write(JSON.stringify({ error: err.message }))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
server.listen(socketPath, () => {
|
||||
console.log('IPC server listening on', socketPath)
|
||||
})
|
||||
```
|
||||
|
||||
### Example 4: Protocol Client
|
||||
|
||||
```js
|
||||
const net = require('bare-net')
|
||||
const { Transform } = require('bare-stream')
|
||||
|
||||
class ProtocolClient {
|
||||
constructor(host, port) {
|
||||
this.host = host
|
||||
this.port = port
|
||||
this.socket = null
|
||||
this.requestId = 0
|
||||
this.pending = new Map()
|
||||
}
|
||||
|
||||
connect() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.socket = net.createConnection(this.port, this.host, () => {
|
||||
console.log('Connected')
|
||||
resolve()
|
||||
})
|
||||
|
||||
this.socket.on('data', (data) => {
|
||||
this._handleResponse(data)
|
||||
})
|
||||
|
||||
this.socket.on('error', reject)
|
||||
this.socket.on('close', () => {
|
||||
console.log('Connection closed')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async send(method, params) {
|
||||
const id = ++this.requestId
|
||||
const request = JSON.stringify({ id, method, params })
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pending.set(id, { resolve, reject })
|
||||
this.socket.write(request + '\n')
|
||||
|
||||
// Timeout
|
||||
setTimeout(() => {
|
||||
if (this.pending.has(id)) {
|
||||
this.pending.delete(id)
|
||||
reject(new Error('Request timeout'))
|
||||
}
|
||||
}, 5000)
|
||||
})
|
||||
}
|
||||
|
||||
_handleResponse(data) {
|
||||
try {
|
||||
const response = JSON.parse(data.toString())
|
||||
const pending = this.pending.get(response.id)
|
||||
|
||||
if (pending) {
|
||||
this.pending.delete(response.id)
|
||||
if (response.error) {
|
||||
pending.reject(new Error(response.error))
|
||||
} else {
|
||||
pending.resolve(response.result)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to parse response:', err)
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
this.socket.end()
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const client = new ProtocolClient('localhost', 3000)
|
||||
await client.connect()
|
||||
|
||||
const result = await client.send('getData', { key: 'test' })
|
||||
console.log('Result:', result)
|
||||
|
||||
client.close()
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Handle Errors
|
||||
|
||||
```js
|
||||
socket.on('error', (err) => {
|
||||
console.error('Socket error:', err.message)
|
||||
})
|
||||
|
||||
server.on('error', (err) => {
|
||||
console.error('Server error:', err.message)
|
||||
})
|
||||
```
|
||||
|
||||
### Set Timeouts
|
||||
|
||||
```js
|
||||
socket.setTimeout(30000, () => {
|
||||
console.log('Socket timeout')
|
||||
socket.end()
|
||||
})
|
||||
```
|
||||
|
||||
### Use Keepalive
|
||||
|
||||
```js
|
||||
socket.setKeepAlive(true, 60000)
|
||||
```
|
||||
|
||||
### Clean Up
|
||||
|
||||
```js
|
||||
process.on('SIGINT', () => {
|
||||
server.close(() => {
|
||||
process.exit(0)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
|
||||
---
|
||||
**Module Type**: Runtime/Networking | **Ecosystem Role**: TCP/IPC | **Dependencies**: bare-stream
|
||||
@@ -0,0 +1,269 @@
|
||||
# bare-pipe - Native I/O Pipes
|
||||
|
||||
## Overview
|
||||
|
||||
bare-pipe provides native I/O pipe operations for JavaScript. It enables direct access to system pipes for inter-process communication and stream redirection.
|
||||
|
||||
### Key Features
|
||||
|
||||
- **System pipes**: Access stdin, stdout, stderr
|
||||
- **Named pipes**: Create and use named pipes
|
||||
- **Stream interface**: Compatible with bare-stream
|
||||
- **Cross-platform**: Works on Unix and Windows
|
||||
|
||||
### Use Cases
|
||||
|
||||
- **CLI tools**: Read/write to standard streams
|
||||
- **Pipe chaining**: Connect processes
|
||||
- **Redirection**: Redirect I/O streams
|
||||
- **Inter-process**: Communication between processes
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install bare-pipe
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Access Standard Streams
|
||||
|
||||
```js
|
||||
const Pipe = require('bare-pipe')
|
||||
|
||||
// Write to stdout
|
||||
const stdout = new Pipe(1)
|
||||
stdout.write('Hello stdout!\n')
|
||||
|
||||
// Read from stdin
|
||||
const stdin = new Pipe(0)
|
||||
stdin.on('data', (data) => {
|
||||
console.log('Received:', data.toString())
|
||||
})
|
||||
```
|
||||
|
||||
### File Descriptors
|
||||
|
||||
```js
|
||||
const Pipe = require('bare-pipe')
|
||||
|
||||
// File descriptor numbers:
|
||||
// 0 = stdin
|
||||
// 1 = stdout
|
||||
// 2 = stderr
|
||||
|
||||
const stderr = new Pipe(2)
|
||||
stderr.write('Error message\n')
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Pipe Class
|
||||
|
||||
#### `new Pipe(fd)`
|
||||
|
||||
Create pipe from file descriptor.
|
||||
|
||||
**Parameters:**
|
||||
- `fd` (number): File descriptor number
|
||||
- `0` - Standard input (stdin)
|
||||
- `1` - Standard output (stdout)
|
||||
- `2` - Standard error (stderr)
|
||||
|
||||
#### `pipe.write(data[, encoding][, callback])`
|
||||
|
||||
Write data to pipe.
|
||||
|
||||
#### `pipe.end([data][, encoding][, callback])`
|
||||
|
||||
End the pipe.
|
||||
|
||||
#### `pipe.destroy([error])`
|
||||
|
||||
Destroy the pipe.
|
||||
|
||||
#### `pipe.on('data', callback)`
|
||||
|
||||
Listen for data events.
|
||||
|
||||
#### `pipe.on('end', callback)`
|
||||
|
||||
Listen for end events.
|
||||
|
||||
#### `pipe.on('error', callback)`
|
||||
|
||||
Listen for error events.
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Example 1: CLI Logger
|
||||
|
||||
```js
|
||||
const Pipe = require('bare-pipe')
|
||||
|
||||
class CLILogger {
|
||||
constructor() {
|
||||
this.stdout = new Pipe(1)
|
||||
this.stderr = new Pipe(2)
|
||||
}
|
||||
|
||||
log(message) {
|
||||
this.stdout.write(`${message}\n`)
|
||||
}
|
||||
|
||||
error(message) {
|
||||
this.stderr.write(`ERROR: ${message}\n`)
|
||||
}
|
||||
|
||||
info(message) {
|
||||
this.stdout.write(`[INFO] ${message}\n`)
|
||||
}
|
||||
|
||||
warn(message) {
|
||||
this.stderr.write(`[WARN] ${message}\n`)
|
||||
}
|
||||
|
||||
close() {
|
||||
this.stdout.end()
|
||||
this.stderr.end()
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const logger = new CLILogger()
|
||||
logger.info('Application started')
|
||||
logger.log('Processing data...')
|
||||
logger.warn('Low memory')
|
||||
logger.error('Failed to save file')
|
||||
logger.close()
|
||||
```
|
||||
|
||||
### Example 2: Interactive CLI
|
||||
|
||||
```js
|
||||
const Pipe = require('bare-pipe')
|
||||
|
||||
class InteractiveCLI {
|
||||
constructor() {
|
||||
this.stdin = new Pipe(0)
|
||||
this.stdout = new Pipe(1)
|
||||
this.buffer = ''
|
||||
}
|
||||
|
||||
start() {
|
||||
this.stdout.write('> ')
|
||||
|
||||
this.stdin.on('data', (data) => {
|
||||
this.buffer += data.toString()
|
||||
|
||||
// Check for complete line
|
||||
const lines = this.buffer.split('\n')
|
||||
this.buffer = lines.pop() // Keep incomplete line
|
||||
|
||||
for (const line of lines) {
|
||||
this.handleCommand(line.trim())
|
||||
}
|
||||
})
|
||||
|
||||
this.stdin.on('end', () => {
|
||||
this.stdout.write('\nGoodbye!\n')
|
||||
})
|
||||
}
|
||||
|
||||
handleCommand(cmd) {
|
||||
switch (cmd) {
|
||||
case 'help':
|
||||
this.stdout.write('Commands: help, echo <msg>, quit\n')
|
||||
break
|
||||
case 'quit':
|
||||
this.stdin.destroy()
|
||||
return
|
||||
default:
|
||||
if (cmd.startsWith('echo ')) {
|
||||
this.stdout.write(cmd.slice(5) + '\n')
|
||||
} else if (cmd) {
|
||||
this.stdout.write(`Unknown command: ${cmd}\n`)
|
||||
}
|
||||
}
|
||||
this.stdout.write('> ')
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const cli = new InteractiveCLI()
|
||||
cli.start()
|
||||
```
|
||||
|
||||
### Example 3: Progress Bar
|
||||
|
||||
```js
|
||||
const Pipe = require('bare-pipe')
|
||||
|
||||
class ProgressBar {
|
||||
constructor(total) {
|
||||
this.total = total
|
||||
this.current = 0
|
||||
this.stdout = new Pipe(1)
|
||||
this.width = 40
|
||||
}
|
||||
|
||||
update(value) {
|
||||
this.current = Math.min(value, this.total)
|
||||
this.render()
|
||||
}
|
||||
|
||||
render() {
|
||||
const percent = this.current / this.total
|
||||
const filled = Math.floor(this.width * percent)
|
||||
const empty = this.width - filled
|
||||
|
||||
const bar = '█'.repeat(filled) + '░'.repeat(empty)
|
||||
const percentage = Math.floor(percent * 100)
|
||||
|
||||
// Clear line and write progress
|
||||
this.stdout.write(`\r[${bar}] ${percentage}%`)
|
||||
|
||||
if (this.current >= this.total) {
|
||||
this.stdout.write('\n')
|
||||
}
|
||||
}
|
||||
|
||||
complete() {
|
||||
this.update(this.total)
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const progress = new ProgressBar(100)
|
||||
|
||||
for (let i = 0; i <= 100; i++) {
|
||||
setTimeout(() => {
|
||||
progress.update(i)
|
||||
}, i * 50)
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Always End Pipes
|
||||
|
||||
```js
|
||||
const pipe = new Pipe(1)
|
||||
pipe.write('data')
|
||||
pipe.end() // Important to signal EOF
|
||||
```
|
||||
|
||||
### Handle Errors
|
||||
|
||||
```js
|
||||
pipe.on('error', (err) => {
|
||||
console.error('Pipe error:', err)
|
||||
})
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
|
||||
---
|
||||
**Module Type**: Runtime/IO | **Ecosystem Role**: System Pipes | **Dependencies**: bare-stream
|
||||
@@ -0,0 +1,354 @@
|
||||
# bare-signals - Signal Handling
|
||||
|
||||
## Overview
|
||||
|
||||
bare-signals provides native signal handling for JavaScript. It enables capturing and handling Unix/Windows signals like SIGINT, SIGTERM, etc.
|
||||
|
||||
### Key Features
|
||||
|
||||
- **Signal handling**: Catch OS signals
|
||||
- **Multiple signals**: Support for various signal types
|
||||
- **Cross-platform**: Unix signals and Windows equivalents
|
||||
- **Event-based**: Event-driven signal handling
|
||||
|
||||
### Use Cases
|
||||
|
||||
- **Graceful shutdown**: Clean up on SIGINT/SIGTERM
|
||||
- **Process management**: Handle process control signals
|
||||
- **Application lifecycle**: Respond to system events
|
||||
- **Debugging**: Catch debug signals
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install bare-signals
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Handle SIGINT
|
||||
|
||||
```js
|
||||
const Signal = require('bare-signals')
|
||||
|
||||
const sigint = new Signal('SIGINT')
|
||||
|
||||
sigint.on('signal', () => {
|
||||
console.log('SIGINT received, shutting down gracefully...')
|
||||
// Clean up resources
|
||||
process.exit(0)
|
||||
})
|
||||
|
||||
sigint.start()
|
||||
|
||||
console.log('Press Ctrl+C to trigger SIGINT')
|
||||
```
|
||||
|
||||
### Handle Multiple Signals
|
||||
|
||||
```js
|
||||
const Signal = require('bare-signals')
|
||||
|
||||
const signals = ['SIGINT', 'SIGTERM', 'SIGHUP']
|
||||
|
||||
for (const name of signals) {
|
||||
const signal = new Signal(name)
|
||||
signal.on('signal', () => {
|
||||
console.log(`${name} received`)
|
||||
shutdown()
|
||||
})
|
||||
signal.start()
|
||||
}
|
||||
|
||||
function shutdown() {
|
||||
console.log('Shutting down...')
|
||||
process.exit(0)
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Signal Class
|
||||
|
||||
#### `new Signal(name)`
|
||||
|
||||
Create signal handler.
|
||||
|
||||
**Parameters:**
|
||||
- `name` (string): Signal name
|
||||
- `'SIGINT'` - Interrupt (Ctrl+C)
|
||||
- `'SIGTERM'` - Termination
|
||||
- `'SIGHUP'` - Hang up
|
||||
- `'SIGUSR1'` - User-defined 1
|
||||
- `'SIGUSR2'` - User-defined 2
|
||||
- `'SIGWINCH'` - Window change
|
||||
|
||||
#### `signal.start()`
|
||||
|
||||
Start listening for signal.
|
||||
|
||||
#### `signal.stop()`
|
||||
|
||||
Stop listening for signal.
|
||||
|
||||
#### `signal.destroy()`
|
||||
|
||||
Destroy signal handler.
|
||||
|
||||
#### `signal.on('signal', callback)`
|
||||
|
||||
Listen for signal events.
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Example 1: Graceful Shutdown
|
||||
|
||||
```js
|
||||
const Signal = require('bare-signals')
|
||||
|
||||
class Application {
|
||||
constructor() {
|
||||
this.running = true
|
||||
this.resources = []
|
||||
this.setupSignals()
|
||||
}
|
||||
|
||||
setupSignals() {
|
||||
// Handle interrupt
|
||||
const sigint = new Signal('SIGINT')
|
||||
sigint.on('signal', () => this.shutdown('SIGINT'))
|
||||
sigint.start()
|
||||
|
||||
// Handle termination
|
||||
const sigterm = new Signal('SIGTERM')
|
||||
sigterm.on('signal', () => this.shutdown('SIGTERM'))
|
||||
sigterm.start()
|
||||
|
||||
// Handle hangup (config reload)
|
||||
const sighup = new Signal('SIGHUP')
|
||||
sighup.on('signal', () => this.reload())
|
||||
sighup.start()
|
||||
}
|
||||
|
||||
addResource(resource) {
|
||||
this.resources.push(resource)
|
||||
}
|
||||
|
||||
async shutdown(signal) {
|
||||
console.log(`\n${signal} received, starting graceful shutdown...`)
|
||||
this.running = false
|
||||
|
||||
// Close all resources
|
||||
for (const resource of this.resources) {
|
||||
if (resource.close) {
|
||||
await resource.close()
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Cleanup complete, exiting')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
async reload() {
|
||||
console.log('SIGHUP received, reloading configuration...')
|
||||
// Reload config
|
||||
}
|
||||
|
||||
run() {
|
||||
console.log('Application running (PID:', process.pid, ')')
|
||||
console.log('Press Ctrl+C to exit')
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const app = new Application()
|
||||
app.run()
|
||||
```
|
||||
|
||||
### Example 2: Signal Debounce
|
||||
|
||||
```js
|
||||
const Signal = require('bare-signals')
|
||||
|
||||
class SignalDebouncer {
|
||||
constructor(signalName, handler, delay = 1000) {
|
||||
this.signal = new Signal(signalName)
|
||||
this.handler = handler
|
||||
this.delay = delay
|
||||
this.timeout = null
|
||||
this.count = 0
|
||||
|
||||
this.signal.on('signal', () => this.onSignal())
|
||||
}
|
||||
|
||||
onSignal() {
|
||||
this.count++
|
||||
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout)
|
||||
}
|
||||
|
||||
this.timeout = setTimeout(() => {
|
||||
this.handler(this.count)
|
||||
this.count = 0
|
||||
}, this.delay)
|
||||
}
|
||||
|
||||
start() {
|
||||
this.signal.start()
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.signal.stop()
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Usage: Count rapid SIGINTs
|
||||
const debouncer = new SignalDebouncer('SIGINT', (count) => {
|
||||
if (count === 1) {
|
||||
console.log('Single interrupt, continuing...')
|
||||
} else {
|
||||
console.log(`${count} interrupts, forcing exit`)
|
||||
process.exit(1)
|
||||
}
|
||||
}, 500)
|
||||
|
||||
debouncer.start()
|
||||
```
|
||||
|
||||
### Example 3: Process Monitor
|
||||
|
||||
```js
|
||||
const Signal = require('bare-signals')
|
||||
|
||||
class ProcessMonitor {
|
||||
constructor() {
|
||||
this.signals = []
|
||||
this.stats = {
|
||||
startTime: Date.now(),
|
||||
signalCounts: {}
|
||||
}
|
||||
this.setupMonitoring()
|
||||
}
|
||||
|
||||
setupMonitoring() {
|
||||
const signalNames = [
|
||||
'SIGINT', 'SIGTERM', 'SIGHUP',
|
||||
'SIGUSR1', 'SIGUSR2', 'SIGWINCH'
|
||||
]
|
||||
|
||||
for (const name of signalNames) {
|
||||
try {
|
||||
const signal = new Signal(name)
|
||||
|
||||
signal.on('signal', () => {
|
||||
this.handleSignal(name)
|
||||
})
|
||||
|
||||
signal.start()
|
||||
this.signals.push(signal)
|
||||
this.stats.signalCounts[name] = 0
|
||||
} catch (err) {
|
||||
// Signal not supported on this platform
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleSignal(name) {
|
||||
this.stats.signalCounts[name]++
|
||||
console.log(`[${new Date().toISOString()}] Signal: ${name}`)
|
||||
|
||||
switch (name) {
|
||||
case 'SIGUSR1':
|
||||
this.dumpStats()
|
||||
break
|
||||
case 'SIGUSR2':
|
||||
this.toggleDebug()
|
||||
break
|
||||
case 'SIGWINCH':
|
||||
this.handleResize()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
dumpStats() {
|
||||
const uptime = (Date.now() - this.stats.startTime) / 1000
|
||||
console.log('=== Process Stats ===')
|
||||
console.log(`Uptime: ${uptime}s`)
|
||||
console.log('Signal counts:', this.stats.signalCounts)
|
||||
console.log('=====================')
|
||||
}
|
||||
|
||||
toggleDebug() {
|
||||
console.log('Toggling debug mode')
|
||||
// Toggle debug flag
|
||||
}
|
||||
|
||||
handleResize() {
|
||||
console.log('Terminal resized')
|
||||
// Adjust terminal UI
|
||||
}
|
||||
|
||||
stop() {
|
||||
for (const signal of this.signals) {
|
||||
signal.stop()
|
||||
signal.destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const monitor = new ProcessMonitor()
|
||||
console.log('Process monitoring started')
|
||||
console.log('Send SIGUSR1 for stats, SIGUSR2 for debug toggle')
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Always Handle Cleanup
|
||||
|
||||
```js
|
||||
const sigint = new Signal('SIGINT')
|
||||
sigint.on('signal', () => {
|
||||
// Always clean up before exit
|
||||
cleanup()
|
||||
process.exit(0)
|
||||
})
|
||||
sigint.start()
|
||||
```
|
||||
|
||||
### Don't Ignore All Signals
|
||||
|
||||
```js
|
||||
// Bad - process becomes unkillable
|
||||
const sigterm = new Signal('SIGTERM')
|
||||
sigterm.on('signal', () => {
|
||||
console.log('Ignoring SIGTERM')
|
||||
})
|
||||
sigterm.start()
|
||||
|
||||
// Good - handle then exit
|
||||
sigterm.on('signal', () => {
|
||||
cleanup()
|
||||
process.exit(0)
|
||||
})
|
||||
```
|
||||
|
||||
### Platform Awareness
|
||||
|
||||
```js
|
||||
// Not all signals available on all platforms
|
||||
const signals = process.platform === 'win32'
|
||||
? ['SIGINT', 'SIGTERM', 'SIGBREAK']
|
||||
: ['SIGINT', 'SIGTERM', 'SIGHUP']
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
|
||||
---
|
||||
**Module Type**: Runtime/Process | **Ecosystem Role**: Signal Handling | **Dependencies**: None
|
||||
@@ -0,0 +1,403 @@
|
||||
# bare-tls - TLS/SSL for JavaScript
|
||||
|
||||
## Overview
|
||||
|
||||
bare-tls provides Transport Layer Security (TLS) streams for JavaScript in the Bare runtime. It enables encrypted network communication with support for both client and server TLS connections.
|
||||
|
||||
### Key Features
|
||||
|
||||
- **TLS encryption**: Secure socket connections
|
||||
- **Client/Server**: Both TLS client and server support
|
||||
- **Certificate management**: Custom certs and keys
|
||||
- **Stream-based**: Built on bare-stream
|
||||
- **Node.js compatible**: Similar to tls module
|
||||
|
||||
### Use Cases
|
||||
|
||||
- **HTTPS servers**: Secure web servers
|
||||
- **Secure clients**: Encrypted connections to services
|
||||
- **Certificate pinning**: Enhanced security
|
||||
- **Mutual TLS**: Two-way authentication
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install bare-tls
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### TLS Server
|
||||
|
||||
```js
|
||||
const tls = require('bare-tls')
|
||||
const fs = require('bare-fs')
|
||||
|
||||
const options = {
|
||||
key: fs.readFileSync('server-key.pem'),
|
||||
cert: fs.readFileSync('server-cert.pem')
|
||||
}
|
||||
|
||||
const server = tls.createServer(options, (socket) => {
|
||||
console.log('Secure connection established')
|
||||
socket.write('Hello secure client!')
|
||||
socket.on('data', (data) => console.log(data.toString()))
|
||||
})
|
||||
|
||||
server.listen(8443, () => {
|
||||
console.log('TLS server listening')
|
||||
})
|
||||
```
|
||||
|
||||
### TLS Client
|
||||
|
||||
```js
|
||||
const tls = require('bare-tls')
|
||||
|
||||
const options = {
|
||||
host: 'localhost',
|
||||
port: 8443,
|
||||
rejectUnauthorized: false // For self-signed certs
|
||||
}
|
||||
|
||||
const socket = tls.connect(options, () => {
|
||||
console.log('Secure connection established')
|
||||
socket.write('Hello secure server!')
|
||||
})
|
||||
|
||||
socket.on('data', (data) => console.log(data.toString()))
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Server
|
||||
|
||||
#### `tls.createServer(options[, secureConnectionListener])`
|
||||
|
||||
Create TLS server.
|
||||
|
||||
**Options:**
|
||||
- `key`: Private key (string | Buffer)
|
||||
- `cert`: Certificate (string | Buffer)
|
||||
- `ca`: CA certificate for client auth
|
||||
- `requestCert`: Request client certificate
|
||||
- `rejectUnauthorized`: Reject unauthorized clients
|
||||
|
||||
#### `server.listen(port[, host][, callback])`
|
||||
|
||||
Start listening.
|
||||
|
||||
#### `server.close([callback])`
|
||||
|
||||
Stop server.
|
||||
|
||||
### Client
|
||||
|
||||
#### `tls.connect(options[, callback])`
|
||||
|
||||
Create TLS connection.
|
||||
|
||||
**Options:**
|
||||
- `host`: Target host
|
||||
- `port`: Target port
|
||||
- `key`: Client private key
|
||||
- `cert`: Client certificate
|
||||
- `ca`: Trusted CA certificates
|
||||
- `rejectUnauthorized`: Reject unauthorized server
|
||||
|
||||
#### `tls.connect(port[, host][, options][, callback])`
|
||||
|
||||
Alternative signature.
|
||||
|
||||
### TLSSocket
|
||||
|
||||
#### `socket.getCipher()`
|
||||
|
||||
Get cipher info.
|
||||
|
||||
**Returns:** `{ name, version }`
|
||||
|
||||
#### `socket.getPeerCertificate()`
|
||||
|
||||
Get peer certificate.
|
||||
|
||||
#### `socket.authorized`
|
||||
|
||||
Whether peer is authorized.
|
||||
|
||||
#### `socket.authorizationError`
|
||||
|
||||
Authorization error if not authorized.
|
||||
|
||||
#### Events
|
||||
|
||||
- `secureConnect` - TLS handshake complete
|
||||
- `error` - TLS error
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Example 1: Mutual TLS Server
|
||||
|
||||
```js
|
||||
const tls = require('bare-tls')
|
||||
const fs = require('bare-fs')
|
||||
|
||||
const server = tls.createServer({
|
||||
key: fs.readFileSync('server-key.pem'),
|
||||
cert: fs.readFileSync('server-cert.pem'),
|
||||
ca: fs.readFileSync('ca-cert.pem'), // Trust this CA
|
||||
requestCert: true, // Request client cert
|
||||
rejectUnauthorized: true // Require valid client cert
|
||||
}, (socket) => {
|
||||
console.log('Client connected')
|
||||
console.log('Client authorized:', socket.authorized)
|
||||
|
||||
if (socket.authorized) {
|
||||
const cert = socket.getPeerCertificate()
|
||||
console.log('Client cert subject:', cert.subject)
|
||||
}
|
||||
|
||||
socket.write('Welcome, authenticated client!')
|
||||
|
||||
socket.on('data', (data) => {
|
||||
console.log('Received:', data.toString())
|
||||
})
|
||||
})
|
||||
|
||||
server.listen(8443, () => {
|
||||
console.log('mTLS server listening on port 8443')
|
||||
})
|
||||
```
|
||||
|
||||
### Example 2: HTTPS Client
|
||||
|
||||
```js
|
||||
const tls = require('bare-tls')
|
||||
|
||||
// Connect to HTTPS server
|
||||
const socket = tls.connect({
|
||||
host: 'api.example.com',
|
||||
port: 443,
|
||||
servername: 'api.example.com' // For SNI
|
||||
}, () => {
|
||||
console.log('Connected')
|
||||
console.log('Cipher:', socket.getCipher())
|
||||
|
||||
// Send HTTP request
|
||||
const request = [
|
||||
'GET /v1/data HTTP/1.1',
|
||||
'Host: api.example.com',
|
||||
'Connection: close',
|
||||
'',
|
||||
''
|
||||
].join('\r\n')
|
||||
|
||||
socket.write(request)
|
||||
})
|
||||
|
||||
let response = ''
|
||||
socket.on('data', (data) => {
|
||||
response += data.toString()
|
||||
})
|
||||
|
||||
socket.on('end', () => {
|
||||
console.log('Response:', response)
|
||||
})
|
||||
|
||||
socket.on('error', (err) => {
|
||||
console.error('TLS error:', err.message)
|
||||
})
|
||||
```
|
||||
|
||||
### Example 3: Secure Proxy
|
||||
|
||||
```js
|
||||
const tls = require('bare-tls')
|
||||
const net = require('bare-net')
|
||||
|
||||
// Simple TLS proxy: plaintext <-> TLS
|
||||
class SecureProxy {
|
||||
constructor(listenPort, targetHost, targetPort, tlsOptions) {
|
||||
this.listenPort = listenPort
|
||||
this.targetHost = targetHost
|
||||
this.targetPort = targetPort
|
||||
this.tlsOptions = tlsOptions
|
||||
}
|
||||
|
||||
start() {
|
||||
this.server = net.createServer((clientSocket) => {
|
||||
console.log('Client connected')
|
||||
|
||||
// Connect to secure backend
|
||||
const serverSocket = tls.connect({
|
||||
host: this.targetHost,
|
||||
port: this.targetPort,
|
||||
...this.tlsOptions
|
||||
}, () => {
|
||||
console.log('Connected to secure backend')
|
||||
|
||||
// Pipe data between client and server
|
||||
clientSocket.pipe(serverSocket)
|
||||
serverSocket.pipe(clientSocket)
|
||||
})
|
||||
|
||||
serverSocket.on('error', (err) => {
|
||||
console.error('Backend error:', err.message)
|
||||
clientSocket.end()
|
||||
})
|
||||
|
||||
clientSocket.on('close', () => {
|
||||
serverSocket.end()
|
||||
})
|
||||
})
|
||||
|
||||
this.server.listen(this.listenPort, () => {
|
||||
console.log(`Proxy listening on port ${this.listenPort}`)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const proxy = new SecureProxy(
|
||||
8080, // Listen on plaintext port
|
||||
'backend', // Backend host
|
||||
8443, // Backend TLS port
|
||||
{
|
||||
rejectUnauthorized: true,
|
||||
ca: require('bare-fs').readFileSync('ca-cert.pem')
|
||||
}
|
||||
)
|
||||
|
||||
proxy.start()
|
||||
```
|
||||
|
||||
### Example 4: Certificate Manager
|
||||
|
||||
```js
|
||||
const tls = require('bare-tls')
|
||||
const fs = require('bare-fs')
|
||||
const crypto = require('bare-crypto')
|
||||
|
||||
class CertificateManager {
|
||||
constructor(certDir) {
|
||||
this.certDir = certDir
|
||||
this.cache = new Map()
|
||||
}
|
||||
|
||||
loadCertificate(domain) {
|
||||
// Check cache
|
||||
if (this.cache.has(domain)) {
|
||||
return this.cache.get(domain)
|
||||
}
|
||||
|
||||
// Load from disk
|
||||
const keyPath = `${this.certDir}/${domain}-key.pem`
|
||||
const certPath = `${this.certDir}/${domain}-cert.pem`
|
||||
|
||||
try {
|
||||
const options = {
|
||||
key: fs.readFileSync(keyPath),
|
||||
cert: fs.readFileSync(certPath)
|
||||
}
|
||||
|
||||
this.cache.set(domain, options)
|
||||
return options
|
||||
} catch (err) {
|
||||
console.error(`Failed to load cert for ${domain}:`, err.message)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
createServer(domain, handler) {
|
||||
const options = this.loadCertificate(domain)
|
||||
if (!options) {
|
||||
throw new Error(`No certificate for ${domain}`)
|
||||
}
|
||||
|
||||
return tls.createServer(options, handler)
|
||||
}
|
||||
|
||||
verifyCertificate(cert) {
|
||||
// Check expiration
|
||||
// Check chain
|
||||
// Return validation result
|
||||
return {
|
||||
valid: true,
|
||||
expires: new Date(),
|
||||
fingerprint: this.getFingerprint(cert)
|
||||
}
|
||||
}
|
||||
|
||||
getFingerprint(cert) {
|
||||
const hash = crypto.createHash('sha256')
|
||||
hash.update(cert)
|
||||
return hash.digest('hex').match(/.{2}/g).join(':')
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const manager = new CertificateManager('./certs')
|
||||
|
||||
const server = manager.createServer('example.com', (socket) => {
|
||||
socket.write('Hello from secure server!')
|
||||
})
|
||||
|
||||
server.listen(443)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Always Verify Certificates
|
||||
|
||||
```js
|
||||
const socket = tls.connect({
|
||||
host: 'api.example.com',
|
||||
port: 443,
|
||||
rejectUnauthorized: true // Don't disable in production!
|
||||
})
|
||||
```
|
||||
|
||||
### Use Strong Cipher Suites
|
||||
|
||||
```js
|
||||
const server = tls.createServer({
|
||||
key: privateKey,
|
||||
cert: certificate,
|
||||
// Server will use strong defaults
|
||||
})
|
||||
```
|
||||
|
||||
### Handle Errors
|
||||
|
||||
```js
|
||||
socket.on('error', (err) => {
|
||||
console.error('TLS error:', err.message)
|
||||
})
|
||||
|
||||
server.on('tlsClientError', (err, socket) => {
|
||||
console.error('Client TLS error:', err.message)
|
||||
})
|
||||
```
|
||||
|
||||
### Certificate Pinning
|
||||
|
||||
```js
|
||||
const EXPECTED_FINGERPRINT = 'AA:BB:CC:DD:...'
|
||||
|
||||
socket.on('secureConnect', () => {
|
||||
const cert = socket.getPeerCertificate()
|
||||
const fingerprint = cert.fingerprint256
|
||||
|
||||
if (fingerprint !== EXPECTED_FINGERPRINT) {
|
||||
console.error('Certificate pin mismatch!')
|
||||
socket.destroy()
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
|
||||
---
|
||||
**Module Type**: Runtime/Security | **Ecosystem Role**: TLS/SSL | **Dependencies**: bare-stream, bare-crypto
|
||||
@@ -0,0 +1,293 @@
|
||||
# bare-url - URL Handling
|
||||
|
||||
## Overview
|
||||
|
||||
bare-url provides WHATWG-compliant URL implementation for JavaScript. It enables parsing, manipulating, and working with URLs in a standardized way.
|
||||
|
||||
### Key Features
|
||||
|
||||
- **WHATWG URL**: Standard URL API
|
||||
- **URL parsing**: Parse URLs into components
|
||||
- **URL manipulation**: Modify URL parts
|
||||
- **File URLs**: Convert file URLs to paths
|
||||
|
||||
### Use Cases
|
||||
|
||||
- **URL parsing**: Extract components from URLs
|
||||
- **URL building**: Construct URLs programmatically
|
||||
- **Path conversion**: file:// to filesystem paths
|
||||
- **URL validation**: Validate URL formats
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install bare-url
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Parse URL
|
||||
|
||||
```js
|
||||
const url = require('bare-url')
|
||||
|
||||
const myURL = new url.URL('https://user:[email protected]:8080/path?query=1#hash')
|
||||
|
||||
console.log(myURL.protocol) // 'https:'
|
||||
console.log(myURL.hostname) // 'example.com'
|
||||
console.log(myURL.port) // '8080'
|
||||
console.log(myURL.pathname) // '/path'
|
||||
console.log(myURL.search) // '?query=1'
|
||||
console.log(myURL.hash) // '#hash'
|
||||
```
|
||||
|
||||
### File URL to Path
|
||||
|
||||
```js
|
||||
const url = require('bare-url')
|
||||
|
||||
const filePath = url.fileURLToPath('file:///home/user/file.txt')
|
||||
console.log(filePath) // '/home/user/file.txt'
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### URL Class
|
||||
|
||||
#### `new URL(input[, base])`
|
||||
|
||||
Create URL object.
|
||||
|
||||
**Parameters:**
|
||||
- `input` (string): URL string
|
||||
- `base` (string | URL): Base URL for relative URLs
|
||||
|
||||
**Properties:**
|
||||
- `href` - Full URL
|
||||
- `protocol` - Protocol scheme
|
||||
- `host` - Host (hostname:port)
|
||||
- `hostname` - Hostname only
|
||||
- `port` - Port number
|
||||
- `pathname` - Path
|
||||
- `search` - Query string
|
||||
- `searchParams` - URLSearchParams object
|
||||
- `hash` - Fragment
|
||||
- `username` - Username
|
||||
- `password` - Password
|
||||
|
||||
### URLSearchParams
|
||||
|
||||
#### `new URLSearchParams([init])`
|
||||
|
||||
Create query string parser.
|
||||
|
||||
**Methods:**
|
||||
- `append(name, value)` - Add parameter
|
||||
- `delete(name)` - Remove parameter
|
||||
- `get(name)` - Get value
|
||||
- `getAll(name)` - Get all values
|
||||
- `has(name)` - Check existence
|
||||
- `set(name, value)` - Set value
|
||||
- `toString()` - Serialize
|
||||
|
||||
**Example:**
|
||||
```js
|
||||
const params = new url.URLSearchParams('?a=1&b=2')
|
||||
params.append('c', '3')
|
||||
console.log(params.toString()) // 'a=1&b=2&c=3'
|
||||
```
|
||||
|
||||
### Utilities
|
||||
|
||||
#### `url.fileURLToPath(url)`
|
||||
|
||||
Convert file URL to path.
|
||||
|
||||
**Example:**
|
||||
```js
|
||||
url.fileURLToPath('file:///C:/Users/file.txt') // 'C:\Users\file.txt' (Windows)
|
||||
url.fileURLToPath('file:///home/user/file.txt') // '/home/user/file.txt' (Unix)
|
||||
```
|
||||
|
||||
#### `url.pathToFileURL(path)`
|
||||
|
||||
Convert path to file URL.
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Example 1: URL Builder
|
||||
|
||||
```js
|
||||
const url = require('bare-url')
|
||||
|
||||
class URLBuilder {
|
||||
constructor(baseURL) {
|
||||
this.url = new url.URL(baseURL)
|
||||
}
|
||||
|
||||
path(segment) {
|
||||
this.url.pathname = segment
|
||||
return this
|
||||
}
|
||||
|
||||
query(params) {
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
this.url.searchParams.set(key, value)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
hash(fragment) {
|
||||
this.url.hash = fragment
|
||||
return this
|
||||
}
|
||||
|
||||
build() {
|
||||
return this.url.toString()
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const apiURL = new URLBuilder('https://api.example.com')
|
||||
.path('/v1/users')
|
||||
.query({ page: 1, limit: 10 })
|
||||
.build()
|
||||
|
||||
console.log(apiURL)
|
||||
// 'https://api.example.com/v1/users?page=1&limit=10'
|
||||
```
|
||||
|
||||
### Example 2: URL Validator
|
||||
|
||||
```js
|
||||
const url = require('bare-url')
|
||||
|
||||
class URLValidator {
|
||||
static isValid(urlString) {
|
||||
try {
|
||||
new url.URL(urlString)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
static isAbsolute(urlString) {
|
||||
return /^[a-z][a-z0-9+.-]*:/i.test(urlString)
|
||||
}
|
||||
|
||||
static isRelative(urlString) {
|
||||
return !this.isAbsolute(urlString)
|
||||
}
|
||||
|
||||
static getType(urlString) {
|
||||
try {
|
||||
const u = new url.URL(urlString)
|
||||
return u.protocol.slice(0, -1) // Remove trailing colon
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
static normalize(urlString) {
|
||||
try {
|
||||
const u = new url.URL(urlString)
|
||||
return u.toString()
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
console.log(URLValidator.isValid('https://example.com')) // true
|
||||
console.log(URLValidator.isValid('not a url')) // false
|
||||
console.log(URLValidator.getType('https://example.com')) // 'https'
|
||||
```
|
||||
|
||||
### Example 3: Request URL Parser
|
||||
|
||||
```js
|
||||
const url = require('bare-url')
|
||||
|
||||
class RequestURL {
|
||||
constructor(requestUrl) {
|
||||
this.url = new url.URL(requestUrl)
|
||||
this.params = {}
|
||||
}
|
||||
|
||||
parseRoute(pattern) {
|
||||
// Pattern: '/users/:id/posts/:postId'
|
||||
const urlParts = this.url.pathname.split('/').filter(Boolean)
|
||||
const patternParts = pattern.split('/').filter(Boolean)
|
||||
|
||||
if (urlParts.length !== patternParts.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
for (let i = 0; i < patternParts.length; i++) {
|
||||
if (patternParts[i].startsWith(':')) {
|
||||
// Parameter
|
||||
const paramName = patternParts[i].slice(1)
|
||||
this.params[paramName] = urlParts[i]
|
||||
} else if (patternParts[i] !== urlParts[i]) {
|
||||
// Mismatch
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return this.params
|
||||
}
|
||||
|
||||
getQueryParam(name) {
|
||||
return this.url.searchParams.get(name)
|
||||
}
|
||||
|
||||
getAllQueryParams() {
|
||||
const params = {}
|
||||
for (const [key, value] of this.url.searchParams) {
|
||||
params[key] = value
|
||||
}
|
||||
return params
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const req = new RequestURL('https://api.example.com/users/123/posts/456?expand=true')
|
||||
|
||||
const params = req.parseRoute('/users/:userId/posts/:postId')
|
||||
console.log(params) // { userId: '123', postId: '456' }
|
||||
|
||||
console.log(req.getQueryParam('expand')) // 'true'
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Always Validate URLs
|
||||
|
||||
```js
|
||||
try {
|
||||
const u = new url.URL(userInput)
|
||||
// Use u
|
||||
} catch {
|
||||
console.error('Invalid URL')
|
||||
}
|
||||
```
|
||||
|
||||
### Use URLSearchParams for Query Strings
|
||||
|
||||
```js
|
||||
// Good
|
||||
const params = new url.URLSearchParams()
|
||||
params.set('key', value)
|
||||
|
||||
// Avoid manual string building
|
||||
const query = `?key=${value}` // Error-prone
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
|
||||
---
|
||||
**Module Type**: Runtime/Standard | **Ecosystem Role**: URL Handling | **Dependencies**: None
|
||||
@@ -0,0 +1,422 @@
|
||||
# bare-worker - Worker Threads
|
||||
|
||||
## Overview
|
||||
|
||||
bare-worker provides higher-level worker threads for JavaScript. It enables running JavaScript in parallel threads with message passing.
|
||||
|
||||
### Key Features
|
||||
|
||||
- **Worker threads**: Parallel execution
|
||||
- **Message passing**: Communicate with workers
|
||||
- **Shared memory**: Transfer ArrayBuffers
|
||||
- **Node.js compatible**: Similar to worker_threads
|
||||
|
||||
### Use Cases
|
||||
|
||||
- **CPU-intensive tasks**: Offload heavy computation
|
||||
- **Parallel processing**: Process data in parallel
|
||||
- **Background tasks**: Run tasks without blocking main thread
|
||||
- **Isolation**: Run untrusted code safely
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install bare-worker
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Worker
|
||||
|
||||
```js
|
||||
const Worker = require('bare-worker')
|
||||
|
||||
if (Worker.isMainThread) {
|
||||
// Main thread
|
||||
const worker = new Worker(__filename)
|
||||
|
||||
worker.on('message', (msg) => {
|
||||
console.log('From worker:', msg)
|
||||
})
|
||||
|
||||
worker.on('exit', (code) => {
|
||||
console.log('Worker exited with code', code)
|
||||
})
|
||||
|
||||
worker.postMessage('Hello worker')
|
||||
} else {
|
||||
// Worker thread
|
||||
Worker.parentPort.on('message', (msg) => {
|
||||
console.log('From main:', msg)
|
||||
Worker.parentPort.postMessage('Hello main')
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Separate Worker File
|
||||
|
||||
```js
|
||||
// main.js
|
||||
const Worker = require('bare-worker')
|
||||
|
||||
const worker = new Worker('./worker.js')
|
||||
|
||||
worker.on('message', (result) => {
|
||||
console.log('Result:', result)
|
||||
})
|
||||
|
||||
worker.postMessage({ n: 40 })
|
||||
```
|
||||
|
||||
```js
|
||||
// worker.js
|
||||
const Worker = require('bare-worker')
|
||||
|
||||
Worker.parentPort.on('message', ({ n }) => {
|
||||
const result = fibonacci(n)
|
||||
Worker.parentPort.postMessage({ n, result })
|
||||
})
|
||||
|
||||
function fibonacci(n) {
|
||||
if (n < 2) return n
|
||||
return fibonacci(n - 1) + fibonacci(n - 2)
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Worker Class
|
||||
|
||||
#### `new Worker(filename[, options])`
|
||||
|
||||
Create worker thread.
|
||||
|
||||
**Parameters:**
|
||||
- `filename` (string): Worker script path
|
||||
- `options` (object):
|
||||
- `workerData`: Data to pass to worker
|
||||
|
||||
**Example:**
|
||||
```js
|
||||
const worker = new Worker('./task.js', {
|
||||
workerData: { input: 'data' }
|
||||
})
|
||||
```
|
||||
|
||||
#### `worker.postMessage(value[, transferList])`
|
||||
|
||||
Send message to worker.
|
||||
|
||||
**TransferList:** Array of ArrayBuffers to transfer ownership.
|
||||
|
||||
#### `worker.terminate()`
|
||||
|
||||
Force terminate worker.
|
||||
|
||||
#### `worker.on('message', callback)`
|
||||
|
||||
Listen for messages from worker.
|
||||
|
||||
#### `worker.on('error', callback)`
|
||||
|
||||
Listen for errors.
|
||||
|
||||
#### `worker.on('exit', callback)`
|
||||
|
||||
Listen for exit events.
|
||||
|
||||
### Worker Properties
|
||||
|
||||
#### `Worker.isMainThread`
|
||||
|
||||
Boolean indicating if in main thread.
|
||||
|
||||
#### `Worker.parentPort`
|
||||
|
||||
Message port in worker thread (to communicate with main).
|
||||
|
||||
#### `Worker.workerData`
|
||||
|
||||
Data passed from main thread.
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Example 1: Worker Pool
|
||||
|
||||
```js
|
||||
const Worker = require('bare-worker')
|
||||
const os = require('bare-os')
|
||||
|
||||
class WorkerPool {
|
||||
constructor(workerScript, poolSize = os.cpus().length) {
|
||||
this.workerScript = workerScript
|
||||
this.poolSize = poolSize
|
||||
this.workers = []
|
||||
this.queue = []
|
||||
this.active = new Map()
|
||||
}
|
||||
|
||||
init() {
|
||||
for (let i = 0; i < this.poolSize; i++) {
|
||||
this.addWorker()
|
||||
}
|
||||
}
|
||||
|
||||
addWorker() {
|
||||
const worker = new Worker(this.workerScript)
|
||||
|
||||
worker.on('message', (result) => {
|
||||
const { resolve } = this.active.get(worker)
|
||||
this.active.delete(worker)
|
||||
resolve(result)
|
||||
this.processQueue()
|
||||
})
|
||||
|
||||
worker.on('error', (err) => {
|
||||
console.error('Worker error:', err)
|
||||
})
|
||||
|
||||
this.workers.push(worker)
|
||||
}
|
||||
|
||||
execute(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.queue.push({ data, resolve, reject })
|
||||
this.processQueue()
|
||||
})
|
||||
}
|
||||
|
||||
processQueue() {
|
||||
if (this.queue.length === 0) return
|
||||
|
||||
// Find available worker
|
||||
const available = this.workers.find(w => !this.active.has(w))
|
||||
if (!available) return // All busy
|
||||
|
||||
const task = this.queue.shift()
|
||||
this.active.set(available, task)
|
||||
available.postMessage(task.data)
|
||||
}
|
||||
|
||||
terminate() {
|
||||
for (const worker of this.workers) {
|
||||
worker.terminate()
|
||||
}
|
||||
this.workers = []
|
||||
}
|
||||
}
|
||||
|
||||
// Worker script: cpu-worker.js
|
||||
if (!require('bare-worker').isMainThread) {
|
||||
const { parentPort } = require('bare-worker')
|
||||
|
||||
parentPort.on('message', (data) => {
|
||||
// Heavy computation
|
||||
let result = 0
|
||||
for (let i = 0; i < data.iterations; i++) {
|
||||
result += Math.sqrt(i)
|
||||
}
|
||||
parentPort.postMessage({ result })
|
||||
})
|
||||
}
|
||||
|
||||
// Usage
|
||||
const pool = new WorkerPool('./cpu-worker.js', 4)
|
||||
pool.init()
|
||||
|
||||
// Run multiple tasks
|
||||
const promises = []
|
||||
for (let i = 0; i < 10; i++) {
|
||||
promises.push(pool.execute({ iterations: 1000000 }))
|
||||
}
|
||||
|
||||
Promise.all(promises).then((results) => {
|
||||
console.log('All tasks complete:', results)
|
||||
pool.terminate()
|
||||
})
|
||||
```
|
||||
|
||||
### Example 2: Parallel Map
|
||||
|
||||
```js
|
||||
const Worker = require('bare-worker')
|
||||
const os = require('bare-os')
|
||||
|
||||
async function parallelMap(array, mapper, options = {}) {
|
||||
const concurrency = options.concurrency || os.cpus().length
|
||||
|
||||
// Create workers
|
||||
const workers = []
|
||||
for (let i = 0; i < concurrency; i++) {
|
||||
workers.push(createMapperWorker(mapper))
|
||||
}
|
||||
|
||||
// Process items
|
||||
const results = new Array(array.length)
|
||||
const iterator = array.entries()
|
||||
|
||||
await Promise.all(workers.map(async (worker) => {
|
||||
for (const [index, item] of iterator) {
|
||||
results[index] = await runOnWorker(worker, item, index)
|
||||
}
|
||||
}))
|
||||
|
||||
// Cleanup
|
||||
workers.forEach(w => w.terminate())
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
function createMapperWorker(mapperFn) {
|
||||
const workerScript = `
|
||||
const { parentPort } = require('bare-worker')
|
||||
|
||||
const mapper = ${mapperFn.toString()}
|
||||
|
||||
parentPort.on('message', async ({ item, index }) => {
|
||||
try {
|
||||
const result = await mapper(item, index)
|
||||
parentPort.postMessage({ success: true, result, index })
|
||||
} catch (error) {
|
||||
parentPort.postMessage({ success: false, error: error.message, index })
|
||||
}
|
||||
})
|
||||
`
|
||||
|
||||
return new Worker(workerScript, { eval: true })
|
||||
}
|
||||
|
||||
function runOnWorker(worker, item, index) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const handler = (msg) => {
|
||||
if (msg.index === index) {
|
||||
worker.off('message', handler)
|
||||
if (msg.success) {
|
||||
resolve(msg.result)
|
||||
} else {
|
||||
reject(new Error(msg.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
worker.on('message', handler)
|
||||
worker.postMessage({ item, index })
|
||||
})
|
||||
}
|
||||
|
||||
// Usage
|
||||
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
|
||||
parallelMap(data, (x) => {
|
||||
// Heavy computation
|
||||
let sum = 0
|
||||
for (let i = 0; i < 1000000; i++) {
|
||||
sum += x * i
|
||||
}
|
||||
return sum
|
||||
}).then((results) => {
|
||||
console.log('Results:', results)
|
||||
})
|
||||
```
|
||||
|
||||
### Example 3: Shared Buffer Processing
|
||||
|
||||
```js
|
||||
const Worker = require('bare-worker')
|
||||
|
||||
// Main thread
|
||||
if (Worker.isMainThread) {
|
||||
// Create shared buffer
|
||||
const sharedBuffer = new SharedArrayBuffer(1024)
|
||||
const view = new Uint8Array(sharedBuffer)
|
||||
|
||||
// Fill with data
|
||||
for (let i = 0; i < view.length; i++) {
|
||||
view[i] = i % 256
|
||||
}
|
||||
|
||||
// Create worker
|
||||
const worker = new Worker(__filename)
|
||||
|
||||
worker.on('message', (msg) => {
|
||||
console.log('Processed:', msg)
|
||||
console.log('First 10 bytes:', view.slice(0, 10))
|
||||
})
|
||||
|
||||
// Transfer shared buffer
|
||||
worker.postMessage({ sharedBuffer }, [sharedBuffer])
|
||||
}
|
||||
else {
|
||||
// Worker thread
|
||||
Worker.parentPort.on('message', ({ sharedBuffer }) => {
|
||||
const view = new Uint8Array(sharedBuffer)
|
||||
|
||||
// Process data in place
|
||||
for (let i = 0; i < view.length; i++) {
|
||||
view[i] = view[i] * 2 // Double each value
|
||||
}
|
||||
|
||||
Worker.parentPort.postMessage({ done: true, length: view.length })
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Don't Block Worker
|
||||
|
||||
```js
|
||||
// Bad - blocks worker
|
||||
while (true) {}
|
||||
|
||||
// Good - use async or break up work
|
||||
async function process() {
|
||||
while (hasWork) {
|
||||
await doChunkOfWork()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Handle Errors
|
||||
|
||||
```js
|
||||
worker.on('error', (err) => {
|
||||
console.error('Worker error:', err)
|
||||
})
|
||||
|
||||
worker.on('exit', (code) => {
|
||||
if (code !== 0) {
|
||||
console.error('Worker stopped with exit code', code)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Clean Up Workers
|
||||
|
||||
```js
|
||||
// Always terminate workers when done
|
||||
pool.terminate()
|
||||
|
||||
// Or for single worker
|
||||
worker.terminate()
|
||||
```
|
||||
|
||||
### Use workerData for Initialization
|
||||
|
||||
```js
|
||||
// Main
|
||||
const worker = new Worker('./worker.js', {
|
||||
workerData: { config: 'data' }
|
||||
})
|
||||
|
||||
// Worker
|
||||
const { workerData } = require('bare-worker')
|
||||
console.log('Config:', workerData.config)
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
|
||||
---
|
||||
**Module Type**: Runtime/Parallel | **Ecosystem Role**: Worker Threads | **Dependencies**: None
|
||||
@@ -0,0 +1,285 @@
|
||||
# bare-zlib - Compression
|
||||
|
||||
## Overview
|
||||
|
||||
bare-zlib provides stream-based zlib compression and decompression for JavaScript. It enables efficient data compression using gzip, deflate, and other formats.
|
||||
|
||||
### Key Features
|
||||
|
||||
- **Multiple formats**: gzip, deflate, brotli
|
||||
- **Stream-based**: Process data incrementally
|
||||
- **Native bindings**: High-performance compression
|
||||
- **Node.js compatible**: Similar to zlib module
|
||||
|
||||
### Use Cases
|
||||
|
||||
- **HTTP compression**: Compress responses
|
||||
- **File compression**: Archive files
|
||||
- **Network optimization**: Reduce bandwidth
|
||||
- **Data storage**: Compressed storage
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install bare-zlib
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Gzip Compression
|
||||
|
||||
```js
|
||||
const zlib = require('bare-zlib')
|
||||
|
||||
// Compress
|
||||
const input = Buffer.from('Hello, World!')
|
||||
const compressed = zlib.gzipSync(input)
|
||||
|
||||
// Decompress
|
||||
const decompressed = zlib.gunzipSync(compressed)
|
||||
console.log(decompressed.toString()) // 'Hello, World!'
|
||||
```
|
||||
|
||||
### Stream Compression
|
||||
|
||||
```js
|
||||
const zlib = require('bare-zlib')
|
||||
const fs = require('bare-fs')
|
||||
|
||||
// Compress file
|
||||
const input = fs.createReadStream('input.txt')
|
||||
const gzip = zlib.createGzip()
|
||||
const output = fs.createWriteStream('input.txt.gz')
|
||||
|
||||
input.pipe(gzip).pipe(output)
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### gzip
|
||||
|
||||
#### `zlib.gzip(buffer[, options], callback)`
|
||||
|
||||
Compress with gzip.
|
||||
|
||||
#### `zlib.gzipSync(buffer[, options])`
|
||||
|
||||
Synchronous gzip.
|
||||
|
||||
#### `zlib.gunzip(buffer[, options], callback)`
|
||||
|
||||
Decompress gzip.
|
||||
|
||||
#### `zlib.gunzipSync(buffer[, options])`
|
||||
|
||||
Synchronous gunzip.
|
||||
|
||||
#### `zlib.createGzip([options])`
|
||||
|
||||
Create gzip stream.
|
||||
|
||||
### deflate
|
||||
|
||||
#### `zlib.deflate(buffer[, options], callback)`
|
||||
|
||||
Compress with deflate.
|
||||
|
||||
#### `zlib.deflateSync(buffer[, options])`
|
||||
|
||||
Synchronous deflate.
|
||||
|
||||
#### `zlib.inflate(buffer[, options], callback)`
|
||||
|
||||
Decompress deflate.
|
||||
|
||||
#### `zlib.inflateSync(buffer[, options])`
|
||||
|
||||
Synchronous inflate.
|
||||
|
||||
#### `zlib.createDeflate([options])`
|
||||
|
||||
Create deflate stream.
|
||||
|
||||
### brotli
|
||||
|
||||
#### `zlib.brotliCompress(buffer[, options], callback)`
|
||||
|
||||
Compress with brotli.
|
||||
|
||||
#### `zlib.brotliCompressSync(buffer[, options])`
|
||||
|
||||
Synchronous brotli.
|
||||
|
||||
#### `zlib.brotliDecompress(buffer[, options], callback)`
|
||||
|
||||
Decompress brotli.
|
||||
|
||||
#### `zlib.createBrotliCompress([options])`
|
||||
|
||||
Create brotli stream.
|
||||
|
||||
### Constants
|
||||
|
||||
- `zlib.constants.Z_NO_COMPRESSION` (0)
|
||||
- `zlib.constants.Z_BEST_SPEED` (1)
|
||||
- `zlib.constants.Z_BEST_COMPRESSION` (9)
|
||||
- `zlib.constants.Z_DEFAULT_COMPRESSION` (-1)
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Example 1: HTTP Compression
|
||||
|
||||
```js
|
||||
const zlib = require('bare-zlib')
|
||||
const http = require('bare-http1')
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
const acceptEncoding = req.headers['accept-encoding'] || ''
|
||||
|
||||
let stream = res
|
||||
|
||||
// Compress based on client support
|
||||
if (acceptEncoding.includes('br')) {
|
||||
res.setHeader('Content-Encoding', 'br')
|
||||
stream = zlib.createBrotliCompress()
|
||||
stream.pipe(res)
|
||||
} else if (acceptEncoding.includes('gzip')) {
|
||||
res.setHeader('Content-Encoding', 'gzip')
|
||||
stream = zlib.createGzip()
|
||||
stream.pipe(res)
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', 'text/plain')
|
||||
stream.write('Hello, compressed world!')
|
||||
stream.end()
|
||||
})
|
||||
|
||||
server.listen(8080)
|
||||
```
|
||||
|
||||
### Example 2: File Archiver
|
||||
|
||||
```js
|
||||
const zlib = require('bare-zlib')
|
||||
const fs = require('bare-fs')
|
||||
const path = require('bare-path')
|
||||
|
||||
class FileArchiver {
|
||||
async compressFile(inputPath, outputPath) {
|
||||
const input = fs.createReadStream(inputPath)
|
||||
const gzip = zlib.createGzip({ level: 9 })
|
||||
const output = fs.createWriteStream(outputPath)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
input.pipe(gzip).pipe(output)
|
||||
output.on('finish', resolve)
|
||||
output.on('error', reject)
|
||||
})
|
||||
}
|
||||
|
||||
async decompressFile(inputPath, outputPath) {
|
||||
const input = fs.createReadStream(inputPath)
|
||||
const gunzip = zlib.createGunzip()
|
||||
const output = fs.createWriteStream(outputPath)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
input.pipe(gunzip).pipe(output)
|
||||
output.on('finish', resolve)
|
||||
output.on('error', reject)
|
||||
})
|
||||
}
|
||||
|
||||
async compressString(str) {
|
||||
return zlib.gzipSync(Buffer.from(str))
|
||||
}
|
||||
|
||||
async decompressString(compressed) {
|
||||
const result = zlib.gunzipSync(compressed)
|
||||
return result.toString()
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const archiver = new FileArchiver()
|
||||
await archiver.compressFile('large-file.txt', 'large-file.txt.gz')
|
||||
await archiver.decompressFile('large-file.txt.gz', 'large-file-restored.txt')
|
||||
```
|
||||
|
||||
### Example 3: Compression Comparison
|
||||
|
||||
```js
|
||||
const zlib = require('bare-zlib')
|
||||
|
||||
function compareCompression(data) {
|
||||
const input = Buffer.from(data)
|
||||
|
||||
// Gzip (default)
|
||||
const gzip = zlib.gzipSync(input)
|
||||
|
||||
// Gzip (best compression)
|
||||
const gzipBest = zlib.gzipSync(input, { level: 9 })
|
||||
|
||||
// Deflate
|
||||
const deflate = zlib.deflateSync(input)
|
||||
|
||||
// Brotli
|
||||
const brotli = zlib.brotliCompressSync(input)
|
||||
|
||||
console.log('Original size:', input.length)
|
||||
console.log('Gzip (default):', gzip.length, `(${((1 - gzip.length/input.length) * 100).toFixed(1)}% reduction)`)
|
||||
console.log('Gzip (best):', gzipBest.length, `(${((1 - gzipBest.length/input.length) * 100).toFixed(1)}% reduction)`)
|
||||
console.log('Deflate:', deflate.length, `(${((1 - deflate.length/input.length) * 100).toFixed(1)}% reduction)`)
|
||||
console.log('Brotli:', brotli.length, `(${((1 - brotli.length/input.length) * 100).toFixed(1)}% reduction)`)
|
||||
}
|
||||
|
||||
// Test with sample data
|
||||
const sampleData = 'The quick brown fox jumps over the lazy dog. '.repeat(100)
|
||||
compareCompression(sampleData)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Choose Right Compression Level
|
||||
|
||||
```js
|
||||
// Speed prioritized
|
||||
const fast = zlib.createGzip({ level: 1 })
|
||||
|
||||
// Balanced (default)
|
||||
const balanced = zlib.createGzip({ level: 6 })
|
||||
|
||||
// Compression prioritized
|
||||
const best = zlib.createGzip({ level: 9 })
|
||||
```
|
||||
|
||||
### Handle Errors
|
||||
|
||||
```js
|
||||
gzip.on('error', (err) => {
|
||||
console.error('Compression error:', err)
|
||||
})
|
||||
|
||||
gunzip.on('error', (err) => {
|
||||
console.error('Decompression error:', err)
|
||||
})
|
||||
```
|
||||
|
||||
### Use Streams for Large Data
|
||||
|
||||
```js
|
||||
// Good for large files
|
||||
fs.createReadStream('large.bin')
|
||||
.pipe(zlib.createGzip())
|
||||
.pipe(fs.createWriteStream('large.bin.gz'))
|
||||
|
||||
// Bad for large files (loads all into memory)
|
||||
const data = fs.readFileSync('large.bin') // May OOM
|
||||
const compressed = zlib.gzipSync(data)
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
|
||||
---
|
||||
**Module Type**: Runtime/Compression | **Ecosystem Role**: Data Compression | **Dependencies**: bare-stream
|
||||
Reference in New Issue
Block a user