{"_id":"@acepad/worker","name":"@acepad/worker","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@acepad/worker","scripts":{"test":"run test/test-acepad-worker.js"},"main":"acepad-worker.js","version":"1.0.0","type":"module","dependencies":{"@hoge1e3/str2worker":"^1.0.0","@acepad/npm":"^1.0.0","@acepad/here":"^1.0.0"},"gitHead":"60c07d97ae73ae35129d6b1dc0335162c1bdd393","_id":"@acepad/worker@1.0.0","description":"Web Worker management for [acepad](https://github.com/hoge1e3/acepad-dev/) and [petit-node](https://www.npmjs.com/package/petit-node). This package enables creating Web Workers that run petit-node modules with RPC (Remote Procedure Call) communication, al","_nodeVersion":"22.17.0","_npmVersion":"11.7.0","dist":{"integrity":"sha512-YNYRehfn+fyMBqAF8hzzLMTNX9p2zAT2UfMkeMp4+s/gaLwGoY8smfk/tjKEpbjDka3T96BNvmGxMK/5qUaWBQ==","shasum":"a74c0ca6bd02a5a7f37ed7b8c3a905c736b9adc2","tarball":"https://registry.npmjs.org/@acepad/worker/-/worker-1.0.0.tgz","fileCount":11,"unpackedSize":27723,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCICH2Gse1qhX0GsKrN8FcPIxnjbwUAt3foIjv6YdhVm5HAiBclgj8VY355O8NO3eWORUA6Ljy/p9TrufGHXft4wSzTw=="}]},"_npmUser":{"name":"acepad","email":"pnode.acepad@gmail.com"},"directories":{},"maintainers":[{"name":"acepad","email":"pnode.acepad@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/worker_1.0.0_1771484872491_0.9228839337902777"},"_hasShrinkwrap":false}},"time":{"created":"2026-02-19T07:07:52.398Z","1.0.0":"2026-02-19T07:07:52.644Z","modified":"2026-02-19T07:07:52.828Z"},"maintainers":[{"name":"acepad","email":"pnode.acepad@gmail.com"}],"description":"Web Worker management for [acepad](https://github.com/hoge1e3/acepad-dev/) and [petit-node](https://www.npmjs.com/package/petit-node). This package enables creating Web Workers that run petit-node modules with RPC (Remote Procedure Call) communication, al","readme":"# @acepad/worker\n\nWeb Worker management for [acepad](https://github.com/hoge1e3/acepad-dev/) and [petit-node](https://www.npmjs.com/package/petit-node). This package enables creating Web Workers that run petit-node modules with RPC (Remote Procedure Call) communication, allowing asynchronous and parallel execution in the browser environment.\n\n## Features\n\n- **Worker creation from petit-node modules**: Run JavaScript modules in Web Workers with full petit-node support\n- **RPC-based communication**: Bidirectional communication between main thread and workers using [@hoge1e3/rpc](https://www.npmjs.com/package/@hoge1e3/rpc)\n- **Automatic proxy generation**: Export functions from modules are automatically exposed as async RPC methods\n- **Console forwarding**: Worker console output is forwarded to the main thread console\n- **File system access**: Workers have access to the petit-node virtual file system\n- **Callback support**: Workers can call back to the main thread using reverse RPC channels\n\n**Note**: This package is designed to run in the acepad/petit-node environment, not in standard Node.js.\n\n## Basic Usage\n\n### Creating a Simple Worker\n\nCreate a worker module that exports functions:\n\n**`worker-module.js`**:\n```js\n// Functions exported from this module become RPC methods\nexport function calculate(x) {\n    console.log(\"Calculating in worker:\", x);\n    return x * 2;\n}\n\nexport function heavyTask(data) {\n    // Perform CPU-intensive work in background\n    let result = 0;\n    for (let i = 0; i < data.length; i++) {\n        result += data[i] * Math.random();\n    }\n    return result;\n}\n```\n\n**Main thread**:\n```js\n#!run\nimport {createProxy} from \"@acepad/worker\";\n\nexport async function main() {\n    const workerModule = this.resolve(\"./worker-module.js\");\n    \n    // Create worker proxy\n    const worker = await createProxy(workerModule);\n    \n    // Call worker methods (all calls are async)\n    const result = await worker.calculate(21);\n    this.echo(`Result: ${result}`);  // Output: Result: 42\n    \n    // Call another method\n    const data = [1, 2, 3, 4, 5];\n    const heavyResult = await worker.heavyTask(data);\n    this.echo(`Heavy task result: ${heavyResult}`);\n}\n```\n\n### Worker with Callbacks\n\nWorkers can call back to the main thread using reverse RPC:\n\n**`callback-worker.js`**:\n```js\nexport async function processWithProgress(items) {\n    for (let i = 0; i < items.length; i++) {\n        // Callback to main thread\n        this.onProgress(i + 1, items.length);\n        \n        // Process item\n        await new Promise(resolve => setTimeout(resolve, 100));\n    }\n    \n    this.onComplete(\"All done!\");\n    return items.length;\n}\n```\n\n**Main thread**:\n```js\n#!run\nimport {createProxy} from \"@acepad/worker\";\n\nexport async function main() {\n    const workerModule = this.resolve(\"./callback-worker.js\");\n    \n    // Provide callback handlers in reverse proxy\n    const worker = await createProxy(workerModule, {\n        onProgress(current, total) {\n            this.echo(`Progress: ${current}/${total}`);\n        },\n        onComplete(message) {\n            this.echo(message);\n        }\n    });\n    \n    const items = [\"a\", \"b\", \"c\", \"d\", \"e\"];\n    await worker.processWithProgress(items);\n}\n```\n\n## Developer Guide\n\n### Core API\n\n#### `createProxy(mainModule, reverseProxy?)`\n\nCreates a Web Worker that runs a petit-node module and returns an RPC proxy for calling its exported functions.\n\n**Parameters:**\n- `mainModule` - SFile object or path string pointing to the worker module\n- `reverseProxy` - Optional object with callback methods that the worker can call\n\n**Returns:** Promise that resolves to an RPC proxy object\n\n**Worker Environment:**\n- Full petit-node runtime initialized\n- File system mounted according to `/fstab.json`\n- Console output forwarded to main thread\n- All exported functions bound to reverse proxy as `this`\n\n**Example:**\n\n```js\nimport {createProxy} from \"@acepad/worker\";\n\nconst workerFile = this.resolve(\"/path/to/worker.js\");\nconst proxy = await createProxy(workerFile);\n\n// Call worker methods\nconst result = await proxy.someMethod(arg1, arg2);\n```\n\n**With reverse proxy:**\n\n```js\nconst proxy = await createProxy(workerFile, {\n    notify(message) {\n        console.log(\"Worker notification:\", message);\n    },\n    updateUI(data) {\n        // Update UI based on worker data\n    }\n});\n\n// Worker can now call this.notify() and this.updateUI()\n```\n\n#### `create(mainModule)`\n\nCreates a Web Worker that simply runs a petit-node module without RPC setup. Useful for fire-and-forget background tasks.\n\n**Parameters:**\n- `mainModule` - SFile object or path string pointing to the module to execute\n\n**Returns:** Promise that resolves to the Worker object\n\n**Example:**\n\n```js\nimport {create} from \"@acepad/worker\";\n\nconst taskModule = this.resolve(\"./background-task.js\");\nconst worker = await create(taskModule);\n\n// Worker runs the module and terminates when done\n// No direct communication except console forwarding\n```\n\n#### `importExpr(file)`\n\nGenerates an import expression string for dynamically importing a module in worker context.\n\n**Parameters:**\n- `file` - SFile object or path string\n\n**Returns:** String containing import expression\n\n**Example:**\n\n```js\nimport {importExpr} from \"@acepad/worker\";\n\nconst moduleFile = this.resolve(\"./my-module.js\");\nconst expr = importExpr(moduleFile);\n// Returns: \"await pNode.importModule(FS.get(\\\"/path/to/my-module.js\\\"))\"\n```\n\nThis is primarily used internally for worker code generation.\n\n### Console Forwarding\n\nThe `cons` module handles console output forwarding between workers and the main thread.\n\n#### `cons.server(worker)`\n\nSets up console forwarding server on the main thread to receive worker console output.\n\n**Parameters:**\n- `worker` - Worker object\n\n**Example:**\n\n```js\nimport {cons} from \"@acepad/worker\";\n\nconst worker = new Worker(\"worker.js\");\ncons.server(worker);  // Now worker console.log/error forwarded to main console\n```\n\n#### `cons.client()`\n\nSets up console forwarding client inside a worker to send console output to main thread. This is automatically called when using `createProxy` or `create`.\n\n**Worker code:**\n```js\nimport {cons} from \"@acepad/worker\";\n\ncons.client();  // Forward this worker's console to main thread\n\nconsole.log(\"This appears in main thread console\");\n```\n\n### Worker Module Patterns\n\n#### Pattern 1: Computation Worker\n\nOffload CPU-intensive calculations:\n\n**`fib-worker.js`**:\n```js\nexport function fibonacci(n) {\n    if (n <= 1) return n;\n    return fibonacci(n - 1) + fibonacci(n - 2);\n}\n\nexport function fibonacciSeries(count) {\n    const series = [];\n    for (let i = 0; i < count; i++) {\n        series.push(fibonacci(i));\n    }\n    return series;\n}\n```\n\n**Usage:**\n```js\n#!run\nimport {createProxy} from \"@acepad/worker\";\n\nexport async function main() {\n    const worker = await createProxy(this.resolve(\"./fib-worker.js\"));\n    \n    // This won't block the UI\n    const series = await worker.fibonacciSeries(30);\n    this.echo(series);\n}\n```\n\n#### Pattern 2: File Processing Worker\n\nProcess large files in the background:\n\n**`file-processor.js`**:\n```js\nimport * as fs from \"fs\";\n\nexport async function processLargeFile(inputPath, outputPath) {\n    const content = fs.readFileSync(inputPath, \"utf-8\");\n    const lines = content.split(\"\\n\");\n    \n    const processed = lines.map(line => {\n        // Heavy processing per line\n        return line.toUpperCase().split(\"\").reverse().join(\"\");\n    });\n    \n    fs.writeFileSync(outputPath, processed.join(\"\\n\"));\n    \n    this.onComplete(processed.length);  // Callback to main thread\n    return processed.length;\n}\n```\n\n**Usage:**\n```js\n#!run\nimport {createProxy} from \"@acepad/worker\";\n\nexport async function main() {\n    const worker = await createProxy(\n        this.resolve(\"./file-processor.js\"),\n        {\n            onComplete(lineCount) {\n                this.echo(`Processed ${lineCount} lines`);\n            }\n        }\n    );\n    \n    await worker.processLargeFile(\n        \"/idb/input.txt\",\n        \"/idb/output.txt\"\n    );\n}\n```\n\n#### Pattern 3: Data Fetching Worker\n\nFetch data without blocking the main thread:\n\n**`data-fetcher.js`**:\n```js\nexport async function fetchMultipleURLs(urls) {\n    const results = [];\n    \n    for (let i = 0; i < urls.length; i++) {\n        this.onProgress(i, urls.length);\n        \n        try {\n            const response = await fetch(urls[i]);\n            const data = await response.json();\n            results.push({ url: urls[i], data, success: true });\n        } catch (error) {\n            results.push({ url: urls[i], error: error.message, success: false });\n        }\n    }\n    \n    return results;\n}\n```\n\n**Usage:**\n```js\n#!run\nimport {createProxy} from \"@acepad/worker\";\n\nexport async function main() {\n    const worker = await createProxy(\n        this.resolve(\"./data-fetcher.js\"),\n        {\n            onProgress(current, total) {\n                this.echo(`Fetching ${current}/${total}...`);\n            }\n        }\n    );\n    \n    const urls = [\n        \"https://api.example.com/data1\",\n        \"https://api.example.com/data2\",\n        \"https://api.example.com/data3\"\n    ];\n    \n    const results = await worker.fetchMultipleURLs(urls);\n    this.echo(JSON.stringify(results, null, 2));\n}\n```\n\n#### Pattern 4: Stateful Worker\n\nMaintain state across multiple calls:\n\n**`stateful-worker.js`**:\n```js\nlet cache = {};\nlet callCount = 0;\n\nexport function addToCache(key, value) {\n    cache[key] = value;\n    callCount++;\n    return Object.keys(cache).length;\n}\n\nexport function getFromCache(key) {\n    callCount++;\n    return cache[key];\n}\n\nexport function getStats() {\n    return {\n        cacheSize: Object.keys(cache).length,\n        totalCalls: callCount\n    };\n}\n\nexport function clearCache() {\n    cache = {};\n    const prevCount = callCount;\n    callCount = 0;\n    return prevCount;\n}\n```\n\n**Usage:**\n```js\n#!run\nimport {createProxy} from \"@acepad/worker\";\n\nexport async function main() {\n    const worker = await createProxy(this.resolve(\"./stateful-worker.js\"));\n    \n    await worker.addToCache(\"user1\", { name: \"Alice\", age: 30 });\n    await worker.addToCache(\"user2\", { name: \"Bob\", age: 25 });\n    \n    const user = await worker.getFromCache(\"user1\");\n    this.echo(JSON.stringify(user));\n    \n    const stats = await worker.getStats();\n    this.echo(`Stats: ${JSON.stringify(stats)}`);\n    // Output: Stats: {\"cacheSize\":2,\"totalCalls\":3}\n}\n```\n\n## Advanced Features\n\n### Accessing File System in Workers\n\nWorkers have full access to the petit-node file system:\n\n**`fs-worker.js`**:\n```js\nimport * as fs from \"fs\";\nimport * as path from \"path\";\n\nexport function listFiles(directory) {\n    const files = fs.readdirSync(directory);\n    return files.map(name => {\n        const fullPath = path.join(directory, name);\n        const stats = fs.statSync(fullPath);\n        return {\n            name,\n            size: stats.size,\n            isDirectory: stats.isDirectory()\n        };\n    });\n}\n\nexport function writeLogEntry(logPath, message) {\n    const timestamp = new Date().toISOString();\n    const entry = `[${timestamp}] ${message}\\n`;\n    fs.appendFileSync(logPath, entry);\n}\n```\n\n### Error Handling\n\nWorker errors are automatically reported:\n\n**`error-worker.js`**:\n```js\nexport function riskyOperation(value) {\n    if (value < 0) {\n        throw new Error(\"Negative values not allowed\");\n    }\n    return Math.sqrt(value);\n}\n\nexport async function asyncRiskyOperation(url) {\n    const response = await fetch(url);\n    if (!response.ok) {\n        throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n    }\n    return await response.json();\n}\n```\n\n**Error handling in main thread:**\n```js\n#!run\nimport {createProxy} from \"@acepad/worker\";\n\nexport async function main() {\n    const worker = await createProxy(this.resolve(\"./error-worker.js\"));\n    \n    try {\n        const result = await worker.riskyOperation(-5);\n    } catch (error) {\n        this.echo(`Error: ${error.message}`);\n        // Output: Error: Negative values not allowed\n    }\n    \n    try {\n        await worker.asyncRiskyOperation(\"https://invalid.url\");\n    } catch (error) {\n        this.echo(`Async error: ${error.message}`);\n    }\n}\n```\n\n### Using npm Packages in Workers\n\nWorkers can use any npm package available in petit-node:\n\n**`npm-worker.js`**:\n```js\nimport _ from \"lodash\";\nimport moment from \"moment\";\n\nexport function processData(data) {\n    // Use lodash\n    const grouped = _.groupBy(data, \"category\");\n    const sorted = _.sortBy(data, \"timestamp\");\n    \n    return {\n        grouped,\n        sorted,\n        count: data.length\n    };\n}\n\nexport function formatDates(timestamps) {\n    // Use moment\n    return timestamps.map(ts => \n        moment(ts).format(\"YYYY-MM-DD HH:mm:ss\")\n    );\n}\n```\n\n## Complete Example\n\nHere's a complete example showing worker creation, callbacks, and error handling:\n\n**`complete-worker.js`**:\n```js\nimport * as fs from \"fs\";\n\nexport async function analyzeFiles(directory) {\n    try {\n        const files = fs.readdirSync(directory);\n        let totalSize = 0;\n        let fileCount = 0;\n        const extensions = {};\n        \n        for (let i = 0; i < files.length; i++) {\n            const file = files[i];\n            const filePath = `${directory}/${file}`;\n            \n            try {\n                const stats = fs.statSync(filePath);\n                \n                if (stats.isFile()) {\n                    fileCount++;\n                    totalSize += stats.size;\n                    \n                    const ext = file.split(\".\").pop() || \"no-ext\";\n                    extensions[ext] = (extensions[ext] || 0) + 1;\n                    \n                    // Progress callback\n                    this.onProgress({\n                        current: i + 1,\n                        total: files.length,\n                        file\n                    });\n                }\n            } catch (err) {\n                this.onError(`Error reading ${file}: ${err.message}`);\n            }\n        }\n        \n        const result = {\n            fileCount,\n            totalSize,\n            averageSize: totalSize / fileCount,\n            extensions\n        };\n        \n        this.onComplete(result);\n        return result;\n        \n    } catch (error) {\n        this.onError(`Fatal error: ${error.message}`);\n        throw error;\n    }\n}\n```\n\n**Main thread:**\n```js\n#!run\nimport {createProxy} from \"@acepad/worker\";\n\nexport async function main() {\n    const sh = this;\n    \n    const worker = await createProxy(\n        this.resolve(\"./complete-worker.js\"),\n        {\n            onProgress(info) {\n                sh.echo(`Processing: ${info.file} (${info.current}/${info.total})`);\n            },\n            onComplete(result) {\n                sh.echo(\"\\n=== Analysis Complete ===\");\n                sh.echo(`Files: ${result.fileCount}`);\n                sh.echo(`Total size: ${result.totalSize} bytes`);\n                sh.echo(`Average: ${result.averageSize.toFixed(2)} bytes`);\n                sh.echo(`Extensions: ${JSON.stringify(result.extensions)}`);\n            },\n            onError(message) {\n                sh.echo(`⚠️  ${message}`);\n            }\n        }\n    );\n    \n    try {\n        const result = await worker.analyzeFiles(\"/idb/run/\");\n        sh.echo(\"\\nAnalysis result:\", JSON.stringify(result, null, 2));\n    } catch (error) {\n        sh.echo(`Failed: ${error.message}`);\n    }\n}\n```\n\n## Testing\n\nThe package includes test files demonstrating various worker patterns:\n\n- `test/testworker.js` - Basic worker with callbacks\n- `test/simple-worker.js` - Simple message-based worker\n- `test/broadcast.js` - BroadcastChannel RPC example\n- `test/test-acepad-worker.js` - Complete test suite\n\nRun tests:\n```bash\nsh: npm test\n```\n\n## Implementation Details\n\n### Worker Code Generation\n\nWhen you call `createProxy(mainModule)`, the package generates worker code that:\n\n1. Imports petit-node runtime\n2. Initializes file system with fstab configuration\n3. Imports the target module\n4. Sets up RPC server for exported functions\n5. Sets up console forwarding\n6. Establishes reverse RPC channel for callbacks\n\nThe generated worker code uses Blob URLs to create workers dynamically.\n\n### RPC Communication\n\n- Uses [@hoge1e3/rpc](https://www.npmjs.com/package/@hoge1e3/rpc) for bidirectional communication\n- Main thread → Worker: \"default\" channel\n- Worker → Main thread: Random channel ID for reverse proxy\n- All function calls are async and return Promises\n\n### File System Access\n\nWorkers access the same file system as the main thread:\n- `/idb/` - IndexedDB-backed persistent storage\n- `/tmp/` - RAM-based temporary storage\n- Custom mounts from `/fstab.json`\n\nFile system operations in workers are isolated but share the same underlying storage.\n\n## Dependencies\n\n- `@hoge1e3/str2worker`: Worker creation from string source\n- `@hoge1e3/rpc`: RPC communication framework\n- `@acepad/npm`: npm package management\n- `@acepad/here`: Path resolution utilities\n- `petit-node`: Browser-based Node.js runtime (peer dependency)\n\n## Limitations\n\n- Workers cannot directly access DOM or UI elements\n- All data passed between main thread and workers must be serializable\n- Some browser APIs may not be available in workers (e.g., `localStorage`)\n- Error stack traces may be less detailed in workers\n\n## See Also\n\n- [acepad](https://hoge1e3.github.io/acepad/) - The acepad programming environment\n- [petit-node](https://www.npmjs.com/package/petit-node) - Browser-based Node.js runtime\n- [@hoge1e3/rpc](https://www.npmjs.com/package/@hoge1e3/rpc) - RPC library\n- [@hoge1e3/str2worker](https://www.npmjs.com/package/@hoge1e3/str2worker) - Dynamic worker creation\n- [Web Workers MDN](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) - Web Workers documentation\n\n## License\n\nISC\n","readmeFilename":"README.md","_rev":"1-bb8f7248fa304d367cf0285b1ce9dbe3"}