From 61cce134f59f6eb8bb77212de197f2c21d37f048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramirez=20Vargas=2C=20Jos=C3=A9=20Pablo?= Date: Sat, 24 Aug 2024 10:29:05 -0600 Subject: [PATCH] feat: Initial commit --- .gitignore | 2 + .vscode/tasks.json | 16 + README.md | 379 +++ package-lock.json | 231 ++ package.json | 30 + pages/.gitignore | 21 + pages/.npmrc | 1 + pages/README.md | 38 + pages/eslint.config.js | 30 + pages/package-lock.json | 2906 +++++++++++++++++++ pages/package.json | 34 + pages/src/app.d.ts | 13 + pages/src/assets/svelte.svg | 1 + pages/src/index.html | 24 + pages/src/lib/CrossOriginNotIsolated.svelte | 28 + pages/src/lib/Instructions.svelte | 25 + pages/src/lib/Primes.svelte | 150 + pages/src/lib/nextControlId.ts | 5 + pages/src/routes/+layout.ts | 1 + pages/src/routes/+page.svelte | 34 + pages/src/workers/exampleWorker.ts | 46 + pages/static/404.html | 14 + pages/static/favicon.png | Bin 0 -> 1571 bytes pages/svelte.config.js | 25 + pages/tsconfig.json | 19 + pages/vite.config.ts | 15 + src/cancellation/CancellationSource.ts | 28 + src/cancellation/CancelledMessage.ts | 15 + src/cancellation/TaskCancelledError.ts | 8 + src/events/AutoResetEvent.ts | 77 + src/events/Event.ts | 41 + src/events/ManualResetEvent.ts | 71 + src/index.ts | 41 + src/misc/Queue.ts | 40 + src/misc/nextWorkItemId.ts | 5 + src/workers.d.ts | 67 + src/workers/AsyncWorker.ts | 131 + src/workers/InternalSharedWorker.ts | 61 + src/workers/InternalWorker.ts | 71 + src/workers/WorkItem.ts | 48 + src/workers/WorkItemInternal.ts | 66 + src/workers/WorkerTerminatedMessage.ts | 15 + src/workers/workerListener.ts | 46 + tsconfig.json | 115 + 44 files changed, 5034 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/tasks.json create mode 100644 README.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 pages/.gitignore create mode 100644 pages/.npmrc create mode 100644 pages/README.md create mode 100644 pages/eslint.config.js create mode 100644 pages/package-lock.json create mode 100644 pages/package.json create mode 100644 pages/src/app.d.ts create mode 100644 pages/src/assets/svelte.svg create mode 100644 pages/src/index.html create mode 100644 pages/src/lib/CrossOriginNotIsolated.svelte create mode 100644 pages/src/lib/Instructions.svelte create mode 100644 pages/src/lib/Primes.svelte create mode 100644 pages/src/lib/nextControlId.ts create mode 100644 pages/src/routes/+layout.ts create mode 100644 pages/src/routes/+page.svelte create mode 100644 pages/src/workers/exampleWorker.ts create mode 100644 pages/static/404.html create mode 100644 pages/static/favicon.png create mode 100644 pages/svelte.config.js create mode 100644 pages/tsconfig.json create mode 100644 pages/vite.config.ts create mode 100644 src/cancellation/CancellationSource.ts create mode 100644 src/cancellation/CancelledMessage.ts create mode 100644 src/cancellation/TaskCancelledError.ts create mode 100644 src/events/AutoResetEvent.ts create mode 100644 src/events/Event.ts create mode 100644 src/events/ManualResetEvent.ts create mode 100644 src/index.ts create mode 100644 src/misc/Queue.ts create mode 100644 src/misc/nextWorkItemId.ts create mode 100644 src/workers.d.ts create mode 100644 src/workers/AsyncWorker.ts create mode 100644 src/workers/InternalSharedWorker.ts create mode 100644 src/workers/InternalWorker.ts create mode 100644 src/workers/WorkItem.ts create mode 100644 src/workers/WorkItemInternal.ts create mode 100644 src/workers/WorkerTerminatedMessage.ts create mode 100644 src/workers/workerListener.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1eae0cf --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +dist/ +node_modules/ diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..30e6ae8 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,16 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "build", + "group": { + "isDefault": true, + "kind": "build" + }, + "problemMatcher": [], + "label": "npm: build", + "detail": "pwsh -Command \"npx tsc && copy src/workers.d.ts dist/\"" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..56b0309 --- /dev/null +++ b/README.md @@ -0,0 +1,379 @@ +# @wjfe/async-workers + +> Provides thread-safe and atomic synchronization objects, and wrappers to easily use web workers with async/await +> syntax. + +[Live Demo](https://wjsoftware.github.io/wjfe-async-workers) + +> [!CAUTION] +> This NPM package is in its experimental phase. Features may be incomplete or still requiring thorough testing. + +## Introduction + +Using web workers imply a call to `Worker.postMessge()` to signal the worker you want the work done, and then expect +some notification back via a listener in `Worker.onmessage` to at least know that the work completed, but usually to +get a result back in the form of data (the result of the work). This is just the core, though. You should also add +a listener to `Worker.onerror` just in case the worker has issues processing your request. Otherwise you'll be waiting +forever for the notification, ignorant that an error has occurred and nothing will ever be returned. + +Oh, but this is just on the user interface side. Then there's the matter of doing the web worker side. No point in +continuing the explanation. The point is made: This is incredibly cumbersome. Multi-threaded runtimes like .Net can +use `async/await` with threads and is far more convenient. The whole point of this NPM package is to bring this +convenience to the web workers world. + +## Quickstart + +These are the recommended steps to get things going: + +1. Write the web worker as an object whose properties are the discrete worker tasks. Use `workerListener()` to listen +to incoming messages. +2. Export the tasks worker object. +3. Create a new instance of `Worker` the way is recommended by your bundler. Usually with the syntax +`new Worker("./myworker.js", impot.meta.url)`. However, this forces you to write the worker in JavaScript, at least +for Vite-powered projects. +4. Create a new instance of `AsyncWorker` (from this package) by passing the worker object and the tasks object from +the previous points. +5. Start worker tasks by using the `AsyncWorker.enqueue` property. The functions found in this object return an object +of type `WorkItem` that exposes the `promise` property and the `cancel()` method. +6. Await the promise to obtain the worker's result. The promise completes when the task finishes. The promise rejects +on unhandled errors in the worker side or when the task is cancelled. + +> **How can I cancel the task?** Use `terminate()`. NO! Just kidding. We have the good stuff. Keep reading. + +### The Worker + +Write your worker. The following example is a simple worker that works in steps: Initialize the data, then sort it +the way you need to, then calculate something (the running total of some property): + +```typescript +// ./my-types.d.ts +import type { MyData } from './my-types.js'; +import { workerListener } from '@wjfe/async-workers'; + +let workerData: MyData[]; + +function getComparerForKey(key: string; desc: boolean) { + return (a, b) => { ... }; +} + +function runningTotal() { + // Process workerData; + return result; +} + +// This is the tasks object. +export const myWorker = { + init(payload: MyData[]) { + workerData = payload; + }, + sortBy(payload: { sortKey: string; desc: boolean; }) { + workerData.sort(getComparerForKey(payload.sortKey, payload.desc)); + }, + calculateRunningTotal() { + return runningTotal(); + } +}; + +// OPTIONAL: For easy typing of props or variables along the way. +export type MyWorker = typeof myWorker; + +// Now listen for messages. +self.onmessage = workerListener(myWorker); +``` + +This is a 3-step worker. The worker simply waits to be informed which step to run from the user interface thread. + +### The Async Worker in the UI Thread + +This is what needs to be done in order to obtain an object that commands the worker: + +> [!IMPORTANT] +> This example is using TypeScript and the following assumes a Vite-powered project. We are deviating from the +> recommended way of obtaining a worker because the recommended way requires the worker to be written in JavaScript +> while in serve mode (`npm run dev`). + +```typescript +import { myWorker } from './myWorker.js'; +// Vite-specific. May also work with other bundlers. Consult your bundler's documentation. +import myWorkerCtor from './myWorker.js?worker'; +import { AsyncWorker } from '@wjfe/async-workers'; + +const myWorkerController = new AsyncWorker(new myWorkerCtor(), myWorker); + +// Done. Do what you must to get this controller to the places where is needed. +// For example, this could be a module and the controller could be exported. +export myWorkerController; +``` + +Now it is a matter of using the controller (async worker) wherever needed: + +```typescript +import { myWorkerController } from './myModule.js'; + +// Use the "enqueue" property to enqueue the worker's tasks and obtain a WorkItem object. +const initWorkItem = myWorkerController.enqueue.init(aBunchOfData); +const defaultSortWorkItem = myWorkerController.enqueue.sortBy({ key: 'eventDate', desc: false }); +const defaultRunningTotalWorkItem = myWorkerController.enqueue.calculateRunningTotal(); +``` + +Yes! The above is valid: You may queue up as many tasks as you wish without having to await for the completion of +previous ones, even if the worker is asynchronous (uses `async/await`). The worker controller will keep perfect record +of the order in which the tasks must be run. + +## Shared Workers + +Shared workers are also supported through the same `AsyncWorker` class. Note, however, the following key differences: + +- Shared workers cannot be terminated. Calling `AsyncWorker.terminate()` will throw an error. +- Shared workers don't have per-connection error handling. It cannot be guaranteed that an error belongs to a +particular connection or work item. All connected (started) work items from all `AsyncWorker` objects that point to +the same shared worker script will have their promises rejected. +- Not related to this NPM package, but note that the listening code of shared workers is different. See below. + +```typescript +self.onconnect = (ev) => { + const port = ev.ports[0]; + port.onmessage = workerListener(myWorker); +} +``` + +## Bi-Directional Communication + +The default functionality is fine for many cases: A worker task is started, the user interface waits for its +completion and when the task finishes, the work item's `promise` property spits out the resultant object when awaited. + +There are also many cases where "interim" communcation between the worker and the UI thread is desired, most commonly: + +1. Progress reports +2. Partial results + +How can a worker send data while the task is still in progress? By using the provied `post()` function. + +In reality, the functions of the worker's tasks object in the quickstart are simplified. In reality we can re-write +the tasks object like this: + +```typescript +import { workerListener, type PostFn, Token } from '@wjfe/async-workers'; + +... + +// This is the tasks object. +export const myWorker = { + init(payload: MyData[], post: PostFn, cancelToken?: Token) { + workerData = payload; + }, + sortBy(payload: { sortKey: string; desc: boolean; }, post: PostFn, cancelToken?: Token) { + workerData.sort(getComparerForKey(sortKey, desc)); + return workerData; + }, + calculateRunningTotal(_: undefined, post: PostFn, cancelToken?: Token) { + return runningTotal(); + } +}; +``` + +Try not to be distracted with the `cancelToken` parameter. Yes, worker tasks can be cancelled in real-time with this +package. This is explained in the [CancellationSource](#cancellationsource) section, later in the document. + +A worker task may use `post()` to send data back to the UI thread at any point: + +```typescript + calculateRunningTotal(_: undefined, post: PostFn, cancelToken?: Token) { + ... + // Only one parameter: The payload (say, progress report or partial result) to send back to the UI. + post(partialResult); + ... + return runningTotal(); + } +``` + +Doing this, however and by default, incorrectly makes the work item's promise in the UI thread to resolve. You must +account for these extra messages in the UI side. This is done when the work item is queued: + +```typescript +const defaultRunningTotalWorkItem = myWorkerController.enqueue.calculateRunningTotal(undefined, { + processMessage: (payload: any) => { + // Examine the payload and determine whether or not, based on its contents, the work item's + // promise should resolve or not. + if (payloadIsProgressReport(payload) || payloadIsPartialResult(payload)) { + // A progress report or partial result. Don't resolve the work item yet. + return false; + } + // Payload is not a known payload. There are 2 options: It's the task's return value, or it's + // an unknown message, which should be impossible, but have this in the back of your head. + if (isExpectedReturnValue(payload)) { + // Ok, this is the return value, so the task completed. Allow the work item's promise to resolve. + return true; + } + else { + // You may choose to ignore the unknown payload by doing nothing and returning false, + // or you may do something else. Your choice. + return false; + } + } +}); +``` + +This is it. Bi-directional communcation is fully set up. + +### How About Sending Something Back? + +You might be tempted to develop a worker that uses `post()` to request extra data mid-flight. Don't. This is +difficult to maintain, not to mention that it requires releasing the worker thread using `await` so the queue of +messages can be processed, which could contain instructions to start an entirely different task, and the list of +problems probably goes on. + +At your own risk, do "partial update" tasks in the worker's tasks object: + +```typescript +export const myWorker = { + init(payload: MyData[]) { + workerData = payload; + }, + sortBy(payload: { sortKey: string; desc: boolean; }) { + workerData.sort(getComparerForKey(sortKey, desc)); + return workerData; + }, + calculateRunningTotal(_: undefined, post: PostFn, cancelToken?: Token) { + return runningTotal(); + } + supplyMissingOrUpdatedDataWhileInTheAir(theData: UpdatedData) { + ... + } +}; +``` + +Inside `processMessage`, do `myWorkerController.enqueue.supplyMissingOrUpdatedDataWhileInTheAir(theData, { outOfOrder: true })` +and hope for the best. + +## Synchronization Objects + +This package provides synchronization objects that use `Atomics` to cross the thread boundary safely. + +> [!IMPORTANT] +> This implementation uses `Atomics` on `SharedArrayBuffer` objects which demands certain +[security requirements](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer#security_requirements). + +### CancellationSource + +This is the web worker equivalent of `AbortController` and provides a token that can be passed to workers in a task's +payload. + +The worker can use `CancellationSource.isSignalled()` or `CancellationSource.throwIfSignaled()` through polling in order +to abort the current work: + +```typescript +import { CancellationSource, type Token } from '@wjfe/async-workers'; + +function computeSomeStuff(cancelToken?: Token) { + for (let i = 0; i < Number.MAX_SAFE_INTEGER; ++i) { + // No thowing will be done if cancelToken is undefined. + CancellationSource.throwIfSignaled(cancelToken); + ... + } +} +``` + +The throw option is the preferred, and if used, this package will take care of catching the exception and making sure +the work item's promise rejects with an object of type `CancelledMessage`. This object has the +`cancelledBeforeStarted` property that will be `true` if cancellation took place before the worker task even got +started. + +Passing the token "by hand" via the `payload` property to eventually get the token where is needed, is boilerplate that +is already simplified. Taking one line from the quickstart example: + +```typescript +const defaultRunningTotalWorkItem = myWorkerController.enqueue.calculateRunningTotal(undefined, { cancellable: true }); +``` + +By adding the `cancellable` option, a cancellation token will be avilable to `calculateRunningTotal` in its third +parameter, as seen in the previous section. + +Whenever cancellation is desired, simply call the work item's `cancel()` method. For more information about this +method, refer to [this section](#the-cancel-method). + +If you're using `CancellationSource` on your own: + ++ Do `const cs = new CancellationSource()` to create a new cancellation source. ++ The token is available via `cs.token`. ++ Signalling the token to trigger cancellation is done with `cs.signal()`. + +### ManualResetEvent + +This is a synchronization object that can be used to signal multiple threads at once because it will remain signalled +until the `reset()` event is invoked. A typical use case is to use it for pausing a worker's work. + +### AutoResetEvent + +This is a synchronization objec that signals a single thread because once awaited, its state is automatically reset. + +Typical use cases involve the need to unlock a single thread at a time. + +## The WorkItem Class + +Instances of the `WorkItem` class is what is returned by the functions in the `AsyncWorker.enqueue` property. The +class exposes the following properties and methods: + ++ `id`: A unique numeric identifier given to the work item upon creation. Payloads carry this ID behind the scenes. ++ `status`: A numeric value (enum) that tells the current status of the work item. ++ `promise`: A promise that accurately represents the lifetime of the worker's task and the one that returns the +task's return value. ++ `cancel()`: A method that can potentially cancel a worker's task. + +### The `cancel()` Method + +For a worker task to be cancelled using `cancel()`, it must fulfill one of the following conditions: + +1. It must have been enqueued using the `cancellable` option **and** the worker's code uses the cancellation token. +2. The work item hasn't been transmitted to the worker. + +In other words, cancellation is not magical. The good news is that any task is potentially cancellable as long as it +hasn't been transmitted to the worker without the need of any extra coding, and if this is the case, the associated +promise immediately rejects, meaning one doesn't have to wait for the task's turn in the queue of tasks to get it +rejected. + +The function returns `true` if action was taken towards cancelling the work item, or `false` otherwise. The latter +case can happen if the work item was already cancelled, or if none of the aforementioned conditions are met. Yes, the +function cannot know if the worker's code uses the cancellation token, so even if `true` is returned, a worker's task +may still not cancel. Be wary of this edge case. + +### Out-of-Order Work Items + +Out-of-order work items are created when enqueueing a task using the `outOfOrder` queueing option. These work items +skip the queue of other "normal" work items and are immediately transmitted to the worker, skipping the waiting line. + +Because of their nature, they are ghosts in the machine. Their running status is not tracked, their completions +trigger no dequeueing of other tasks, and their execution delays, for seemingly no good reason, the execution of +"normal" work items. Avoid the need for these ghosts as much as possible. + +## The AsyncWorker Class + +This is the top-level class used in the user interface side, and is the wrapper of the native `Worker` class. It +combines the worker with the worker tasks and orchestrates things behind the scenes. It exposes the following +properties and methods: + ++ `enqueue`: The property used to enqueue worker tasks. ++ `terminate`: Terminates the worker. + +Terminating the worker immediately rejects the promises of all work items, and further use of `enqueue` will produce +work items whose promise immediately rejects as well. Promises that reject due to termination will have a reason +object of type `WorkerTerminatedMessage`. + +Generally speaking, terminated workers should be disposed. Do so as fast as possible. + +## Usage in Node.Js + +This package, by design, is for use in the browser. However, there is this NPM package called [web-worker](https://www.npmjs.com/package/web-worker) +that claims to bring the browser API into Node. If this is indeed the case, using these 2 packages together should +work properly in Node. + +## Roadmap + +| Synchronization Objects | Dedicated Worker | Shared Worker | +| - | - | - | +| [x] ManualResetEvent | [x] Simple request/response scenario | [x] Simple request/response scenario | +| [x] AutoResetEvent | [x] Request/multiple response scenario | [x] Request/multiple response scenario | +| [ ] Semaphore | [x] Strongly-typed tasks | [x] Strongly-typed tasks | +| [x] CancellationSource | [x] Worker termination | | +| | [x] Out-of-order work items | [x] Out-of-order work items | +| | [x] Task cancellation | [x] Task cancellation| diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..ca700ad --- /dev/null +++ b/package-lock.json @@ -0,0 +1,231 @@ +{ + "name": "@wjfe/workers", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@wjfe/workers", + "version": "0.1.0", + "license": "MIT", + "devDependencies": { + "publint": "^0.2.10", + "typescript": "^5.5.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ignore-walk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-5.0.1.tgz", + "integrity": "sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minimatch": "^5.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-bundled": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-2.0.1.tgz", + "integrity": "sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz", + "integrity": "sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-packlist": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-5.1.3.tgz", + "integrity": "sha512-263/0NGrn32YFYi4J533qzrQ/krmmrWwhKkzwTuM4f/07ug51odoaNjUexxO4vxlzURHcmYMH1QjvHjsNDKLVg==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^8.0.1", + "ignore-walk": "^5.0.1", + "npm-bundled": "^2.0.0", + "npm-normalize-package-bin": "^2.0.0" + }, + "bin": { + "npm-packlist": "bin/index.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true, + "license": "ISC" + }, + "node_modules/publint": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/publint/-/publint-0.2.10.tgz", + "integrity": "sha512-5TzYaAFiGpgkYX/k6VaItRMT2uHI4zO5OWJ2k7Er0Ot3jutBCNTJB1qRHuy1lYq07JhRczzFs6HFPB4D+A47xA==", + "dev": true, + "license": "MIT", + "dependencies": { + "npm-packlist": "^5.1.3", + "picocolors": "^1.0.1", + "sade": "^1.8.1" + }, + "bin": { + "publint": "lib/cli.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://bjornlu.com/sponsor" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/typescript": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c6163ae --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "@wjfe/workers", + "version": "0.1.0", + "description": "Provides thread-safe and atomic synchronization objects, and wrappers to easily use web workers with async/await syntax.", + "types": "dist/index.d.ts", + "main": "dist/index.js", + "scripts": { + "build": "pwsh -Command \"npx tsc && copy src/workers.d.ts dist/\"", + "postbuild": "publint", + "deploy-pages": "cd ./pages && npm run deploy-pages", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "author": "José Pablo Ramírez ", + "license": "MIT", + "devDependencies": { + "publint": "^0.2.10", + "typescript": "^5.5.4" + }, + "type": "module" +} diff --git a/pages/.gitignore b/pages/.gitignore new file mode 100644 index 0000000..79518f7 --- /dev/null +++ b/pages/.gitignore @@ -0,0 +1,21 @@ +node_modules + +# Output +.output +.vercel +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/pages/.npmrc b/pages/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/pages/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/pages/README.md b/pages/README.md new file mode 100644 index 0000000..5ce6766 --- /dev/null +++ b/pages/README.md @@ -0,0 +1,38 @@ +# create-svelte + +Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte). + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```bash +# create a new project in the current directory +npm create svelte@latest + +# create a new project in my-app +npm create svelte@latest my-app +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```bash +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +To create a production version of your app: + +```bash +npm run build +``` + +You can preview the production build with `npm run preview`. + +> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. diff --git a/pages/eslint.config.js b/pages/eslint.config.js new file mode 100644 index 0000000..4c98b8e --- /dev/null +++ b/pages/eslint.config.js @@ -0,0 +1,30 @@ +import js from '@eslint/js'; +import ts from 'typescript-eslint'; +import svelte from 'eslint-plugin-svelte'; +import globals from 'globals'; + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + js.configs.recommended, + ...ts.configs.recommended, + ...svelte.configs['flat/recommended'], + { + languageOptions: { + globals: { + ...globals.browser, + ...globals.node + } + } + }, + { + files: ['**/*.svelte'], + languageOptions: { + parserOptions: { + parser: ts.parser + } + } + }, + { + ignores: ['build/', '.svelte-kit/', 'dist/'] + } +]; diff --git a/pages/package-lock.json b/pages/package-lock.json new file mode 100644 index 0000000..66b721f --- /dev/null +++ b/pages/package-lock.json @@ -0,0 +1,2906 @@ +{ + "name": "pages", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pages", + "version": "0.0.1", + "dependencies": { + "@sveltejs/adapter-static": "^3.0.4", + "web-worker": "^1.3.0" + }, + "devDependencies": { + "@sveltejs/adapter-auto": "^3.0.0", + "@sveltejs/kit": "^2.0.0", + "@sveltejs/vite-plugin-svelte": "^4.0.0-next.6", + "@types/eslint": "^9.6.0", + "eslint": "^9.0.0", + "eslint-plugin-svelte": "^2.36.0", + "gh-pages": "^6.1.1", + "globals": "^15.0.0", + "svelte": "^5.0.0-next.1", + "svelte-check": "^3.6.0", + "typescript": "^5.0.0", + "typescript-eslint": "^8.0.0", + "vite": "^5.0.3" + } + }, + "..": { + "name": "@wjfe/workers", + "version": "0.0.1", + "extraneous": true, + "license": "ISC", + "devDependencies": { + "publint": "^0.2.10", + "typescript": "^5.5.3" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.17.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.9.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.3.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.25", + "license": "MIT" + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.21.0", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sveltejs/adapter-auto": { + "version": "3.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "import-meta-resolve": "^4.1.0" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.0.0" + } + }, + "node_modules/@sveltejs/adapter-static": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.4.tgz", + "integrity": "sha512-Qm4GAHCnRXwfWG9/AtnQ7mqjyjTs7i0Opyb8H2KH9rMR7fLxqiPx/tXeoE6HHo66+72CjyOb4nFH3lrejY4vzA==", + "license": "MIT", + "peerDependencies": { + "@sveltejs/kit": "^2.0.0" + } + }, + "node_modules/@sveltejs/kit": { + "version": "2.5.24", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^0.6.0", + "devalue": "^5.0.0", + "esm-env": "^1.0.0", + "import-meta-resolve": "^4.1.0", + "kleur": "^4.1.5", + "magic-string": "^0.30.5", + "mrmime": "^2.0.0", + "sade": "^1.8.1", + "set-cookie-parser": "^2.6.0", + "sirv": "^2.0.4", + "tiny-glob": "^0.2.9" + }, + "bin": { + "svelte-kit": "svelte-kit.js" + }, + "engines": { + "node": ">=18.13" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "vite": "^5.0.3" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "4.0.0-next.6", + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^3.0.0-next.0||^3.0.0", + "debug": "^4.3.6", + "deepmerge": "^4.3.1", + "kleur": "^4.1.5", + "magic-string": "^0.30.11", + "vitefu": "^0.2.5" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "svelte": "^5.0.0-next.96 || ^5.0.0", + "vite": "^5.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "3.0.0-next.3", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^4.0.0-next.0||^4.0.0", + "svelte": "^5.0.0-next.96 || ^5.0.0", + "vite": "^5.0.0" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "license": "MIT" + }, + "node_modules/@types/eslint": { + "version": "9.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/pug": { + "version": "2.0.10", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.2.0", + "@typescript-eslint/type-utils": "8.2.0", + "@typescript-eslint/utils": "8.2.0", + "@typescript-eslint/visitor-keys": "8.2.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.2.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "8.2.0", + "@typescript-eslint/types": "8.2.0", + "@typescript-eslint/typescript-estree": "8.2.0", + "@typescript-eslint/visitor-keys": "8.2.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.2.0", + "@typescript-eslint/visitor-keys": "8.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.2.0", + "@typescript-eslint/utils": "8.2.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.2.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "8.2.0", + "@typescript-eslint/visitor-keys": "8.2.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.2.0", + "@typescript-eslint/types": "8.2.0", + "@typescript-eslint/typescript-estree": "8.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.2.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-typescript": { + "version": "1.4.13", + "license": "MIT", + "peerDependencies": { + "acorn": ">=8.9.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.0", + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.6.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/debug": { + "version": "4.3.6", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-indent": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.0.0", + "license": "MIT" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/email-addresses": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-5.0.0.tgz", + "integrity": "sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es6-promise": { + "version": "3.3.1", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.9.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.17.1", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.9.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.0.2", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-compat-utils": { + "version": "0.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-plugin-svelte": { + "version": "2.43.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@jridgewell/sourcemap-codec": "^1.4.15", + "eslint-compat-utils": "^0.5.1", + "esutils": "^2.0.3", + "known-css-properties": "^0.34.0", + "postcss": "^8.4.38", + "postcss-load-config": "^3.1.4", + "postcss-safe-parser": "^6.0.0", + "postcss-selector-parser": "^6.1.0", + "semver": "^7.6.2", + "svelte-eslint-parser": "^0.41.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0-0 || ^9.0.0-0", + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.191" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.0.2", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esm-env": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/espree": { + "version": "10.1.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrap": { + "version": "1.2.2", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "@types/estree": "^1.0.1" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.17.1", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "dev": true, + "license": "ISC" + }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/gh-pages": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-6.1.1.tgz", + "integrity": "sha512-upnohfjBwN5hBP9w2dPE7HO5JJTHzSGMV1JrLrHvNuqmjoYHg6TBrCcnEoorjG/e0ejbuvnwyKMdTyM40PEByw==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^3.2.4", + "commander": "^11.0.0", + "email-addresses": "^5.0.0", + "filenamify": "^4.3.0", + "find-cache-dir": "^3.3.1", + "fs-extra": "^11.1.1", + "globby": "^6.1.0" + }, + "bin": { + "gh-pages": "bin/gh-pages.js", + "gh-pages-clean": "bin/gh-pages-clean.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gh-pages/node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gh-pages/node_modules/globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "15.9.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalyzer": { + "version": "0.1.0", + "license": "MIT" + }, + "node_modules/globby": { + "version": "11.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "license": "MIT" + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.1.0", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "dev": true, + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-reference": { + "version": "3.0.2", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/known-css-properties": { + "version": "0.34.0", + "dev": true, + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.11", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.7", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.1", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/postcss": { + "version": "8.4.41", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-safe-parser": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } + }, + "node_modules/postcss-scss": { + "version": "4.0.9", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-scss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.4.29" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/rollup": { + "version": "4.21.0", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.21.0", + "@rollup/rollup-android-arm64": "4.21.0", + "@rollup/rollup-darwin-arm64": "4.21.0", + "@rollup/rollup-darwin-x64": "4.21.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.21.0", + "@rollup/rollup-linux-arm-musleabihf": "4.21.0", + "@rollup/rollup-linux-arm64-gnu": "4.21.0", + "@rollup/rollup-linux-arm64-musl": "4.21.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.21.0", + "@rollup/rollup-linux-riscv64-gnu": "4.21.0", + "@rollup/rollup-linux-s390x-gnu": "4.21.0", + "@rollup/rollup-linux-x64-gnu": "4.21.0", + "@rollup/rollup-linux-x64-musl": "4.21.0", + "@rollup/rollup-win32-arm64-msvc": "4.21.0", + "@rollup/rollup-win32-ia32-msvc": "4.21.0", + "@rollup/rollup-win32-x64-msvc": "4.21.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/sander": { + "version": "0.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "es6-promise": "^3.1.2", + "graceful-fs": "^4.1.3", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.2" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.0", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sirv": { + "version": "2.0.4", + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sorcery": { + "version": "0.11.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.14", + "buffer-crc32": "^1.0.0", + "minimist": "^1.2.0", + "sander": "^0.5.0" + }, + "bin": { + "sorcery": "bin/sorcery" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-outer/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/svelte": { + "version": "5.0.0-next.229", + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@jridgewell/sourcemap-codec": "^1.4.15", + "@types/estree": "^1.0.5", + "acorn": "^8.11.3", + "acorn-typescript": "^1.4.13", + "aria-query": "^5.3.0", + "axobject-query": "^4.0.0", + "esm-env": "^1.0.0", + "esrap": "^1.2.2", + "is-reference": "^3.0.2", + "locate-character": "^3.0.0", + "magic-string": "^0.30.5", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-check": { + "version": "3.8.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "chokidar": "^3.4.1", + "picocolors": "^1.0.0", + "sade": "^1.7.4", + "svelte-preprocess": "^5.1.3", + "typescript": "^5.0.3" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "peerDependencies": { + "svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0" + } + }, + "node_modules/svelte-eslint-parser": { + "version": "0.41.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "postcss": "^8.4.39", + "postcss-scss": "^4.0.9" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.191" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/svelte-eslint-parser/node_modules/eslint-scope": { + "version": "7.2.2", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/svelte-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/svelte-eslint-parser/node_modules/espree": { + "version": "9.6.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/svelte-preprocess": { + "version": "5.1.4", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@types/pug": "^2.0.6", + "detect-indent": "^6.1.0", + "magic-string": "^0.30.5", + "sorcery": "^0.11.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">= 16.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.10.2", + "coffeescript": "^2.5.1", + "less": "^3.11.3 || ^4.0.0", + "postcss": "^7 || ^8", + "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", + "pug": "^3.0.0", + "sass": "^1.26.8", + "stylus": "^0.55.0", + "sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0", + "svelte": "^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0", + "typescript": ">=3.9.5 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "coffeescript": { + "optional": true + }, + "less": { + "optional": true + }, + "postcss": { + "optional": true + }, + "postcss-load-config": { + "optional": true + }, + "pug": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/tiny-glob": { + "version": "0.2.9", + "license": "MIT", + "dependencies": { + "globalyzer": "0.1.0", + "globrex": "^0.1.2" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/trim-repeated/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.5.4", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.2.0", + "@typescript-eslint/parser": "8.2.0", + "@typescript-eslint/utils": "8.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.2", + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.41", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "0.2.5", + "license": "MIT", + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/web-worker": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.3.0.tgz", + "integrity": "sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA==", + "license": "Apache-2.0" + }, + "node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "1.10.2", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zimmerframe": { + "version": "1.1.2", + "license": "MIT" + } + } +} diff --git a/pages/package.json b/pages/package.json new file mode 100644 index 0000000..de245c5 --- /dev/null +++ b/pages/package.json @@ -0,0 +1,34 @@ +{ + "name": "pages", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "deploy-pages": "npm ci && npm run build && gh-pages -b Pages -d build -t true", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "lint": "eslint ." + }, + "devDependencies": { + "@sveltejs/adapter-auto": "^3.0.0", + "@sveltejs/kit": "^2.0.0", + "@sveltejs/vite-plugin-svelte": "^4.0.0-next.6", + "@types/eslint": "^9.6.0", + "eslint": "^9.0.0", + "eslint-plugin-svelte": "^2.36.0", + "gh-pages": "^6.1.1", + "globals": "^15.0.0", + "svelte": "^5.0.0-next.1", + "svelte-check": "^3.6.0", + "typescript": "^5.0.0", + "typescript-eslint": "^8.0.0", + "vite": "^5.0.3" + }, + "type": "module", + "dependencies": { + "@sveltejs/adapter-static": "^3.0.4", + "web-worker": "^1.3.0" + } +} diff --git a/pages/src/app.d.ts b/pages/src/app.d.ts new file mode 100644 index 0000000..743f07b --- /dev/null +++ b/pages/src/app.d.ts @@ -0,0 +1,13 @@ +// See https://kit.svelte.dev/docs/types#app +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/pages/src/assets/svelte.svg b/pages/src/assets/svelte.svg new file mode 100644 index 0000000..c5e0848 --- /dev/null +++ b/pages/src/assets/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pages/src/index.html b/pages/src/index.html new file mode 100644 index 0000000..cf67593 --- /dev/null +++ b/pages/src/index.html @@ -0,0 +1,24 @@ + + + + + + + + + %sveltekit.head% + + + +
%sveltekit.body%
+ + + diff --git a/pages/src/lib/CrossOriginNotIsolated.svelte b/pages/src/lib/CrossOriginNotIsolated.svelte new file mode 100644 index 0000000..50b7964 --- /dev/null +++ b/pages/src/lib/CrossOriginNotIsolated.svelte @@ -0,0 +1,28 @@ +{#if !crossOriginIsolated} +
+
+
+

 Cross-Origin Not Isolated

+

+ Unfortunately, cross-origin isolation requirements are not fulfilled. This means that synchronization + objects cannot be used as they depend on SharedArrayBuffer. +

+

+ Without the possiblity of using SharedArrayBuffer, the only way to cancel a work item + (task) is by cancelling it before it starts. Generally speaking, the following cannot be used: +

+
    +
  • ManualResetEvent
  • +
  • AutoResetEvent
  • +
  • CancellationSource
  • +
+

+ In order to solve this problem in your own project/site, + + read about the security requirements in the MDN website + . +

+
+
+
+{/if} diff --git a/pages/src/lib/Instructions.svelte b/pages/src/lib/Instructions.svelte new file mode 100644 index 0000000..f5863c4 --- /dev/null +++ b/pages/src/lib/Instructions.svelte @@ -0,0 +1,25 @@ +
+

 Instructions

+

+ Play around with the three work items below. Each will run the same example worker, which is a worker that + calculates prime numbers using a not-so-good algorithm that runs up to the shown number. All three work items + use the same worker object, so they run serially between each other. +

+ {#if crossOriginIsolated} +

+ Since cross origin is isolated (see + + the requirements + here), you may pause or cancel the worker at any time. +

+

+ NOTE: Notice how significant the pause wait is in terms of performance. Waiting on the + token is expensive for sure, so do this only on well-thought-out scenarios. +

+ {:else} + Since cross origin is not isolated (see + + the requirements + here), you may only cancel the worker before it starts. Furthermore, you cannot pause the worker. + {/if} +
\ No newline at end of file diff --git a/pages/src/lib/Primes.svelte b/pages/src/lib/Primes.svelte new file mode 100644 index 0000000..1e6ba50 --- /dev/null +++ b/pages/src/lib/Primes.svelte @@ -0,0 +1,150 @@ + + +{#snippet numeric(value: number)} + {numFormatter.format(value)} +{/snippet} + +{#snippet result(variant: string)} +
+ + {@render numeric(maxPrime)} + + + Total primes: {@render numeric(primeCount)} + +
+{/snippet} + +
+
+
Primes to {@render numeric(toNumber)}
+ {#if work?.id} + + ID: {work.id} + + {/if} +
+
+ {#if !work} +

Press Start to discover primes.

+ {:else} + {#await work.promise} + + {@render numeric(maxPrime)} + + {:then} + {@render result('success')} + {:catch reason} + {#if reason instanceof CancelledMessage} + {@render result('danger')} + {:else} +
+

An Error Occurred

+
+ Details +
{reason?.toString()}
+
+
+ {/if} + {/await} + {/if} +
+
    +
  • + + +
  • +
  • + + +
  • +
+
diff --git a/pages/src/lib/nextControlId.ts b/pages/src/lib/nextControlId.ts new file mode 100644 index 0000000..1007b41 --- /dev/null +++ b/pages/src/lib/nextControlId.ts @@ -0,0 +1,5 @@ +let nextId = 0; + +export function nextControlId(prefix?: string) { + return `${prefix ?? 'control'}-${++nextId}`; +}; diff --git a/pages/src/routes/+layout.ts b/pages/src/routes/+layout.ts new file mode 100644 index 0000000..a3d1578 --- /dev/null +++ b/pages/src/routes/+layout.ts @@ -0,0 +1 @@ +export const ssr = false; diff --git a/pages/src/routes/+page.svelte b/pages/src/routes/+page.svelte new file mode 100644 index 0000000..3b2ac1d --- /dev/null +++ b/pages/src/routes/+page.svelte @@ -0,0 +1,34 @@ + +
+
+

+ + Svelte Logo + + Prime Workers +

+ + +
+
+ +
+
+ +
+
+ +
+
+
+
diff --git a/pages/src/workers/exampleWorker.ts b/pages/src/workers/exampleWorker.ts new file mode 100644 index 0000000..a758dd6 --- /dev/null +++ b/pages/src/workers/exampleWorker.ts @@ -0,0 +1,46 @@ +import { CancellationSource, ManualResetEvent, workerListener, type PostFn, type Token } from "../../../dist/index.js"; + +function isPrime(n: number, cancelToken?: Token) { + // Made unecessarily inefficient for demo purposes. + for (let i = 2; i <= n / 2; ++i) { + CancellationSource.throwIfSignaled(cancelToken); + if (n % i === 0) { + return false; + } + } + return true; +} + +function isPrimePausable(n: number, pause: Token, cancelToken?: Token) { + // Made unecessarily inefficient for demo purposes. + for (let i = 2; i <= n / 2; ++i) { + CancellationSource.throwIfSignaled(cancelToken); + ManualResetEvent.wait(pause); + if (n % i === 0) { + return false; + } + } + return true; +} + +function getAllPrimes(max: number, pause: Token | undefined, post: PostFn, cancelToken?: Token) { + let isPrimeFn = !!pause ? (n: number) => isPrimePausable(n, pause, cancelToken) : (n: number) => isPrime(n, cancelToken); + for (let i = 1; i < max; ++i) { + if (isPrimeFn(i)) { + post(i); + } + } +} + +export const exampleWorker = { + sayHello(payload: { name: string; }) { + console.log('Hello, %s!', payload.name); + }, + calculatePrimes(payload: { to: number; pause?: Token }, post: PostFn, cancelToken?: Token) { + getAllPrimes(payload.to, payload.pause, post, cancelToken); + } +}; + +export type ExampleWorker = typeof exampleWorker; + +self.onmessage = workerListener(exampleWorker); diff --git a/pages/static/404.html b/pages/static/404.html new file mode 100644 index 0000000..76104c7 --- /dev/null +++ b/pages/static/404.html @@ -0,0 +1,14 @@ +!DOCTYPE html> + + + + + + + + + + + \ No newline at end of file diff --git a/pages/static/favicon.png b/pages/static/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..825b9e65af7c104cfb07089bb28659393b4f2097 GIT binary patch literal 1571 zcmV+;2Hg3HP)Px)-AP12RCwC$UE6KzI1p6{F2N z1VK2vi|pOpn{~#djwYcWXTI_im_u^TJgMZ4JMOsSj!0ma>B?-(Hr@X&W@|R-$}W@Z zgj#$x=!~7LGqHW?IO8+*oE1MyDp!G=L0#^lUx?;!fXv@l^6SvTnf^ac{5OurzC#ZMYc20lI%HhX816AYVs1T3heS1*WaWH z%;x>)-J}YB5#CLzU@GBR6sXYrD>Vw(Fmt#|JP;+}<#6b63Ike{Fuo!?M{yEffez;| zp!PfsuaC)>h>-AdbnwN13g*1LowNjT5?+lFVd#9$!8Z9HA|$*6dQ8EHLu}U|obW6f z2%uGv?vr=KNq7YYa2Roj;|zooo<)lf=&2yxM@e`kM$CmCR#x>gI>I|*Ubr({5Y^rb zghxQU22N}F51}^yfDSt786oMTc!W&V;d?76)9KXX1 z+6Okem(d}YXmmOiZq$!IPk5t8nnS{%?+vDFz3BevmFNgpIod~R{>@#@5x9zJKEHLHv!gHeK~n)Ld!M8DB|Kfe%~123&Hz1Z(86nU7*G5chmyDe ziV7$pB7pJ=96hpxHv9rCR29%bLOXlKU<_13_M8x)6;P8E1Kz6G<&P?$P^%c!M5`2` zfY2zg;VK5~^>TJGQzc+33-n~gKt{{of8GzUkWmU110IgI0DLxRIM>0US|TsM=L|@F z0Bun8U!cRB7-2apz=y-7*UxOxz@Z0)@QM)9wSGki1AZ38ceG7Q72z5`i;i=J`ILzL z@iUO?SBBG-0cQuo+an4TsLy-g-x;8P4UVwk|D8{W@U1Zi z!M)+jqy@nQ$p?5tsHp-6J304Q={v-B>66$P0IDx&YT(`IcZ~bZfmn11#rXd7<5s}y zBi9eim&zQc0Dk|2>$bs0PnLmDfMP5lcXRY&cvJ=zKxI^f0%-d$tD!`LBf9^jMSYUA zI8U?CWdY@}cRq6{5~y+)#h1!*-HcGW@+gZ4B};0OnC~`xQOyH19z*TA!!BJ%9s0V3F?CAJ{hTd#*tf+ur-W9MOURF-@B77_-OshsY}6 zOXRY=5%C^*26z?l)1=$bz30!so5tfABdSYzO+H=CpV~aaUefmjvfZ3Ttu9W&W3Iu6 zROlh0MFA5h;my}8lB0tAV-Rvc2Zs_CCSJnx@d`**$idgy-iMob4dJWWw|21b4NB=LfsYp0Aeh{Ov)yztQi;eL4y5 zMi>8^SzKqk8~k?UiQK^^-5d8c%bV?$F8%X~czyiaKCI2=UH { + server.middlewares.use((_req, res, next) => { + res.setHeader("Cross-Origin-Embedder-Policy", "require-corp"); + res.setHeader("Cross-Origin-Opener-Policy", "same-origin"); + next(); + }); + }, + }] +}); diff --git a/src/cancellation/CancellationSource.ts b/src/cancellation/CancellationSource.ts new file mode 100644 index 0000000..a2befc3 --- /dev/null +++ b/src/cancellation/CancellationSource.ts @@ -0,0 +1,28 @@ +import { ManualResetEvent } from "../events/ManualResetEvent.js"; +import type { Token } from "../workers.js"; +import { TaskCancelledError } from "./TaskCancelledError.js"; + +/** + * Specialized synchronization event object for the purposes of signalling cancellation intent. + */ +export class CancellationSource extends ManualResetEvent { + /** + * Do not call. Cancellation tokens cannot be reset. + */ + reset(): void { + throw new Error("Cancellation tokens cannot be reset."); + } + /** + * Checks the given cancellation token and throws an instance of `TaskCancelledError` if the token is in its + * signalled state. + * @param token Cancellation token to check. + */ + static throwIfSignalled(token: Token | undefined) { + if (!token) { + return; + } + if (this.isSignalled(token)) { + throw new TaskCancelledError(); + } + } +}; diff --git a/src/cancellation/CancelledMessage.ts b/src/cancellation/CancelledMessage.ts new file mode 100644 index 0000000..07865e3 --- /dev/null +++ b/src/cancellation/CancelledMessage.ts @@ -0,0 +1,15 @@ +/** + * Special message that is used to reject work item promises whenever the work item is cancelled. + */ +export class CancelledMessage { + #cancelledBeforeStarted; + constructor(cancelledBeforeStarted: boolean) { + this.#cancelledBeforeStarted = cancelledBeforeStarted; + } + /** + * Returns `true` if cancellation happened before the work item started, or `false` otherwise. + */ + get cancelledBeforeStarted() { + return this.#cancelledBeforeStarted; + } +}; diff --git a/src/cancellation/TaskCancelledError.ts b/src/cancellation/TaskCancelledError.ts new file mode 100644 index 0000000..5113967 --- /dev/null +++ b/src/cancellation/TaskCancelledError.ts @@ -0,0 +1,8 @@ +/** + * Error class used by `CancellationSource.throwIfSignalled` to abort a worker thread's current work. + */ +export class TaskCancelledError extends Error { + constructor(message?: string, options?: ErrorOptions) { + super(message, options); + } +}; diff --git a/src/events/AutoResetEvent.ts b/src/events/AutoResetEvent.ts new file mode 100644 index 0000000..8094f3b --- /dev/null +++ b/src/events/AutoResetEvent.ts @@ -0,0 +1,77 @@ +import type { Token } from "../workers.js"; +import { checkToken, Event } from "./Event.js"; + +const identifier = 0x02; +const checkData = [ + identifier, + 'automatically-reset event', + 'an' +] as const; + +/** + * Synchronization event object that automatically resets whenever a worker thread is unlocked by it. + * + * It is useful to ensure that only one thread at a time runs at any given time. + */ +export class AutoResetEvent extends Event { + constructor() { + super(identifier); + } + + /** + * Signals the event, unblocking at most one blocked thread. + */ + signal(): void { + super.signal(); + Atomics.notify(super.token, 0, 1); + } + /** + * Checks whether or not an auto-resettable event's token is in its signalled state. + * + * Auto-reset events automatically reset when a thread is freed by it, so expect this function to only return + * `true` if the token has been signalled and there were no threads blocked by it. + * @param token Auto-resettable token to check. + * @returns `true` if the token is signalled, or `false` otherwise. + */ + static isSignalled(token: Token) { + checkToken(token, ...checkData); + return Atomics.load(token, 0) === 1; + } + /** + * Waits on the specified auto-resettable token to be signalled. + * + * Use this method to block a worker's thread for whatever reason. + * @param token Auto-reset token to wait on. + * @param timeout Maximum time to wait on the token. Don't specify a value to wait indefinitely. + * @returns `'ok'` when the waiting is over because the token signalled, `timed-out` when the specified timeout + * elapsed and the token did not signal, or `not-equal` if no wait took place (which should not happen for + * auto-reset events). + */ + static wait(token: Token, timeout?: number) { + checkToken(token, ...checkData); + const result = Atomics.wait(token, 0, 0, timeout); + if (result === 'ok') { + Atomics.store(token, 0, 0); + } + return result; + } + /** + * Asynchronously waits on the specified auto-reset token to be signalled. + * + * Use this method to stop the current work and release the worker thread (to pick up on new messages, perhaps). + * @param token Auto-reset token to wait on. + * @param timeout Maximum time to wait on the token. Don't specify a value to wait indefinitely. + * @returns `'ok'` when the waiting is over because the token signalled, `timed-out` when the specified timeout + * elapsed and the token did not signal, or `not-equal` if no wait took place (which should not happen for + * auto-reset events). + */ + static async waitAsync(token: Token, timeout?: number) { + checkToken(token, ...checkData); + const result = Atomics.waitAsync(token, 0, 0, timeout); + const finalResult = result.async ? await result.value : result.value; + if (finalResult !== 'timed-out') { + Atomics.store(token, 0, 0); + } + return finalResult; + } +}; diff --git a/src/events/Event.ts b/src/events/Event.ts new file mode 100644 index 0000000..8bf1ec9 --- /dev/null +++ b/src/events/Event.ts @@ -0,0 +1,41 @@ +import type { Token } from "../workers"; + +export class Event { + #buffer; + #array; + #identifier; + constructor(identifier: number) { + if (!crossOriginIsolated) { + throw new Error('Cannot operate: Cross origin is not isolated. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer#security_requirements for details.'); + } + this.#buffer = new SharedArrayBuffer(8); + this.#array = new Int32Array(this.#buffer); + this.#identifier = identifier; + Atomics.store(this.#array, 1, identifier); + } + /** + * Gets the synchronization event's token. + */ + get token(): Token { + return this.#array; + } + /** + * Signals the event. + */ + signal() { + Atomics.store(this.#array, 0, 1); + } +}; + +/** + * Ensures the given token is of the expected type by throwing an error if this is not the case. + * @param token Token to check for. + * @param identifier Token type identifier. + * @param objectName Object name, used for constructing the error's message. + * @param article Article for the object name, so the error's message is written in proper English. + */ +export function checkToken(token: Token, identifier: number, objectName: string, article: string) { + if (Atomics.load(token, 1) !== identifier) { + throw new Error(`The provided token is not that of ${article} ${objectName}.`); + } +} diff --git a/src/events/ManualResetEvent.ts b/src/events/ManualResetEvent.ts new file mode 100644 index 0000000..613fe9e --- /dev/null +++ b/src/events/ManualResetEvent.ts @@ -0,0 +1,71 @@ +import type { Token } from "../workers.js"; +import { checkToken, Event } from "./Event.js"; + +const identifier = 0x01; +const checkData = [ + identifier, + 'manually-reset event', + 'a' +] as const; + +/** + * Synchronization event object that can be signalled on-demand whenever it is appropriate. + * + * It is useful in cases where external control is the priority, such as pausing work. + */ +export class ManualResetEvent extends Event { + constructor() { + super(identifier); + } + /** + * Signals the event, unblocking all threads that are waiting on it. Use `reset()` to revert the signalled state. + */ + signal(): void { + super.signal(); + Atomics.notify(super.token, 0); + } + /** + * Resets the token. + */ + reset() { + Atomics.store(super.token, 0, 0); + } + /** + * Checks whether or not a manually-resettable event's token is in its signalled state. + * + * This method may be used by worker threads in polling mode. + * @param token Manually-resettable token to check. + * @returns `true` if the token is signalled, or `false` otherwise. + */ + static isSignalled(token: Token) { + checkToken(token, ...checkData); + return Atomics.load(token, 0) === 1; + } + /** + * Waits on the specified manually-resettable token to be signalled. + * + * Use this method to block a worker's thread for whatever reason. + * @param token Manually-resettable token to wait on. + * @param timeout Maximum time to wait on the token. Don't specify a value to wait indefinitely. + * @returns `'ok'` when the waiting is over because the token signalled, `timed-out` when the specified timeout + * elapsed and the token did not signal, or `not-equal` if no wait took place. + */ + static wait(token: Token, timeout?: number) { + checkToken(token, ...checkData); + return Atomics.wait(token, 0, 0, timeout); + } + /** + * Asynchronously waits on the specified manually-resettable token to be signalled. + * + * Use this method to stop the current work and release the worker thread (to pick up on new messages, perhaps). + * @param token Manually-resettable token to wait on. + * @param timeout Maximum time to wait on the token. Don't specify a value to wait indefinitely. + * @returns `'ok'` when the waiting is over because the token signalled, `timed-out` when the specified timeout + * elapsed and the token did not signal, or `not-equal` if no wait took place. + */ + static async waitAsync(token: Token, timeout?: number) { + checkToken(token, ...checkData); + const result = Atomics.waitAsync(token, 0, 0, timeout); + return result.async ? await result.value : result.value; + } +}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..4b345d4 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,41 @@ +// cancellation +export * from "./cancellation/CancellationSource.js"; +export * from "./cancellation/CancelledMessage.js"; +export * from "./cancellation/TaskCancelledError.js"; +// events +export * from "./events/AutoResetEvent.js"; +export * from "./events/ManualResetEvent.js"; +// workers +export * from './workers/AsyncWorker.js'; +export * from "./workers/workerListener.js"; +export * from './workers/WorkerTerminatedMessage.js'; +export * from "./workers/WorkItem.js"; + +/** + * Token primitive type. + */ +export type Token = Int32Array; + +/** + * Helper type that enables proper inference of payload and return types. + * + * Used by the async workers to provide proper Intellisense. + */ +export type WorkerTasks any>> = { + [K in keyof T]: { + payload: Parameters[0]; + return: ReturnType; + } +}; + +/** + * Message sent to a worker. + */ +export type AsyncMessage any>> = { + task: keyof Tasks; + workItemId: number; + cancelToken?: Token; + payload?: WorkerTasks[keyof Tasks]['payload']; +}; + +export type ProcessMessageFn = (payload: any, cancelToken?: Token) => boolean; diff --git a/src/misc/Queue.ts b/src/misc/Queue.ts new file mode 100644 index 0000000..42c0b9d --- /dev/null +++ b/src/misc/Queue.ts @@ -0,0 +1,40 @@ +export class Queue { + #items: Record; + #head: number; + #tail: number; + + constructor() { + this.#items = {}; + this.#head = 0; + this.#tail = 0; + } + + get length() { + return this.#tail - this.#head; + } + + get isEmpty() { + return this.length === 0; + } + + enqueue(item: TItem) { + this.#items[this.#tail] = item; + return this.#tail++; + } + + dequeue() { + if (this.isEmpty) { + throw new Error("Cannot dequeue from an empty queue."); + } + const item = this.#items[this.#head]; + delete this.#items[this.#head++]; + return item; + } + + peek() { + if (this.isEmpty) { + throw new Error("Cannot peek on an empty queue."); + } + return this.#items[this.#head]; + } +}; diff --git a/src/misc/nextWorkItemId.ts b/src/misc/nextWorkItemId.ts new file mode 100644 index 0000000..1ab0fc0 --- /dev/null +++ b/src/misc/nextWorkItemId.ts @@ -0,0 +1,5 @@ +let nextId = 0; + +export function nextWorkItemId() { + return ++nextId; +}; diff --git a/src/workers.d.ts b/src/workers.d.ts new file mode 100644 index 0000000..e3b65a2 --- /dev/null +++ b/src/workers.d.ts @@ -0,0 +1,67 @@ + +/** + * Token primitive type. + */ +export type Token = Int32Array; + +/** + * Helper type that enables proper inference of payload and return types. + * + * Used by the async workers to provide proper Intellisense. + */ +export type WorkerTasks any>> = { + [K in keyof T]: { + payload: Parameters[0]; + return: ReturnType; + } +}; + +/** + * Message sent to a worker. + */ +export type AsyncMessage = { + workItemId: number; + task: string; + cancelToken?: Token; + payload?: any; +}; + +export type AsyncResponse = { + workItemId: number; + payload?: any; +}; + +export type ProcessMessageFn = (payload: any) => boolean; + +export type WorkItemData = { + id: number; + task: string; + promise: Promise; + resolve: (result: any) => void; + reject: (reason: any) => void; + payload: any, +}; + +export type TaskCancelledMessage = { + workItemId: number; + payload: { + _$cancelled: true; + } +}; + +export type QueueingOptions = { + cancellable?: boolean; + processMessage?: ProcessMessageFn; + transferables?: Transferable[]; + outOfOrder?: boolean; +} + +export interface IWorker { + connect(id: number, processMessage: ProcessMessageFn, resolve: (data: any) => void, reject: (reason: any) => void): DisconnectFn; + post(message: AsyncMessage, transferables: Transferable[] | undefined): void; + terminate(): boolean; +} + +export type DisconnectFn = () => void; + +export type RejectFn = (reason: any) => void; diff --git a/src/workers/AsyncWorker.ts b/src/workers/AsyncWorker.ts new file mode 100644 index 0000000..1ae6476 --- /dev/null +++ b/src/workers/AsyncWorker.ts @@ -0,0 +1,131 @@ +import { Queue } from "../misc/Queue.js"; +import { nextWorkItemId } from "../misc/nextWorkItemId.js"; +import type { IWorker, QueueingOptions, RejectFn, WorkerTasks, WorkItemData } from "../workers.js"; +import { InternalSharedWorker } from "./InternalSharedWorker.js"; +import { InternalWorker } from "./InternalWorker.js"; +import { WorkItem } from "./WorkItem.js"; +import { WorkItemInternal } from "./WorkItemInternal.js"; + +type EnqueueFn any> = (payload: Parameters[0], options?: QueueingOptions) => WorkItem>; + +type Enqueue any>> = { + [K in keyof T]: EnqueueFn; +}; + +/** + * Defines all possible work item status values. + */ +export const WorkItemStatus = Object.freeze({ + /** + * Initial work item status. This status value will not be seen in work items that immediately get transmitted to + * the worker (such as out-of-order work items). + */ + Enqueued: 0, + /** + * The work item has started. In this context, "started" means that the message that starts the worker's task has + * been transmitted. Whether or not the actual work has started is unknown. + */ + Started: 1, + /** + * The work item has been cancelled using its `cancel()` method. + */ + Cancelled: 2, + /** + * The work item has been terminated because the worker was terminated using the `terminate()` method. + */ + Terminated: 3, + /** + * The work item has completed and its promise has been fulfilled (resolved or rejected). + */ + Completed: 4, +}); + +/** + * Defines the type of the work item status values. + */ +export type WorkItemStatusEnum = typeof WorkItemStatus[keyof typeof WorkItemStatus]; + +/** + * Abstracts a worker (dedicated or shared) and provides asynchronous syntax to control its functionality. + */ +export class AsyncWorker any>> { + #iWorker: IWorker; + #queue; + #taskRunning; + #enqueue; + constructor(worker: Worker | SharedWorker, tasks: Tasks) { + this.#iWorker = worker instanceof Worker ? new InternalWorker(worker) : new InternalSharedWorker(worker); + this.#queue = new Queue(); + this.#taskRunning = false; + this.#enqueue = Object.keys(tasks).reduce((prev, curr) => { + prev[curr as keyof Tasks] = this.#enqueueTask.bind(this, curr); + return prev; + }, {} as Enqueue); + } + + #createTaskPromise() { + let resolve: (data: T | PromiseLike) => void; + let reject: RejectFn; + const promise = new Promise((rslv, rjct) => { + resolve = rslv; + reject = rjct; + }); + return { + resolve: resolve!, + reject: reject!, + promise + }; + } + /** + * Returns the object used to enqueue work items. + */ + get enqueue() { + return this.#enqueue; + } + + #enqueueTask>(task: K, payload: WorkerTasks[K]['payload'], options?: QueueingOptions) { + const workItemId = nextWorkItemId(); + const { resolve, reject, promise } = this.#createTaskPromise[K]["return"]>(); + const workItemData = { + id: workItemId, + task: task as string, + promise, + resolve, + reject, + payload + } satisfies WorkItemData[K]["return"]>; + const workItem = new WorkItemInternal(this.#iWorker, workItemData, options); + if (this.#taskRunning && !options?.outOfOrder) { + this.#queue.enqueue(workItem); + } + else { + if (!options?.outOfOrder) { + this.#taskRunning = true; + promise.finally(this.#startNextWorkItem.bind(this)); + } + workItem.start(); + } + return new WorkItem(workItem); + } + + #startNextWorkItem() { + if (!this.#queue.isEmpty) { + const nextWorkItem = this.#queue.dequeue(); + nextWorkItem.data.promise.finally(this.#startNextWorkItem.bind(this)); + this.#taskRunning = true; + nextWorkItem.start(); + } + else { + this.#taskRunning = false; + } + } + /** + * Terminates the underlying web worker. Note that this is only possible for dedicated workers. If an attempt to + * terminate a shared worker is tried, an error will be thrown. + * @returns `true` if the worker is terminated and all pending work items' promises have been rejected, or `false` + * if the worker had already been terminated and therefore no further action took place. + */ + terminate() { + return this.#iWorker.terminate(); + } +}; diff --git a/src/workers/InternalSharedWorker.ts b/src/workers/InternalSharedWorker.ts new file mode 100644 index 0000000..f86f62e --- /dev/null +++ b/src/workers/InternalSharedWorker.ts @@ -0,0 +1,61 @@ +import { CancelledMessage } from "../cancellation/CancelledMessage.js"; +import type { AsyncMessage, AsyncResponse, DisconnectFn, IWorker, ProcessMessageFn, RejectFn, TaskCancelledMessage } from "../workers.js"; + +function isTaskCancelledMessage(message: any): message is TaskCancelledMessage { + return message?.payload?._$cancelled === true && typeof message.workItemId === 'number'; +} + +function isAsyncResponse(message: any): message is AsyncResponse { + return typeof message.workItemId === 'number'; +} + +export class InternalSharedWorker implements IWorker { + #worker; + #rejectMap; + constructor(worker: SharedWorker) { + this.#worker = worker; + this.#rejectMap = new Map(); + } + + #listenerFactory(id: number, processMessage: ProcessMessageFn, resolve: (data:any) => void) { + return (ev: MessageEvent) => { + if (isTaskCancelledMessage(ev.data) && ev.data.workItemId === id) { + console.log('Received a cancellation: %o', ev.data); + this.#rejectMap.get(id)?.(new CancelledMessage(false)); + } else if (isAsyncResponse(ev.data) && (ev.data.workItemId === id)) { + if (processMessage(ev.data.payload)) { + resolve(ev.data.payload); + } + } + else { + console.warn('Work item ID %d: Unidentified message event: %o', id, ev); + } + }; + } + + connect( + id: number, + processMessage: ProcessMessageFn, + resolve: (data: any) => void, + reject: (reason: any) => void + ): DisconnectFn { + const listener = this.#listenerFactory(id, processMessage, resolve); + this.#worker.port.addEventListener('message', listener); + this.#worker.port.start(); + this.#worker.addEventListener('error', reject); + this.#rejectMap.set(id, reject); + return () => { + this.#worker.removeEventListener('error', reject); + this.#worker.port.removeEventListener('message', listener); + this.#rejectMap.delete(id); + }; + } + + post(message: AsyncMessage, transferables: Transferable[] | undefined): void { + this.#worker.port.postMessage(message, { transfer: transferables }); + } + + terminate(): boolean { + throw new Error('Shared workers cannot be terminated.'); + } +} diff --git a/src/workers/InternalWorker.ts b/src/workers/InternalWorker.ts new file mode 100644 index 0000000..f8be109 --- /dev/null +++ b/src/workers/InternalWorker.ts @@ -0,0 +1,71 @@ +import { CancelledMessage } from "../cancellation/CancelledMessage.js"; +import type { AsyncMessage, AsyncResponse, DisconnectFn, IWorker, ProcessMessageFn, RejectFn, TaskCancelledMessage } from "../workers.js"; +import { WorkerTerminatedMessage } from "./WorkerTerminatedMessage.js"; + +function isTaskCancelledMessage(message: any): message is TaskCancelledMessage { + return message?.payload?._$cancelled === true && typeof message.workItemId === 'number'; +} + +function isAsyncResponse(message: any): message is AsyncResponse { + return typeof message.workItemId === 'number'; +} + +export class InternalWorker implements IWorker { + #worker; + #terminated; + #rejectMap; + constructor(worker: Worker) { + this.#worker = worker; + this.#terminated = false; + this.#rejectMap = new Map(); + } + + #listenerFactory(id: number, processMessage: ProcessMessageFn, resolve: (data:any) => void) { + return (ev: MessageEvent) => { + if (isTaskCancelledMessage(ev.data) && ev.data.workItemId === id) { + console.log('Received a cancellation: %o', ev.data); + this.#rejectMap.get(id)?.(new CancelledMessage(false)); + } else if (isAsyncResponse(ev.data) && (ev.data.workItemId === id)) { + if (processMessage(ev.data.payload)) { + resolve(ev.data.payload); + } + } + else { + console.warn('Work item ID %d: Unidentified message event: %o', id, ev); + } + }; + } + + connect(id: number, processMessage: ProcessMessageFn, resolve: (data: any) => void, reject: (reason: any) => void): DisconnectFn { + if (this.#terminated) { + reject(new WorkerTerminatedMessage(id)); + } + const listener = this.#listenerFactory(id, processMessage, resolve); + this.#worker.addEventListener('message', listener); + this.#worker.addEventListener('error', reject); + this.#rejectMap.set(id, reject); + return () => { + this.#worker.removeEventListener('error', reject); + this.#worker.removeEventListener('message', listener); + this.#rejectMap.delete(id); + }; + } + + post(message: AsyncMessage, transferables: Transferable[] | undefined): void { + if (this.#terminated) { + throw new Error('The worker has been terminated and cannot accept new messages.'); + } + this.#worker.postMessage(message, { transfer: transferables }); + } + + terminate() { + if (this.#terminated) { + return false; + } + this.#worker.terminate(); + for (let [id, r] of this.#rejectMap.entries()) { + r(new WorkerTerminatedMessage(id)); + } + return this.#terminated = true; + } +} diff --git a/src/workers/WorkItem.ts b/src/workers/WorkItem.ts new file mode 100644 index 0000000..08db87c --- /dev/null +++ b/src/workers/WorkItem.ts @@ -0,0 +1,48 @@ +import { CancelledMessage } from "../cancellation/CancelledMessage"; +import { WorkItemStatus } from "./AsyncWorker"; +import { WorkItemInternal } from "./WorkItemInternal"; + +/** + * Defines a work item object, which is the abstract representation of a worker's task. + */ +export class WorkItem { + #internal; + constructor(internal: WorkItemInternal) { + this.#internal = internal; + } + /** + * Gets the promise object that will resolve or reject once the work item completes, the work item is cancelled or + * the underlying web worker is terminated. + */ + get promise() { + return this.#internal.data.promise; + } + /** + * Gets the work item's assigned identifier. Useful for tracing and debugging. + */ + get id() { + return this.#internal.data.id; + } + /** + * Gets the work item's current status. + */ + get status() { + return this.#internal.status; + } + /** + * Attempts to cancel the work item. A work item can only be cancelled if: + * + * + It was queued using the `cancellable` queueing option. + * + Its current status is `Enqueued`, meaning the work item hasn't started. + * @returns `true` if any form of cancellation took place, or `false` otherwise. + */ + cancel() { + if (this.#internal.cancellationSource) { + this.#internal.cancellationSource.signal(); + } + if (this.status === WorkItemStatus.Enqueued) { + this.#internal.data.reject(new CancelledMessage(true)); + } + return this.#internal.cancellationSource || !this.#internal.disconnect; + } +} diff --git a/src/workers/WorkItemInternal.ts b/src/workers/WorkItemInternal.ts new file mode 100644 index 0000000..498abf7 --- /dev/null +++ b/src/workers/WorkItemInternal.ts @@ -0,0 +1,66 @@ +import { CancellationSource } from "../cancellation/CancellationSource.js"; +import { CancelledMessage } from "../cancellation/CancelledMessage.js"; +import type { AsyncMessage, DisconnectFn, IWorker, QueueingOptions, WorkItemData } from "../workers.js"; +import { WorkItemStatus, type WorkItemStatusEnum } from "./AsyncWorker.js"; +import { WorkerTerminatedMessage } from "./WorkerTerminatedMessage"; + +export class WorkItemInternal { + worker; + data; + options; + cancellationSource; + status: WorkItemStatusEnum; + disconnect: DisconnectFn | undefined; + + constructor(worker: IWorker, data: WorkItemData, options: QueueingOptions | undefined) { + this.status = WorkItemStatus.Enqueued; + this.worker = worker; + this.data = { + ...data, + resolve: (x: TResult) => { + data.resolve(x); + this.disconnect?.(); + this.status = WorkItemStatus.Completed; + }, + reject: (reason: any) => { + data.reject(reason); + if (reason instanceof CancelledMessage) { + this.status = WorkItemStatus.Cancelled; + } + else if (reason instanceof WorkerTerminatedMessage) { + this.status = WorkItemStatus.Terminated; + } + else { + this.status = WorkItemStatus.Completed; + } + this.disconnect?.(); + } + }; + this.options = options; + this.cancellationSource = options?.cancellable ? new CancellationSource() : undefined; + } + + #defaultProcessMessage(_payload: any) { + return true; + } + + start() { + if (this.cancellationSource && CancellationSource.isSignalled(this.cancellationSource.token)) { + return; + } + this.disconnect = this.worker.connect( + this.data.id, + this.options?.processMessage ?? this.#defaultProcessMessage.bind(this), + this.data.resolve, + this.data.reject + ); + this.status = WorkItemStatus.Started; + const wiPayload: AsyncMessage = { + workItemId: this.data.id, + task: this.data.task, + cancelToken: this.cancellationSource?.token, + payload: this.data.payload, + }; + this.worker.post(wiPayload, this.options?.transferables); + } +}; diff --git a/src/workers/WorkerTerminatedMessage.ts b/src/workers/WorkerTerminatedMessage.ts new file mode 100644 index 0000000..1bbe073 --- /dev/null +++ b/src/workers/WorkerTerminatedMessage.ts @@ -0,0 +1,15 @@ +/** + * Special message used to reject promises of workers that are terminated. + */ +export class WorkerTerminatedMessage { + #id; + constructor(id: number) { + this.#id = id; + } + /** + * Gets the work item's identifier the message was created for. + */ + get id() { + return this.#id; + } +}; diff --git a/src/workers/workerListener.ts b/src/workers/workerListener.ts new file mode 100644 index 0000000..7138f9a --- /dev/null +++ b/src/workers/workerListener.ts @@ -0,0 +1,46 @@ +import { TaskCancelledError } from "../cancellation/TaskCancelledError.js"; +import { AsyncMessage, AsyncResponse } from "../workers.js"; + +/** + * Defines the function provided to worker tasks so workers can communicate back to the calling thread. + */ +export type PostFn = (payload: any, options?: WindowPostMessageOptions) => void; + +function isAsyncMessage(msg: any): msg is AsyncMessage { + return typeof msg.workItemId === 'number' && msg.task && typeof msg.task === 'string'; +} + +function post(workItemId: number, payload: any, options?: WindowPostMessageOptions) { + self.postMessage({ + workItemId, + payload + } satisfies AsyncResponse, options); +} + +/** + * Creates a listener function specialized for the given worker tasks object. The listener is then assigned to the web + * worker's `onmessage` property. + * @param workerTasks Worker tasks object that define the web worker. + * @returns The sought listener. + */ +export function workerListener any>>(workerTasks: Tasks) { + return (ev: MessageEvent) => { + if (!isAsyncMessage(ev.data)) { + return; + } + const taskFn = workerTasks[ev.data.task]; + if (typeof taskFn !== 'function') { + console.warn('Async message with task "$s" yields no task function.'); + return; + } + try { + const result = taskFn(ev.data.payload, post.bind(null, ev.data.workItemId), ev.data.cancelToken); + post(ev.data.workItemId, result); + } + catch (error) { + if (error instanceof TaskCancelledError) { + post(ev.data.workItemId, { _$cancelled: true }); + } + } + }; +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..19d954f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,115 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "ES2023", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "ESNext", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": false /* Skip type checking all .d.ts files. */ + }, + "exclude": [ + "src/**/*.test.ts", + "src/**/*.spec.ts" + ], + "include": [ + "src/**/*.ts" + ] +}