parent
c46f2f2fc3
commit
6c7facadce
1831 changed files with 477243 additions and 2607 deletions
11
.env.example
11
.env.example
|
|
@ -1,7 +1,12 @@
|
||||||
TWITCH_CLIENT_ID=your_client_id
|
TWITCH_CLIENT_ID=your_client_id
|
||||||
TWITCH_CLIENT_SECRET=your_client_secret
|
TWITCH_CLIENT_SECRET=your_client_secret
|
||||||
TWITCH_REDIRECT_URI=http://localhost:8000/auth/callback
|
|
||||||
|
# Local dev:
|
||||||
|
# TWITCH_REDIRECT_URI=http://localhost:3000/auth/callback
|
||||||
|
# Production:
|
||||||
|
TWITCH_REDIRECT_URI=https://overlay.bashynx.com/auth/callback
|
||||||
|
|
||||||
SECRET_KEY=change_this_to_a_random_string
|
SECRET_KEY=change_this_to_a_random_string
|
||||||
APP_BASE_URL=http://localhost:8000
|
APP_BASE_URL=https://overlay.bashynx.com
|
||||||
DATABASE_URL=sqlite:///./bashyoverlay.db
|
DB_PATH=./bashyoverlay.db
|
||||||
|
PORT=3000
|
||||||
|
|
|
||||||
36
.github/workflows/ci.yml
vendored
36
.github/workflows/ci.yml
vendored
|
|
@ -13,38 +13,24 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
python-version: '3.11'
|
node-version: '20'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: npm ci
|
||||||
python -m pip install --upgrade pip
|
|
||||||
pip install -r requirements.txt
|
|
||||||
|
|
||||||
- name: Syntax check
|
- name: Syntax check
|
||||||
run: |
|
run: node --check src/app.js src/server.js src/db.js src/eventsub.js src/dispatch.js src/ws.js src/cooldowns.js src/routers/*.js
|
||||||
python3 -c "
|
|
||||||
import ast, sys, pathlib
|
|
||||||
errors = []
|
|
||||||
for f in pathlib.Path('app').rglob('*.py'):
|
|
||||||
try:
|
|
||||||
ast.parse(f.read_text())
|
|
||||||
except SyntaxError as e:
|
|
||||||
errors.append(f'{f}: {e}')
|
|
||||||
if errors:
|
|
||||||
for e in errors: print('SYNTAX ERROR:', e)
|
|
||||||
sys.exit(1)
|
|
||||||
files = list(pathlib.Path('app').rglob('*.py'))
|
|
||||||
print(f'All {len(files)} Python files OK')
|
|
||||||
"
|
|
||||||
|
|
||||||
- name: Import check
|
- name: Import check
|
||||||
run: python -c "from app.main import app; print('App import OK')"
|
run: node -e "require('./src/app')"
|
||||||
env:
|
env:
|
||||||
DATABASE_URL: sqlite:///./test.db
|
|
||||||
SECRET_KEY: ci-test-secret-not-real
|
|
||||||
TWITCH_CLIENT_ID: ci-placeholder
|
TWITCH_CLIENT_ID: ci-placeholder
|
||||||
TWITCH_CLIENT_SECRET: ci-placeholder
|
TWITCH_CLIENT_SECRET: ci-placeholder
|
||||||
TWITCH_REDIRECT_URI: http://localhost:8000/auth/callback
|
TWITCH_REDIRECT_URI: http://localhost:3000/auth/callback
|
||||||
APP_BASE_URL: http://localhost:8000
|
SECRET_KEY: ci-test-secret-not-real
|
||||||
|
APP_BASE_URL: http://localhost:3000
|
||||||
|
DB_PATH: ./test.db
|
||||||
|
PORT: 3000
|
||||||
|
|
|
||||||
224
CLAUDE.md
224
CLAUDE.md
|
|
@ -3,128 +3,214 @@
|
||||||
## Running the app
|
## Running the app
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
source .venv/bin/activate
|
npm install
|
||||||
uvicorn app.main:app --reload
|
npm run dev # development (nodemon)
|
||||||
|
npm start # production
|
||||||
```
|
```
|
||||||
|
|
||||||
Requires `.env` — copy from `.env.example`. Database is created automatically on startup (`SQLModel.metadata.create_all`).
|
Requires `.env` — copy from `.env.example`. Database is created automatically on startup (better-sqlite3 runs migrations on init).
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
- **Node.js** · Express · EJS templates
|
||||||
|
- **better-sqlite3** — SQLite, synchronous, simple
|
||||||
|
- **ws** — WebSocket server for pushing alerts to overlay clients
|
||||||
|
- **tmi.js** — Twitch IRC, runs in the browser on the overlay page (no server-side IRC)
|
||||||
|
- **multer** — file uploads
|
||||||
|
- **uuid** — UUID generation
|
||||||
|
- **dotenv** — env vars
|
||||||
|
- **express-session** — session management (signed cookie)
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
```
|
```
|
||||||
Twitch ──EventSub WebSocket──► app/eventsub.py ──► dispatch.py ──► ws.py ──► OBS overlay
|
Twitch ──EventSub WebSocket──► src/eventsub.js ──► dispatch.js ──► ws.js ──► OBS overlay
|
||||||
Twitch ──IRC (tmi.js)────────────────────────────────────────────────────► OBS overlay
|
Twitch ──IRC (tmi.js in browser) ────────────────────────────────────────► OBS overlay
|
||||||
```
|
```
|
||||||
|
|
||||||
- `eventsub.py` runs as an asyncio background task started in `main.py` lifespan
|
- `eventsub.js` opens a WebSocket to Twitch on startup, managed as a module-level singleton
|
||||||
- When it receives a notification, it calls `dispatch.dispatch_event()`
|
- On notification it calls `dispatch.dispatchEvent()`
|
||||||
- `dispatch_event()` opens its own DB session (not request-scoped), resolves matching `EventAction` rows, checks cooldowns, logs to `ActivityLog`, and calls `ws.manager.broadcast()`
|
- `dispatch.dispatchEvent()` queries matching `event_actions` rows, checks cooldowns, logs to `activity_log`, calls `wss.broadcast()`
|
||||||
- `ws.manager` holds a list of active WebSocket connections from `/ws/overlay`
|
- `wss` (ws.js) holds all active WebSocket connections from `/ws/overlay`
|
||||||
- The overlay JS receives the broadcast and plays the action
|
- The overlay JS receives the broadcast and plays the action
|
||||||
|
- Chat messages come directly from tmi.js running in the browser — no server relay
|
||||||
|
|
||||||
|
## Project structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
app.js — Express app setup, middleware, router mounting
|
||||||
|
server.js — HTTP server + WebSocket server startup, EventSub init
|
||||||
|
db.js — better-sqlite3 setup, migrations, singleton helpers
|
||||||
|
eventsub.js — Twitch EventSub WebSocket client (module singleton)
|
||||||
|
dispatch.js — event → action resolution, cooldowns, DB log, broadcast
|
||||||
|
ws.js — WebSocket connection manager
|
||||||
|
cooldowns.js — in-memory cooldown tracking
|
||||||
|
routers/
|
||||||
|
auth.js — /auth/* (Twitch OAuth)
|
||||||
|
dashboard.js — / (activity log, EventSub status)
|
||||||
|
overlay.js — /overlay/:uuid, /overlay/:uuid/preview, /ws/overlay
|
||||||
|
actions.js — /actions CRUD
|
||||||
|
media.js — /media upload/delete
|
||||||
|
layout.js — /layout editor + /layout/positions
|
||||||
|
settings.js — /settings
|
||||||
|
views/
|
||||||
|
layout.ejs — base shell (nav, Pico CSS)
|
||||||
|
login.ejs
|
||||||
|
dashboard.ejs
|
||||||
|
overlay.ejs — OBS browser source
|
||||||
|
overlay_preview.ejs
|
||||||
|
layout_editor.ejs
|
||||||
|
actions.ejs
|
||||||
|
media.ejs
|
||||||
|
settings.ejs
|
||||||
|
public/
|
||||||
|
media/ — uploaded files (gitignored)
|
||||||
|
```
|
||||||
|
|
||||||
## Key conventions
|
## Key conventions
|
||||||
|
|
||||||
**No passwords anywhere.** Auth is Twitch OAuth only. Session is a signed cookie via `SessionMiddleware`. `request.session["user_id"]` stores the DB user ID after login.
|
**No passwords anywhere.** Auth is Twitch OAuth only. Session stored via `express-session` with a signed cookie. `req.session.userId` stores the DB user ID after login.
|
||||||
|
|
||||||
**Singleton tables.** `Settings` and `OverlayLayout` each have one row. `database.get_settings()` and `database.get_layout()` create the row on first call. Both return detached objects (safe to use after session close — no lazy relationships).
|
**Singleton tables.** `settings` and `overlay_layout` each have one row (id=1). `db.getSettings()` and `db.getLayout()` create the row on first call if missing.
|
||||||
|
|
||||||
**Background DB sessions.** `eventsub.py` and `dispatch.py` use `with Session(engine) as session` directly — not FastAPI's `Depends(get_session)` which is request-scoped. Never pass a request-scoped session to a background task.
|
**Cooldowns are in-memory.** `cooldowns.js` tracks `{ actionId: triggeredAt }`. Restarting the server resets all cooldowns. Intentional — DB cooldowns not worth the complexity.
|
||||||
|
|
||||||
**Cooldowns are in-memory.** `app/cooldowns.py` tracks `{action_id: (triggered_at, duration)}`. Restarting the server resets all cooldowns. This is intentional — keeping them in the DB is not worth the complexity for this use case.
|
**Multiple actions per event.** `dispatchEvent()` loops through ALL matching `event_actions` rows and fires each one (subject to its own cooldown). No "first match wins".
|
||||||
|
|
||||||
**Multiple actions per event.** `dispatch_event()` loops through ALL matching `EventAction` rows for the event type and fires each one (subject to its own cooldown). There is no "first match wins" logic.
|
|
||||||
|
|
||||||
**Event matching rules:**
|
**Event matching rules:**
|
||||||
- `event_type="command"`: `event_detail` must match the command name (case-insensitive, required)
|
- `event_type="command"`: `event_detail` must match the command name (case-insensitive)
|
||||||
- `event_type="bits"`: if `event_detail` is set, it's treated as a minimum bits threshold (`bits_amount >= int(event_detail)`)
|
- `event_type="bits"`: if `event_detail` set, treat as minimum bits threshold
|
||||||
- `event_type="channel_points"`: `event_detail` is the reward ID; None matches any reward
|
- `event_type="channel_points"`: `event_detail` is the reward ID; null matches any reward
|
||||||
- `event_type="sub"/"gift_sub"`: `event_detail` is the tier ("1000", "2000", "3000"); None matches any tier
|
- `event_type="sub"/"gift_sub"`: `event_detail` is tier ("1000", "2000", "3000"); null matches any
|
||||||
- `event_type="follow"/"raid"`: `event_detail` not used for matching
|
- `event_type="follow"/"raid"`: `event_detail` not used
|
||||||
|
|
||||||
|
**UUID overlays.** `settings.overlay_id` is a UUID generated on first run. OBS browser source URL is `/overlay/:uuid`. Future multi-user support creates multiple settings rows.
|
||||||
|
|
||||||
## Route map
|
## Route map
|
||||||
|
|
||||||
| Method | Path | File | Notes |
|
| Method | Path | File | Notes |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| GET | `/` | routers/dashboard.py | Activity log, EventSub status |
|
| GET | `/` | routers/dashboard.js | Activity log, EventSub status |
|
||||||
| GET | `/auth/login` | routers/auth.py | Login page |
|
| GET | `/auth/login` | routers/auth.js | Login page |
|
||||||
| GET | `/auth/twitch` | routers/auth.py | Redirect to Twitch OAuth |
|
| GET | `/auth/twitch` | routers/auth.js | Redirect to Twitch OAuth |
|
||||||
| GET | `/auth/callback` | routers/auth.py | Exchange code, save user, start EventSub |
|
| GET | `/auth/callback` | routers/auth.js | Exchange code, save user, start EventSub |
|
||||||
| GET | `/auth/logout` | routers/auth.py | Clear session |
|
| GET | `/auth/logout` | routers/auth.js | Clear session |
|
||||||
| GET | `/overlay` | routers/overlay.py | Live OBS browser source |
|
| GET | `/overlay/:uuid` | routers/overlay.js | Live OBS browser source |
|
||||||
| GET | `/overlay/preview` | routers/overlay.py | Mock preview for layout editor |
|
| GET | `/overlay/:uuid/preview` | routers/overlay.js | Mock preview for layout editor |
|
||||||
| WS | `/ws/overlay` | routers/overlay.py | Bidirectional: commands up, actions down |
|
| WS | `/ws/overlay` | routers/overlay.js | Bidirectional: commands up, actions down |
|
||||||
| GET/POST | `/actions` | routers/actions.py | List and create actions (HTMX) |
|
| GET/POST | `/actions` | routers/actions.js | List and create actions |
|
||||||
| POST | `/actions/{id}/test` | routers/actions.py | Fire action immediately, skip cooldown |
|
| POST | `/actions/:id/test` | routers/actions.js | Fire immediately, skip cooldown |
|
||||||
| POST | `/actions/{id}/toggle` | routers/actions.py | Enable/disable (HTMX) |
|
| POST | `/actions/:id/toggle` | routers/actions.js | Enable/disable |
|
||||||
| DELETE | `/actions/{id}` | routers/actions.py | Delete (HTMX) |
|
| DELETE | `/actions/:id` | routers/actions.js | Delete |
|
||||||
| GET/POST | `/media` | routers/media.py | Upload list (HTMX) |
|
| GET/POST | `/media` | routers/media.js | Upload, list |
|
||||||
| DELETE | `/media/{id}` | routers/media.py | Delete file from disk and DB |
|
| DELETE | `/media/:id` | routers/media.js | Delete file + DB row |
|
||||||
| GET/POST | `/layout` | routers/layout.py | Style form |
|
| GET/POST | `/layout` | routers/layout.js | Style form |
|
||||||
| POST | `/layout/positions` | routers/layout.py | JSON endpoint called by drag JS |
|
| POST | `/layout/positions` | routers/layout.js | JSON, called by drag JS |
|
||||||
| GET/POST | `/settings` | routers/settings.py | Channel, gap audio, custom CSS/JS |
|
| GET/POST | `/settings` | routers/settings.js | Channel, gap audio, custom CSS/JS |
|
||||||
| POST | `/settings/reconnect` | routers/settings.py | Restart EventSub task |
|
| POST | `/settings/reconnect` | routers/settings.js | Restart EventSub |
|
||||||
|
|
||||||
## Overlay CSS variable system
|
## Overlay CSS variable system
|
||||||
|
|
||||||
All overlay positioning and styling is driven by CSS custom properties injected into `overlay.html` and `overlay_preview.html` from `OverlayLayout`:
|
All overlay positioning and styling uses CSS custom properties rendered into `overlay.ejs` and `overlay_preview.ejs` from the `overlay_layout` row:
|
||||||
|
|
||||||
| Variable | Controls |
|
| Variable | Controls |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `--chat-left`, `--chat-top` | Chat box position (%) |
|
| `--chat-left`, `--chat-top` | Chat box position (%) |
|
||||||
| `--chat-width`, `--chat-height` | Chat box size (%) |
|
| `--chat-width`, `--chat-height` | Chat box size (%) |
|
||||||
| `--chat-font-size` | Chat font size (px) |
|
| `--chat-font-size` | Chat font size (px) |
|
||||||
|
| `--chat-font-family` | Chat font family |
|
||||||
| `--chat-bg-opacity` | Chat message background opacity |
|
| `--chat-bg-opacity` | Chat message background opacity |
|
||||||
| `--chat-text-color` | Chat message text color |
|
| `--chat-text-color` | Chat message text colour |
|
||||||
|
| `--chat-border-radius` | Message border radius (px) |
|
||||||
| `--alert-left`, `--alert-top` | Alert position (%, centered via transform) |
|
| `--alert-left`, `--alert-top` | Alert position (%, centered via transform) |
|
||||||
| `--alert-font-size` | Alert font size (px) |
|
| `--alert-font-size` | Alert font size (px) |
|
||||||
| `--alert-bg-opacity` | Alert background opacity |
|
| `--alert-bg-opacity` | Alert background opacity |
|
||||||
| `--alert-text-color` | Alert text color |
|
| `--alert-text-color` | Alert text colour |
|
||||||
|
|
||||||
The layout editor (`/layout`) has drag handles in the parent document overlaid on an iframe pointing to `/overlay/preview`. Since both are same-origin, the editor updates the iframe's CSS variables directly via `iframe.contentDocument.documentElement.style.setProperty(...)` for live preview. Positions are persisted via `POST /layout/positions` (JSON) on drag end.
|
The layout editor (`/layout`) overlays drag handles on an iframe pointing to `/overlay/:uuid/preview`. Same-origin, so the editor sets CSS variables directly on the iframe's document for live preview. Saved via `POST /layout/positions`.
|
||||||
|
|
||||||
|
## Gap / new-message audio
|
||||||
|
|
||||||
|
Configured in `settings`. When a chat message arrives in the overlay, tmi.js checks:
|
||||||
|
1. Is `gap_audio_enabled` true and `gap_audio_url` set?
|
||||||
|
2. Has at least `gap_audio_min_silence` seconds passed since the last message?
|
||||||
|
3. If yes: play the audio. Volume = linear interpolation between `gap_audio_min_volume` and `gap_audio_max_volume` based on silence duration vs `gap_audio_loud_after`.
|
||||||
|
|
||||||
|
All logic runs in the browser. The server only serves the config.
|
||||||
|
|
||||||
## Custom events dispatched to overlay JS
|
## Custom events dispatched to overlay JS
|
||||||
|
|
||||||
When the overlay WebSocket receives an `execute` message, it dispatches a `CustomEvent` on `document` before playing the alert:
|
When the overlay WebSocket receives an action broadcast:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
document.dispatchEvent(new CustomEvent(`bashyoverlay:${msg.event_type}`, { detail: msg }));
|
document.dispatchEvent(new CustomEvent(`bashyoverlay:${msg.event_type}`, { detail: msg }));
|
||||||
```
|
```
|
||||||
|
|
||||||
Available event types: `sub`, `gift_sub`, `bits`, `channel_points`, `command`, `follow`, `raid`.
|
Available: `sub`, `gift_sub`, `bits`, `channel_points`, `command`, `follow`, `raid`.
|
||||||
|
|
||||||
`msg.detail` contains `{ event_type, action_type, media_url, alert_text }`.
|
`msg` shape: `{ event_type, action_type, media_url, alert_text, username }`.
|
||||||
|
|
||||||
## Data model
|
## Database schema
|
||||||
|
|
||||||
|
```sql
|
||||||
|
users — Twitch OAuth user (twitch_id unique)
|
||||||
|
settings — singleton id=1: overlay_id, channel, gap audio, message_timeout, custom_css/js
|
||||||
|
overlay_layout — singleton id=1: widget positions and styles
|
||||||
|
media_files — uploaded sounds/videos
|
||||||
|
event_actions — event → action mapping (media_file_id FK)
|
||||||
|
activity_log — append-only event log
|
||||||
```
|
```
|
||||||
User ──────────────────────────────── Twitch OAuth user (one per login)
|
|
||||||
Settings ──────────────────────────── Singleton: channel, gap audio, custom CSS/JS
|
|
||||||
OverlayLayout ─────────────────────── Singleton: widget positions and styles
|
|
||||||
MediaFile ──────────────────────────── Uploaded sound or video file
|
|
||||||
EventAction ──── media_file (FK) ───── Event → action mapping
|
|
||||||
ActivityLog ────────────────────────── Append-only event log
|
|
||||||
```
|
|
||||||
|
|
||||||
`Claim` unique constraint on `(item_id, attendee_id)` prevents double-claiming — wait, that's IllBring. BashyOverlay has no Claim model. `EventAction` has no unique constraints; multiple rows with the same `event_type` are allowed and all fire.
|
|
||||||
|
|
||||||
## EventSub subscriptions
|
## EventSub subscriptions
|
||||||
|
|
||||||
| Twitch event | Condition | Scope required |
|
| Twitch event | Version | Condition | Scope |
|
||||||
|---|---|---|
|
|---|---|---|---|
|
||||||
| `channel.subscribe` v1 | `broadcaster_user_id` | `channel:read:subscriptions` |
|
| `channel.subscribe` | 1 | `broadcaster_user_id` | `channel:read:subscriptions` |
|
||||||
| `channel.subscription.message` v1 | `broadcaster_user_id` | `channel:read:subscriptions` |
|
| `channel.subscription.message` | 1 | `broadcaster_user_id` | `channel:read:subscriptions` |
|
||||||
| `channel.subscription.gift` v1 | `broadcaster_user_id` | `channel:read:subscriptions` |
|
| `channel.subscription.gift` | 1 | `broadcaster_user_id` | `channel:read:subscriptions` |
|
||||||
| `channel.cheer` v1 | `broadcaster_user_id` | `bits:read` |
|
| `channel.cheer` | 1 | `broadcaster_user_id` | `bits:read` |
|
||||||
| `channel.channel_points_custom_reward_redemption.add` v1 | `broadcaster_user_id` | `channel:read:redemptions` |
|
| `channel.channel_points_custom_reward_redemption.add` | 1 | `broadcaster_user_id` | `channel:read:redemptions` |
|
||||||
| `channel.follow` v2 | `broadcaster_user_id` + `moderator_user_id` | `moderator:read:followers` |
|
| `channel.follow` | 2 | `broadcaster_user_id` + `moderator_user_id` | `moderator:read:followers` |
|
||||||
| `channel.raid` v1 | `to_broadcaster_user_id` | none |
|
| `channel.raid` | 1 | `to_broadcaster_user_id` | none |
|
||||||
|
|
||||||
Token refresh is handled in `eventsub._refresh_token()` — triggered on 401 during subscription.
|
Token refresh triggered on 401 during subscription.
|
||||||
|
|
||||||
|
## Production deployment
|
||||||
|
|
||||||
|
Runs behind Apache on the VPS as a reverse proxy to Node on port 3000.
|
||||||
|
Production domain: `https://overlay.bashynx.com`
|
||||||
|
|
||||||
|
Minimal Apache vhost config (requires `mod_proxy`, `mod_proxy_http`, `mod_proxy_wstunnel`):
|
||||||
|
```apache
|
||||||
|
<VirtualHost *:443>
|
||||||
|
ServerName overlay.bashynx.com
|
||||||
|
|
||||||
|
ProxyPreserveHost On
|
||||||
|
ProxyPass /ws/overlay ws://127.0.0.1:3000/ws/overlay
|
||||||
|
ProxyPassReverse /ws/overlay ws://127.0.0.1:3000/ws/overlay
|
||||||
|
ProxyPass / http://127.0.0.1:3000/
|
||||||
|
ProxyPassReverse / http://127.0.0.1:3000/
|
||||||
|
|
||||||
|
# SSL config here (certbot/Let's Encrypt)
|
||||||
|
</VirtualHost>
|
||||||
|
```
|
||||||
|
|
||||||
|
The `/ws/overlay` must be proxied separately with `ws://` via `mod_proxy_wstunnel` — if it's not listed before the catch-all `/`, WebSocket connections will fail. Enable the required modules with:
|
||||||
|
```bash
|
||||||
|
sudo a2enmod proxy proxy_http proxy_wstunnel
|
||||||
|
sudo systemctl reload apache2
|
||||||
|
```
|
||||||
|
|
||||||
|
Twitch requires HTTPS for OAuth redirect URIs in production. Register both in the Twitch dev console:
|
||||||
|
- `http://localhost:3000/auth/callback` (dev)
|
||||||
|
- `https://overlay.bashynx.com/auth/callback` (production)
|
||||||
|
|
||||||
## Not yet implemented
|
## Not yet implemented
|
||||||
|
|
||||||
- Alert variable interpolation (`{username}` in alert text)
|
- Alert variable interpolation (`{username}` in alert text)
|
||||||
- Mod access (currently streamer-only login)
|
- Mod access (streamer-only login for now)
|
||||||
- Blerp / HudFX integrations
|
- Chat commands triggering actions
|
||||||
- Crypto payment notifications
|
- Stripe / PayPal / ETH payment notifications
|
||||||
- Morning-of reminder emails (IllBring feature, not applicable here)
|
- Multiple overlays (multi-user; UUID foundation is in place)
|
||||||
|
- HudFX integration
|
||||||
|
|
|
||||||
274
INSTRUCTIONS.md
274
INSTRUCTIONS.md
|
|
@ -1,274 +0,0 @@
|
||||||
# Deployment & Git Instructions
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- Python 3.10 or higher
|
|
||||||
- A Twitch account (the channel you stream on)
|
|
||||||
- Git
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Initial Deploy
|
|
||||||
|
|
||||||
### 1. Register a Twitch application
|
|
||||||
|
|
||||||
1. Go to [dev.twitch.tv/console/apps](https://dev.twitch.tv/console/apps)
|
|
||||||
2. Click **Register Your Application**
|
|
||||||
3. Fill in:
|
|
||||||
- **Name:** anything (e.g. `BashyOverlay`)
|
|
||||||
- **OAuth Redirect URLs:** `http://localhost:8000/auth/callback`
|
|
||||||
- **Category:** Application Integration
|
|
||||||
4. Click **Create**
|
|
||||||
5. On the next screen, click **Manage** to see your app
|
|
||||||
6. Copy the **Client ID**
|
|
||||||
7. Click **New Secret** and copy the **Client Secret**
|
|
||||||
|
|
||||||
Keep both — you will need them in the next step.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. Clone the repository
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/yourusername/BashyOverlay.git
|
|
||||||
cd BashyOverlay
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. Configure environment variables
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp .env.example .env
|
|
||||||
```
|
|
||||||
|
|
||||||
Open `.env` and fill in:
|
|
||||||
|
|
||||||
```env
|
|
||||||
TWITCH_CLIENT_ID=paste_your_client_id_here
|
|
||||||
TWITCH_CLIENT_SECRET=paste_your_client_secret_here
|
|
||||||
TWITCH_REDIRECT_URI=http://localhost:8000/auth/callback
|
|
||||||
|
|
||||||
SECRET_KEY=paste_a_random_string_here
|
|
||||||
APP_BASE_URL=http://localhost:8000
|
|
||||||
DATABASE_URL=sqlite:///./bashyoverlay.db
|
|
||||||
```
|
|
||||||
|
|
||||||
Generate a `SECRET_KEY` with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 -c "import secrets; print(secrets.token_hex(32))"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. Run the deploy script
|
|
||||||
|
|
||||||
```bash
|
|
||||||
chmod +x deploy.sh
|
|
||||||
./deploy.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
The script will:
|
|
||||||
- Check that Python 3.10+ is installed
|
|
||||||
- Create a `.venv` virtual environment
|
|
||||||
- Install all Python dependencies
|
|
||||||
- Verify `.env` is filled in
|
|
||||||
- Create the `static/media/` directory
|
|
||||||
- Start the server at `http://localhost:8000`
|
|
||||||
|
|
||||||
To stop the server: `Ctrl+C`
|
|
||||||
|
|
||||||
To start again without the setup steps:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
source .venv/bin/activate
|
|
||||||
uvicorn app.main:app --reload
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. Log in with Twitch
|
|
||||||
|
|
||||||
1. Open `http://localhost:8000` in your browser
|
|
||||||
2. Click **Login with Twitch**
|
|
||||||
3. Authorise the app — you will be asked to grant permissions for subscriptions, bits, channel points, and follower data
|
|
||||||
4. You are redirected to the dashboard; EventSub connects automatically
|
|
||||||
|
|
||||||
**Bookmark the dashboard URL.** There are no accounts or passwords — the session cookie keeps you logged in.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 6. Configure OBS browser source
|
|
||||||
|
|
||||||
1. In OBS, go to **Sources → + → Browser**
|
|
||||||
2. Set the URL to the value shown on the Settings page (default: `http://localhost:8000/overlay`)
|
|
||||||
3. Set **Width** and **Height** to match your stream resolution (typically `1920` × `1080`)
|
|
||||||
4. Check **Shutdown source when not visible**
|
|
||||||
5. In the **Custom CSS** box paste:
|
|
||||||
```css
|
|
||||||
body { background: transparent !important; }
|
|
||||||
```
|
|
||||||
6. Click OK
|
|
||||||
|
|
||||||
The overlay will appear transparent in OBS. Chat messages and alerts show on top of your game/content.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 7. Upload media
|
|
||||||
|
|
||||||
1. Go to **Media** in the management UI
|
|
||||||
2. Click **Choose File** and upload sounds (MP3, OGG, WAV) and/or videos (MP4, WebM)
|
|
||||||
3. Preview them in the browser before assigning them to actions
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 8. Configure actions
|
|
||||||
|
|
||||||
1. Go to **Actions**
|
|
||||||
2. For each event you want to react to, fill in the **Add action** form:
|
|
||||||
- **Event type:** `sub`, `gift_sub`, `bits`, `channel_points`, `command`, `follow`, or `raid`
|
|
||||||
- **Event detail:** command name (for commands), minimum bits (for bits), reward ID (for channel points), tier (for subs) — leave blank to match any
|
|
||||||
- **Action type:** `sound`, `video`, or `alert`
|
|
||||||
- **Media file:** pick an uploaded file (for sound/video)
|
|
||||||
- **Alert text:** message shown on screen (for alert type)
|
|
||||||
- **Cooldown:** seconds before this action can fire again
|
|
||||||
3. Click **Test** on each action to verify it fires before going live
|
|
||||||
|
|
||||||
To find a channel point reward ID: open your Twitch dashboard, go to Channel Points → Manage Rewards, then click a reward. The ID is in the page URL.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 9. Configure the layout
|
|
||||||
|
|
||||||
1. Go to **Layout**
|
|
||||||
2. Drag the **Chat** handle to where you want chat to appear on screen
|
|
||||||
3. Drag the bottom-right corner of the Chat handle to resize it
|
|
||||||
4. Drag the **Alert** handle to where you want alerts to appear
|
|
||||||
5. Adjust font size, opacity, and text color in the form below the canvas
|
|
||||||
6. Click **▶ Preview alert** to see the animation with your current settings
|
|
||||||
7. Click **Save styles** when done
|
|
||||||
8. **Refresh the OBS browser source** (`Right-click source → Refresh`) to apply layout changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 10. Optional: Custom CSS and JS
|
|
||||||
|
|
||||||
Go to **Settings** and scroll to the bottom.
|
|
||||||
|
|
||||||
**Custom CSS example** — glow effect on alerts:
|
|
||||||
```css
|
|
||||||
#alert {
|
|
||||||
box-shadow: 0 0 30px #9146ff;
|
|
||||||
border: 2px solid #9146ff;
|
|
||||||
}
|
|
||||||
.chat-msg {
|
|
||||||
border-left: 3px solid;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Custom JS example** — react to a raid:
|
|
||||||
```javascript
|
|
||||||
document.addEventListener('bashyoverlay:raid', (e) => {
|
|
||||||
console.log('Raided!', e.detail.alert_text);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Available events: `bashyoverlay:sub`, `bashyoverlay:gift_sub`, `bashyoverlay:bits`, `bashyoverlay:channel_points`, `bashyoverlay:command`, `bashyoverlay:follow`, `bashyoverlay:raid`.
|
|
||||||
|
|
||||||
Click **Save settings** then refresh the OBS browser source.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Git Actions
|
|
||||||
|
|
||||||
### First push to GitHub
|
|
||||||
|
|
||||||
After creating a new empty repository on GitHub:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git remote add origin https://github.com/yourusername/BashyOverlay.git
|
|
||||||
git branch -M main
|
|
||||||
git push -u origin main
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Day-to-day workflow
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check what has changed
|
|
||||||
git status
|
|
||||||
git diff
|
|
||||||
|
|
||||||
# Stage and commit changes
|
|
||||||
git add app/routers/actions.py templates/actions.html
|
|
||||||
git commit -m "add sub tier filtering to action matching"
|
|
||||||
|
|
||||||
# Push to remote
|
|
||||||
git push
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### GitHub Actions CI
|
|
||||||
|
|
||||||
The repository includes `.github/workflows/ci.yml`. It runs automatically on every push and pull request and does the following:
|
|
||||||
|
|
||||||
1. Installs Python 3.11
|
|
||||||
2. Installs all dependencies from `requirements.txt`
|
|
||||||
3. Syntax-checks every Python file under `app/`
|
|
||||||
4. Verifies the FastAPI app imports without error
|
|
||||||
|
|
||||||
No secrets are required for CI — it uses placeholder values for Twitch credentials.
|
|
||||||
|
|
||||||
To view CI results: go to your repository on GitHub → **Actions** tab.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Adding GitHub repository secrets (for future CD)
|
|
||||||
|
|
||||||
If you later want to deploy automatically, add secrets in GitHub:
|
|
||||||
|
|
||||||
1. Go to your repository → **Settings → Secrets and variables → Actions**
|
|
||||||
2. Click **New repository secret**
|
|
||||||
3. Add:
|
|
||||||
- `TWITCH_CLIENT_ID`
|
|
||||||
- `TWITCH_CLIENT_SECRET`
|
|
||||||
- `SECRET_KEY`
|
|
||||||
|
|
||||||
Reference them in a workflow step with `${{ secrets.TWITCH_CLIENT_ID }}`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Branches
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create a feature branch
|
|
||||||
git checkout -b feature/alert-interpolation
|
|
||||||
|
|
||||||
# Work, commit, push
|
|
||||||
git push -u origin feature/alert-interpolation
|
|
||||||
|
|
||||||
# Merge back to main when done (on GitHub via pull request, or locally)
|
|
||||||
git checkout main
|
|
||||||
git merge feature/alert-interpolation
|
|
||||||
git push
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Updating a deployment
|
|
||||||
|
|
||||||
If BashyOverlay is running on another machine or you pull in new changes:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git pull
|
|
||||||
source .venv/bin/activate
|
|
||||||
pip install -r requirements.txt # in case dependencies changed
|
|
||||||
uvicorn app.main:app --reload
|
|
||||||
```
|
|
||||||
|
|
||||||
The SQLite database is updated automatically on startup — new tables and columns are created by `SQLModel.metadata.create_all`. Existing data is preserved.
|
|
||||||
|
|
||||||
> **Note:** `create_all` only adds new tables and columns — it does not handle column renames or deletions. If a migration requires dropping a column, do it manually with `sqlite3 bashyoverlay.db "ALTER TABLE ..."` before restarting.
|
|
||||||
78
README.md
78
README.md
|
|
@ -1,78 +0,0 @@
|
||||||
# BashyOverlay
|
|
||||||
|
|
||||||
Self-hosted Twitch stream alert and overlay platform. Subs, bits, channel points, follows, raids, and chat commands trigger configurable sound and video reactions — all running locally on your machine, no third-party alert service required.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- **OBS browser source overlay** — transparent chat display with gap audio (tone plays when chat goes quiet, volume and pitch scale with silence duration)
|
|
||||||
- **Twitch EventSub** — real-time events for subs, gift subs, bits, channel points, follows, and raids
|
|
||||||
- **Chat commands** — `!command` triggers via tmi.js, no bot account needed for public channels
|
|
||||||
- **Action system** — map any event to a sound, video clip, or on-screen alert; multiple actions per event; per-action cooldowns and enable/disable toggle
|
|
||||||
- **Test button** — fire any action instantly from the management UI without waiting for a real event
|
|
||||||
- **Self-hosted media library** — upload your own MP3s and MP4s
|
|
||||||
- **Layout editor** — WYSIWYG iframe editor; drag chat and alert widgets, resize chat, live style preview
|
|
||||||
- **Custom CSS / JS** — inject code directly into the overlay; hook into `bashyoverlay:sub`, `bashyoverlay:raid`, etc.
|
|
||||||
- **Management UI** — dashboard with activity log, EventSub status, action config, media library
|
|
||||||
- **Twitch OAuth** — login with your Twitch account; no passwords anywhere
|
|
||||||
|
|
||||||
## Stack
|
|
||||||
|
|
||||||
- Python 3.10+ · FastAPI · SQLModel · SQLite
|
|
||||||
- Jinja2 · HTMX · Pico CSS (dark theme)
|
|
||||||
- tmi.js · Twitch EventSub WebSocket
|
|
||||||
- interact.js (layout editor drag and resize)
|
|
||||||
|
|
||||||
## Quick start
|
|
||||||
|
|
||||||
See **[INSTRUCTIONS.md](INSTRUCTIONS.md)** for the full setup guide including Twitch app registration and OBS configuration.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp .env.example .env
|
|
||||||
# fill in your Twitch credentials and a SECRET_KEY
|
|
||||||
./deploy.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## Project structure
|
|
||||||
|
|
||||||
```
|
|
||||||
app/
|
|
||||||
main.py — FastAPI app, lifespan, EventSub startup
|
|
||||||
models.py — SQLModel table definitions
|
|
||||||
database.py — engine, session dependency, singleton helpers
|
|
||||||
auth.py — Twitch OAuth helpers, session dependency
|
|
||||||
eventsub.py — EventSub WebSocket background task, token refresh
|
|
||||||
dispatch.py — event → action resolution, cooldowns, DB log, broadcast
|
|
||||||
ws.py — WebSocket connection manager for overlay clients
|
|
||||||
cooldowns.py — in-memory cooldown tracking per action
|
|
||||||
routers/
|
|
||||||
auth.py — /auth/login, /auth/callback, /auth/logout
|
|
||||||
dashboard.py — / dashboard with activity log
|
|
||||||
overlay.py — /overlay (live), /overlay/preview, /ws/overlay
|
|
||||||
actions.py — /actions CRUD, test, toggle
|
|
||||||
media.py — /media upload and delete
|
|
||||||
layout.py — /layout editor, /layout/positions (drag save)
|
|
||||||
settings.py — /settings
|
|
||||||
templates/
|
|
||||||
overlay.html — OBS browser source (live tmi.js + WebSocket)
|
|
||||||
overlay_preview.html — layout editor preview (mock data, no live connections)
|
|
||||||
layout.html — iframe-based drag-and-drop layout editor
|
|
||||||
base.html — management UI shell
|
|
||||||
dashboard.html — activity log
|
|
||||||
actions.html — action configuration
|
|
||||||
media.html — media library
|
|
||||||
settings.html — settings including custom CSS/JS
|
|
||||||
login.html — Twitch OAuth login page
|
|
||||||
partials/ — HTMX response fragments
|
|
||||||
static/
|
|
||||||
overlay.css — overlay styles using CSS custom properties
|
|
||||||
style.css — management UI styles
|
|
||||||
media/ — uploaded sounds and videos (gitignored)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Deferred / upcoming
|
|
||||||
|
|
||||||
- Blerp sound library integration
|
|
||||||
- HudFX video library integration
|
|
||||||
- Crypto payment notifications (Base/ETH wallet watching)
|
|
||||||
- Alert variable interpolation (`{username}` in alert text)
|
|
||||||
- Mod access (currently streamer-only login)
|
|
||||||
95
SPEC.md
95
SPEC.md
|
|
@ -1,95 +0,0 @@
|
||||||
# BashyOverlay — spec
|
|
||||||
|
|
||||||
Self-hosted Twitch stream event platform. Subs, bits, channel points, and chat commands trigger configurable sound and video reactions. Think StreamElements/Streamlabs alerts, but owned and customisable.
|
|
||||||
|
|
||||||
No smart lights.
|
|
||||||
|
|
||||||
## Event sources
|
|
||||||
|
|
||||||
| Source | How |
|
|
||||||
|---|---|
|
|
||||||
| Chat commands (`!name`) | tmi.js IRC WebSocket — no auth needed for public chat |
|
|
||||||
| Subs, bits, channel points | Twitch EventSub WebSocket — requires OAuth token |
|
|
||||||
|
|
||||||
## Action types
|
|
||||||
|
|
||||||
| Action | Notes |
|
|
||||||
|---|---|
|
|
||||||
| Play a sound | From Blerp library — see open questions |
|
|
||||||
| Play a video/clip overlay | From HudFX library — see open questions |
|
|
||||||
| On-screen alert | Text, image, or animation shown in the OBS browser source |
|
|
||||||
|
|
||||||
## Media libraries
|
|
||||||
|
|
||||||
- **Sounds — Blerp.** Meme/clip sound platform. Has Twitch integration history. API details need confirming before building — check developer docs for programmatic library access and licensing.
|
|
||||||
- **Videos — HudFX.** Overlay video/animation library. API access and terms need confirming before building.
|
|
||||||
|
|
||||||
Both are external dependencies. If their APIs aren't open enough, fallback is a self-hosted media library (upload your own files via the management UI).
|
|
||||||
|
|
||||||
## The overlay
|
|
||||||
|
|
||||||
OBS browser source pointed at the local management server. Receives events from the backend via WebSocket and plays the configured reaction.
|
|
||||||
|
|
||||||
### Chat gap audio
|
|
||||||
|
|
||||||
Passive audio notification for slow chat — not tied to a command, just fires when a new message arrives after a long silence:
|
|
||||||
- **Under 30 seconds** — no sound
|
|
||||||
- **30s to ~2 minutes** — volume scales up with gap
|
|
||||||
- **2+ minutes** — full volume + pitch scales up (caps ~5 min)
|
|
||||||
|
|
||||||
Thresholds configurable in management UI.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
Twitch ──EventSub WebSocket──► Backend ──WebSocket──► OBS Browser Source
|
|
||||||
Twitch ──IRC (tmi.js)────────────────────────────────► OBS Browser Source
|
|
||||||
```
|
|
||||||
|
|
||||||
The overlay holds the tmi.js connection directly. The backend holds the EventSub connection and relays events to the overlay.
|
|
||||||
|
|
||||||
## Management platform
|
|
||||||
|
|
||||||
Local web app running on the streamer's machine.
|
|
||||||
|
|
||||||
**Stack:** FastAPI + SQLite + Jinja2 + HTMX (same as IllBring)
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Configure actions per event type (sub, bits tier, channel point reward, chat command)
|
|
||||||
- Search and preview Blerp sounds
|
|
||||||
- Browse and preview HudFX videos
|
|
||||||
- Upload custom sounds/videos as fallback
|
|
||||||
- Configure gap-audio thresholds
|
|
||||||
- Activity log / dashboard
|
|
||||||
- User access via Twitch OAuth — mods verified against channel mod list, no separate user table
|
|
||||||
|
|
||||||
## Authentication
|
|
||||||
|
|
||||||
Twitch OAuth throughout:
|
|
||||||
- Streamer logs in → full admin access
|
|
||||||
- Mods log in → access if verified as mod of the channel via Twitch API
|
|
||||||
- Token used for EventSub subscriptions
|
|
||||||
|
|
||||||
## Deferred
|
|
||||||
|
|
||||||
- **Crypto payment notifications** — watch a wallet address for incoming transactions via a blockchain API (Alchemy, Moralis, etc.), fire an overlay event on receipt. Which chains to support and whether to use raw wallet watching vs a payment processor (BTCPay Server, etc.) TBD.
|
|
||||||
|
|
||||||
## Estimated effort
|
|
||||||
|
|
||||||
| Piece | Effort |
|
|
||||||
|---|---|
|
|
||||||
| OBS overlay + gap audio | 1–2 hours |
|
|
||||||
| Twitch OAuth | half day |
|
|
||||||
| EventSub WebSocket + subscriptions | half–1 day |
|
|
||||||
| Event → action mapping + management UI | 1 day |
|
|
||||||
| Blerp integration | half day (pending API review) |
|
|
||||||
| HudFX integration | half day (pending API review) |
|
|
||||||
| Dashboard / activity log | half day |
|
|
||||||
| **Total v1** | **~3–4 days** |
|
|
||||||
|
|
||||||
## Open questions before building
|
|
||||||
|
|
||||||
- Does Blerp have a public API with library search + programmatic playback?
|
|
||||||
- Does HudFX expose a video library via API?
|
|
||||||
- If either doesn't — self-hosted media library, or a different provider?
|
|
||||||
- Crypto: which chains, wallet watching vs payment processor?
|
|
||||||
64
app/auth.py
64
app/auth.py
|
|
@ -1,64 +0,0 @@
|
||||||
import os
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
from fastapi import Depends, HTTPException, Request
|
|
||||||
from fastapi.responses import RedirectResponse
|
|
||||||
from sqlmodel import Session, select
|
|
||||||
|
|
||||||
from app.database import get_session
|
|
||||||
from app.models import User
|
|
||||||
|
|
||||||
TWITCH_CLIENT_ID = os.getenv("TWITCH_CLIENT_ID", "")
|
|
||||||
TWITCH_CLIENT_SECRET = os.getenv("TWITCH_CLIENT_SECRET", "")
|
|
||||||
TWITCH_REDIRECT_URI = os.getenv("TWITCH_REDIRECT_URI", "http://localhost:8000/auth/callback")
|
|
||||||
|
|
||||||
SCOPES = "channel:read:subscriptions bits:read channel:read:redemptions moderator:read:followers"
|
|
||||||
|
|
||||||
TWITCH_AUTH_URL = (
|
|
||||||
"https://id.twitch.tv/oauth2/authorize"
|
|
||||||
f"?client_id={TWITCH_CLIENT_ID}"
|
|
||||||
f"&redirect_uri={TWITCH_REDIRECT_URI}"
|
|
||||||
"&response_type=code"
|
|
||||||
f"&scope={SCOPES.replace(' ', '+')}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def exchange_code(code: str) -> dict:
|
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
r = await client.post("https://id.twitch.tv/oauth2/token", data={
|
|
||||||
"client_id": TWITCH_CLIENT_ID,
|
|
||||||
"client_secret": TWITCH_CLIENT_SECRET,
|
|
||||||
"code": code,
|
|
||||||
"grant_type": "authorization_code",
|
|
||||||
"redirect_uri": TWITCH_REDIRECT_URI,
|
|
||||||
})
|
|
||||||
r.raise_for_status()
|
|
||||||
return r.json()
|
|
||||||
|
|
||||||
|
|
||||||
async def get_twitch_user(access_token: str) -> dict:
|
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
r = await client.get(
|
|
||||||
"https://api.twitch.tv/helix/users",
|
|
||||||
headers={"Authorization": f"Bearer {access_token}", "Client-Id": TWITCH_CLIENT_ID},
|
|
||||||
)
|
|
||||||
r.raise_for_status()
|
|
||||||
return r.json()["data"][0]
|
|
||||||
|
|
||||||
|
|
||||||
def get_current_user(request: Request, session: Session = Depends(get_session)) -> Optional[User]:
|
|
||||||
user_id = request.session.get("user_id")
|
|
||||||
if not user_id:
|
|
||||||
return None
|
|
||||||
return session.get(User, user_id)
|
|
||||||
|
|
||||||
|
|
||||||
def require_user(request: Request, session: Session = Depends(get_session)) -> User:
|
|
||||||
user_id = request.session.get("user_id")
|
|
||||||
if not user_id:
|
|
||||||
raise HTTPException(status_code=307, headers={"Location": "/auth/login"})
|
|
||||||
user = session.get(User, user_id)
|
|
||||||
if not user:
|
|
||||||
raise HTTPException(status_code=307, headers={"Location": "/auth/login"})
|
|
||||||
return user
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
_store: dict[int, tuple[datetime, int]] = {}
|
|
||||||
|
|
||||||
|
|
||||||
def is_on_cooldown(action_id: int) -> bool:
|
|
||||||
if action_id not in _store:
|
|
||||||
return False
|
|
||||||
triggered_at, duration = _store[action_id]
|
|
||||||
return (datetime.utcnow() - triggered_at).total_seconds() < duration
|
|
||||||
|
|
||||||
|
|
||||||
def set_cooldown(action_id: int, duration_seconds: int) -> None:
|
|
||||||
if duration_seconds > 0:
|
|
||||||
_store[action_id] = (datetime.utcnow(), duration_seconds)
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
import os
|
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
from sqlmodel import Session, SQLModel, create_engine, select
|
|
||||||
|
|
||||||
load_dotenv()
|
|
||||||
|
|
||||||
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./bashyoverlay.db")
|
|
||||||
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
|
|
||||||
|
|
||||||
|
|
||||||
def create_db() -> None:
|
|
||||||
SQLModel.metadata.create_all(engine)
|
|
||||||
|
|
||||||
|
|
||||||
def get_session():
|
|
||||||
with Session(engine) as session:
|
|
||||||
yield session
|
|
||||||
|
|
||||||
|
|
||||||
def get_settings():
|
|
||||||
from app.models import Settings
|
|
||||||
with Session(engine) as session:
|
|
||||||
s = session.exec(select(Settings)).first()
|
|
||||||
if not s:
|
|
||||||
s = Settings()
|
|
||||||
session.add(s)
|
|
||||||
session.commit()
|
|
||||||
session.refresh(s)
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
def get_layout():
|
|
||||||
from app.models import OverlayLayout
|
|
||||||
with Session(engine) as session:
|
|
||||||
layout = session.exec(select(OverlayLayout)).first()
|
|
||||||
if not layout:
|
|
||||||
layout = OverlayLayout()
|
|
||||||
session.add(layout)
|
|
||||||
session.commit()
|
|
||||||
session.refresh(layout)
|
|
||||||
return layout
|
|
||||||
|
|
||||||
|
|
||||||
def get_admin_user():
|
|
||||||
from app.models import User
|
|
||||||
with Session(engine) as session:
|
|
||||||
return session.exec(select(User).where(User.is_admin == True)).first()
|
|
||||||
109
app/dispatch.py
109
app/dispatch.py
|
|
@ -1,109 +0,0 @@
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from sqlmodel import Session, select
|
|
||||||
|
|
||||||
from app.cooldowns import is_on_cooldown, set_cooldown
|
|
||||||
from app.database import engine
|
|
||||||
from app.models import ActivityLog, EventAction, MediaFile
|
|
||||||
from app.ws import manager
|
|
||||||
|
|
||||||
|
|
||||||
async def dispatch_event(
|
|
||||||
event_type: str,
|
|
||||||
username: Optional[str] = None,
|
|
||||||
event_detail: Optional[str] = None,
|
|
||||||
bits_amount: Optional[int] = None,
|
|
||||||
) -> None:
|
|
||||||
with Session(engine) as session:
|
|
||||||
actions = session.exec(
|
|
||||||
select(EventAction)
|
|
||||||
.where(EventAction.event_type == event_type)
|
|
||||||
.where(EventAction.enabled == True)
|
|
||||||
).all()
|
|
||||||
|
|
||||||
fired = []
|
|
||||||
for action in actions:
|
|
||||||
if not _matches(action, event_detail, bits_amount):
|
|
||||||
continue
|
|
||||||
if is_on_cooldown(action.id):
|
|
||||||
continue
|
|
||||||
|
|
||||||
payload = _resolve_payload(action, session)
|
|
||||||
alert_text = action.alert_text or _default_alert(event_type, username, event_detail, bits_amount)
|
|
||||||
|
|
||||||
set_cooldown(action.id, action.cooldown_seconds)
|
|
||||||
await manager.broadcast({
|
|
||||||
"type": "execute",
|
|
||||||
"event_type": event_type,
|
|
||||||
"action_type": action.action_type,
|
|
||||||
"media_url": payload.get("media_url"),
|
|
||||||
"alert_text": alert_text,
|
|
||||||
})
|
|
||||||
fired.append(f"{action.action_type}: {payload.get('media_url') or alert_text or ''}")
|
|
||||||
|
|
||||||
action_taken = ", ".join(fired) if fired else None
|
|
||||||
_log(session, event_type, username, event_detail, bits_amount, action_taken)
|
|
||||||
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
def _matches(action: EventAction, event_detail: Optional[str], bits_amount: Optional[int]) -> bool:
|
|
||||||
if not action.event_detail:
|
|
||||||
return True
|
|
||||||
if action.event_type == "bits" and bits_amount is not None:
|
|
||||||
try:
|
|
||||||
return bits_amount >= int(action.event_detail)
|
|
||||||
except ValueError:
|
|
||||||
return False
|
|
||||||
if action.event_type == "command":
|
|
||||||
return action.event_detail.lower() == (event_detail or "").lower()
|
|
||||||
return action.event_detail == event_detail
|
|
||||||
|
|
||||||
|
|
||||||
def _resolve_payload(action: EventAction, session: Session) -> dict:
|
|
||||||
if action.media_file_id:
|
|
||||||
media = session.get(MediaFile, action.media_file_id)
|
|
||||||
if media:
|
|
||||||
return {"media_url": f"/static/media/{media.filename}"}
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
def _log(session, event_type, username, event_detail, bits_amount, action_taken):
|
|
||||||
session.add(ActivityLog(
|
|
||||||
event_type=event_type,
|
|
||||||
username=username,
|
|
||||||
detail=_detail_str(event_type, event_detail, bits_amount),
|
|
||||||
action_taken=action_taken,
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
def _detail_str(event_type, event_detail, bits_amount) -> Optional[str]:
|
|
||||||
if event_type == "sub":
|
|
||||||
return f"tier {event_detail}" if event_detail else None
|
|
||||||
if event_type == "bits":
|
|
||||||
return f"{bits_amount} bits" if bits_amount else None
|
|
||||||
if event_type == "channel_points":
|
|
||||||
return event_detail
|
|
||||||
if event_type == "command":
|
|
||||||
return f"!{event_detail}" if event_detail else None
|
|
||||||
if event_type == "raid":
|
|
||||||
return f"{event_detail} viewers" if event_detail else None
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _default_alert(event_type, username, event_detail, bits_amount) -> Optional[str]:
|
|
||||||
u = username or "Someone"
|
|
||||||
if event_type == "sub":
|
|
||||||
return f"{u} just subscribed!"
|
|
||||||
if event_type == "gift_sub":
|
|
||||||
return f"{u} gifted a sub!"
|
|
||||||
if event_type == "bits":
|
|
||||||
return f"{u} cheered {bits_amount} bits!"
|
|
||||||
if event_type == "channel_points":
|
|
||||||
return f"{u} redeemed channel points!"
|
|
||||||
if event_type == "follow":
|
|
||||||
return f"{u} just followed!"
|
|
||||||
if event_type == "raid":
|
|
||||||
viewers = f" with {event_detail} viewers" if event_detail else ""
|
|
||||||
return f"{u} is raiding{viewers}!"
|
|
||||||
return None
|
|
||||||
141
app/eventsub.py
141
app/eventsub.py
|
|
@ -1,141 +0,0 @@
|
||||||
import asyncio
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
import websockets
|
|
||||||
|
|
||||||
from app.dispatch import dispatch_event
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
EVENTSUB_WS_URL = "wss://eventsub.wss.twitch.tv/ws"
|
|
||||||
TWITCH_CLIENT_ID = os.getenv("TWITCH_CLIENT_ID", "")
|
|
||||||
TWITCH_CLIENT_SECRET = os.getenv("TWITCH_CLIENT_SECRET", "")
|
|
||||||
|
|
||||||
SUBSCRIPTIONS = [
|
|
||||||
("channel.subscribe", "1", lambda bid: {"broadcaster_user_id": bid}),
|
|
||||||
("channel.subscription.message", "1", lambda bid: {"broadcaster_user_id": bid}),
|
|
||||||
("channel.subscription.gift", "1", lambda bid: {"broadcaster_user_id": bid}),
|
|
||||||
("channel.cheer", "1", lambda bid: {"broadcaster_user_id": bid}),
|
|
||||||
("channel.channel_points_custom_reward_redemption.add", "1", lambda bid: {"broadcaster_user_id": bid}),
|
|
||||||
("channel.follow", "2", lambda bid: {"broadcaster_user_id": bid, "moderator_user_id": bid}),
|
|
||||||
("channel.raid", "1", lambda bid: {"to_broadcaster_user_id": bid}),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
async def run_eventsub(broadcaster_id: str, access_token: str) -> None:
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
async with websockets.connect(EVENTSUB_WS_URL) as ws:
|
|
||||||
logger.info("EventSub connected")
|
|
||||||
async for raw in ws:
|
|
||||||
msg = json.loads(raw)
|
|
||||||
msg_type = msg.get("metadata", {}).get("message_type")
|
|
||||||
|
|
||||||
if msg_type == "session_welcome":
|
|
||||||
session_id = msg["payload"]["session"]["id"]
|
|
||||||
token = await _subscribe_all(session_id, broadcaster_id, access_token)
|
|
||||||
if token:
|
|
||||||
access_token = token
|
|
||||||
|
|
||||||
elif msg_type == "notification":
|
|
||||||
await _handle_notification(msg["payload"])
|
|
||||||
|
|
||||||
elif msg_type == "session_reconnect":
|
|
||||||
reconnect_url = msg["payload"]["session"]["reconnect_url"]
|
|
||||||
logger.info("EventSub reconnect requested")
|
|
||||||
break
|
|
||||||
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
logger.info("EventSub task cancelled")
|
|
||||||
return
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"EventSub error: {e}, reconnecting in 10s")
|
|
||||||
await asyncio.sleep(10)
|
|
||||||
|
|
||||||
|
|
||||||
async def _subscribe_all(session_id: str, broadcaster_id: str, access_token: str) -> str | None:
|
|
||||||
transport = {"method": "websocket", "session_id": session_id}
|
|
||||||
condition = {"broadcaster_user_id": broadcaster_id}
|
|
||||||
headers = {
|
|
||||||
"Authorization": f"Bearer {access_token}",
|
|
||||||
"Client-Id": TWITCH_CLIENT_ID,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
}
|
|
||||||
|
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
for sub_type, version, condition_fn in SUBSCRIPTIONS:
|
|
||||||
condition = condition_fn(broadcaster_id)
|
|
||||||
r = await client.post(
|
|
||||||
"https://api.twitch.tv/helix/eventsub/subscriptions",
|
|
||||||
headers=headers,
|
|
||||||
json={"type": sub_type, "version": version, "condition": condition, "transport": transport},
|
|
||||||
)
|
|
||||||
if r.status_code == 401:
|
|
||||||
new_token = await _refresh_token(access_token)
|
|
||||||
if new_token:
|
|
||||||
headers["Authorization"] = f"Bearer {new_token}"
|
|
||||||
await client.post(
|
|
||||||
"https://api.twitch.tv/helix/eventsub/subscriptions",
|
|
||||||
headers=headers,
|
|
||||||
json={"type": sub_type, "version": version, "condition": condition, "transport": transport},
|
|
||||||
)
|
|
||||||
return new_token
|
|
||||||
elif r.status_code not in (200, 202, 409):
|
|
||||||
logger.warning(f"Failed to subscribe to {sub_type}: {r.status_code} {r.text}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
async def _refresh_token(old_token: str) -> str | None:
|
|
||||||
from app.database import engine
|
|
||||||
from app.models import User
|
|
||||||
from sqlmodel import Session, select
|
|
||||||
|
|
||||||
with Session(engine) as session:
|
|
||||||
user = session.exec(select(User).where(User.access_token == old_token)).first()
|
|
||||||
if not user:
|
|
||||||
return None
|
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
r = await client.post("https://id.twitch.tv/oauth2/token", data={
|
|
||||||
"client_id": TWITCH_CLIENT_ID,
|
|
||||||
"client_secret": TWITCH_CLIENT_SECRET,
|
|
||||||
"grant_type": "refresh_token",
|
|
||||||
"refresh_token": user.refresh_token,
|
|
||||||
})
|
|
||||||
if r.status_code != 200:
|
|
||||||
return None
|
|
||||||
data = r.json()
|
|
||||||
user.access_token = data["access_token"]
|
|
||||||
user.refresh_token = data.get("refresh_token", user.refresh_token)
|
|
||||||
session.add(user)
|
|
||||||
session.commit()
|
|
||||||
return user.access_token
|
|
||||||
|
|
||||||
|
|
||||||
async def _handle_notification(payload: dict) -> None:
|
|
||||||
sub_type = payload["subscription"]["type"]
|
|
||||||
event = payload["event"]
|
|
||||||
username = event.get("user_name") or event.get("user_login")
|
|
||||||
|
|
||||||
if sub_type in ("channel.subscribe", "channel.subscription.message"):
|
|
||||||
await dispatch_event("sub", username=username, event_detail=event.get("tier", "1000"))
|
|
||||||
|
|
||||||
elif sub_type == "channel.subscription.gift":
|
|
||||||
await dispatch_event("gift_sub", username=username, event_detail=event.get("tier", "1000"))
|
|
||||||
|
|
||||||
elif sub_type == "channel.cheer":
|
|
||||||
await dispatch_event("bits", username=username, bits_amount=event.get("bits", 0))
|
|
||||||
|
|
||||||
elif sub_type == "channel.channel_points_custom_reward_redemption.add":
|
|
||||||
reward_id = event.get("reward", {}).get("id")
|
|
||||||
await dispatch_event("channel_points", username=username, event_detail=reward_id)
|
|
||||||
|
|
||||||
elif sub_type == "channel.follow":
|
|
||||||
await dispatch_event("follow", username=username)
|
|
||||||
|
|
||||||
elif sub_type == "channel.raid":
|
|
||||||
raider = event.get("from_broadcaster_user_name") or event.get("from_broadcaster_user_login")
|
|
||||||
viewers = str(event.get("viewers", ""))
|
|
||||||
await dispatch_event("raid", username=raider, event_detail=viewers)
|
|
||||||
60
app/main.py
60
app/main.py
|
|
@ -1,60 +0,0 @@
|
||||||
import asyncio
|
|
||||||
import os
|
|
||||||
from contextlib import asynccontextmanager
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
from fastapi import FastAPI
|
|
||||||
from fastapi.staticfiles import StaticFiles
|
|
||||||
from starlette.middleware.sessions import SessionMiddleware
|
|
||||||
|
|
||||||
from app.database import create_db, get_admin_user
|
|
||||||
from app.routers import actions, auth, dashboard, layout, media, overlay, settings
|
|
||||||
|
|
||||||
load_dotenv()
|
|
||||||
|
|
||||||
_eventsub_task: Optional[asyncio.Task] = None
|
|
||||||
|
|
||||||
|
|
||||||
def get_eventsub_task() -> Optional[asyncio.Task]:
|
|
||||||
return _eventsub_task
|
|
||||||
|
|
||||||
|
|
||||||
async def start_eventsub() -> None:
|
|
||||||
global _eventsub_task
|
|
||||||
from app.eventsub import run_eventsub
|
|
||||||
|
|
||||||
admin = get_admin_user()
|
|
||||||
if not admin:
|
|
||||||
return
|
|
||||||
|
|
||||||
if _eventsub_task and not _eventsub_task.done():
|
|
||||||
_eventsub_task.cancel()
|
|
||||||
try:
|
|
||||||
await _eventsub_task
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
_eventsub_task = asyncio.create_task(run_eventsub(admin.broadcaster_id, admin.access_token))
|
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
|
||||||
async def lifespan(app: FastAPI):
|
|
||||||
create_db()
|
|
||||||
await start_eventsub()
|
|
||||||
yield
|
|
||||||
if _eventsub_task and not _eventsub_task.done():
|
|
||||||
_eventsub_task.cancel()
|
|
||||||
|
|
||||||
|
|
||||||
app = FastAPI(title="BashyOverlay", lifespan=lifespan)
|
|
||||||
app.add_middleware(SessionMiddleware, secret_key=os.getenv("SECRET_KEY", "dev-secret-change-me"))
|
|
||||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
||||||
|
|
||||||
app.include_router(auth.router)
|
|
||||||
app.include_router(dashboard.router)
|
|
||||||
app.include_router(overlay.router)
|
|
||||||
app.include_router(actions.router)
|
|
||||||
app.include_router(media.router)
|
|
||||||
app.include_router(layout.router)
|
|
||||||
app.include_router(settings.router)
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
from datetime import datetime
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from sqlmodel import Field, Relationship, SQLModel
|
|
||||||
|
|
||||||
|
|
||||||
class User(SQLModel, table=True):
|
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
|
||||||
twitch_id: str = Field(unique=True, index=True)
|
|
||||||
twitch_login: str
|
|
||||||
twitch_display_name: str
|
|
||||||
broadcaster_id: str
|
|
||||||
access_token: str
|
|
||||||
refresh_token: str
|
|
||||||
is_admin: bool = False
|
|
||||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
||||||
|
|
||||||
|
|
||||||
class MediaFile(SQLModel, table=True):
|
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
|
||||||
filename: str
|
|
||||||
original_name: str
|
|
||||||
file_type: str # "audio" | "video"
|
|
||||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
||||||
|
|
||||||
actions: List["EventAction"] = Relationship(back_populates="media_file")
|
|
||||||
|
|
||||||
|
|
||||||
class EventAction(SQLModel, table=True):
|
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
|
||||||
event_type: str # "sub" | "gift_sub" | "bits" | "channel_points" | "command"
|
|
||||||
event_detail: Optional[str] = None # command name, reward ID, sub tier, bits minimum
|
|
||||||
action_type: str # "sound" | "video" | "alert"
|
|
||||||
media_file_id: Optional[int] = Field(default=None, foreign_key="mediafile.id")
|
|
||||||
alert_text: Optional[str] = None
|
|
||||||
cooldown_seconds: int = 0
|
|
||||||
enabled: bool = True
|
|
||||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
||||||
|
|
||||||
media_file: Optional["MediaFile"] = Relationship(back_populates="actions")
|
|
||||||
|
|
||||||
|
|
||||||
class ActivityLog(SQLModel, table=True):
|
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
|
||||||
event_type: str
|
|
||||||
username: Optional[str] = None
|
|
||||||
detail: Optional[str] = None
|
|
||||||
action_taken: Optional[str] = None
|
|
||||||
triggered_at: datetime = Field(default_factory=datetime.utcnow)
|
|
||||||
|
|
||||||
|
|
||||||
class Settings(SQLModel, table=True):
|
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
|
||||||
twitch_channel: str = ""
|
|
||||||
gap_silence_threshold: int = 30
|
|
||||||
gap_scale_end: int = 120
|
|
||||||
gap_pitch_scale_end: int = 300
|
|
||||||
custom_css: str = ""
|
|
||||||
custom_js: str = ""
|
|
||||||
|
|
||||||
|
|
||||||
class OverlayLayout(SQLModel, table=True):
|
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
|
||||||
# Chat widget — position/size as % of canvas
|
|
||||||
chat_left: float = 72.0
|
|
||||||
chat_top: float = 5.0
|
|
||||||
chat_width: float = 27.0
|
|
||||||
chat_height: float = 88.0
|
|
||||||
chat_font_size: int = 14
|
|
||||||
chat_bg_opacity: float = 0.55
|
|
||||||
chat_text_color: str = "#ffffff"
|
|
||||||
chat_max_messages: int = 20
|
|
||||||
# Alert widget — position as % of canvas (centered via transform)
|
|
||||||
alert_left: float = 50.0
|
|
||||||
alert_top: float = 40.0
|
|
||||||
alert_font_size: int = 24
|
|
||||||
alert_bg_opacity: float = 0.8
|
|
||||||
alert_text_color: str = "#ffffff"
|
|
||||||
alert_duration: int = 4
|
|
||||||
|
|
@ -1,140 +0,0 @@
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Form, HTTPException, Request
|
|
||||||
from fastapi.responses import HTMLResponse
|
|
||||||
from sqlmodel import Session, select
|
|
||||||
|
|
||||||
from app.auth import require_user
|
|
||||||
from app.database import get_session
|
|
||||||
from app.models import ActivityLog, EventAction, MediaFile
|
|
||||||
from app.templates import templates
|
|
||||||
from app.ws import manager
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/actions")
|
|
||||||
|
|
||||||
EVENT_TYPES = ["sub", "gift_sub", "bits", "channel_points", "command", "follow", "raid"]
|
|
||||||
ACTION_TYPES = ["sound", "video", "alert"]
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("", response_class=HTMLResponse)
|
|
||||||
async def actions_page(request: Request, session: Session = Depends(get_session), user=Depends(require_user)):
|
|
||||||
actions = session.exec(select(EventAction)).all()
|
|
||||||
media_files = session.exec(select(MediaFile)).all()
|
|
||||||
return templates.TemplateResponse("actions.html", {
|
|
||||||
"request": request,
|
|
||||||
"user": user,
|
|
||||||
"actions": actions,
|
|
||||||
"media_files": media_files,
|
|
||||||
"event_types": EVENT_TYPES,
|
|
||||||
"action_types": ACTION_TYPES,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("", response_class=HTMLResponse)
|
|
||||||
async def create_action(
|
|
||||||
request: Request,
|
|
||||||
event_type: str = Form(...),
|
|
||||||
event_detail: Optional[str] = Form(default=None),
|
|
||||||
action_type: str = Form(...),
|
|
||||||
media_file_id: Optional[int] = Form(default=None),
|
|
||||||
alert_text: Optional[str] = Form(default=None),
|
|
||||||
cooldown_seconds: int = Form(default=0),
|
|
||||||
session: Session = Depends(get_session),
|
|
||||||
user=Depends(require_user),
|
|
||||||
):
|
|
||||||
action = EventAction(
|
|
||||||
event_type=event_type,
|
|
||||||
event_detail=event_detail.strip() if event_detail and event_detail.strip() else None,
|
|
||||||
action_type=action_type,
|
|
||||||
media_file_id=media_file_id or None,
|
|
||||||
alert_text=alert_text.strip() if alert_text and alert_text.strip() else None,
|
|
||||||
cooldown_seconds=max(0, cooldown_seconds),
|
|
||||||
)
|
|
||||||
session.add(action)
|
|
||||||
session.commit()
|
|
||||||
session.refresh(action)
|
|
||||||
|
|
||||||
media_files = session.exec(select(MediaFile)).all()
|
|
||||||
return templates.TemplateResponse("partials/action_row.html", {
|
|
||||||
"request": request,
|
|
||||||
"action": action,
|
|
||||||
"media_files": media_files,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{action_id}/test", response_class=HTMLResponse)
|
|
||||||
async def test_action(
|
|
||||||
request: Request,
|
|
||||||
action_id: int,
|
|
||||||
session: Session = Depends(get_session),
|
|
||||||
user=Depends(require_user),
|
|
||||||
):
|
|
||||||
action = session.get(EventAction, action_id)
|
|
||||||
if not action:
|
|
||||||
raise HTTPException(status_code=404)
|
|
||||||
|
|
||||||
media_url = None
|
|
||||||
if action.media_file_id:
|
|
||||||
media = session.get(MediaFile, action.media_file_id)
|
|
||||||
if media:
|
|
||||||
media_url = f"/static/media/{media.filename}"
|
|
||||||
|
|
||||||
await manager.broadcast({
|
|
||||||
"type": "execute",
|
|
||||||
"action_type": action.action_type,
|
|
||||||
"media_url": media_url,
|
|
||||||
"alert_text": action.alert_text or "Test!",
|
|
||||||
})
|
|
||||||
|
|
||||||
session.add(ActivityLog(
|
|
||||||
event_type="test",
|
|
||||||
username=user.twitch_display_name,
|
|
||||||
detail=f"{action.event_type}:{action.event_detail or 'any'}",
|
|
||||||
action_taken=f"{action.action_type}: {media_url or action.alert_text or ''}",
|
|
||||||
))
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
media_files = session.exec(select(MediaFile)).all()
|
|
||||||
return templates.TemplateResponse("partials/action_row.html", {
|
|
||||||
"request": request,
|
|
||||||
"action": action,
|
|
||||||
"media_files": media_files,
|
|
||||||
"just_tested": True,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{action_id}", response_class=HTMLResponse)
|
|
||||||
async def delete_action(
|
|
||||||
action_id: int,
|
|
||||||
session: Session = Depends(get_session),
|
|
||||||
user=Depends(require_user),
|
|
||||||
):
|
|
||||||
action = session.get(EventAction, action_id)
|
|
||||||
if not action:
|
|
||||||
raise HTTPException(status_code=404)
|
|
||||||
session.delete(action)
|
|
||||||
session.commit()
|
|
||||||
return HTMLResponse("")
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{action_id}/toggle", response_class=HTMLResponse)
|
|
||||||
async def toggle_action(
|
|
||||||
request: Request,
|
|
||||||
action_id: int,
|
|
||||||
session: Session = Depends(get_session),
|
|
||||||
user=Depends(require_user),
|
|
||||||
):
|
|
||||||
action = session.get(EventAction, action_id)
|
|
||||||
if not action:
|
|
||||||
raise HTTPException(status_code=404)
|
|
||||||
action.enabled = not action.enabled
|
|
||||||
session.add(action)
|
|
||||||
session.commit()
|
|
||||||
session.refresh(action)
|
|
||||||
|
|
||||||
media_files = session.exec(select(MediaFile)).all()
|
|
||||||
return templates.TemplateResponse("partials/action_row.html", {
|
|
||||||
"request": request,
|
|
||||||
"action": action,
|
|
||||||
"media_files": media_files,
|
|
||||||
})
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
||||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
|
||||||
from sqlmodel import Session, select
|
|
||||||
|
|
||||||
from app.auth import TWITCH_AUTH_URL, exchange_code, get_twitch_user, get_current_user
|
|
||||||
from app.database import get_session
|
|
||||||
from app.models import Settings, User
|
|
||||||
from app.templates import templates
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/auth")
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/login", response_class=HTMLResponse)
|
|
||||||
async def login_page(request: Request, user=Depends(get_current_user)):
|
|
||||||
if user:
|
|
||||||
return RedirectResponse(url="/")
|
|
||||||
return templates.TemplateResponse("login.html", {"request": request})
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/twitch")
|
|
||||||
async def twitch_redirect():
|
|
||||||
return RedirectResponse(url=TWITCH_AUTH_URL)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/callback")
|
|
||||||
async def twitch_callback(request: Request, code: str, session: Session = Depends(get_session)):
|
|
||||||
try:
|
|
||||||
tokens = await exchange_code(code)
|
|
||||||
twitch_user = await get_twitch_user(tokens["access_token"])
|
|
||||||
except Exception:
|
|
||||||
raise HTTPException(status_code=400, detail="OAuth failed")
|
|
||||||
|
|
||||||
user = session.exec(select(User).where(User.twitch_id == twitch_user["id"])).first()
|
|
||||||
is_first = user is None
|
|
||||||
|
|
||||||
if not user:
|
|
||||||
user = User(
|
|
||||||
twitch_id=twitch_user["id"],
|
|
||||||
twitch_login=twitch_user["login"],
|
|
||||||
twitch_display_name=twitch_user["display_name"],
|
|
||||||
broadcaster_id=twitch_user["id"],
|
|
||||||
access_token=tokens["access_token"],
|
|
||||||
refresh_token=tokens.get("refresh_token", ""),
|
|
||||||
is_admin=True,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
user.access_token = tokens["access_token"]
|
|
||||||
user.refresh_token = tokens.get("refresh_token", user.refresh_token)
|
|
||||||
user.twitch_display_name = twitch_user["display_name"]
|
|
||||||
|
|
||||||
session.add(user)
|
|
||||||
|
|
||||||
if is_first:
|
|
||||||
settings = session.exec(select(Settings)).first()
|
|
||||||
if not settings:
|
|
||||||
settings = Settings()
|
|
||||||
settings.twitch_channel = twitch_user["login"]
|
|
||||||
session.add(settings)
|
|
||||||
|
|
||||||
session.commit()
|
|
||||||
session.refresh(user)
|
|
||||||
request.session["user_id"] = user.id
|
|
||||||
|
|
||||||
if is_first:
|
|
||||||
from app.main import start_eventsub
|
|
||||||
import asyncio
|
|
||||||
asyncio.create_task(start_eventsub())
|
|
||||||
|
|
||||||
return RedirectResponse(url="/", status_code=303)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/logout")
|
|
||||||
async def logout(request: Request):
|
|
||||||
request.session.clear()
|
|
||||||
return RedirectResponse(url="/auth/login")
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
from fastapi import APIRouter, Depends, Request
|
|
||||||
from fastapi.responses import HTMLResponse
|
|
||||||
from sqlmodel import Session, select, desc
|
|
||||||
|
|
||||||
from app.auth import require_user
|
|
||||||
from app.database import get_session
|
|
||||||
from app.models import ActivityLog
|
|
||||||
from app.templates import templates
|
|
||||||
|
|
||||||
router = APIRouter()
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/", response_class=HTMLResponse)
|
|
||||||
async def dashboard(request: Request, session: Session = Depends(get_session), user=Depends(require_user)):
|
|
||||||
from app.main import get_eventsub_task
|
|
||||||
task = get_eventsub_task()
|
|
||||||
eventsub_connected = task is not None and not task.done()
|
|
||||||
|
|
||||||
logs = session.exec(
|
|
||||||
select(ActivityLog).order_by(desc(ActivityLog.triggered_at)).limit(50)
|
|
||||||
).all()
|
|
||||||
|
|
||||||
return templates.TemplateResponse("dashboard.html", {
|
|
||||||
"request": request,
|
|
||||||
"user": user,
|
|
||||||
"eventsub_connected": eventsub_connected,
|
|
||||||
"logs": logs,
|
|
||||||
})
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Form, Request
|
|
||||||
from fastapi.responses import HTMLResponse
|
|
||||||
from pydantic import BaseModel
|
|
||||||
from sqlmodel import Session, select
|
|
||||||
|
|
||||||
from app.auth import require_user
|
|
||||||
from app.database import get_session
|
|
||||||
from app.models import OverlayLayout
|
|
||||||
from app.templates import templates
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/layout")
|
|
||||||
|
|
||||||
|
|
||||||
def _get_or_create(session: Session) -> OverlayLayout:
|
|
||||||
layout = session.exec(select(OverlayLayout)).first()
|
|
||||||
if not layout:
|
|
||||||
layout = OverlayLayout()
|
|
||||||
session.add(layout)
|
|
||||||
session.commit()
|
|
||||||
session.refresh(layout)
|
|
||||||
return layout
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("", response_class=HTMLResponse)
|
|
||||||
async def layout_page(request: Request, session: Session = Depends(get_session), user=Depends(require_user)):
|
|
||||||
layout = _get_or_create(session)
|
|
||||||
return templates.TemplateResponse("layout.html", {"request": request, "user": user, "layout": layout})
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("", response_class=HTMLResponse)
|
|
||||||
async def save_styles(
|
|
||||||
request: Request,
|
|
||||||
chat_font_size: int = Form(default=14),
|
|
||||||
chat_bg_opacity: float = Form(default=0.55),
|
|
||||||
chat_text_color: str = Form(default="#ffffff"),
|
|
||||||
chat_max_messages: int = Form(default=20),
|
|
||||||
alert_font_size: int = Form(default=24),
|
|
||||||
alert_bg_opacity: float = Form(default=0.8),
|
|
||||||
alert_text_color: str = Form(default="#ffffff"),
|
|
||||||
alert_duration: int = Form(default=4),
|
|
||||||
session: Session = Depends(get_session),
|
|
||||||
user=Depends(require_user),
|
|
||||||
):
|
|
||||||
layout = _get_or_create(session)
|
|
||||||
layout.chat_font_size = max(8, min(48, chat_font_size))
|
|
||||||
layout.chat_bg_opacity = max(0.0, min(1.0, chat_bg_opacity))
|
|
||||||
layout.chat_text_color = chat_text_color
|
|
||||||
layout.chat_max_messages = max(1, min(50, chat_max_messages))
|
|
||||||
layout.alert_font_size = max(8, min(72, alert_font_size))
|
|
||||||
layout.alert_bg_opacity = max(0.0, min(1.0, alert_bg_opacity))
|
|
||||||
layout.alert_text_color = alert_text_color
|
|
||||||
layout.alert_duration = max(1, min(30, alert_duration))
|
|
||||||
session.add(layout)
|
|
||||||
session.commit()
|
|
||||||
session.refresh(layout)
|
|
||||||
return templates.TemplateResponse("layout.html", {
|
|
||||||
"request": request,
|
|
||||||
"user": user,
|
|
||||||
"layout": layout,
|
|
||||||
"saved": True,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class PositionUpdate(BaseModel):
|
|
||||||
widget: str
|
|
||||||
left: float
|
|
||||||
top: float
|
|
||||||
width: Optional[float] = None
|
|
||||||
height: Optional[float] = None
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/positions")
|
|
||||||
async def save_positions(
|
|
||||||
data: PositionUpdate,
|
|
||||||
session: Session = Depends(get_session),
|
|
||||||
user=Depends(require_user),
|
|
||||||
):
|
|
||||||
layout = _get_or_create(session)
|
|
||||||
if data.widget == "chat":
|
|
||||||
layout.chat_left = round(max(0.0, min(95.0, data.left)), 2)
|
|
||||||
layout.chat_top = round(max(0.0, min(95.0, data.top)), 2)
|
|
||||||
if data.width is not None: layout.chat_width = round(max(5.0, min(100.0, data.width)), 2)
|
|
||||||
if data.height is not None: layout.chat_height = round(max(5.0, min(100.0, data.height)), 2)
|
|
||||||
elif data.widget == "alert":
|
|
||||||
layout.alert_left = round(max(0.0, min(100.0, data.left)), 2)
|
|
||||||
layout.alert_top = round(max(0.0, min(100.0, data.top)), 2)
|
|
||||||
session.add(layout)
|
|
||||||
session.commit()
|
|
||||||
return {"ok": True}
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
import os
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Request, UploadFile
|
|
||||||
from fastapi.responses import HTMLResponse
|
|
||||||
from sqlmodel import Session, select
|
|
||||||
|
|
||||||
from app.auth import require_user
|
|
||||||
from app.database import get_session
|
|
||||||
from app.models import MediaFile
|
|
||||||
from app.templates import templates
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/media")
|
|
||||||
|
|
||||||
MEDIA_DIR = "static/media"
|
|
||||||
AUDIO_EXTENSIONS = {".mp3", ".ogg", ".wav", ".m4a"}
|
|
||||||
VIDEO_EXTENSIONS = {".mp4", ".webm", ".mov"}
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("", response_class=HTMLResponse)
|
|
||||||
async def media_page(request: Request, session: Session = Depends(get_session), user=Depends(require_user)):
|
|
||||||
files = session.exec(select(MediaFile)).all()
|
|
||||||
return templates.TemplateResponse("media.html", {
|
|
||||||
"request": request,
|
|
||||||
"user": user,
|
|
||||||
"files": files,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("", response_class=HTMLResponse)
|
|
||||||
async def upload_file(
|
|
||||||
request: Request,
|
|
||||||
file: UploadFile,
|
|
||||||
session: Session = Depends(get_session),
|
|
||||||
user=Depends(require_user),
|
|
||||||
):
|
|
||||||
ext = os.path.splitext(file.filename)[1].lower()
|
|
||||||
if ext in AUDIO_EXTENSIONS:
|
|
||||||
file_type = "audio"
|
|
||||||
elif ext in VIDEO_EXTENSIONS:
|
|
||||||
file_type = "video"
|
|
||||||
else:
|
|
||||||
raise HTTPException(status_code=400, detail="Unsupported file type")
|
|
||||||
|
|
||||||
filename = f"{uuid.uuid4().hex}{ext}"
|
|
||||||
path = os.path.join(MEDIA_DIR, filename)
|
|
||||||
|
|
||||||
content = await file.read()
|
|
||||||
with open(path, "wb") as f:
|
|
||||||
f.write(content)
|
|
||||||
|
|
||||||
media = MediaFile(filename=filename, original_name=file.filename, file_type=file_type)
|
|
||||||
session.add(media)
|
|
||||||
session.commit()
|
|
||||||
session.refresh(media)
|
|
||||||
|
|
||||||
return templates.TemplateResponse("partials/media_item.html", {
|
|
||||||
"request": request,
|
|
||||||
"file": media,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{file_id}", response_class=HTMLResponse)
|
|
||||||
async def delete_file(
|
|
||||||
file_id: int,
|
|
||||||
session: Session = Depends(get_session),
|
|
||||||
user=Depends(require_user),
|
|
||||||
):
|
|
||||||
media = session.get(MediaFile, file_id)
|
|
||||||
if not media:
|
|
||||||
raise HTTPException(status_code=404)
|
|
||||||
|
|
||||||
path = os.path.join(MEDIA_DIR, media.filename)
|
|
||||||
if os.path.exists(path):
|
|
||||||
os.remove(path)
|
|
||||||
|
|
||||||
session.delete(media)
|
|
||||||
session.commit()
|
|
||||||
return HTMLResponse("")
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
|
||||||
from fastapi.requests import Request
|
|
||||||
from fastapi.responses import HTMLResponse
|
|
||||||
|
|
||||||
from app.database import get_layout, get_settings
|
|
||||||
from app.dispatch import dispatch_event
|
|
||||||
from app.templates import templates
|
|
||||||
from app.ws import manager
|
|
||||||
|
|
||||||
router = APIRouter()
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/overlay", response_class=HTMLResponse)
|
|
||||||
async def overlay_page(request: Request):
|
|
||||||
settings = get_settings()
|
|
||||||
layout = get_layout()
|
|
||||||
return templates.TemplateResponse("overlay.html", {
|
|
||||||
"request": request,
|
|
||||||
"channel": settings.twitch_channel,
|
|
||||||
"gap_silence": settings.gap_silence_threshold,
|
|
||||||
"gap_scale_end": settings.gap_scale_end,
|
|
||||||
"gap_pitch_end": settings.gap_pitch_scale_end,
|
|
||||||
"layout": layout,
|
|
||||||
"custom_css": settings.custom_css,
|
|
||||||
"custom_js": settings.custom_js,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/overlay/preview", response_class=HTMLResponse)
|
|
||||||
async def overlay_preview(request: Request):
|
|
||||||
settings = get_settings()
|
|
||||||
layout = get_layout()
|
|
||||||
return templates.TemplateResponse("overlay_preview.html", {
|
|
||||||
"request": request,
|
|
||||||
"layout": layout,
|
|
||||||
"custom_css": settings.custom_css,
|
|
||||||
"custom_js": settings.custom_js,
|
|
||||||
"alert_duration_ms": layout.alert_duration * 1000,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@router.websocket("/ws/overlay")
|
|
||||||
async def overlay_ws(websocket: WebSocket):
|
|
||||||
await manager.connect(websocket)
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
data = await websocket.receive_json()
|
|
||||||
if data.get("type") == "command":
|
|
||||||
await dispatch_event(
|
|
||||||
"command",
|
|
||||||
username=data.get("user"),
|
|
||||||
event_detail=data.get("name"),
|
|
||||||
)
|
|
||||||
except WebSocketDisconnect:
|
|
||||||
manager.disconnect(websocket)
|
|
||||||
except Exception:
|
|
||||||
manager.disconnect(websocket)
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
from fastapi import APIRouter, Depends, Form, Request
|
|
||||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
|
||||||
from sqlmodel import Session, select
|
|
||||||
|
|
||||||
from app.auth import require_user
|
|
||||||
from app.database import get_session
|
|
||||||
from app.models import Settings
|
|
||||||
from app.templates import templates
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/settings")
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("", response_class=HTMLResponse)
|
|
||||||
async def settings_page(request: Request, session: Session = Depends(get_session), user=Depends(require_user)):
|
|
||||||
settings = session.exec(select(Settings)).first() or Settings()
|
|
||||||
return templates.TemplateResponse("settings.html", {
|
|
||||||
"request": request,
|
|
||||||
"user": user,
|
|
||||||
"settings": settings,
|
|
||||||
"overlay_url": str(request.base_url) + "overlay",
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("", response_class=HTMLResponse)
|
|
||||||
async def save_settings(
|
|
||||||
request: Request,
|
|
||||||
twitch_channel: str = Form(...),
|
|
||||||
gap_silence_threshold: int = Form(default=30),
|
|
||||||
gap_scale_end: int = Form(default=120),
|
|
||||||
gap_pitch_scale_end: int = Form(default=300),
|
|
||||||
custom_css: str = Form(default=""),
|
|
||||||
custom_js: str = Form(default=""),
|
|
||||||
session: Session = Depends(get_session),
|
|
||||||
user=Depends(require_user),
|
|
||||||
):
|
|
||||||
settings = session.exec(select(Settings)).first()
|
|
||||||
if not settings:
|
|
||||||
settings = Settings()
|
|
||||||
|
|
||||||
settings.twitch_channel = twitch_channel.strip().lstrip("#")
|
|
||||||
settings.gap_silence_threshold = max(0, gap_silence_threshold)
|
|
||||||
settings.gap_scale_end = max(settings.gap_silence_threshold + 1, gap_scale_end)
|
|
||||||
settings.gap_pitch_scale_end = max(settings.gap_scale_end + 1, gap_pitch_scale_end)
|
|
||||||
settings.custom_css = custom_css
|
|
||||||
settings.custom_js = custom_js
|
|
||||||
|
|
||||||
session.add(settings)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
return templates.TemplateResponse("settings.html", {
|
|
||||||
"request": request,
|
|
||||||
"user": user,
|
|
||||||
"settings": settings,
|
|
||||||
"overlay_url": str(request.base_url) + "overlay",
|
|
||||||
"saved": True,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/reconnect")
|
|
||||||
async def reconnect_eventsub(user=Depends(require_user)):
|
|
||||||
from app.main import start_eventsub
|
|
||||||
import asyncio
|
|
||||||
asyncio.create_task(start_eventsub())
|
|
||||||
return RedirectResponse(url="/settings", status_code=303)
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
from fastapi.templating import Jinja2Templates
|
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="templates")
|
|
||||||
28
app/ws.py
28
app/ws.py
|
|
@ -1,28 +0,0 @@
|
||||||
from fastapi import WebSocket
|
|
||||||
|
|
||||||
|
|
||||||
class ConnectionManager:
|
|
||||||
def __init__(self):
|
|
||||||
self.active: list[WebSocket] = []
|
|
||||||
|
|
||||||
async def connect(self, ws: WebSocket) -> None:
|
|
||||||
await ws.accept()
|
|
||||||
self.active.append(ws)
|
|
||||||
|
|
||||||
def disconnect(self, ws: WebSocket) -> None:
|
|
||||||
self.active.discard(ws) if hasattr(self.active, "discard") else None
|
|
||||||
if ws in self.active:
|
|
||||||
self.active.remove(ws)
|
|
||||||
|
|
||||||
async def broadcast(self, message: dict) -> None:
|
|
||||||
dead = []
|
|
||||||
for ws in self.active:
|
|
||||||
try:
|
|
||||||
await ws.send_json(message)
|
|
||||||
except Exception:
|
|
||||||
dead.append(ws)
|
|
||||||
for ws in dead:
|
|
||||||
self.disconnect(ws)
|
|
||||||
|
|
||||||
|
|
||||||
manager = ConnectionManager()
|
|
||||||
155
bashyoverlay.md
Normal file
155
bashyoverlay.md
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
# BashyOverlay — Server Notes
|
||||||
|
**Last updated:** 2026-04-26
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What it is
|
||||||
|
|
||||||
|
BashyOverlay is a Twitch stream overlay tool built with Node.js/Express. It receives Twitch EventSub events (follows, subs, bits, channel points, raids) and broadcasts them to OBS browser sources via WebSocket.
|
||||||
|
|
||||||
|
Source: `/home/bashy/projects/BashyOverlay` (Linux Mint)
|
||||||
|
Deployed to: `/opt/heystreamer/` (VPS)
|
||||||
|
|
||||||
|
**Stack:** Node.js, Express, EJS, better-sqlite3, ws, tmi.js, dotenv, express-session
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Domains
|
||||||
|
|
||||||
|
| Domain | Role |
|
||||||
|
|---|---|
|
||||||
|
| `overlay.bashynx.com` | Primary — dashboard, OBS browser source |
|
||||||
|
| `heystreamer.bashynx.com` | Alias — same app |
|
||||||
|
|
||||||
|
Both are reverse-proxied by Apache to `localhost:3000`.
|
||||||
|
WebSocket at `/ws/overlay` requires `mod_proxy_wstunnel`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Server Location
|
||||||
|
|
||||||
|
```
|
||||||
|
/opt/heystreamer/
|
||||||
|
├── src/
|
||||||
|
│ ├── server.js — entry point
|
||||||
|
│ ├── app.js — Express app, session config
|
||||||
|
│ ├── db.js — SQLite setup + migrations
|
||||||
|
│ ├── eventsub.js — Twitch EventSub WebSocket client
|
||||||
|
│ └── ...
|
||||||
|
├── views/ — EJS templates
|
||||||
|
├── public/
|
||||||
|
│ └── media/ — uploaded sounds/videos (gitignored)
|
||||||
|
├── .env — credentials (not in git)
|
||||||
|
└── database.db — SQLite database
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Systemd Service
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl status bashyoverlay
|
||||||
|
sudo systemctl restart bashyoverlay
|
||||||
|
sudo journalctl -u bashyoverlay -f
|
||||||
|
```
|
||||||
|
|
||||||
|
Service file: `/etc/systemd/system/bashyoverlay.service`
|
||||||
|
Runs as: `bashynx` user
|
||||||
|
Port: `3000` (localhost only, Apache proxies it)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Environment Variables (.env)
|
||||||
|
|
||||||
|
Required:
|
||||||
|
```
|
||||||
|
TWITCH_CLIENT_ID=
|
||||||
|
TWITCH_CLIENT_SECRET=
|
||||||
|
TWITCH_REDIRECT_URI=https://overlay.bashynx.com/auth/callback
|
||||||
|
SECRET_KEY=
|
||||||
|
APP_BASE_URL=https://overlay.bashynx.com
|
||||||
|
```
|
||||||
|
|
||||||
|
Register both callback URLs in Twitch dev console:
|
||||||
|
- `http://localhost:3000/auth/callback` (dev)
|
||||||
|
- `https://overlay.bashynx.com/auth/callback` (production)
|
||||||
|
|
||||||
|
**Note:** Use only one `TWITCH_REDIRECT_URI` entry in `.env` — duplicates cause the OAuth redirect to fail silently.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Apache Vhost
|
||||||
|
|
||||||
|
File: `/etc/apache2/sites-available/overlay.bashynx.conf`
|
||||||
|
|
||||||
|
Key directives:
|
||||||
|
```apache
|
||||||
|
RequestHeader set X-Forwarded-Proto "https"
|
||||||
|
|
||||||
|
ProxyPass /ws/overlay ws://127.0.0.1:3000/ws/overlay
|
||||||
|
ProxyPassReverse /ws/overlay ws://127.0.0.1:3000/ws/overlay
|
||||||
|
ProxyPass / http://127.0.0.1:3000/
|
||||||
|
ProxyPassReverse / http://127.0.0.1:3000/
|
||||||
|
|
||||||
|
Header always unset Content-Security-Policy
|
||||||
|
Header always set Content-Security-Policy "default-src 'self' 'unsafe-inline' 'unsafe-eval' https: wss: data:;"
|
||||||
|
```
|
||||||
|
|
||||||
|
Required modules: `proxy`, `proxy_http`, `proxy_wstunnel`, `headers`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Known Issue — Twitch OAuth Login
|
||||||
|
|
||||||
|
**Status:** Login button fires the request but redirects back to login page.
|
||||||
|
|
||||||
|
**Root cause:** Express doesn't know it's behind an HTTPS proxy, so the session
|
||||||
|
cookie's `secure: true` flag prevents it from being set over what Express perceives
|
||||||
|
as an HTTP connection.
|
||||||
|
|
||||||
|
**Fix needed (two parts):**
|
||||||
|
|
||||||
|
1. In `src/app.js` — already applied locally:
|
||||||
|
```javascript
|
||||||
|
app.set('trust proxy', 1); // before session middleware
|
||||||
|
```
|
||||||
|
|
||||||
|
2. In Apache vhost — add:
|
||||||
|
```apache
|
||||||
|
RequestHeader set X-Forwarded-Proto "https"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Redeploy from Linux Mint:
|
||||||
|
```bash
|
||||||
|
rsync -avzO --exclude='node_modules' --exclude='.env' --exclude='public/media' \
|
||||||
|
~/projects/BashyOverlay/ bashynx@185.8.164.40:/opt/heystreamer/
|
||||||
|
|
||||||
|
ssh bashynx@185.8.164.40 "cd /opt/heystreamer && npm install --production"
|
||||||
|
sudo systemctl restart bashyoverlay
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deploying Updates
|
||||||
|
|
||||||
|
From Linux Mint:
|
||||||
|
```bash
|
||||||
|
rsync -avzO \
|
||||||
|
--exclude='node_modules' \
|
||||||
|
--exclude='.env' \
|
||||||
|
--exclude='public/media' \
|
||||||
|
~/projects/BashyOverlay/ \
|
||||||
|
bashynx@37.205.8.118:/opt/heystreamer/
|
||||||
|
|
||||||
|
ssh bashynx@37.205.8.118 \
|
||||||
|
"cd /opt/heystreamer && npm install --production && sudo systemctl restart bashyoverlay"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## OBS Setup
|
||||||
|
|
||||||
|
Browser source URL: `https://overlay.bashynx.com/overlay/<uuid>`
|
||||||
|
|
||||||
|
The UUID is generated on first run and stored in the `settings` table.
|
||||||
|
Find it at: `https://overlay.bashynx.com/settings`
|
||||||
87
deploy.sh
87
deploy.sh
|
|
@ -1,72 +1,31 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -e
|
||||||
|
|
||||||
RED='\033[0;31m'
|
echo "==> Checking Node.js..."
|
||||||
GREEN='\033[0;32m'
|
node_version=$(node -v 2>/dev/null | cut -c2- | cut -d. -f1)
|
||||||
YELLOW='\033[1;33m'
|
if [ -z "$node_version" ] || [ "$node_version" -lt 18 ]; then
|
||||||
NC='\033[0m'
|
echo "ERROR: Node.js 18+ required. Install via: https://nodejs.org"
|
||||||
|
exit 1
|
||||||
info() { echo -e "${GREEN}[BashyOverlay]${NC} $1"; }
|
|
||||||
warn() { echo -e "${YELLOW}[BashyOverlay]${NC} $1"; }
|
|
||||||
error() { echo -e "${RED}[BashyOverlay]${NC} $1"; exit 1; }
|
|
||||||
|
|
||||||
# ── Python version check ──────────────────────────────────────────────────────
|
|
||||||
PYTHON=$(command -v python3 || command -v python || error "Python not found")
|
|
||||||
PY_VER=$($PYTHON -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
|
|
||||||
PY_MAJOR=$($PYTHON -c 'import sys; print(sys.version_info.major)')
|
|
||||||
PY_MINOR=$($PYTHON -c 'import sys; print(sys.version_info.minor)')
|
|
||||||
|
|
||||||
if [ "$PY_MAJOR" -lt 3 ] || { [ "$PY_MAJOR" -eq 3 ] && [ "$PY_MINOR" -lt 10 ]; }; then
|
|
||||||
error "Python 3.10+ required (found $PY_VER)"
|
|
||||||
fi
|
fi
|
||||||
info "Python $PY_VER OK"
|
echo " Node $(node -v) OK"
|
||||||
|
|
||||||
# ── Virtual environment ───────────────────────────────────────────────────────
|
echo "==> Installing dependencies..."
|
||||||
if [ ! -d ".venv" ]; then
|
npm install --production
|
||||||
info "Creating virtual environment..."
|
|
||||||
$PYTHON -m venv .venv
|
echo "==> Checking .env..."
|
||||||
|
if [ ! -f .env ]; then
|
||||||
|
echo "ERROR: .env not found. Copy .env.example and fill in your values."
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
source .venv/bin/activate
|
for var in TWITCH_CLIENT_ID TWITCH_CLIENT_SECRET SECRET_KEY APP_BASE_URL; do
|
||||||
info "Virtual environment active"
|
val=$(grep "^${var}=" .env | cut -d= -f2-)
|
||||||
|
if [ -z "$val" ] || echo "$val" | grep -q "your_\|change_this"; then
|
||||||
# ── Dependencies ──────────────────────────────────────────────────────────────
|
echo "ERROR: $var is not set in .env"
|
||||||
info "Installing dependencies..."
|
exit 1
|
||||||
pip install -q --upgrade pip
|
|
||||||
pip install -q -r requirements.txt
|
|
||||||
info "Dependencies installed"
|
|
||||||
|
|
||||||
# ── .env ──────────────────────────────────────────────────────────────────────
|
|
||||||
if [ ! -f ".env" ]; then
|
|
||||||
cp .env.example .env
|
|
||||||
warn ".env created from .env.example"
|
|
||||||
warn "Edit .env with your Twitch credentials before continuing."
|
|
||||||
warn ""
|
|
||||||
warn " TWITCH_CLIENT_ID and TWITCH_CLIENT_SECRET:"
|
|
||||||
warn " Register an app at https://dev.twitch.tv/console/apps"
|
|
||||||
warn " Set OAuth Redirect URL to: http://localhost:8000/auth/callback"
|
|
||||||
warn ""
|
|
||||||
echo -n "Have you filled in .env? [y/N] "
|
|
||||||
read -r answer
|
|
||||||
if [[ ! "$answer" =~ ^[Yy]$ ]]; then
|
|
||||||
warn "Edit .env and run deploy.sh again."
|
|
||||||
exit 0
|
|
||||||
fi
|
fi
|
||||||
fi
|
done
|
||||||
|
echo " .env OK"
|
||||||
|
|
||||||
# ── Media directory ───────────────────────────────────────────────────────────
|
echo "==> Starting BashyOverlay..."
|
||||||
mkdir -p static/media
|
node src/server.js
|
||||||
info "Media directory ready"
|
|
||||||
|
|
||||||
# ── Launch ────────────────────────────────────────────────────────────────────
|
|
||||||
HOST="${HOST:-0.0.0.0}"
|
|
||||||
PORT="${PORT:-8000}"
|
|
||||||
|
|
||||||
info "Starting BashyOverlay at http://localhost:${PORT}"
|
|
||||||
info "Open that URL in your browser to log in with Twitch."
|
|
||||||
info "OBS browser source URL: http://localhost:${PORT}/overlay"
|
|
||||||
info ""
|
|
||||||
info "Press Ctrl+C to stop."
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
exec uvicorn app.main:app --host "$HOST" --port "$PORT" --reload
|
|
||||||
|
|
|
||||||
1
node_modules/.bin/ejs
generated
vendored
Symbolic link
1
node_modules/.bin/ejs
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../ejs/bin/cli.js
|
||||||
1
node_modules/.bin/jake
generated
vendored
Symbolic link
1
node_modules/.bin/jake
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../jake/bin/cli.js
|
||||||
1
node_modules/.bin/mime
generated
vendored
Symbolic link
1
node_modules/.bin/mime
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../mime/cli.js
|
||||||
1
node_modules/.bin/mkdirp
generated
vendored
Symbolic link
1
node_modules/.bin/mkdirp
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../mkdirp/bin/cmd.js
|
||||||
1
node_modules/.bin/nodemon
generated
vendored
Symbolic link
1
node_modules/.bin/nodemon
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../nodemon/bin/nodemon.js
|
||||||
1
node_modules/.bin/nodetouch
generated
vendored
Symbolic link
1
node_modules/.bin/nodetouch
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../touch/bin/nodetouch.js
|
||||||
1
node_modules/.bin/prebuild-install
generated
vendored
Symbolic link
1
node_modules/.bin/prebuild-install
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../prebuild-install/bin.js
|
||||||
1
node_modules/.bin/rc
generated
vendored
Symbolic link
1
node_modules/.bin/rc
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../rc/cli.js
|
||||||
1
node_modules/.bin/semver
generated
vendored
Symbolic link
1
node_modules/.bin/semver
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../semver/bin/semver.js
|
||||||
1
node_modules/.bin/uuid
generated
vendored
Symbolic link
1
node_modules/.bin/uuid
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../uuid/dist/bin/uuid
|
||||||
1948
node_modules/.package-lock.json
generated
vendored
Normal file
1948
node_modules/.package-lock.json
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
243
node_modules/accepts/HISTORY.md
generated
vendored
Normal file
243
node_modules/accepts/HISTORY.md
generated
vendored
Normal file
|
|
@ -0,0 +1,243 @@
|
||||||
|
1.3.8 / 2022-02-02
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.34
|
||||||
|
- deps: mime-db@~1.51.0
|
||||||
|
* deps: negotiator@0.6.3
|
||||||
|
|
||||||
|
1.3.7 / 2019-04-29
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: negotiator@0.6.2
|
||||||
|
- Fix sorting charset, encoding, and language with extra parameters
|
||||||
|
|
||||||
|
1.3.6 / 2019-04-28
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.24
|
||||||
|
- deps: mime-db@~1.40.0
|
||||||
|
|
||||||
|
1.3.5 / 2018-02-28
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.18
|
||||||
|
- deps: mime-db@~1.33.0
|
||||||
|
|
||||||
|
1.3.4 / 2017-08-22
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.16
|
||||||
|
- deps: mime-db@~1.29.0
|
||||||
|
|
||||||
|
1.3.3 / 2016-05-02
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.11
|
||||||
|
- deps: mime-db@~1.23.0
|
||||||
|
* deps: negotiator@0.6.1
|
||||||
|
- perf: improve `Accept` parsing speed
|
||||||
|
- perf: improve `Accept-Charset` parsing speed
|
||||||
|
- perf: improve `Accept-Encoding` parsing speed
|
||||||
|
- perf: improve `Accept-Language` parsing speed
|
||||||
|
|
||||||
|
1.3.2 / 2016-03-08
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.10
|
||||||
|
- Fix extension of `application/dash+xml`
|
||||||
|
- Update primary extension for `audio/mp4`
|
||||||
|
- deps: mime-db@~1.22.0
|
||||||
|
|
||||||
|
1.3.1 / 2016-01-19
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.9
|
||||||
|
- deps: mime-db@~1.21.0
|
||||||
|
|
||||||
|
1.3.0 / 2015-09-29
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.7
|
||||||
|
- deps: mime-db@~1.19.0
|
||||||
|
* deps: negotiator@0.6.0
|
||||||
|
- Fix including type extensions in parameters in `Accept` parsing
|
||||||
|
- Fix parsing `Accept` parameters with quoted equals
|
||||||
|
- Fix parsing `Accept` parameters with quoted semicolons
|
||||||
|
- Lazy-load modules from main entry point
|
||||||
|
- perf: delay type concatenation until needed
|
||||||
|
- perf: enable strict mode
|
||||||
|
- perf: hoist regular expressions
|
||||||
|
- perf: remove closures getting spec properties
|
||||||
|
- perf: remove a closure from media type parsing
|
||||||
|
- perf: remove property delete from media type parsing
|
||||||
|
|
||||||
|
1.2.13 / 2015-09-06
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.6
|
||||||
|
- deps: mime-db@~1.18.0
|
||||||
|
|
||||||
|
1.2.12 / 2015-07-30
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.4
|
||||||
|
- deps: mime-db@~1.16.0
|
||||||
|
|
||||||
|
1.2.11 / 2015-07-16
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.3
|
||||||
|
- deps: mime-db@~1.15.0
|
||||||
|
|
||||||
|
1.2.10 / 2015-07-01
|
||||||
|
===================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.2
|
||||||
|
- deps: mime-db@~1.14.0
|
||||||
|
|
||||||
|
1.2.9 / 2015-06-08
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.1
|
||||||
|
- perf: fix deopt during mapping
|
||||||
|
|
||||||
|
1.2.8 / 2015-06-07
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.1.0
|
||||||
|
- deps: mime-db@~1.13.0
|
||||||
|
* perf: avoid argument reassignment & argument slice
|
||||||
|
* perf: avoid negotiator recursive construction
|
||||||
|
* perf: enable strict mode
|
||||||
|
* perf: remove unnecessary bitwise operator
|
||||||
|
|
||||||
|
1.2.7 / 2015-05-10
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: negotiator@0.5.3
|
||||||
|
- Fix media type parameter matching to be case-insensitive
|
||||||
|
|
||||||
|
1.2.6 / 2015-05-07
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.0.11
|
||||||
|
- deps: mime-db@~1.9.1
|
||||||
|
* deps: negotiator@0.5.2
|
||||||
|
- Fix comparing media types with quoted values
|
||||||
|
- Fix splitting media types with quoted commas
|
||||||
|
|
||||||
|
1.2.5 / 2015-03-13
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.0.10
|
||||||
|
- deps: mime-db@~1.8.0
|
||||||
|
|
||||||
|
1.2.4 / 2015-02-14
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Support Node.js 0.6
|
||||||
|
* deps: mime-types@~2.0.9
|
||||||
|
- deps: mime-db@~1.7.0
|
||||||
|
* deps: negotiator@0.5.1
|
||||||
|
- Fix preference sorting to be stable for long acceptable lists
|
||||||
|
|
||||||
|
1.2.3 / 2015-01-31
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.0.8
|
||||||
|
- deps: mime-db@~1.6.0
|
||||||
|
|
||||||
|
1.2.2 / 2014-12-30
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.0.7
|
||||||
|
- deps: mime-db@~1.5.0
|
||||||
|
|
||||||
|
1.2.1 / 2014-12-30
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.0.5
|
||||||
|
- deps: mime-db@~1.3.1
|
||||||
|
|
||||||
|
1.2.0 / 2014-12-19
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: negotiator@0.5.0
|
||||||
|
- Fix list return order when large accepted list
|
||||||
|
- Fix missing identity encoding when q=0 exists
|
||||||
|
- Remove dynamic building of Negotiator class
|
||||||
|
|
||||||
|
1.1.4 / 2014-12-10
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.0.4
|
||||||
|
- deps: mime-db@~1.3.0
|
||||||
|
|
||||||
|
1.1.3 / 2014-11-09
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.0.3
|
||||||
|
- deps: mime-db@~1.2.0
|
||||||
|
|
||||||
|
1.1.2 / 2014-10-14
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: negotiator@0.4.9
|
||||||
|
- Fix error when media type has invalid parameter
|
||||||
|
|
||||||
|
1.1.1 / 2014-09-28
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: mime-types@~2.0.2
|
||||||
|
- deps: mime-db@~1.1.0
|
||||||
|
* deps: negotiator@0.4.8
|
||||||
|
- Fix all negotiations to be case-insensitive
|
||||||
|
- Stable sort preferences of same quality according to client order
|
||||||
|
|
||||||
|
1.1.0 / 2014-09-02
|
||||||
|
==================
|
||||||
|
|
||||||
|
* update `mime-types`
|
||||||
|
|
||||||
|
1.0.7 / 2014-07-04
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fix wrong type returned from `type` when match after unknown extension
|
||||||
|
|
||||||
|
1.0.6 / 2014-06-24
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: negotiator@0.4.7
|
||||||
|
|
||||||
|
1.0.5 / 2014-06-20
|
||||||
|
==================
|
||||||
|
|
||||||
|
* fix crash when unknown extension given
|
||||||
|
|
||||||
|
1.0.4 / 2014-06-19
|
||||||
|
==================
|
||||||
|
|
||||||
|
* use `mime-types`
|
||||||
|
|
||||||
|
1.0.3 / 2014-06-11
|
||||||
|
==================
|
||||||
|
|
||||||
|
* deps: negotiator@0.4.6
|
||||||
|
- Order by specificity when quality is the same
|
||||||
|
|
||||||
|
1.0.2 / 2014-05-29
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fix interpretation when header not in request
|
||||||
|
* deps: pin negotiator@0.4.5
|
||||||
|
|
||||||
|
1.0.1 / 2014-01-18
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Identity encoding isn't always acceptable
|
||||||
|
* deps: negotiator@~0.4.0
|
||||||
|
|
||||||
|
1.0.0 / 2013-12-27
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Genesis
|
||||||
23
node_modules/accepts/LICENSE
generated
vendored
Normal file
23
node_modules/accepts/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
(The MIT License)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Jonathan Ong <me@jongleberry.com>
|
||||||
|
Copyright (c) 2015 Douglas Christopher Wilson <doug@somethingdoug.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
'Software'), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
140
node_modules/accepts/README.md
generated
vendored
Normal file
140
node_modules/accepts/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
# accepts
|
||||||
|
|
||||||
|
[![NPM Version][npm-version-image]][npm-url]
|
||||||
|
[![NPM Downloads][npm-downloads-image]][npm-url]
|
||||||
|
[![Node.js Version][node-version-image]][node-version-url]
|
||||||
|
[![Build Status][github-actions-ci-image]][github-actions-ci-url]
|
||||||
|
[![Test Coverage][coveralls-image]][coveralls-url]
|
||||||
|
|
||||||
|
Higher level content negotiation based on [negotiator](https://www.npmjs.com/package/negotiator).
|
||||||
|
Extracted from [koa](https://www.npmjs.com/package/koa) for general use.
|
||||||
|
|
||||||
|
In addition to negotiator, it allows:
|
||||||
|
|
||||||
|
- Allows types as an array or arguments list, ie `(['text/html', 'application/json'])`
|
||||||
|
as well as `('text/html', 'application/json')`.
|
||||||
|
- Allows type shorthands such as `json`.
|
||||||
|
- Returns `false` when no types match
|
||||||
|
- Treats non-existent headers as `*`
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
This is a [Node.js](https://nodejs.org/en/) module available through the
|
||||||
|
[npm registry](https://www.npmjs.com/). Installation is done using the
|
||||||
|
[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ npm install accepts
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```js
|
||||||
|
var accepts = require('accepts')
|
||||||
|
```
|
||||||
|
|
||||||
|
### accepts(req)
|
||||||
|
|
||||||
|
Create a new `Accepts` object for the given `req`.
|
||||||
|
|
||||||
|
#### .charset(charsets)
|
||||||
|
|
||||||
|
Return the first accepted charset. If nothing in `charsets` is accepted,
|
||||||
|
then `false` is returned.
|
||||||
|
|
||||||
|
#### .charsets()
|
||||||
|
|
||||||
|
Return the charsets that the request accepts, in the order of the client's
|
||||||
|
preference (most preferred first).
|
||||||
|
|
||||||
|
#### .encoding(encodings)
|
||||||
|
|
||||||
|
Return the first accepted encoding. If nothing in `encodings` is accepted,
|
||||||
|
then `false` is returned.
|
||||||
|
|
||||||
|
#### .encodings()
|
||||||
|
|
||||||
|
Return the encodings that the request accepts, in the order of the client's
|
||||||
|
preference (most preferred first).
|
||||||
|
|
||||||
|
#### .language(languages)
|
||||||
|
|
||||||
|
Return the first accepted language. If nothing in `languages` is accepted,
|
||||||
|
then `false` is returned.
|
||||||
|
|
||||||
|
#### .languages()
|
||||||
|
|
||||||
|
Return the languages that the request accepts, in the order of the client's
|
||||||
|
preference (most preferred first).
|
||||||
|
|
||||||
|
#### .type(types)
|
||||||
|
|
||||||
|
Return the first accepted type (and it is returned as the same text as what
|
||||||
|
appears in the `types` array). If nothing in `types` is accepted, then `false`
|
||||||
|
is returned.
|
||||||
|
|
||||||
|
The `types` array can contain full MIME types or file extensions. Any value
|
||||||
|
that is not a full MIME types is passed to `require('mime-types').lookup`.
|
||||||
|
|
||||||
|
#### .types()
|
||||||
|
|
||||||
|
Return the types that the request accepts, in the order of the client's
|
||||||
|
preference (most preferred first).
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Simple type negotiation
|
||||||
|
|
||||||
|
This simple example shows how to use `accepts` to return a different typed
|
||||||
|
respond body based on what the client wants to accept. The server lists it's
|
||||||
|
preferences in order and will get back the best match between the client and
|
||||||
|
server.
|
||||||
|
|
||||||
|
```js
|
||||||
|
var accepts = require('accepts')
|
||||||
|
var http = require('http')
|
||||||
|
|
||||||
|
function app (req, res) {
|
||||||
|
var accept = accepts(req)
|
||||||
|
|
||||||
|
// the order of this list is significant; should be server preferred order
|
||||||
|
switch (accept.type(['json', 'html'])) {
|
||||||
|
case 'json':
|
||||||
|
res.setHeader('Content-Type', 'application/json')
|
||||||
|
res.write('{"hello":"world!"}')
|
||||||
|
break
|
||||||
|
case 'html':
|
||||||
|
res.setHeader('Content-Type', 'text/html')
|
||||||
|
res.write('<b>hello, world!</b>')
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
// the fallback is text/plain, so no need to specify it above
|
||||||
|
res.setHeader('Content-Type', 'text/plain')
|
||||||
|
res.write('hello, world!')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
res.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
http.createServer(app).listen(3000)
|
||||||
|
```
|
||||||
|
|
||||||
|
You can test this out with the cURL program:
|
||||||
|
```sh
|
||||||
|
curl -I -H'Accept: text/html' http://localhost:3000/
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[MIT](LICENSE)
|
||||||
|
|
||||||
|
[coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/accepts/master
|
||||||
|
[coveralls-url]: https://coveralls.io/r/jshttp/accepts?branch=master
|
||||||
|
[github-actions-ci-image]: https://badgen.net/github/checks/jshttp/accepts/master?label=ci
|
||||||
|
[github-actions-ci-url]: https://github.com/jshttp/accepts/actions/workflows/ci.yml
|
||||||
|
[node-version-image]: https://badgen.net/npm/node/accepts
|
||||||
|
[node-version-url]: https://nodejs.org/en/download
|
||||||
|
[npm-downloads-image]: https://badgen.net/npm/dm/accepts
|
||||||
|
[npm-url]: https://npmjs.org/package/accepts
|
||||||
|
[npm-version-image]: https://badgen.net/npm/v/accepts
|
||||||
238
node_modules/accepts/index.js
generated
vendored
Normal file
238
node_modules/accepts/index.js
generated
vendored
Normal file
|
|
@ -0,0 +1,238 @@
|
||||||
|
/*!
|
||||||
|
* accepts
|
||||||
|
* Copyright(c) 2014 Jonathan Ong
|
||||||
|
* Copyright(c) 2015 Douglas Christopher Wilson
|
||||||
|
* MIT Licensed
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Negotiator = require('negotiator')
|
||||||
|
var mime = require('mime-types')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module exports.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = Accepts
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Accepts object for the given req.
|
||||||
|
*
|
||||||
|
* @param {object} req
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Accepts (req) {
|
||||||
|
if (!(this instanceof Accepts)) {
|
||||||
|
return new Accepts(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headers = req.headers
|
||||||
|
this.negotiator = new Negotiator(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given `type(s)` is acceptable, returning
|
||||||
|
* the best match when true, otherwise `undefined`, in which
|
||||||
|
* case you should respond with 406 "Not Acceptable".
|
||||||
|
*
|
||||||
|
* The `type` value may be a single mime type string
|
||||||
|
* such as "application/json", the extension name
|
||||||
|
* such as "json" or an array `["json", "html", "text/plain"]`. When a list
|
||||||
|
* or array is given the _best_ match, if any is returned.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* // Accept: text/html
|
||||||
|
* this.types('html');
|
||||||
|
* // => "html"
|
||||||
|
*
|
||||||
|
* // Accept: text/*, application/json
|
||||||
|
* this.types('html');
|
||||||
|
* // => "html"
|
||||||
|
* this.types('text/html');
|
||||||
|
* // => "text/html"
|
||||||
|
* this.types('json', 'text');
|
||||||
|
* // => "json"
|
||||||
|
* this.types('application/json');
|
||||||
|
* // => "application/json"
|
||||||
|
*
|
||||||
|
* // Accept: text/*, application/json
|
||||||
|
* this.types('image/png');
|
||||||
|
* this.types('png');
|
||||||
|
* // => undefined
|
||||||
|
*
|
||||||
|
* // Accept: text/*;q=.5, application/json
|
||||||
|
* this.types(['html', 'json']);
|
||||||
|
* this.types('html', 'json');
|
||||||
|
* // => "json"
|
||||||
|
*
|
||||||
|
* @param {String|Array} types...
|
||||||
|
* @return {String|Array|Boolean}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
Accepts.prototype.type =
|
||||||
|
Accepts.prototype.types = function (types_) {
|
||||||
|
var types = types_
|
||||||
|
|
||||||
|
// support flattened arguments
|
||||||
|
if (types && !Array.isArray(types)) {
|
||||||
|
types = new Array(arguments.length)
|
||||||
|
for (var i = 0; i < types.length; i++) {
|
||||||
|
types[i] = arguments[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no types, return all requested types
|
||||||
|
if (!types || types.length === 0) {
|
||||||
|
return this.negotiator.mediaTypes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// no accept header, return first given type
|
||||||
|
if (!this.headers.accept) {
|
||||||
|
return types[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
var mimes = types.map(extToMime)
|
||||||
|
var accepts = this.negotiator.mediaTypes(mimes.filter(validMime))
|
||||||
|
var first = accepts[0]
|
||||||
|
|
||||||
|
return first
|
||||||
|
? types[mimes.indexOf(first)]
|
||||||
|
: false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return accepted encodings or best fit based on `encodings`.
|
||||||
|
*
|
||||||
|
* Given `Accept-Encoding: gzip, deflate`
|
||||||
|
* an array sorted by quality is returned:
|
||||||
|
*
|
||||||
|
* ['gzip', 'deflate']
|
||||||
|
*
|
||||||
|
* @param {String|Array} encodings...
|
||||||
|
* @return {String|Array}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
Accepts.prototype.encoding =
|
||||||
|
Accepts.prototype.encodings = function (encodings_) {
|
||||||
|
var encodings = encodings_
|
||||||
|
|
||||||
|
// support flattened arguments
|
||||||
|
if (encodings && !Array.isArray(encodings)) {
|
||||||
|
encodings = new Array(arguments.length)
|
||||||
|
for (var i = 0; i < encodings.length; i++) {
|
||||||
|
encodings[i] = arguments[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no encodings, return all requested encodings
|
||||||
|
if (!encodings || encodings.length === 0) {
|
||||||
|
return this.negotiator.encodings()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.negotiator.encodings(encodings)[0] || false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return accepted charsets or best fit based on `charsets`.
|
||||||
|
*
|
||||||
|
* Given `Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5`
|
||||||
|
* an array sorted by quality is returned:
|
||||||
|
*
|
||||||
|
* ['utf-8', 'utf-7', 'iso-8859-1']
|
||||||
|
*
|
||||||
|
* @param {String|Array} charsets...
|
||||||
|
* @return {String|Array}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
Accepts.prototype.charset =
|
||||||
|
Accepts.prototype.charsets = function (charsets_) {
|
||||||
|
var charsets = charsets_
|
||||||
|
|
||||||
|
// support flattened arguments
|
||||||
|
if (charsets && !Array.isArray(charsets)) {
|
||||||
|
charsets = new Array(arguments.length)
|
||||||
|
for (var i = 0; i < charsets.length; i++) {
|
||||||
|
charsets[i] = arguments[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no charsets, return all requested charsets
|
||||||
|
if (!charsets || charsets.length === 0) {
|
||||||
|
return this.negotiator.charsets()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.negotiator.charsets(charsets)[0] || false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return accepted languages or best fit based on `langs`.
|
||||||
|
*
|
||||||
|
* Given `Accept-Language: en;q=0.8, es, pt`
|
||||||
|
* an array sorted by quality is returned:
|
||||||
|
*
|
||||||
|
* ['es', 'pt', 'en']
|
||||||
|
*
|
||||||
|
* @param {String|Array} langs...
|
||||||
|
* @return {Array|String}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
Accepts.prototype.lang =
|
||||||
|
Accepts.prototype.langs =
|
||||||
|
Accepts.prototype.language =
|
||||||
|
Accepts.prototype.languages = function (languages_) {
|
||||||
|
var languages = languages_
|
||||||
|
|
||||||
|
// support flattened arguments
|
||||||
|
if (languages && !Array.isArray(languages)) {
|
||||||
|
languages = new Array(arguments.length)
|
||||||
|
for (var i = 0; i < languages.length; i++) {
|
||||||
|
languages[i] = arguments[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no languages, return all requested languages
|
||||||
|
if (!languages || languages.length === 0) {
|
||||||
|
return this.negotiator.languages()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.negotiator.languages(languages)[0] || false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert extnames to mime.
|
||||||
|
*
|
||||||
|
* @param {String} type
|
||||||
|
* @return {String}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function extToMime (type) {
|
||||||
|
return type.indexOf('/') === -1
|
||||||
|
? mime.lookup(type)
|
||||||
|
: type
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if mime is valid.
|
||||||
|
*
|
||||||
|
* @param {String} type
|
||||||
|
* @return {String}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
function validMime (type) {
|
||||||
|
return typeof type === 'string'
|
||||||
|
}
|
||||||
47
node_modules/accepts/package.json
generated
vendored
Normal file
47
node_modules/accepts/package.json
generated
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
{
|
||||||
|
"name": "accepts",
|
||||||
|
"description": "Higher-level content negotiation",
|
||||||
|
"version": "1.3.8",
|
||||||
|
"contributors": [
|
||||||
|
"Douglas Christopher Wilson <doug@somethingdoug.com>",
|
||||||
|
"Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": "jshttp/accepts",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-types": "~2.1.34",
|
||||||
|
"negotiator": "0.6.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"deep-equal": "1.0.1",
|
||||||
|
"eslint": "7.32.0",
|
||||||
|
"eslint-config-standard": "14.1.1",
|
||||||
|
"eslint-plugin-import": "2.25.4",
|
||||||
|
"eslint-plugin-markdown": "2.2.1",
|
||||||
|
"eslint-plugin-node": "11.1.0",
|
||||||
|
"eslint-plugin-promise": "4.3.1",
|
||||||
|
"eslint-plugin-standard": "4.1.0",
|
||||||
|
"mocha": "9.2.0",
|
||||||
|
"nyc": "15.1.0"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"LICENSE",
|
||||||
|
"HISTORY.md",
|
||||||
|
"index.js"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint .",
|
||||||
|
"test": "mocha --reporter spec --check-leaks --bail test/",
|
||||||
|
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
|
||||||
|
"test-cov": "nyc --reporter=html --reporter=text npm test"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"content",
|
||||||
|
"negotiation",
|
||||||
|
"accept",
|
||||||
|
"accepts"
|
||||||
|
]
|
||||||
|
}
|
||||||
15
node_modules/anymatch/LICENSE
generated
vendored
Normal file
15
node_modules/anymatch/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
The ISC License
|
||||||
|
|
||||||
|
Copyright (c) 2019 Elan Shanker, Paul Miller (https://paulmillr.com)
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||||
|
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
87
node_modules/anymatch/README.md
generated
vendored
Normal file
87
node_modules/anymatch/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
anymatch [](https://travis-ci.org/micromatch/anymatch) [](https://coveralls.io/r/micromatch/anymatch?branch=master)
|
||||||
|
======
|
||||||
|
Javascript module to match a string against a regular expression, glob, string,
|
||||||
|
or function that takes the string as an argument and returns a truthy or falsy
|
||||||
|
value. The matcher can also be an array of any or all of these. Useful for
|
||||||
|
allowing a very flexible user-defined config to define things like file paths.
|
||||||
|
|
||||||
|
__Note: This module has Bash-parity, please be aware that Windows-style backslashes are not supported as separators. See https://github.com/micromatch/micromatch#backslashes for more information.__
|
||||||
|
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
```sh
|
||||||
|
npm install anymatch
|
||||||
|
```
|
||||||
|
|
||||||
|
#### anymatch(matchers, testString, [returnIndex], [options])
|
||||||
|
* __matchers__: (_Array|String|RegExp|Function_)
|
||||||
|
String to be directly matched, string with glob patterns, regular expression
|
||||||
|
test, function that takes the testString as an argument and returns a truthy
|
||||||
|
value if it should be matched, or an array of any number and mix of these types.
|
||||||
|
* __testString__: (_String|Array_) The string to test against the matchers. If
|
||||||
|
passed as an array, the first element of the array will be used as the
|
||||||
|
`testString` for non-function matchers, while the entire array will be applied
|
||||||
|
as the arguments for function matchers.
|
||||||
|
* __options__: (_Object_ [optional]_) Any of the [picomatch](https://github.com/micromatch/picomatch#options) options.
|
||||||
|
* __returnIndex__: (_Boolean [optional]_) If true, return the array index of
|
||||||
|
the first matcher that that testString matched, or -1 if no match, instead of a
|
||||||
|
boolean result.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const anymatch = require('anymatch');
|
||||||
|
|
||||||
|
const matchers = [ 'path/to/file.js', 'path/anyjs/**/*.js', /foo.js$/, string => string.includes('bar') && string.length > 10 ] ;
|
||||||
|
|
||||||
|
anymatch(matchers, 'path/to/file.js'); // true
|
||||||
|
anymatch(matchers, 'path/anyjs/baz.js'); // true
|
||||||
|
anymatch(matchers, 'path/to/foo.js'); // true
|
||||||
|
anymatch(matchers, 'path/to/bar.js'); // true
|
||||||
|
anymatch(matchers, 'bar.js'); // false
|
||||||
|
|
||||||
|
// returnIndex = true
|
||||||
|
anymatch(matchers, 'foo.js', {returnIndex: true}); // 2
|
||||||
|
anymatch(matchers, 'path/anyjs/foo.js', {returnIndex: true}); // 1
|
||||||
|
|
||||||
|
// any picomatc
|
||||||
|
|
||||||
|
// using globs to match directories and their children
|
||||||
|
anymatch('node_modules', 'node_modules'); // true
|
||||||
|
anymatch('node_modules', 'node_modules/somelib/index.js'); // false
|
||||||
|
anymatch('node_modules/**', 'node_modules/somelib/index.js'); // true
|
||||||
|
anymatch('node_modules/**', '/absolute/path/to/node_modules/somelib/index.js'); // false
|
||||||
|
anymatch('**/node_modules/**', '/absolute/path/to/node_modules/somelib/index.js'); // true
|
||||||
|
|
||||||
|
const matcher = anymatch(matchers);
|
||||||
|
['foo.js', 'bar.js'].filter(matcher); // [ 'foo.js' ]
|
||||||
|
anymatch master* ❯
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
#### anymatch(matchers)
|
||||||
|
You can also pass in only your matcher(s) to get a curried function that has
|
||||||
|
already been bound to the provided matching criteria. This can be used as an
|
||||||
|
`Array#filter` callback.
|
||||||
|
|
||||||
|
```js
|
||||||
|
var matcher = anymatch(matchers);
|
||||||
|
|
||||||
|
matcher('path/to/file.js'); // true
|
||||||
|
matcher('path/anyjs/baz.js', true); // 1
|
||||||
|
|
||||||
|
['foo.js', 'bar.js'].filter(matcher); // ['foo.js']
|
||||||
|
```
|
||||||
|
|
||||||
|
Changelog
|
||||||
|
----------
|
||||||
|
[See release notes page on GitHub](https://github.com/micromatch/anymatch/releases)
|
||||||
|
|
||||||
|
- **v3.0:** Removed `startIndex` and `endIndex` arguments. Node 8.x-only.
|
||||||
|
- **v2.0:** [micromatch](https://github.com/jonschlinkert/micromatch) moves away from minimatch-parity and inline with Bash. This includes handling backslashes differently (see https://github.com/micromatch/micromatch#backslashes for more information).
|
||||||
|
- **v1.2:** anymatch uses [micromatch](https://github.com/jonschlinkert/micromatch)
|
||||||
|
for glob pattern matching. Issues with glob pattern matching should be
|
||||||
|
reported directly to the [micromatch issue tracker](https://github.com/jonschlinkert/micromatch/issues).
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
[ISC](https://raw.github.com/micromatch/anymatch/master/LICENSE)
|
||||||
20
node_modules/anymatch/index.d.ts
generated
vendored
Normal file
20
node_modules/anymatch/index.d.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
type AnymatchFn = (testString: string) => boolean;
|
||||||
|
type AnymatchPattern = string|RegExp|AnymatchFn;
|
||||||
|
type AnymatchMatcher = AnymatchPattern|AnymatchPattern[]
|
||||||
|
type AnymatchTester = {
|
||||||
|
(testString: string|any[], returnIndex: true): number;
|
||||||
|
(testString: string|any[]): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type PicomatchOptions = {dot: boolean};
|
||||||
|
|
||||||
|
declare const anymatch: {
|
||||||
|
(matchers: AnymatchMatcher): AnymatchTester;
|
||||||
|
(matchers: AnymatchMatcher, testString: null, returnIndex: true | PicomatchOptions): AnymatchTester;
|
||||||
|
(matchers: AnymatchMatcher, testString: string|any[], returnIndex: true | PicomatchOptions): number;
|
||||||
|
(matchers: AnymatchMatcher, testString: string|any[]): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {AnymatchMatcher as Matcher}
|
||||||
|
export {AnymatchTester as Tester}
|
||||||
|
export default anymatch
|
||||||
104
node_modules/anymatch/index.js
generated
vendored
Normal file
104
node_modules/anymatch/index.js
generated
vendored
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
|
||||||
|
const picomatch = require('picomatch');
|
||||||
|
const normalizePath = require('normalize-path');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {(testString: string) => boolean} AnymatchFn
|
||||||
|
* @typedef {string|RegExp|AnymatchFn} AnymatchPattern
|
||||||
|
* @typedef {AnymatchPattern|AnymatchPattern[]} AnymatchMatcher
|
||||||
|
*/
|
||||||
|
const BANG = '!';
|
||||||
|
const DEFAULT_OPTIONS = {returnIndex: false};
|
||||||
|
const arrify = (item) => Array.isArray(item) ? item : [item];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {AnymatchPattern} matcher
|
||||||
|
* @param {object} options
|
||||||
|
* @returns {AnymatchFn}
|
||||||
|
*/
|
||||||
|
const createPattern = (matcher, options) => {
|
||||||
|
if (typeof matcher === 'function') {
|
||||||
|
return matcher;
|
||||||
|
}
|
||||||
|
if (typeof matcher === 'string') {
|
||||||
|
const glob = picomatch(matcher, options);
|
||||||
|
return (string) => matcher === string || glob(string);
|
||||||
|
}
|
||||||
|
if (matcher instanceof RegExp) {
|
||||||
|
return (string) => matcher.test(string);
|
||||||
|
}
|
||||||
|
return (string) => false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array<Function>} patterns
|
||||||
|
* @param {Array<Function>} negPatterns
|
||||||
|
* @param {String|Array} args
|
||||||
|
* @param {Boolean} returnIndex
|
||||||
|
* @returns {boolean|number}
|
||||||
|
*/
|
||||||
|
const matchPatterns = (patterns, negPatterns, args, returnIndex) => {
|
||||||
|
const isList = Array.isArray(args);
|
||||||
|
const _path = isList ? args[0] : args;
|
||||||
|
if (!isList && typeof _path !== 'string') {
|
||||||
|
throw new TypeError('anymatch: second argument must be a string: got ' +
|
||||||
|
Object.prototype.toString.call(_path))
|
||||||
|
}
|
||||||
|
const path = normalizePath(_path, false);
|
||||||
|
|
||||||
|
for (let index = 0; index < negPatterns.length; index++) {
|
||||||
|
const nglob = negPatterns[index];
|
||||||
|
if (nglob(path)) {
|
||||||
|
return returnIndex ? -1 : false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const applied = isList && [path].concat(args.slice(1));
|
||||||
|
for (let index = 0; index < patterns.length; index++) {
|
||||||
|
const pattern = patterns[index];
|
||||||
|
if (isList ? pattern(...applied) : pattern(path)) {
|
||||||
|
return returnIndex ? index : true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnIndex ? -1 : false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {AnymatchMatcher} matchers
|
||||||
|
* @param {Array|string} testString
|
||||||
|
* @param {object} options
|
||||||
|
* @returns {boolean|number|Function}
|
||||||
|
*/
|
||||||
|
const anymatch = (matchers, testString, options = DEFAULT_OPTIONS) => {
|
||||||
|
if (matchers == null) {
|
||||||
|
throw new TypeError('anymatch: specify first argument');
|
||||||
|
}
|
||||||
|
const opts = typeof options === 'boolean' ? {returnIndex: options} : options;
|
||||||
|
const returnIndex = opts.returnIndex || false;
|
||||||
|
|
||||||
|
// Early cache for matchers.
|
||||||
|
const mtchers = arrify(matchers);
|
||||||
|
const negatedGlobs = mtchers
|
||||||
|
.filter(item => typeof item === 'string' && item.charAt(0) === BANG)
|
||||||
|
.map(item => item.slice(1))
|
||||||
|
.map(item => picomatch(item, opts));
|
||||||
|
const patterns = mtchers
|
||||||
|
.filter(item => typeof item !== 'string' || (typeof item === 'string' && item.charAt(0) !== BANG))
|
||||||
|
.map(matcher => createPattern(matcher, opts));
|
||||||
|
|
||||||
|
if (testString == null) {
|
||||||
|
return (testString, ri = false) => {
|
||||||
|
const returnIndex = typeof ri === 'boolean' ? ri : false;
|
||||||
|
return matchPatterns(patterns, negatedGlobs, testString, returnIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchPatterns(patterns, negatedGlobs, testString, returnIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
anymatch.default = anymatch;
|
||||||
|
module.exports = anymatch;
|
||||||
48
node_modules/anymatch/package.json
generated
vendored
Normal file
48
node_modules/anymatch/package.json
generated
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
{
|
||||||
|
"name": "anymatch",
|
||||||
|
"version": "3.1.3",
|
||||||
|
"description": "Matches strings against configurable strings, globs, regular expressions, and/or functions",
|
||||||
|
"files": [
|
||||||
|
"index.js",
|
||||||
|
"index.d.ts"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"normalize-path": "^3.0.0",
|
||||||
|
"picomatch": "^2.0.4"
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"name": "Elan Shanker",
|
||||||
|
"url": "https://github.com/es128"
|
||||||
|
},
|
||||||
|
"license": "ISC",
|
||||||
|
"homepage": "https://github.com/micromatch/anymatch",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/micromatch/anymatch"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"match",
|
||||||
|
"any",
|
||||||
|
"string",
|
||||||
|
"file",
|
||||||
|
"fs",
|
||||||
|
"list",
|
||||||
|
"glob",
|
||||||
|
"regex",
|
||||||
|
"regexp",
|
||||||
|
"regular",
|
||||||
|
"expression",
|
||||||
|
"function"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"test": "nyc mocha",
|
||||||
|
"mocha": "mocha"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"mocha": "^6.1.3",
|
||||||
|
"nyc": "^14.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
node_modules/append-field/.npmignore
generated
vendored
Normal file
1
node_modules/append-field/.npmignore
generated
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
node_modules/
|
||||||
21
node_modules/append-field/LICENSE
generated
vendored
Normal file
21
node_modules/append-field/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Linus Unnebäck
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
44
node_modules/append-field/README.md
generated
vendored
Normal file
44
node_modules/append-field/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
# `append-field`
|
||||||
|
|
||||||
|
A [W3C HTML JSON forms spec](http://www.w3.org/TR/html-json-forms/) compliant
|
||||||
|
field appender (for lack of a better name). Useful for people implementing
|
||||||
|
`application/x-www-form-urlencoded` and `multipart/form-data` parsers.
|
||||||
|
|
||||||
|
It works best on objects created with `Object.create(null)`. Otherwise it might
|
||||||
|
conflict with variables from the prototype (e.g. `hasOwnProperty`).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install --save append-field
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var appendField = require('append-field')
|
||||||
|
var obj = Object.create(null)
|
||||||
|
|
||||||
|
appendField(obj, 'pets[0][species]', 'Dahut')
|
||||||
|
appendField(obj, 'pets[0][name]', 'Hypatia')
|
||||||
|
appendField(obj, 'pets[1][species]', 'Felis Stultus')
|
||||||
|
appendField(obj, 'pets[1][name]', 'Billie')
|
||||||
|
|
||||||
|
console.log(obj)
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
{ pets:
|
||||||
|
[ { species: 'Dahut', name: 'Hypatia' },
|
||||||
|
{ species: 'Felis Stultus', name: 'Billie' } ] }
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### `appendField(store, key, value)`
|
||||||
|
|
||||||
|
Adds the field named `key` with the value `value` to the object `store`.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
12
node_modules/append-field/index.js
generated
vendored
Normal file
12
node_modules/append-field/index.js
generated
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
var parsePath = require('./lib/parse-path')
|
||||||
|
var setValue = require('./lib/set-value')
|
||||||
|
|
||||||
|
function appendField (store, key, value) {
|
||||||
|
var steps = parsePath(key)
|
||||||
|
|
||||||
|
steps.reduce(function (context, step) {
|
||||||
|
return setValue(context, step, context[step.key], value)
|
||||||
|
}, store)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = appendField
|
||||||
53
node_modules/append-field/lib/parse-path.js
generated
vendored
Normal file
53
node_modules/append-field/lib/parse-path.js
generated
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
var reFirstKey = /^[^\[]*/
|
||||||
|
var reDigitPath = /^\[(\d+)\]/
|
||||||
|
var reNormalPath = /^\[([^\]]+)\]/
|
||||||
|
|
||||||
|
function parsePath (key) {
|
||||||
|
function failure () {
|
||||||
|
return [{ type: 'object', key: key, last: true }]
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstKey = reFirstKey.exec(key)[0]
|
||||||
|
if (!firstKey) return failure()
|
||||||
|
|
||||||
|
var len = key.length
|
||||||
|
var pos = firstKey.length
|
||||||
|
var tail = { type: 'object', key: firstKey }
|
||||||
|
var steps = [tail]
|
||||||
|
|
||||||
|
while (pos < len) {
|
||||||
|
var m
|
||||||
|
|
||||||
|
if (key[pos] === '[' && key[pos + 1] === ']') {
|
||||||
|
pos += 2
|
||||||
|
tail.append = true
|
||||||
|
if (pos !== len) return failure()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
m = reDigitPath.exec(key.substring(pos))
|
||||||
|
if (m !== null) {
|
||||||
|
pos += m[0].length
|
||||||
|
tail.nextType = 'array'
|
||||||
|
tail = { type: 'array', key: parseInt(m[1], 10) }
|
||||||
|
steps.push(tail)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
m = reNormalPath.exec(key.substring(pos))
|
||||||
|
if (m !== null) {
|
||||||
|
pos += m[0].length
|
||||||
|
tail.nextType = 'object'
|
||||||
|
tail = { type: 'object', key: m[1] }
|
||||||
|
steps.push(tail)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return failure()
|
||||||
|
}
|
||||||
|
|
||||||
|
tail.last = true
|
||||||
|
return steps
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = parsePath
|
||||||
64
node_modules/append-field/lib/set-value.js
generated
vendored
Normal file
64
node_modules/append-field/lib/set-value.js
generated
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
function valueType (value) {
|
||||||
|
if (value === undefined) return 'undefined'
|
||||||
|
if (Array.isArray(value)) return 'array'
|
||||||
|
if (typeof value === 'object') return 'object'
|
||||||
|
return 'scalar'
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLastValue (context, step, currentValue, entryValue) {
|
||||||
|
switch (valueType(currentValue)) {
|
||||||
|
case 'undefined':
|
||||||
|
if (step.append) {
|
||||||
|
context[step.key] = [entryValue]
|
||||||
|
} else {
|
||||||
|
context[step.key] = entryValue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'array':
|
||||||
|
context[step.key].push(entryValue)
|
||||||
|
break
|
||||||
|
case 'object':
|
||||||
|
return setLastValue(currentValue, { type: 'object', key: '', last: true }, currentValue[''], entryValue)
|
||||||
|
case 'scalar':
|
||||||
|
context[step.key] = [context[step.key], entryValue]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
function setValue (context, step, currentValue, entryValue) {
|
||||||
|
if (step.last) return setLastValue(context, step, currentValue, entryValue)
|
||||||
|
|
||||||
|
var obj
|
||||||
|
switch (valueType(currentValue)) {
|
||||||
|
case 'undefined':
|
||||||
|
if (step.nextType === 'array') {
|
||||||
|
context[step.key] = []
|
||||||
|
} else {
|
||||||
|
context[step.key] = Object.create(null)
|
||||||
|
}
|
||||||
|
return context[step.key]
|
||||||
|
case 'object':
|
||||||
|
return context[step.key]
|
||||||
|
case 'array':
|
||||||
|
if (step.nextType === 'array') {
|
||||||
|
return currentValue
|
||||||
|
}
|
||||||
|
|
||||||
|
obj = Object.create(null)
|
||||||
|
context[step.key] = obj
|
||||||
|
currentValue.forEach(function (item, i) {
|
||||||
|
if (item !== undefined) obj['' + i] = item
|
||||||
|
})
|
||||||
|
|
||||||
|
return obj
|
||||||
|
case 'scalar':
|
||||||
|
obj = Object.create(null)
|
||||||
|
obj[''] = currentValue
|
||||||
|
context[step.key] = obj
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = setValue
|
||||||
19
node_modules/append-field/package.json
generated
vendored
Normal file
19
node_modules/append-field/package.json
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "append-field",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "Linus Unnebäck <linus@folkdatorn.se>",
|
||||||
|
"main": "index.js",
|
||||||
|
"devDependencies": {
|
||||||
|
"mocha": "^2.2.4",
|
||||||
|
"standard": "^6.0.5",
|
||||||
|
"testdata-w3c-json-form": "^0.2.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "standard && mocha"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "http://github.com/LinusU/node-append-field.git"
|
||||||
|
}
|
||||||
|
}
|
||||||
19
node_modules/append-field/test/forms.js
generated
vendored
Normal file
19
node_modules/append-field/test/forms.js
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
/* eslint-env mocha */
|
||||||
|
|
||||||
|
var assert = require('assert')
|
||||||
|
var appendField = require('../')
|
||||||
|
var testData = require('testdata-w3c-json-form')
|
||||||
|
|
||||||
|
describe('Append Field', function () {
|
||||||
|
for (var test of testData) {
|
||||||
|
it('handles ' + test.name, function () {
|
||||||
|
var store = Object.create(null)
|
||||||
|
|
||||||
|
for (var field of test.fields) {
|
||||||
|
appendField(store, field.key, field.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.deepEqual(store, test.expected)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
21
node_modules/array-flatten/LICENSE
generated
vendored
Normal file
21
node_modules/array-flatten/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
43
node_modules/array-flatten/README.md
generated
vendored
Normal file
43
node_modules/array-flatten/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
# Array Flatten
|
||||||
|
|
||||||
|
[![NPM version][npm-image]][npm-url]
|
||||||
|
[![NPM downloads][downloads-image]][downloads-url]
|
||||||
|
[![Build status][travis-image]][travis-url]
|
||||||
|
[![Test coverage][coveralls-image]][coveralls-url]
|
||||||
|
|
||||||
|
> Flatten an array of nested arrays into a single flat array. Accepts an optional depth.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install array-flatten --save
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var flatten = require('array-flatten')
|
||||||
|
|
||||||
|
flatten([1, [2, [3, [4, [5], 6], 7], 8], 9])
|
||||||
|
//=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||||
|
|
||||||
|
flatten([1, [2, [3, [4, [5], 6], 7], 8], 9], 2)
|
||||||
|
//=> [1, 2, 3, [4, [5], 6], 7, 8, 9]
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
flatten(arguments) //=> [1, 2, 3]
|
||||||
|
})(1, [2, 3])
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
|
[npm-image]: https://img.shields.io/npm/v/array-flatten.svg?style=flat
|
||||||
|
[npm-url]: https://npmjs.org/package/array-flatten
|
||||||
|
[downloads-image]: https://img.shields.io/npm/dm/array-flatten.svg?style=flat
|
||||||
|
[downloads-url]: https://npmjs.org/package/array-flatten
|
||||||
|
[travis-image]: https://img.shields.io/travis/blakeembrey/array-flatten.svg?style=flat
|
||||||
|
[travis-url]: https://travis-ci.org/blakeembrey/array-flatten
|
||||||
|
[coveralls-image]: https://img.shields.io/coveralls/blakeembrey/array-flatten.svg?style=flat
|
||||||
|
[coveralls-url]: https://coveralls.io/r/blakeembrey/array-flatten?branch=master
|
||||||
64
node_modules/array-flatten/array-flatten.js
generated
vendored
Normal file
64
node_modules/array-flatten/array-flatten.js
generated
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose `arrayFlatten`.
|
||||||
|
*/
|
||||||
|
module.exports = arrayFlatten
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive flatten function with depth.
|
||||||
|
*
|
||||||
|
* @param {Array} array
|
||||||
|
* @param {Array} result
|
||||||
|
* @param {Number} depth
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
function flattenWithDepth (array, result, depth) {
|
||||||
|
for (var i = 0; i < array.length; i++) {
|
||||||
|
var value = array[i]
|
||||||
|
|
||||||
|
if (depth > 0 && Array.isArray(value)) {
|
||||||
|
flattenWithDepth(value, result, depth - 1)
|
||||||
|
} else {
|
||||||
|
result.push(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive flatten function. Omitting depth is slightly faster.
|
||||||
|
*
|
||||||
|
* @param {Array} array
|
||||||
|
* @param {Array} result
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
function flattenForever (array, result) {
|
||||||
|
for (var i = 0; i < array.length; i++) {
|
||||||
|
var value = array[i]
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
flattenForever(value, result)
|
||||||
|
} else {
|
||||||
|
result.push(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flatten an array, with the ability to define a depth.
|
||||||
|
*
|
||||||
|
* @param {Array} array
|
||||||
|
* @param {Number} depth
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
function arrayFlatten (array, depth) {
|
||||||
|
if (depth == null) {
|
||||||
|
return flattenForever(array, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
return flattenWithDepth(array, [], depth)
|
||||||
|
}
|
||||||
39
node_modules/array-flatten/package.json
generated
vendored
Normal file
39
node_modules/array-flatten/package.json
generated
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"name": "array-flatten",
|
||||||
|
"version": "1.1.1",
|
||||||
|
"description": "Flatten an array of nested arrays into a single flat array",
|
||||||
|
"main": "array-flatten.js",
|
||||||
|
"files": [
|
||||||
|
"array-flatten.js",
|
||||||
|
"LICENSE"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"test": "istanbul cover _mocha -- -R spec"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git://github.com/blakeembrey/array-flatten.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"array",
|
||||||
|
"flatten",
|
||||||
|
"arguments",
|
||||||
|
"depth"
|
||||||
|
],
|
||||||
|
"author": {
|
||||||
|
"name": "Blake Embrey",
|
||||||
|
"email": "hello@blakeembrey.com",
|
||||||
|
"url": "http://blakeembrey.me"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/blakeembrey/array-flatten/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/blakeembrey/array-flatten",
|
||||||
|
"devDependencies": {
|
||||||
|
"istanbul": "^0.3.13",
|
||||||
|
"mocha": "^2.2.4",
|
||||||
|
"pre-commit": "^1.0.7",
|
||||||
|
"standard": "^3.7.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
351
node_modules/async/CHANGELOG.md
generated
vendored
Normal file
351
node_modules/async/CHANGELOG.md
generated
vendored
Normal file
|
|
@ -0,0 +1,351 @@
|
||||||
|
# v3.2.5
|
||||||
|
- Ensure `Error` objects such as `AggregateError` are propagated without modification (#1920)
|
||||||
|
|
||||||
|
# v3.2.4
|
||||||
|
- Fix a bug in `priorityQueue` where it didn't wait for the result. (#1725)
|
||||||
|
- Fix a bug where `unshiftAsync` was included in `priorityQueue`. (#1790)
|
||||||
|
|
||||||
|
# v3.2.3
|
||||||
|
- Fix bugs in comment parsing in `autoInject`. (#1767, #1780)
|
||||||
|
|
||||||
|
# v3.2.2
|
||||||
|
- Fix potential prototype pollution exploit
|
||||||
|
|
||||||
|
# v3.2.1
|
||||||
|
- Use `queueMicrotask` if available to the environment (#1761)
|
||||||
|
- Minor perf improvement in `priorityQueue` (#1727)
|
||||||
|
- More examples in documentation (#1726)
|
||||||
|
- Various doc fixes (#1708, #1712, #1717, #1740, #1739, #1749, #1756)
|
||||||
|
- Improved test coverage (#1754)
|
||||||
|
|
||||||
|
# v3.2.0
|
||||||
|
- Fix a bug in Safari related to overwriting `func.name`
|
||||||
|
- Remove built-in browserify configuration (#1653)
|
||||||
|
- Varios doc fixes (#1688, #1703, #1704)
|
||||||
|
|
||||||
|
# v3.1.1
|
||||||
|
- Allow redefining `name` property on wrapped functions.
|
||||||
|
|
||||||
|
# v3.1.0
|
||||||
|
|
||||||
|
- Added `q.pushAsync` and `q.unshiftAsync`, analagous to `q.push` and `q.unshift`, except they always do not accept a callback, and reject if processing the task errors. (#1659)
|
||||||
|
- Promises returned from `q.push` and `q.unshift` when a callback is not passed now resolve even if an error ocurred. (#1659)
|
||||||
|
- Fixed a parsing bug in `autoInject` with complicated function bodies (#1663)
|
||||||
|
- Added ES6+ configuration for Browserify bundlers (#1653)
|
||||||
|
- Various doc fixes (#1664, #1658, #1665, #1652)
|
||||||
|
|
||||||
|
# v3.0.1
|
||||||
|
|
||||||
|
## Bug fixes
|
||||||
|
- Fixed a regression where arrays passed to `queue` and `cargo` would be completely flattened. (#1645)
|
||||||
|
- Clarified Async's browser support (#1643)
|
||||||
|
|
||||||
|
|
||||||
|
# v3.0.0
|
||||||
|
|
||||||
|
The `async`/`await` release!
|
||||||
|
|
||||||
|
There are a lot of new features and subtle breaking changes in this major version, but the biggest feature is that most Async methods return a Promise if you omit the callback, meaning you can `await` them from within an `async` function.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const results = await async.mapLimit(urls, 5, async url => {
|
||||||
|
const resp = await fetch(url)
|
||||||
|
return resp.body
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Breaking Changes
|
||||||
|
- Most Async methods return a Promise when the final callback is omitted, making them `await`-able! (#1572)
|
||||||
|
- We are now making heavy use of ES2015 features, this means we have dropped out-of-the-box support for Node 4 and earlier, and many old versions of browsers. (#1541, #1553)
|
||||||
|
- In `queue`, `priorityQueue`, `cargo` and `cargoQueue`, the "event"-style methods, like `q.drain` and `q.saturated` are now methods that register a callback, rather than properties you assign a callback to. They are now of the form `q.drain(callback)`. If you do not pass a callback a Promise will be returned for the next occurrence of the event, making them `await`-able, e.g. `await q.drain()`. (#1586, #1641)
|
||||||
|
- Calling `callback(false)` will cancel an async method, preventing further iteration and callback calls. This is useful for preventing memory leaks when you break out of an async flow by calling an outer callback. (#1064, #1542)
|
||||||
|
- `during` and `doDuring` have been removed, and instead `whilst`, `doWhilst`, `until` and `doUntil` now have asynchronous `test` functions. (#850, #1557)
|
||||||
|
- `limits` of less than 1 now cause an error to be thrown in queues and collection methods. (#1249, #1552)
|
||||||
|
- `memoize` no longer memoizes errors (#1465, #1466)
|
||||||
|
- `applyEach`/`applyEachSeries` have a simpler interface, to make them more easily type-able. It always returns a function that takes in a single callback argument. If that callback is omitted, a promise is returned, making it awaitable. (#1228, #1640)
|
||||||
|
|
||||||
|
## New Features
|
||||||
|
- Async generators are now supported in all the Collection methods. (#1560)
|
||||||
|
- Added `cargoQueue`, a queue with both `concurrency` and `payload` size parameters. (#1567)
|
||||||
|
- Queue objects returned from `queue` now have a `Symbol.iterator` method, meaning they can be iterated over to inspect the current list of items in the queue. (#1459, #1556)
|
||||||
|
- A ESM-flavored `async.mjs` is included in the `async` package. This is described in the `package.json` `"module"` field, meaning it should be automatically used by Webpack and other compatible bundlers.
|
||||||
|
|
||||||
|
## Bug fixes
|
||||||
|
- Better handle arbitrary error objects in `asyncify` (#1568, #1569)
|
||||||
|
|
||||||
|
## Other
|
||||||
|
- Removed Lodash as a dependency (#1283, #1528)
|
||||||
|
- Miscellaneous docs fixes (#1393, #1501, #1540, #1543, #1558, #1563, #1564, #1579, #1581)
|
||||||
|
- Miscellaneous test fixes (#1538)
|
||||||
|
|
||||||
|
-------
|
||||||
|
|
||||||
|
# v2.6.1
|
||||||
|
- Updated lodash to prevent `npm audit` warnings. (#1532, #1533)
|
||||||
|
- Made `async-es` more optimized for webpack users (#1517)
|
||||||
|
- Fixed a stack overflow with large collections and a synchronous iterator (#1514)
|
||||||
|
- Various small fixes/chores (#1505, #1511, #1527, #1530)
|
||||||
|
|
||||||
|
# v2.6.0
|
||||||
|
- Added missing aliases for many methods. Previously, you could not (e.g.) `require('async/find')` or use `async.anyLimit`. (#1483)
|
||||||
|
- Improved `queue` performance. (#1448, #1454)
|
||||||
|
- Add missing sourcemap (#1452, #1453)
|
||||||
|
- Various doc updates (#1448, #1471, #1483)
|
||||||
|
|
||||||
|
# v2.5.0
|
||||||
|
- Added `concatLimit`, the `Limit` equivalent of [`concat`](https://caolan.github.io/async/docs.html#concat) ([#1426](https://github.com/caolan/async/issues/1426), [#1430](https://github.com/caolan/async/pull/1430))
|
||||||
|
- `concat` improvements: it now preserves order, handles falsy values and the `iteratee` callback takes a variable number of arguments ([#1437](https://github.com/caolan/async/issues/1437), [#1436](https://github.com/caolan/async/pull/1436))
|
||||||
|
- Fixed an issue in `queue` where there was a size discrepancy between `workersList().length` and `running()` ([#1428](https://github.com/caolan/async/issues/1428), [#1429](https://github.com/caolan/async/pull/1429))
|
||||||
|
- Various doc fixes ([#1422](https://github.com/caolan/async/issues/1422), [#1424](https://github.com/caolan/async/pull/1424))
|
||||||
|
|
||||||
|
# v2.4.1
|
||||||
|
- Fixed a bug preventing functions wrapped with `timeout()` from being re-used. ([#1418](https://github.com/caolan/async/issues/1418), [#1419](https://github.com/caolan/async/issues/1419))
|
||||||
|
|
||||||
|
# v2.4.0
|
||||||
|
- Added `tryEach`, for running async functions in parallel, where you only expect one to succeed. ([#1365](https://github.com/caolan/async/issues/1365), [#687](https://github.com/caolan/async/issues/687))
|
||||||
|
- Improved performance, most notably in `parallel` and `waterfall` ([#1395](https://github.com/caolan/async/issues/1395))
|
||||||
|
- Added `queue.remove()`, for removing items in a `queue` ([#1397](https://github.com/caolan/async/issues/1397), [#1391](https://github.com/caolan/async/issues/1391))
|
||||||
|
- Fixed using `eval`, preventing Async from running in pages with Content Security Policy ([#1404](https://github.com/caolan/async/issues/1404), [#1403](https://github.com/caolan/async/issues/1403))
|
||||||
|
- Fixed errors thrown in an `asyncify`ed function's callback being caught by the underlying Promise ([#1408](https://github.com/caolan/async/issues/1408))
|
||||||
|
- Fixed timing of `queue.empty()` ([#1367](https://github.com/caolan/async/issues/1367))
|
||||||
|
- Various doc fixes ([#1314](https://github.com/caolan/async/issues/1314), [#1394](https://github.com/caolan/async/issues/1394), [#1412](https://github.com/caolan/async/issues/1412))
|
||||||
|
|
||||||
|
# v2.3.0
|
||||||
|
- Added support for ES2017 `async` functions. Wherever you can pass a Node-style/CPS function that uses a callback, you can also pass an `async` function. Previously, you had to wrap `async` functions with `asyncify`. The caveat is that it will only work if `async` functions are supported natively in your environment, transpiled implementations can't be detected. ([#1386](https://github.com/caolan/async/issues/1386), [#1390](https://github.com/caolan/async/issues/1390))
|
||||||
|
- Small doc fix ([#1392](https://github.com/caolan/async/issues/1392))
|
||||||
|
|
||||||
|
# v2.2.0
|
||||||
|
- Added `groupBy`, and the `Series`/`Limit` equivalents, analogous to [`_.groupBy`](http://lodash.com/docs#groupBy) ([#1364](https://github.com/caolan/async/issues/1364))
|
||||||
|
- Fixed `transform` bug when `callback` was not passed ([#1381](https://github.com/caolan/async/issues/1381))
|
||||||
|
- Added note about `reflect` to `parallel` docs ([#1385](https://github.com/caolan/async/issues/1385))
|
||||||
|
|
||||||
|
# v2.1.5
|
||||||
|
- Fix `auto` bug when function names collided with Array.prototype ([#1358](https://github.com/caolan/async/issues/1358))
|
||||||
|
- Improve some error messages ([#1349](https://github.com/caolan/async/issues/1349))
|
||||||
|
- Avoid stack overflow case in queue
|
||||||
|
- Fixed an issue in `some`, `every` and `find` where processing would continue after the result was determined.
|
||||||
|
- Cleanup implementations of `some`, `every` and `find`
|
||||||
|
|
||||||
|
# v2.1.3
|
||||||
|
- Make bundle size smaller
|
||||||
|
- Create optimized hotpath for `filter` in array case.
|
||||||
|
|
||||||
|
# v2.1.2
|
||||||
|
- Fixed a stackoverflow bug with `detect`, `some`, `every` on large inputs ([#1293](https://github.com/caolan/async/issues/1293)).
|
||||||
|
|
||||||
|
# v2.1.0
|
||||||
|
|
||||||
|
- `retry` and `retryable` now support an optional `errorFilter` function that determines if the `task` should retry on the error ([#1256](https://github.com/caolan/async/issues/1256), [#1261](https://github.com/caolan/async/issues/1261))
|
||||||
|
- Optimized array iteration in `race`, `cargo`, `queue`, and `priorityQueue` ([#1253](https://github.com/caolan/async/issues/1253))
|
||||||
|
- Added alias documentation to doc site ([#1251](https://github.com/caolan/async/issues/1251), [#1254](https://github.com/caolan/async/issues/1254))
|
||||||
|
- Added [BootStrap scrollspy](http://getbootstrap.com/javascript/#scrollspy) to docs to highlight in the sidebar the current method being viewed ([#1289](https://github.com/caolan/async/issues/1289), [#1300](https://github.com/caolan/async/issues/1300))
|
||||||
|
- Various minor doc fixes ([#1263](https://github.com/caolan/async/issues/1263), [#1264](https://github.com/caolan/async/issues/1264), [#1271](https://github.com/caolan/async/issues/1271), [#1278](https://github.com/caolan/async/issues/1278), [#1280](https://github.com/caolan/async/issues/1280), [#1282](https://github.com/caolan/async/issues/1282), [#1302](https://github.com/caolan/async/issues/1302))
|
||||||
|
|
||||||
|
# v2.0.1
|
||||||
|
|
||||||
|
- Significantly optimized all iteration based collection methods such as `each`, `map`, `filter`, etc ([#1245](https://github.com/caolan/async/issues/1245), [#1246](https://github.com/caolan/async/issues/1246), [#1247](https://github.com/caolan/async/issues/1247)).
|
||||||
|
|
||||||
|
# v2.0.0
|
||||||
|
|
||||||
|
Lots of changes here!
|
||||||
|
|
||||||
|
First and foremost, we have a slick new [site for docs](https://caolan.github.io/async/). Special thanks to [**@hargasinski**](https://github.com/hargasinski) for his work converting our old docs to `jsdoc` format and implementing the new website. Also huge ups to [**@ivanseidel**](https://github.com/ivanseidel) for designing our new logo. It was a long process for both of these tasks, but I think these changes turned out extraordinary well.
|
||||||
|
|
||||||
|
The biggest feature is modularization. You can now `require("async/series")` to only require the `series` function. Every Async library function is available this way. You still can `require("async")` to require the entire library, like you could do before.
|
||||||
|
|
||||||
|
We also provide Async as a collection of ES2015 modules. You can now `import {each} from 'async-es'` or `import waterfall from 'async-es/waterfall'`. If you are using only a few Async functions, and are using a ES bundler such as Rollup, this can significantly lower your build size.
|
||||||
|
|
||||||
|
Major thanks to [**@Kikobeats**](github.com/Kikobeats), [**@aearly**](github.com/aearly) and [**@megawac**](github.com/megawac) for doing the majority of the modularization work, as well as [**@jdalton**](github.com/jdalton) and [**@Rich-Harris**](github.com/Rich-Harris) for advisory work on the general modularization strategy.
|
||||||
|
|
||||||
|
Another one of the general themes of the 2.0 release is standardization of what an "async" function is. We are now more strictly following the node-style continuation passing style. That is, an async function is a function that:
|
||||||
|
|
||||||
|
1. Takes a variable number of arguments
|
||||||
|
2. The last argument is always a callback
|
||||||
|
3. The callback can accept any number of arguments
|
||||||
|
4. The first argument passed to the callback will be treated as an error result, if the argument is truthy
|
||||||
|
5. Any number of result arguments can be passed after the "error" argument
|
||||||
|
6. The callback is called once and exactly once, either on the same tick or later tick of the JavaScript event loop.
|
||||||
|
|
||||||
|
There were several cases where Async accepted some functions that did not strictly have these properties, most notably `auto`, `every`, `some`, `filter`, `reject` and `detect`.
|
||||||
|
|
||||||
|
Another theme is performance. We have eliminated internal deferrals in all cases where they make sense. For example, in `waterfall` and `auto`, there was a `setImmediate` between each task -- these deferrals have been removed. A `setImmediate` call can add up to 1ms of delay. This might not seem like a lot, but it can add up if you are using many Async functions in the course of processing a HTTP request, for example. Nearly all asynchronous functions that do I/O already have some sort of deferral built in, so the extra deferral is unnecessary. The trade-off of this change is removing our built-in stack-overflow defense. Many synchronous callback calls in series can quickly overflow the JS call stack. If you do have a function that is sometimes synchronous (calling its callback on the same tick), and are running into stack overflows, wrap it with `async.ensureAsync()`.
|
||||||
|
|
||||||
|
Another big performance win has been re-implementing `queue`, `cargo`, and `priorityQueue` with [doubly linked lists](https://en.wikipedia.org/wiki/Doubly_linked_list) instead of arrays. This has lead to queues being an order of [magnitude faster on large sets of tasks](https://github.com/caolan/async/pull/1205).
|
||||||
|
|
||||||
|
## New Features
|
||||||
|
|
||||||
|
- Async is now modularized. Individual functions can be `require()`d from the main package. (`require('async/auto')`) ([#984](https://github.com/caolan/async/issues/984), [#996](https://github.com/caolan/async/issues/996))
|
||||||
|
- Async is also available as a collection of ES2015 modules in the new `async-es` package. (`import {forEachSeries} from 'async-es'`) ([#984](https://github.com/caolan/async/issues/984), [#996](https://github.com/caolan/async/issues/996))
|
||||||
|
- Added `race`, analogous to `Promise.race()`. It will run an array of async tasks in parallel and will call its callback with the result of the first task to respond. ([#568](https://github.com/caolan/async/issues/568), [#1038](https://github.com/caolan/async/issues/1038))
|
||||||
|
- Collection methods now accept ES2015 iterators. Maps, Sets, and anything that implements the iterator spec can now be passed directly to `each`, `map`, `parallel`, etc.. ([#579](https://github.com/caolan/async/issues/579), [#839](https://github.com/caolan/async/issues/839), [#1074](https://github.com/caolan/async/issues/1074))
|
||||||
|
- Added `mapValues`, for mapping over the properties of an object and returning an object with the same keys. ([#1157](https://github.com/caolan/async/issues/1157), [#1177](https://github.com/caolan/async/issues/1177))
|
||||||
|
- Added `timeout`, a wrapper for an async function that will make the task time-out after the specified time. ([#1007](https://github.com/caolan/async/issues/1007), [#1027](https://github.com/caolan/async/issues/1027))
|
||||||
|
- Added `reflect` and `reflectAll`, analagous to [`Promise.reflect()`](http://bluebirdjs.com/docs/api/reflect.html), a wrapper for async tasks that always succeeds, by gathering results and errors into an object. ([#942](https://github.com/caolan/async/issues/942), [#1012](https://github.com/caolan/async/issues/1012), [#1095](https://github.com/caolan/async/issues/1095))
|
||||||
|
- `constant` supports dynamic arguments -- it will now always use its last argument as the callback. ([#1016](https://github.com/caolan/async/issues/1016), [#1052](https://github.com/caolan/async/issues/1052))
|
||||||
|
- `setImmediate` and `nextTick` now support arguments to partially apply to the deferred function, like the node-native versions do. ([#940](https://github.com/caolan/async/issues/940), [#1053](https://github.com/caolan/async/issues/1053))
|
||||||
|
- `auto` now supports resolving cyclic dependencies using [Kahn's algorithm](https://en.wikipedia.org/wiki/Topological_sorting#Kahn.27s_algorithm) ([#1140](https://github.com/caolan/async/issues/1140)).
|
||||||
|
- Added `autoInject`, a relative of `auto` that automatically spreads a task's dependencies as arguments to the task function. ([#608](https://github.com/caolan/async/issues/608), [#1055](https://github.com/caolan/async/issues/1055), [#1099](https://github.com/caolan/async/issues/1099), [#1100](https://github.com/caolan/async/issues/1100))
|
||||||
|
- You can now limit the concurrency of `auto` tasks. ([#635](https://github.com/caolan/async/issues/635), [#637](https://github.com/caolan/async/issues/637))
|
||||||
|
- Added `retryable`, a relative of `retry` that wraps an async function, making it retry when called. ([#1058](https://github.com/caolan/async/issues/1058))
|
||||||
|
- `retry` now supports specifying a function that determines the next time interval, useful for exponential backoff, logging and other retry strategies. ([#1161](https://github.com/caolan/async/issues/1161))
|
||||||
|
- `retry` will now pass all of the arguments the task function was resolved with to the callback ([#1231](https://github.com/caolan/async/issues/1231)).
|
||||||
|
- Added `q.unsaturated` -- callback called when a `queue`'s number of running workers falls below a threshold. ([#868](https://github.com/caolan/async/issues/868), [#1030](https://github.com/caolan/async/issues/1030), [#1033](https://github.com/caolan/async/issues/1033), [#1034](https://github.com/caolan/async/issues/1034))
|
||||||
|
- Added `q.error` -- a callback called whenever a `queue` task calls its callback with an error. ([#1170](https://github.com/caolan/async/issues/1170))
|
||||||
|
- `applyEach` and `applyEachSeries` now pass results to the final callback. ([#1088](https://github.com/caolan/async/issues/1088))
|
||||||
|
|
||||||
|
## Breaking changes
|
||||||
|
|
||||||
|
- Calling a callback more than once is considered an error, and an error will be thrown. This had an explicit breaking change in `waterfall`. If you were relying on this behavior, you should more accurately represent your control flow as an event emitter or stream. ([#814](https://github.com/caolan/async/issues/814), [#815](https://github.com/caolan/async/issues/815), [#1048](https://github.com/caolan/async/issues/1048), [#1050](https://github.com/caolan/async/issues/1050))
|
||||||
|
- `auto` task functions now always take the callback as the last argument. If a task has dependencies, the `results` object will be passed as the first argument. To migrate old task functions, wrap them with [`_.flip`](https://lodash.com/docs#flip) ([#1036](https://github.com/caolan/async/issues/1036), [#1042](https://github.com/caolan/async/issues/1042))
|
||||||
|
- Internal `setImmediate` calls have been refactored away. This may make existing flows vulnerable to stack overflows if you use many synchronous functions in series. Use `ensureAsync` to work around this. ([#696](https://github.com/caolan/async/issues/696), [#704](https://github.com/caolan/async/issues/704), [#1049](https://github.com/caolan/async/issues/1049), [#1050](https://github.com/caolan/async/issues/1050))
|
||||||
|
- `map` used to return an object when iterating over an object. `map` now always returns an array, like in other libraries. The previous object behavior has been split out into `mapValues`. ([#1157](https://github.com/caolan/async/issues/1157), [#1177](https://github.com/caolan/async/issues/1177))
|
||||||
|
- `filter`, `reject`, `some`, `every`, `detect` and their families like `{METHOD}Series` and `{METHOD}Limit` now expect an error as the first callback argument, rather than just a simple boolean. Pass `null` as the first argument, or use `fs.access` instead of `fs.exists`. ([#118](https://github.com/caolan/async/issues/118), [#774](https://github.com/caolan/async/issues/774), [#1028](https://github.com/caolan/async/issues/1028), [#1041](https://github.com/caolan/async/issues/1041))
|
||||||
|
- `{METHOD}` and `{METHOD}Series` are now implemented in terms of `{METHOD}Limit`. This is a major internal simplification, and is not expected to cause many problems, but it does subtly affect how functions execute internally. ([#778](https://github.com/caolan/async/issues/778), [#847](https://github.com/caolan/async/issues/847))
|
||||||
|
- `retry`'s callback is now optional. Previously, omitting the callback would partially apply the function, meaning it could be passed directly as a task to `series` or `auto`. The partially applied "control-flow" behavior has been separated out into `retryable`. ([#1054](https://github.com/caolan/async/issues/1054), [#1058](https://github.com/caolan/async/issues/1058))
|
||||||
|
- The test function for `whilst`, `until`, and `during` used to be passed non-error args from the iteratee function's callback, but this led to weirdness where the first call of the test function would be passed no args. We have made it so the test function is never passed extra arguments, and only the `doWhilst`, `doUntil`, and `doDuring` functions pass iteratee callback arguments to the test function ([#1217](https://github.com/caolan/async/issues/1217), [#1224](https://github.com/caolan/async/issues/1224))
|
||||||
|
- The `q.tasks` array has been renamed `q._tasks` and is now implemented as a doubly linked list (DLL). Any code that used to interact with this array will need to be updated to either use the provided helpers or support DLLs ([#1205](https://github.com/caolan/async/issues/1205)).
|
||||||
|
- The timing of the `q.saturated()` callback in a `queue` has been modified to better reflect when tasks pushed to the queue will start queueing. ([#724](https://github.com/caolan/async/issues/724), [#1078](https://github.com/caolan/async/issues/1078))
|
||||||
|
- Removed `iterator` method in favour of [ES2015 iterator protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators ) which natively supports arrays ([#1237](https://github.com/caolan/async/issues/1237))
|
||||||
|
- Dropped support for Component, Jam, SPM, and Volo ([#1175](https://github.com/caolan/async/issues/1175), #[#176](https://github.com/caolan/async/issues/176))
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
|
||||||
|
- Improved handling of no dependency cases in `auto` & `autoInject` ([#1147](https://github.com/caolan/async/issues/1147)).
|
||||||
|
- Fixed a bug where the callback generated by `asyncify` with `Promises` could resolve twice ([#1197](https://github.com/caolan/async/issues/1197)).
|
||||||
|
- Fixed several documented optional callbacks not actually being optional ([#1223](https://github.com/caolan/async/issues/1223)).
|
||||||
|
|
||||||
|
## Other
|
||||||
|
|
||||||
|
- Added `someSeries` and `everySeries` for symmetry, as well as a complete set of `any`/`anyLimit`/`anySeries` and `all`/`/allLmit`/`allSeries` aliases.
|
||||||
|
- Added `find` as an alias for `detect. (as well as `findLimit` and `findSeries`).
|
||||||
|
- Various doc fixes ([#1005](https://github.com/caolan/async/issues/1005), [#1008](https://github.com/caolan/async/issues/1008), [#1010](https://github.com/caolan/async/issues/1010), [#1015](https://github.com/caolan/async/issues/1015), [#1021](https://github.com/caolan/async/issues/1021), [#1037](https://github.com/caolan/async/issues/1037), [#1039](https://github.com/caolan/async/issues/1039), [#1051](https://github.com/caolan/async/issues/1051), [#1102](https://github.com/caolan/async/issues/1102), [#1107](https://github.com/caolan/async/issues/1107), [#1121](https://github.com/caolan/async/issues/1121), [#1123](https://github.com/caolan/async/issues/1123), [#1129](https://github.com/caolan/async/issues/1129), [#1135](https://github.com/caolan/async/issues/1135), [#1138](https://github.com/caolan/async/issues/1138), [#1141](https://github.com/caolan/async/issues/1141), [#1153](https://github.com/caolan/async/issues/1153), [#1216](https://github.com/caolan/async/issues/1216), [#1217](https://github.com/caolan/async/issues/1217), [#1232](https://github.com/caolan/async/issues/1232), [#1233](https://github.com/caolan/async/issues/1233), [#1236](https://github.com/caolan/async/issues/1236), [#1238](https://github.com/caolan/async/issues/1238))
|
||||||
|
|
||||||
|
Thank you [**@aearly**](github.com/aearly) and [**@megawac**](github.com/megawac) for taking the lead on version 2 of async.
|
||||||
|
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
# v1.5.2
|
||||||
|
- Allow using `"constructor"` as an argument in `memoize` ([#998](https://github.com/caolan/async/issues/998))
|
||||||
|
- Give a better error messsage when `auto` dependency checking fails ([#994](https://github.com/caolan/async/issues/994))
|
||||||
|
- Various doc updates ([#936](https://github.com/caolan/async/issues/936), [#956](https://github.com/caolan/async/issues/956), [#979](https://github.com/caolan/async/issues/979), [#1002](https://github.com/caolan/async/issues/1002))
|
||||||
|
|
||||||
|
# v1.5.1
|
||||||
|
- Fix issue with `pause` in `queue` with concurrency enabled ([#946](https://github.com/caolan/async/issues/946))
|
||||||
|
- `while` and `until` now pass the final result to callback ([#963](https://github.com/caolan/async/issues/963))
|
||||||
|
- `auto` will properly handle concurrency when there is no callback ([#966](https://github.com/caolan/async/issues/966))
|
||||||
|
- `auto` will no. properly stop execution when an error occurs ([#988](https://github.com/caolan/async/issues/988), [#993](https://github.com/caolan/async/issues/993))
|
||||||
|
- Various doc fixes ([#971](https://github.com/caolan/async/issues/971), [#980](https://github.com/caolan/async/issues/980))
|
||||||
|
|
||||||
|
# v1.5.0
|
||||||
|
|
||||||
|
- Added `transform`, analogous to [`_.transform`](http://lodash.com/docs#transform) ([#892](https://github.com/caolan/async/issues/892))
|
||||||
|
- `map` now returns an object when an object is passed in, rather than array with non-numeric keys. `map` will begin always returning an array with numeric indexes in the next major release. ([#873](https://github.com/caolan/async/issues/873))
|
||||||
|
- `auto` now accepts an optional `concurrency` argument to limit the number o. running tasks ([#637](https://github.com/caolan/async/issues/637))
|
||||||
|
- Added `queue#workersList()`, to retrieve the lis. of currently running tasks. ([#891](https://github.com/caolan/async/issues/891))
|
||||||
|
- Various code simplifications ([#896](https://github.com/caolan/async/issues/896), [#904](https://github.com/caolan/async/issues/904))
|
||||||
|
- Various doc fixes :scroll: ([#890](https://github.com/caolan/async/issues/890), [#894](https://github.com/caolan/async/issues/894), [#903](https://github.com/caolan/async/issues/903), [#905](https://github.com/caolan/async/issues/905), [#912](https://github.com/caolan/async/issues/912))
|
||||||
|
|
||||||
|
# v1.4.2
|
||||||
|
|
||||||
|
- Ensure coverage files don't get published on npm ([#879](https://github.com/caolan/async/issues/879))
|
||||||
|
|
||||||
|
# v1.4.1
|
||||||
|
|
||||||
|
- Add in overlooked `detectLimit` method ([#866](https://github.com/caolan/async/issues/866))
|
||||||
|
- Removed unnecessary files from npm releases ([#861](https://github.com/caolan/async/issues/861))
|
||||||
|
- Removed usage of a reserved word to prevent :boom: in older environments ([#870](https://github.com/caolan/async/issues/870))
|
||||||
|
|
||||||
|
# v1.4.0
|
||||||
|
|
||||||
|
- `asyncify` now supports promises ([#840](https://github.com/caolan/async/issues/840))
|
||||||
|
- Added `Limit` versions of `filter` and `reject` ([#836](https://github.com/caolan/async/issues/836))
|
||||||
|
- Add `Limit` versions of `detect`, `some` and `every` ([#828](https://github.com/caolan/async/issues/828), [#829](https://github.com/caolan/async/issues/829))
|
||||||
|
- `some`, `every` and `detect` now short circuit early ([#828](https://github.com/caolan/async/issues/828), [#829](https://github.com/caolan/async/issues/829))
|
||||||
|
- Improve detection of the global object ([#804](https://github.com/caolan/async/issues/804)), enabling use in WebWorkers
|
||||||
|
- `whilst` now called with arguments from iterator ([#823](https://github.com/caolan/async/issues/823))
|
||||||
|
- `during` now gets called with arguments from iterator ([#824](https://github.com/caolan/async/issues/824))
|
||||||
|
- Code simplifications and optimizations aplenty ([diff](https://github.com/caolan/async/compare/v1.3.0...v1.4.0))
|
||||||
|
|
||||||
|
|
||||||
|
# v1.3.0
|
||||||
|
|
||||||
|
New Features:
|
||||||
|
- Added `constant`
|
||||||
|
- Added `asyncify`/`wrapSync` for making sync functions work with callbacks. ([#671](https://github.com/caolan/async/issues/671), [#806](https://github.com/caolan/async/issues/806))
|
||||||
|
- Added `during` and `doDuring`, which are like `whilst` with an async truth test. ([#800](https://github.com/caolan/async/issues/800))
|
||||||
|
- `retry` now accepts an `interval` parameter to specify a delay between retries. ([#793](https://github.com/caolan/async/issues/793))
|
||||||
|
- `async` should work better in Web Workers due to better `root` detection ([#804](https://github.com/caolan/async/issues/804))
|
||||||
|
- Callbacks are now optional in `whilst`, `doWhilst`, `until`, and `doUntil` ([#642](https://github.com/caolan/async/issues/642))
|
||||||
|
- Various internal updates ([#786](https://github.com/caolan/async/issues/786), [#801](https://github.com/caolan/async/issues/801), [#802](https://github.com/caolan/async/issues/802), [#803](https://github.com/caolan/async/issues/803))
|
||||||
|
- Various doc fixes ([#790](https://github.com/caolan/async/issues/790), [#794](https://github.com/caolan/async/issues/794))
|
||||||
|
|
||||||
|
Bug Fixes:
|
||||||
|
- `cargo` now exposes the `payload` size, and `cargo.payload` can be changed on the fly after the `cargo` is created. ([#740](https://github.com/caolan/async/issues/740), [#744](https://github.com/caolan/async/issues/744), [#783](https://github.com/caolan/async/issues/783))
|
||||||
|
|
||||||
|
|
||||||
|
# v1.2.1
|
||||||
|
|
||||||
|
Bug Fix:
|
||||||
|
|
||||||
|
- Small regression with synchronous iterator behavior in `eachSeries` with a 1-element array. Before 1.1.0, `eachSeries`'s callback was called on the same tick, which this patch restores. In 2.0.0, it will be called on the next tick. ([#782](https://github.com/caolan/async/issues/782))
|
||||||
|
|
||||||
|
|
||||||
|
# v1.2.0
|
||||||
|
|
||||||
|
New Features:
|
||||||
|
|
||||||
|
- Added `timesLimit` ([#743](https://github.com/caolan/async/issues/743))
|
||||||
|
- `concurrency` can be changed after initialization in `queue` by setting `q.concurrency`. The new concurrency will be reflected the next time a task is processed. ([#747](https://github.com/caolan/async/issues/747), [#772](https://github.com/caolan/async/issues/772))
|
||||||
|
|
||||||
|
Bug Fixes:
|
||||||
|
|
||||||
|
- Fixed a regression in `each` and family with empty arrays that have additional properties. ([#775](https://github.com/caolan/async/issues/775), [#777](https://github.com/caolan/async/issues/777))
|
||||||
|
|
||||||
|
|
||||||
|
# v1.1.1
|
||||||
|
|
||||||
|
Bug Fix:
|
||||||
|
|
||||||
|
- Small regression with synchronous iterator behavior in `eachSeries` with a 1-element array. Before 1.1.0, `eachSeries`'s callback was called on the same tick, which this patch restores. In 2.0.0, it will be called on the next tick. ([#782](https://github.com/caolan/async/issues/782))
|
||||||
|
|
||||||
|
|
||||||
|
# v1.1.0
|
||||||
|
|
||||||
|
New Features:
|
||||||
|
|
||||||
|
- `cargo` now supports all of the same methods and event callbacks as `queue`.
|
||||||
|
- Added `ensureAsync` - A wrapper that ensures an async function calls its callback on a later tick. ([#769](https://github.com/caolan/async/issues/769))
|
||||||
|
- Optimized `map`, `eachOf`, and `waterfall` families of functions
|
||||||
|
- Passing a `null` or `undefined` array to `map`, `each`, `parallel` and families will be treated as an empty array ([#667](https://github.com/caolan/async/issues/667)).
|
||||||
|
- The callback is now optional for the composed results of `compose` and `seq`. ([#618](https://github.com/caolan/async/issues/618))
|
||||||
|
- Reduced file size by 4kb, (minified version by 1kb)
|
||||||
|
- Added code coverage through `nyc` and `coveralls` ([#768](https://github.com/caolan/async/issues/768))
|
||||||
|
|
||||||
|
Bug Fixes:
|
||||||
|
|
||||||
|
- `forever` will no longer stack overflow with a synchronous iterator ([#622](https://github.com/caolan/async/issues/622))
|
||||||
|
- `eachLimit` and other limit functions will stop iterating once an error occurs ([#754](https://github.com/caolan/async/issues/754))
|
||||||
|
- Always pass `null` in callbacks when there is no error ([#439](https://github.com/caolan/async/issues/439))
|
||||||
|
- Ensure proper conditions when calling `drain()` after pushing an empty data set to a queue ([#668](https://github.com/caolan/async/issues/668))
|
||||||
|
- `each` and family will properly handle an empty array ([#578](https://github.com/caolan/async/issues/578))
|
||||||
|
- `eachSeries` and family will finish if the underlying array is modified during execution ([#557](https://github.com/caolan/async/issues/557))
|
||||||
|
- `queue` will throw if a non-function is passed to `q.push()` ([#593](https://github.com/caolan/async/issues/593))
|
||||||
|
- Doc fixes ([#629](https://github.com/caolan/async/issues/629), [#766](https://github.com/caolan/async/issues/766))
|
||||||
|
|
||||||
|
|
||||||
|
# v1.0.0
|
||||||
|
|
||||||
|
No known breaking changes, we are simply complying with semver from here on out.
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
- Start using a changelog!
|
||||||
|
- Add `forEachOf` for iterating over Objects (or to iterate Arrays with indexes available) ([#168](https://github.com/caolan/async/issues/168) [#704](https://github.com/caolan/async/issues/704) [#321](https://github.com/caolan/async/issues/321))
|
||||||
|
- Detect deadlocks in `auto` ([#663](https://github.com/caolan/async/issues/663))
|
||||||
|
- Better support for require.js ([#527](https://github.com/caolan/async/issues/527))
|
||||||
|
- Throw if queue created with concurrency `0` ([#714](https://github.com/caolan/async/issues/714))
|
||||||
|
- Fix unneeded iteration in `queue.resume()` ([#758](https://github.com/caolan/async/issues/758))
|
||||||
|
- Guard against timer mocking overriding `setImmediate` ([#609](https://github.com/caolan/async/issues/609) [#611](https://github.com/caolan/async/issues/611))
|
||||||
|
- Miscellaneous doc fixes ([#542](https://github.com/caolan/async/issues/542) [#596](https://github.com/caolan/async/issues/596) [#615](https://github.com/caolan/async/issues/615) [#628](https://github.com/caolan/async/issues/628) [#631](https://github.com/caolan/async/issues/631) [#690](https://github.com/caolan/async/issues/690) [#729](https://github.com/caolan/async/issues/729))
|
||||||
|
- Use single noop function internally ([#546](https://github.com/caolan/async/issues/546))
|
||||||
|
- Optimize internal `_each`, `_map` and `_keys` functions.
|
||||||
19
node_modules/async/LICENSE
generated
vendored
Normal file
19
node_modules/async/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2010-2018 Caolan McMahon
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
59
node_modules/async/README.md
generated
vendored
Normal file
59
node_modules/async/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
[](https://www.npmjs.com/package/async)
|
||||||
|
[](https://coveralls.io/r/caolan/async?branch=master)
|
||||||
|
[](https://gitter.im/caolan/async?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
[](https://www.jsdelivr.com/package/npm/async)
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|Linux|Windows|MacOS|
|
||||||
|
|-|-|-|
|
||||||
|
|[](https://dev.azure.com/caolanmcmahon/async/_build/latest?definitionId=1&branchName=master) | [](https://dev.azure.com/caolanmcmahon/async/_build/latest?definitionId=1&branchName=master) | [](https://dev.azure.com/caolanmcmahon/async/_build/latest?definitionId=1&branchName=master)| -->
|
||||||
|
|
||||||
|
Async is a utility module which provides straight-forward, powerful functions for working with [asynchronous JavaScript](http://caolan.github.io/async/v3/global.html). Although originally designed for use with [Node.js](https://nodejs.org/) and installable via `npm i async`, it can also be used directly in the browser. An ESM/MJS version is included in the main `async` package that should automatically be used with compatible bundlers such as Webpack and Rollup.
|
||||||
|
|
||||||
|
A pure ESM version of Async is available as [`async-es`](https://www.npmjs.com/package/async-es).
|
||||||
|
|
||||||
|
For Documentation, visit <https://caolan.github.io/async/>
|
||||||
|
|
||||||
|
*For Async v1.5.x documentation, go [HERE](https://github.com/caolan/async/blob/v1.5.2/README.md)*
|
||||||
|
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// for use with Node-style callbacks...
|
||||||
|
var async = require("async");
|
||||||
|
|
||||||
|
var obj = {dev: "/dev.json", test: "/test.json", prod: "/prod.json"};
|
||||||
|
var configs = {};
|
||||||
|
|
||||||
|
async.forEachOf(obj, (value, key, callback) => {
|
||||||
|
fs.readFile(__dirname + value, "utf8", (err, data) => {
|
||||||
|
if (err) return callback(err);
|
||||||
|
try {
|
||||||
|
configs[key] = JSON.parse(data);
|
||||||
|
} catch (e) {
|
||||||
|
return callback(e);
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}, err => {
|
||||||
|
if (err) console.error(err.message);
|
||||||
|
// configs is now a map of JSON data
|
||||||
|
doSomethingWith(configs);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var async = require("async");
|
||||||
|
|
||||||
|
// ...or ES2017 async functions
|
||||||
|
async.mapLimit(urls, 5, async function(url) {
|
||||||
|
const response = await fetch(url)
|
||||||
|
return response.body
|
||||||
|
}, (err, results) => {
|
||||||
|
if (err) throw err
|
||||||
|
// results is now an array of the response bodies
|
||||||
|
console.log(results)
|
||||||
|
})
|
||||||
|
```
|
||||||
119
node_modules/async/all.js
generated
vendored
Normal file
119
node_modules/async/all.js
generated
vendored
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _createTester = require('./internal/createTester.js');
|
||||||
|
|
||||||
|
var _createTester2 = _interopRequireDefault(_createTester);
|
||||||
|
|
||||||
|
var _eachOf = require('./eachOf.js');
|
||||||
|
|
||||||
|
var _eachOf2 = _interopRequireDefault(_eachOf);
|
||||||
|
|
||||||
|
var _awaitify = require('./internal/awaitify.js');
|
||||||
|
|
||||||
|
var _awaitify2 = _interopRequireDefault(_awaitify);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns `true` if every element in `coll` satisfies an async test. If any
|
||||||
|
* iteratee call returns `false`, the main `callback` is immediately called.
|
||||||
|
*
|
||||||
|
* @name every
|
||||||
|
* @static
|
||||||
|
* @memberOf module:Collections
|
||||||
|
* @method
|
||||||
|
* @alias all
|
||||||
|
* @category Collection
|
||||||
|
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
|
||||||
|
* @param {AsyncFunction} iteratee - An async truth test to apply to each item
|
||||||
|
* in the collection in parallel.
|
||||||
|
* The iteratee must complete with a boolean result value.
|
||||||
|
* Invoked with (item, callback).
|
||||||
|
* @param {Function} [callback] - A callback which is called after all the
|
||||||
|
* `iteratee` functions have finished. Result will be either `true` or `false`
|
||||||
|
* depending on the values of the async tests. Invoked with (err, result).
|
||||||
|
* @returns {Promise} a promise, if no callback provided
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* // dir1 is a directory that contains file1.txt, file2.txt
|
||||||
|
* // dir2 is a directory that contains file3.txt, file4.txt
|
||||||
|
* // dir3 is a directory that contains file5.txt
|
||||||
|
* // dir4 does not exist
|
||||||
|
*
|
||||||
|
* const fileList = ['dir1/file1.txt','dir2/file3.txt','dir3/file5.txt'];
|
||||||
|
* const withMissingFileList = ['file1.txt','file2.txt','file4.txt'];
|
||||||
|
*
|
||||||
|
* // asynchronous function that checks if a file exists
|
||||||
|
* function fileExists(file, callback) {
|
||||||
|
* fs.access(file, fs.constants.F_OK, (err) => {
|
||||||
|
* callback(null, !err);
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // Using callbacks
|
||||||
|
* async.every(fileList, fileExists, function(err, result) {
|
||||||
|
* console.log(result);
|
||||||
|
* // true
|
||||||
|
* // result is true since every file exists
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* async.every(withMissingFileList, fileExists, function(err, result) {
|
||||||
|
* console.log(result);
|
||||||
|
* // false
|
||||||
|
* // result is false since NOT every file exists
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Using Promises
|
||||||
|
* async.every(fileList, fileExists)
|
||||||
|
* .then( result => {
|
||||||
|
* console.log(result);
|
||||||
|
* // true
|
||||||
|
* // result is true since every file exists
|
||||||
|
* }).catch( err => {
|
||||||
|
* console.log(err);
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* async.every(withMissingFileList, fileExists)
|
||||||
|
* .then( result => {
|
||||||
|
* console.log(result);
|
||||||
|
* // false
|
||||||
|
* // result is false since NOT every file exists
|
||||||
|
* }).catch( err => {
|
||||||
|
* console.log(err);
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Using async/await
|
||||||
|
* async () => {
|
||||||
|
* try {
|
||||||
|
* let result = await async.every(fileList, fileExists);
|
||||||
|
* console.log(result);
|
||||||
|
* // true
|
||||||
|
* // result is true since every file exists
|
||||||
|
* }
|
||||||
|
* catch (err) {
|
||||||
|
* console.log(err);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* async () => {
|
||||||
|
* try {
|
||||||
|
* let result = await async.every(withMissingFileList, fileExists);
|
||||||
|
* console.log(result);
|
||||||
|
* // false
|
||||||
|
* // result is false since NOT every file exists
|
||||||
|
* }
|
||||||
|
* catch (err) {
|
||||||
|
* console.log(err);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function every(coll, iteratee, callback) {
|
||||||
|
return (0, _createTester2.default)(bool => !bool, res => !res)(_eachOf2.default, coll, iteratee, callback);
|
||||||
|
}
|
||||||
|
exports.default = (0, _awaitify2.default)(every, 3);
|
||||||
|
module.exports = exports.default;
|
||||||
46
node_modules/async/allLimit.js
generated
vendored
Normal file
46
node_modules/async/allLimit.js
generated
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _createTester = require('./internal/createTester.js');
|
||||||
|
|
||||||
|
var _createTester2 = _interopRequireDefault(_createTester);
|
||||||
|
|
||||||
|
var _eachOfLimit = require('./internal/eachOfLimit.js');
|
||||||
|
|
||||||
|
var _eachOfLimit2 = _interopRequireDefault(_eachOfLimit);
|
||||||
|
|
||||||
|
var _awaitify = require('./internal/awaitify.js');
|
||||||
|
|
||||||
|
var _awaitify2 = _interopRequireDefault(_awaitify);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The same as [`every`]{@link module:Collections.every} but runs a maximum of `limit` async operations at a time.
|
||||||
|
*
|
||||||
|
* @name everyLimit
|
||||||
|
* @static
|
||||||
|
* @memberOf module:Collections
|
||||||
|
* @method
|
||||||
|
* @see [async.every]{@link module:Collections.every}
|
||||||
|
* @alias allLimit
|
||||||
|
* @category Collection
|
||||||
|
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
|
||||||
|
* @param {number} limit - The maximum number of async operations at a time.
|
||||||
|
* @param {AsyncFunction} iteratee - An async truth test to apply to each item
|
||||||
|
* in the collection in parallel.
|
||||||
|
* The iteratee must complete with a boolean result value.
|
||||||
|
* Invoked with (item, callback).
|
||||||
|
* @param {Function} [callback] - A callback which is called after all the
|
||||||
|
* `iteratee` functions have finished. Result will be either `true` or `false`
|
||||||
|
* depending on the values of the async tests. Invoked with (err, result).
|
||||||
|
* @returns {Promise} a promise, if no callback provided
|
||||||
|
*/
|
||||||
|
function everyLimit(coll, limit, iteratee, callback) {
|
||||||
|
return (0, _createTester2.default)(bool => !bool, res => !res)((0, _eachOfLimit2.default)(limit), coll, iteratee, callback);
|
||||||
|
}
|
||||||
|
exports.default = (0, _awaitify2.default)(everyLimit, 4);
|
||||||
|
module.exports = exports.default;
|
||||||
45
node_modules/async/allSeries.js
generated
vendored
Normal file
45
node_modules/async/allSeries.js
generated
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _createTester = require('./internal/createTester.js');
|
||||||
|
|
||||||
|
var _createTester2 = _interopRequireDefault(_createTester);
|
||||||
|
|
||||||
|
var _eachOfSeries = require('./eachOfSeries.js');
|
||||||
|
|
||||||
|
var _eachOfSeries2 = _interopRequireDefault(_eachOfSeries);
|
||||||
|
|
||||||
|
var _awaitify = require('./internal/awaitify.js');
|
||||||
|
|
||||||
|
var _awaitify2 = _interopRequireDefault(_awaitify);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The same as [`every`]{@link module:Collections.every} but runs only a single async operation at a time.
|
||||||
|
*
|
||||||
|
* @name everySeries
|
||||||
|
* @static
|
||||||
|
* @memberOf module:Collections
|
||||||
|
* @method
|
||||||
|
* @see [async.every]{@link module:Collections.every}
|
||||||
|
* @alias allSeries
|
||||||
|
* @category Collection
|
||||||
|
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
|
||||||
|
* @param {AsyncFunction} iteratee - An async truth test to apply to each item
|
||||||
|
* in the collection in series.
|
||||||
|
* The iteratee must complete with a boolean result value.
|
||||||
|
* Invoked with (item, callback).
|
||||||
|
* @param {Function} [callback] - A callback which is called after all the
|
||||||
|
* `iteratee` functions have finished. Result will be either `true` or `false`
|
||||||
|
* depending on the values of the async tests. Invoked with (err, result).
|
||||||
|
* @returns {Promise} a promise, if no callback provided
|
||||||
|
*/
|
||||||
|
function everySeries(coll, iteratee, callback) {
|
||||||
|
return (0, _createTester2.default)(bool => !bool, res => !res)(_eachOfSeries2.default, coll, iteratee, callback);
|
||||||
|
}
|
||||||
|
exports.default = (0, _awaitify2.default)(everySeries, 3);
|
||||||
|
module.exports = exports.default;
|
||||||
122
node_modules/async/any.js
generated
vendored
Normal file
122
node_modules/async/any.js
generated
vendored
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _createTester = require('./internal/createTester.js');
|
||||||
|
|
||||||
|
var _createTester2 = _interopRequireDefault(_createTester);
|
||||||
|
|
||||||
|
var _eachOf = require('./eachOf.js');
|
||||||
|
|
||||||
|
var _eachOf2 = _interopRequireDefault(_eachOf);
|
||||||
|
|
||||||
|
var _awaitify = require('./internal/awaitify.js');
|
||||||
|
|
||||||
|
var _awaitify2 = _interopRequireDefault(_awaitify);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns `true` if at least one element in the `coll` satisfies an async test.
|
||||||
|
* If any iteratee call returns `true`, the main `callback` is immediately
|
||||||
|
* called.
|
||||||
|
*
|
||||||
|
* @name some
|
||||||
|
* @static
|
||||||
|
* @memberOf module:Collections
|
||||||
|
* @method
|
||||||
|
* @alias any
|
||||||
|
* @category Collection
|
||||||
|
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
|
||||||
|
* @param {AsyncFunction} iteratee - An async truth test to apply to each item
|
||||||
|
* in the collections in parallel.
|
||||||
|
* The iteratee should complete with a boolean `result` value.
|
||||||
|
* Invoked with (item, callback).
|
||||||
|
* @param {Function} [callback] - A callback which is called as soon as any
|
||||||
|
* iteratee returns `true`, or after all the iteratee functions have finished.
|
||||||
|
* Result will be either `true` or `false` depending on the values of the async
|
||||||
|
* tests. Invoked with (err, result).
|
||||||
|
* @returns {Promise} a promise, if no callback provided
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* // dir1 is a directory that contains file1.txt, file2.txt
|
||||||
|
* // dir2 is a directory that contains file3.txt, file4.txt
|
||||||
|
* // dir3 is a directory that contains file5.txt
|
||||||
|
* // dir4 does not exist
|
||||||
|
*
|
||||||
|
* // asynchronous function that checks if a file exists
|
||||||
|
* function fileExists(file, callback) {
|
||||||
|
* fs.access(file, fs.constants.F_OK, (err) => {
|
||||||
|
* callback(null, !err);
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // Using callbacks
|
||||||
|
* async.some(['dir1/missing.txt','dir2/missing.txt','dir3/file5.txt'], fileExists,
|
||||||
|
* function(err, result) {
|
||||||
|
* console.log(result);
|
||||||
|
* // true
|
||||||
|
* // result is true since some file in the list exists
|
||||||
|
* }
|
||||||
|
*);
|
||||||
|
*
|
||||||
|
* async.some(['dir1/missing.txt','dir2/missing.txt','dir4/missing.txt'], fileExists,
|
||||||
|
* function(err, result) {
|
||||||
|
* console.log(result);
|
||||||
|
* // false
|
||||||
|
* // result is false since none of the files exists
|
||||||
|
* }
|
||||||
|
*);
|
||||||
|
*
|
||||||
|
* // Using Promises
|
||||||
|
* async.some(['dir1/missing.txt','dir2/missing.txt','dir3/file5.txt'], fileExists)
|
||||||
|
* .then( result => {
|
||||||
|
* console.log(result);
|
||||||
|
* // true
|
||||||
|
* // result is true since some file in the list exists
|
||||||
|
* }).catch( err => {
|
||||||
|
* console.log(err);
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* async.some(['dir1/missing.txt','dir2/missing.txt','dir4/missing.txt'], fileExists)
|
||||||
|
* .then( result => {
|
||||||
|
* console.log(result);
|
||||||
|
* // false
|
||||||
|
* // result is false since none of the files exists
|
||||||
|
* }).catch( err => {
|
||||||
|
* console.log(err);
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Using async/await
|
||||||
|
* async () => {
|
||||||
|
* try {
|
||||||
|
* let result = await async.some(['dir1/missing.txt','dir2/missing.txt','dir3/file5.txt'], fileExists);
|
||||||
|
* console.log(result);
|
||||||
|
* // true
|
||||||
|
* // result is true since some file in the list exists
|
||||||
|
* }
|
||||||
|
* catch (err) {
|
||||||
|
* console.log(err);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* async () => {
|
||||||
|
* try {
|
||||||
|
* let result = await async.some(['dir1/missing.txt','dir2/missing.txt','dir4/missing.txt'], fileExists);
|
||||||
|
* console.log(result);
|
||||||
|
* // false
|
||||||
|
* // result is false since none of the files exists
|
||||||
|
* }
|
||||||
|
* catch (err) {
|
||||||
|
* console.log(err);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function some(coll, iteratee, callback) {
|
||||||
|
return (0, _createTester2.default)(Boolean, res => res)(_eachOf2.default, coll, iteratee, callback);
|
||||||
|
}
|
||||||
|
exports.default = (0, _awaitify2.default)(some, 3);
|
||||||
|
module.exports = exports.default;
|
||||||
47
node_modules/async/anyLimit.js
generated
vendored
Normal file
47
node_modules/async/anyLimit.js
generated
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _createTester = require('./internal/createTester.js');
|
||||||
|
|
||||||
|
var _createTester2 = _interopRequireDefault(_createTester);
|
||||||
|
|
||||||
|
var _eachOfLimit = require('./internal/eachOfLimit.js');
|
||||||
|
|
||||||
|
var _eachOfLimit2 = _interopRequireDefault(_eachOfLimit);
|
||||||
|
|
||||||
|
var _awaitify = require('./internal/awaitify.js');
|
||||||
|
|
||||||
|
var _awaitify2 = _interopRequireDefault(_awaitify);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The same as [`some`]{@link module:Collections.some} but runs a maximum of `limit` async operations at a time.
|
||||||
|
*
|
||||||
|
* @name someLimit
|
||||||
|
* @static
|
||||||
|
* @memberOf module:Collections
|
||||||
|
* @method
|
||||||
|
* @see [async.some]{@link module:Collections.some}
|
||||||
|
* @alias anyLimit
|
||||||
|
* @category Collection
|
||||||
|
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
|
||||||
|
* @param {number} limit - The maximum number of async operations at a time.
|
||||||
|
* @param {AsyncFunction} iteratee - An async truth test to apply to each item
|
||||||
|
* in the collections in parallel.
|
||||||
|
* The iteratee should complete with a boolean `result` value.
|
||||||
|
* Invoked with (item, callback).
|
||||||
|
* @param {Function} [callback] - A callback which is called as soon as any
|
||||||
|
* iteratee returns `true`, or after all the iteratee functions have finished.
|
||||||
|
* Result will be either `true` or `false` depending on the values of the async
|
||||||
|
* tests. Invoked with (err, result).
|
||||||
|
* @returns {Promise} a promise, if no callback provided
|
||||||
|
*/
|
||||||
|
function someLimit(coll, limit, iteratee, callback) {
|
||||||
|
return (0, _createTester2.default)(Boolean, res => res)((0, _eachOfLimit2.default)(limit), coll, iteratee, callback);
|
||||||
|
}
|
||||||
|
exports.default = (0, _awaitify2.default)(someLimit, 4);
|
||||||
|
module.exports = exports.default;
|
||||||
46
node_modules/async/anySeries.js
generated
vendored
Normal file
46
node_modules/async/anySeries.js
generated
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _createTester = require('./internal/createTester.js');
|
||||||
|
|
||||||
|
var _createTester2 = _interopRequireDefault(_createTester);
|
||||||
|
|
||||||
|
var _eachOfSeries = require('./eachOfSeries.js');
|
||||||
|
|
||||||
|
var _eachOfSeries2 = _interopRequireDefault(_eachOfSeries);
|
||||||
|
|
||||||
|
var _awaitify = require('./internal/awaitify.js');
|
||||||
|
|
||||||
|
var _awaitify2 = _interopRequireDefault(_awaitify);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The same as [`some`]{@link module:Collections.some} but runs only a single async operation at a time.
|
||||||
|
*
|
||||||
|
* @name someSeries
|
||||||
|
* @static
|
||||||
|
* @memberOf module:Collections
|
||||||
|
* @method
|
||||||
|
* @see [async.some]{@link module:Collections.some}
|
||||||
|
* @alias anySeries
|
||||||
|
* @category Collection
|
||||||
|
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
|
||||||
|
* @param {AsyncFunction} iteratee - An async truth test to apply to each item
|
||||||
|
* in the collections in series.
|
||||||
|
* The iteratee should complete with a boolean `result` value.
|
||||||
|
* Invoked with (item, callback).
|
||||||
|
* @param {Function} [callback] - A callback which is called as soon as any
|
||||||
|
* iteratee returns `true`, or after all the iteratee functions have finished.
|
||||||
|
* Result will be either `true` or `false` depending on the values of the async
|
||||||
|
* tests. Invoked with (err, result).
|
||||||
|
* @returns {Promise} a promise, if no callback provided
|
||||||
|
*/
|
||||||
|
function someSeries(coll, iteratee, callback) {
|
||||||
|
return (0, _createTester2.default)(Boolean, res => res)(_eachOfSeries2.default, coll, iteratee, callback);
|
||||||
|
}
|
||||||
|
exports.default = (0, _awaitify2.default)(someSeries, 3);
|
||||||
|
module.exports = exports.default;
|
||||||
11
node_modules/async/apply.js
generated
vendored
Normal file
11
node_modules/async/apply.js
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.default = function (fn, ...args) {
|
||||||
|
return (...callArgs) => fn(...args, ...callArgs);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = exports.default;
|
||||||
57
node_modules/async/applyEach.js
generated
vendored
Normal file
57
node_modules/async/applyEach.js
generated
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _applyEach = require('./internal/applyEach.js');
|
||||||
|
|
||||||
|
var _applyEach2 = _interopRequireDefault(_applyEach);
|
||||||
|
|
||||||
|
var _map = require('./map.js');
|
||||||
|
|
||||||
|
var _map2 = _interopRequireDefault(_map);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the provided arguments to each function in the array, calling
|
||||||
|
* `callback` after all functions have completed. If you only provide the first
|
||||||
|
* argument, `fns`, then it will return a function which lets you pass in the
|
||||||
|
* arguments as if it were a single function call. If more arguments are
|
||||||
|
* provided, `callback` is required while `args` is still optional. The results
|
||||||
|
* for each of the applied async functions are passed to the final callback
|
||||||
|
* as an array.
|
||||||
|
*
|
||||||
|
* @name applyEach
|
||||||
|
* @static
|
||||||
|
* @memberOf module:ControlFlow
|
||||||
|
* @method
|
||||||
|
* @category Control Flow
|
||||||
|
* @param {Array|Iterable|AsyncIterable|Object} fns - A collection of {@link AsyncFunction}s
|
||||||
|
* to all call with the same arguments
|
||||||
|
* @param {...*} [args] - any number of separate arguments to pass to the
|
||||||
|
* function.
|
||||||
|
* @param {Function} [callback] - the final argument should be the callback,
|
||||||
|
* called when all functions have completed processing.
|
||||||
|
* @returns {AsyncFunction} - Returns a function that takes no args other than
|
||||||
|
* an optional callback, that is the result of applying the `args` to each
|
||||||
|
* of the functions.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* const appliedFn = async.applyEach([enableSearch, updateSchema], 'bucket')
|
||||||
|
*
|
||||||
|
* appliedFn((err, results) => {
|
||||||
|
* // results[0] is the results for `enableSearch`
|
||||||
|
* // results[1] is the results for `updateSchema`
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // partial application example:
|
||||||
|
* async.each(
|
||||||
|
* buckets,
|
||||||
|
* async (bucket) => async.applyEach([enableSearch, updateSchema], bucket)(),
|
||||||
|
* callback
|
||||||
|
* );
|
||||||
|
*/
|
||||||
|
exports.default = (0, _applyEach2.default)(_map2.default);
|
||||||
|
module.exports = exports.default;
|
||||||
37
node_modules/async/applyEachSeries.js
generated
vendored
Normal file
37
node_modules/async/applyEachSeries.js
generated
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _applyEach = require('./internal/applyEach.js');
|
||||||
|
|
||||||
|
var _applyEach2 = _interopRequireDefault(_applyEach);
|
||||||
|
|
||||||
|
var _mapSeries = require('./mapSeries.js');
|
||||||
|
|
||||||
|
var _mapSeries2 = _interopRequireDefault(_mapSeries);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The same as [`applyEach`]{@link module:ControlFlow.applyEach} but runs only a single async operation at a time.
|
||||||
|
*
|
||||||
|
* @name applyEachSeries
|
||||||
|
* @static
|
||||||
|
* @memberOf module:ControlFlow
|
||||||
|
* @method
|
||||||
|
* @see [async.applyEach]{@link module:ControlFlow.applyEach}
|
||||||
|
* @category Control Flow
|
||||||
|
* @param {Array|Iterable|AsyncIterable|Object} fns - A collection of {@link AsyncFunction}s to all
|
||||||
|
* call with the same arguments
|
||||||
|
* @param {...*} [args] - any number of separate arguments to pass to the
|
||||||
|
* function.
|
||||||
|
* @param {Function} [callback] - the final argument should be the callback,
|
||||||
|
* called when all functions have completed processing.
|
||||||
|
* @returns {AsyncFunction} - A function, that when called, is the result of
|
||||||
|
* appling the `args` to the list of functions. It takes no args, other than
|
||||||
|
* a callback.
|
||||||
|
*/
|
||||||
|
exports.default = (0, _applyEach2.default)(_mapSeries2.default);
|
||||||
|
module.exports = exports.default;
|
||||||
118
node_modules/async/asyncify.js
generated
vendored
Normal file
118
node_modules/async/asyncify.js
generated
vendored
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.default = asyncify;
|
||||||
|
|
||||||
|
var _initialParams = require('./internal/initialParams.js');
|
||||||
|
|
||||||
|
var _initialParams2 = _interopRequireDefault(_initialParams);
|
||||||
|
|
||||||
|
var _setImmediate = require('./internal/setImmediate.js');
|
||||||
|
|
||||||
|
var _setImmediate2 = _interopRequireDefault(_setImmediate);
|
||||||
|
|
||||||
|
var _wrapAsync = require('./internal/wrapAsync.js');
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take a sync function and make it async, passing its return value to a
|
||||||
|
* callback. This is useful for plugging sync functions into a waterfall,
|
||||||
|
* series, or other async functions. Any arguments passed to the generated
|
||||||
|
* function will be passed to the wrapped function (except for the final
|
||||||
|
* callback argument). Errors thrown will be passed to the callback.
|
||||||
|
*
|
||||||
|
* If the function passed to `asyncify` returns a Promise, that promises's
|
||||||
|
* resolved/rejected state will be used to call the callback, rather than simply
|
||||||
|
* the synchronous return value.
|
||||||
|
*
|
||||||
|
* This also means you can asyncify ES2017 `async` functions.
|
||||||
|
*
|
||||||
|
* @name asyncify
|
||||||
|
* @static
|
||||||
|
* @memberOf module:Utils
|
||||||
|
* @method
|
||||||
|
* @alias wrapSync
|
||||||
|
* @category Util
|
||||||
|
* @param {Function} func - The synchronous function, or Promise-returning
|
||||||
|
* function to convert to an {@link AsyncFunction}.
|
||||||
|
* @returns {AsyncFunction} An asynchronous wrapper of the `func`. To be
|
||||||
|
* invoked with `(args..., callback)`.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* // passing a regular synchronous function
|
||||||
|
* async.waterfall([
|
||||||
|
* async.apply(fs.readFile, filename, "utf8"),
|
||||||
|
* async.asyncify(JSON.parse),
|
||||||
|
* function (data, next) {
|
||||||
|
* // data is the result of parsing the text.
|
||||||
|
* // If there was a parsing error, it would have been caught.
|
||||||
|
* }
|
||||||
|
* ], callback);
|
||||||
|
*
|
||||||
|
* // passing a function returning a promise
|
||||||
|
* async.waterfall([
|
||||||
|
* async.apply(fs.readFile, filename, "utf8"),
|
||||||
|
* async.asyncify(function (contents) {
|
||||||
|
* return db.model.create(contents);
|
||||||
|
* }),
|
||||||
|
* function (model, next) {
|
||||||
|
* // `model` is the instantiated model object.
|
||||||
|
* // If there was an error, this function would be skipped.
|
||||||
|
* }
|
||||||
|
* ], callback);
|
||||||
|
*
|
||||||
|
* // es2017 example, though `asyncify` is not needed if your JS environment
|
||||||
|
* // supports async functions out of the box
|
||||||
|
* var q = async.queue(async.asyncify(async function(file) {
|
||||||
|
* var intermediateStep = await processFile(file);
|
||||||
|
* return await somePromise(intermediateStep)
|
||||||
|
* }));
|
||||||
|
*
|
||||||
|
* q.push(files);
|
||||||
|
*/
|
||||||
|
function asyncify(func) {
|
||||||
|
if ((0, _wrapAsync.isAsync)(func)) {
|
||||||
|
return function (...args /*, callback*/) {
|
||||||
|
const callback = args.pop();
|
||||||
|
const promise = func.apply(this, args);
|
||||||
|
return handlePromise(promise, callback);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return (0, _initialParams2.default)(function (args, callback) {
|
||||||
|
var result;
|
||||||
|
try {
|
||||||
|
result = func.apply(this, args);
|
||||||
|
} catch (e) {
|
||||||
|
return callback(e);
|
||||||
|
}
|
||||||
|
// if result is Promise object
|
||||||
|
if (result && typeof result.then === 'function') {
|
||||||
|
return handlePromise(result, callback);
|
||||||
|
} else {
|
||||||
|
callback(null, result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePromise(promise, callback) {
|
||||||
|
return promise.then(value => {
|
||||||
|
invokeCallback(callback, null, value);
|
||||||
|
}, err => {
|
||||||
|
invokeCallback(callback, err && (err instanceof Error || err.message) ? err : new Error(err));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function invokeCallback(callback, error, value) {
|
||||||
|
try {
|
||||||
|
callback(error, value);
|
||||||
|
} catch (err) {
|
||||||
|
(0, _setImmediate2.default)(e => {
|
||||||
|
throw e;
|
||||||
|
}, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
module.exports = exports.default;
|
||||||
333
node_modules/async/auto.js
generated
vendored
Normal file
333
node_modules/async/auto.js
generated
vendored
Normal file
|
|
@ -0,0 +1,333 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.default = auto;
|
||||||
|
|
||||||
|
var _once = require('./internal/once.js');
|
||||||
|
|
||||||
|
var _once2 = _interopRequireDefault(_once);
|
||||||
|
|
||||||
|
var _onlyOnce = require('./internal/onlyOnce.js');
|
||||||
|
|
||||||
|
var _onlyOnce2 = _interopRequireDefault(_onlyOnce);
|
||||||
|
|
||||||
|
var _wrapAsync = require('./internal/wrapAsync.js');
|
||||||
|
|
||||||
|
var _wrapAsync2 = _interopRequireDefault(_wrapAsync);
|
||||||
|
|
||||||
|
var _promiseCallback = require('./internal/promiseCallback.js');
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the best order for running the {@link AsyncFunction}s in `tasks`, based on
|
||||||
|
* their requirements. Each function can optionally depend on other functions
|
||||||
|
* being completed first, and each function is run as soon as its requirements
|
||||||
|
* are satisfied.
|
||||||
|
*
|
||||||
|
* If any of the {@link AsyncFunction}s pass an error to their callback, the `auto` sequence
|
||||||
|
* will stop. Further tasks will not execute (so any other functions depending
|
||||||
|
* on it will not run), and the main `callback` is immediately called with the
|
||||||
|
* error.
|
||||||
|
*
|
||||||
|
* {@link AsyncFunction}s also receive an object containing the results of functions which
|
||||||
|
* have completed so far as the first argument, if they have dependencies. If a
|
||||||
|
* task function has no dependencies, it will only be passed a callback.
|
||||||
|
*
|
||||||
|
* @name auto
|
||||||
|
* @static
|
||||||
|
* @memberOf module:ControlFlow
|
||||||
|
* @method
|
||||||
|
* @category Control Flow
|
||||||
|
* @param {Object} tasks - An object. Each of its properties is either a
|
||||||
|
* function or an array of requirements, with the {@link AsyncFunction} itself the last item
|
||||||
|
* in the array. The object's key of a property serves as the name of the task
|
||||||
|
* defined by that property, i.e. can be used when specifying requirements for
|
||||||
|
* other tasks. The function receives one or two arguments:
|
||||||
|
* * a `results` object, containing the results of the previously executed
|
||||||
|
* functions, only passed if the task has any dependencies,
|
||||||
|
* * a `callback(err, result)` function, which must be called when finished,
|
||||||
|
* passing an `error` (which can be `null`) and the result of the function's
|
||||||
|
* execution.
|
||||||
|
* @param {number} [concurrency=Infinity] - An optional `integer` for
|
||||||
|
* determining the maximum number of tasks that can be run in parallel. By
|
||||||
|
* default, as many as possible.
|
||||||
|
* @param {Function} [callback] - An optional callback which is called when all
|
||||||
|
* the tasks have been completed. It receives the `err` argument if any `tasks`
|
||||||
|
* pass an error to their callback. Results are always returned; however, if an
|
||||||
|
* error occurs, no further `tasks` will be performed, and the results object
|
||||||
|
* will only contain partial results. Invoked with (err, results).
|
||||||
|
* @returns {Promise} a promise, if a callback is not passed
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* //Using Callbacks
|
||||||
|
* async.auto({
|
||||||
|
* get_data: function(callback) {
|
||||||
|
* // async code to get some data
|
||||||
|
* callback(null, 'data', 'converted to array');
|
||||||
|
* },
|
||||||
|
* make_folder: function(callback) {
|
||||||
|
* // async code to create a directory to store a file in
|
||||||
|
* // this is run at the same time as getting the data
|
||||||
|
* callback(null, 'folder');
|
||||||
|
* },
|
||||||
|
* write_file: ['get_data', 'make_folder', function(results, callback) {
|
||||||
|
* // once there is some data and the directory exists,
|
||||||
|
* // write the data to a file in the directory
|
||||||
|
* callback(null, 'filename');
|
||||||
|
* }],
|
||||||
|
* email_link: ['write_file', function(results, callback) {
|
||||||
|
* // once the file is written let's email a link to it...
|
||||||
|
* callback(null, {'file':results.write_file, 'email':'user@example.com'});
|
||||||
|
* }]
|
||||||
|
* }, function(err, results) {
|
||||||
|
* if (err) {
|
||||||
|
* console.log('err = ', err);
|
||||||
|
* }
|
||||||
|
* console.log('results = ', results);
|
||||||
|
* // results = {
|
||||||
|
* // get_data: ['data', 'converted to array']
|
||||||
|
* // make_folder; 'folder',
|
||||||
|
* // write_file: 'filename'
|
||||||
|
* // email_link: { file: 'filename', email: 'user@example.com' }
|
||||||
|
* // }
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* //Using Promises
|
||||||
|
* async.auto({
|
||||||
|
* get_data: function(callback) {
|
||||||
|
* console.log('in get_data');
|
||||||
|
* // async code to get some data
|
||||||
|
* callback(null, 'data', 'converted to array');
|
||||||
|
* },
|
||||||
|
* make_folder: function(callback) {
|
||||||
|
* console.log('in make_folder');
|
||||||
|
* // async code to create a directory to store a file in
|
||||||
|
* // this is run at the same time as getting the data
|
||||||
|
* callback(null, 'folder');
|
||||||
|
* },
|
||||||
|
* write_file: ['get_data', 'make_folder', function(results, callback) {
|
||||||
|
* // once there is some data and the directory exists,
|
||||||
|
* // write the data to a file in the directory
|
||||||
|
* callback(null, 'filename');
|
||||||
|
* }],
|
||||||
|
* email_link: ['write_file', function(results, callback) {
|
||||||
|
* // once the file is written let's email a link to it...
|
||||||
|
* callback(null, {'file':results.write_file, 'email':'user@example.com'});
|
||||||
|
* }]
|
||||||
|
* }).then(results => {
|
||||||
|
* console.log('results = ', results);
|
||||||
|
* // results = {
|
||||||
|
* // get_data: ['data', 'converted to array']
|
||||||
|
* // make_folder; 'folder',
|
||||||
|
* // write_file: 'filename'
|
||||||
|
* // email_link: { file: 'filename', email: 'user@example.com' }
|
||||||
|
* // }
|
||||||
|
* }).catch(err => {
|
||||||
|
* console.log('err = ', err);
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* //Using async/await
|
||||||
|
* async () => {
|
||||||
|
* try {
|
||||||
|
* let results = await async.auto({
|
||||||
|
* get_data: function(callback) {
|
||||||
|
* // async code to get some data
|
||||||
|
* callback(null, 'data', 'converted to array');
|
||||||
|
* },
|
||||||
|
* make_folder: function(callback) {
|
||||||
|
* // async code to create a directory to store a file in
|
||||||
|
* // this is run at the same time as getting the data
|
||||||
|
* callback(null, 'folder');
|
||||||
|
* },
|
||||||
|
* write_file: ['get_data', 'make_folder', function(results, callback) {
|
||||||
|
* // once there is some data and the directory exists,
|
||||||
|
* // write the data to a file in the directory
|
||||||
|
* callback(null, 'filename');
|
||||||
|
* }],
|
||||||
|
* email_link: ['write_file', function(results, callback) {
|
||||||
|
* // once the file is written let's email a link to it...
|
||||||
|
* callback(null, {'file':results.write_file, 'email':'user@example.com'});
|
||||||
|
* }]
|
||||||
|
* });
|
||||||
|
* console.log('results = ', results);
|
||||||
|
* // results = {
|
||||||
|
* // get_data: ['data', 'converted to array']
|
||||||
|
* // make_folder; 'folder',
|
||||||
|
* // write_file: 'filename'
|
||||||
|
* // email_link: { file: 'filename', email: 'user@example.com' }
|
||||||
|
* // }
|
||||||
|
* }
|
||||||
|
* catch (err) {
|
||||||
|
* console.log(err);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function auto(tasks, concurrency, callback) {
|
||||||
|
if (typeof concurrency !== 'number') {
|
||||||
|
// concurrency is optional, shift the args.
|
||||||
|
callback = concurrency;
|
||||||
|
concurrency = null;
|
||||||
|
}
|
||||||
|
callback = (0, _once2.default)(callback || (0, _promiseCallback.promiseCallback)());
|
||||||
|
var numTasks = Object.keys(tasks).length;
|
||||||
|
if (!numTasks) {
|
||||||
|
return callback(null);
|
||||||
|
}
|
||||||
|
if (!concurrency) {
|
||||||
|
concurrency = numTasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
var results = {};
|
||||||
|
var runningTasks = 0;
|
||||||
|
var canceled = false;
|
||||||
|
var hasError = false;
|
||||||
|
|
||||||
|
var listeners = Object.create(null);
|
||||||
|
|
||||||
|
var readyTasks = [];
|
||||||
|
|
||||||
|
// for cycle detection:
|
||||||
|
var readyToCheck = []; // tasks that have been identified as reachable
|
||||||
|
// without the possibility of returning to an ancestor task
|
||||||
|
var uncheckedDependencies = {};
|
||||||
|
|
||||||
|
Object.keys(tasks).forEach(key => {
|
||||||
|
var task = tasks[key];
|
||||||
|
if (!Array.isArray(task)) {
|
||||||
|
// no dependencies
|
||||||
|
enqueueTask(key, [task]);
|
||||||
|
readyToCheck.push(key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dependencies = task.slice(0, task.length - 1);
|
||||||
|
var remainingDependencies = dependencies.length;
|
||||||
|
if (remainingDependencies === 0) {
|
||||||
|
enqueueTask(key, task);
|
||||||
|
readyToCheck.push(key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uncheckedDependencies[key] = remainingDependencies;
|
||||||
|
|
||||||
|
dependencies.forEach(dependencyName => {
|
||||||
|
if (!tasks[dependencyName]) {
|
||||||
|
throw new Error('async.auto task `' + key + '` has a non-existent dependency `' + dependencyName + '` in ' + dependencies.join(', '));
|
||||||
|
}
|
||||||
|
addListener(dependencyName, () => {
|
||||||
|
remainingDependencies--;
|
||||||
|
if (remainingDependencies === 0) {
|
||||||
|
enqueueTask(key, task);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
checkForDeadlocks();
|
||||||
|
processQueue();
|
||||||
|
|
||||||
|
function enqueueTask(key, task) {
|
||||||
|
readyTasks.push(() => runTask(key, task));
|
||||||
|
}
|
||||||
|
|
||||||
|
function processQueue() {
|
||||||
|
if (canceled) return;
|
||||||
|
if (readyTasks.length === 0 && runningTasks === 0) {
|
||||||
|
return callback(null, results);
|
||||||
|
}
|
||||||
|
while (readyTasks.length && runningTasks < concurrency) {
|
||||||
|
var run = readyTasks.shift();
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addListener(taskName, fn) {
|
||||||
|
var taskListeners = listeners[taskName];
|
||||||
|
if (!taskListeners) {
|
||||||
|
taskListeners = listeners[taskName] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
taskListeners.push(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
function taskComplete(taskName) {
|
||||||
|
var taskListeners = listeners[taskName] || [];
|
||||||
|
taskListeners.forEach(fn => fn());
|
||||||
|
processQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
function runTask(key, task) {
|
||||||
|
if (hasError) return;
|
||||||
|
|
||||||
|
var taskCallback = (0, _onlyOnce2.default)((err, ...result) => {
|
||||||
|
runningTasks--;
|
||||||
|
if (err === false) {
|
||||||
|
canceled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (result.length < 2) {
|
||||||
|
[result] = result;
|
||||||
|
}
|
||||||
|
if (err) {
|
||||||
|
var safeResults = {};
|
||||||
|
Object.keys(results).forEach(rkey => {
|
||||||
|
safeResults[rkey] = results[rkey];
|
||||||
|
});
|
||||||
|
safeResults[key] = result;
|
||||||
|
hasError = true;
|
||||||
|
listeners = Object.create(null);
|
||||||
|
if (canceled) return;
|
||||||
|
callback(err, safeResults);
|
||||||
|
} else {
|
||||||
|
results[key] = result;
|
||||||
|
taskComplete(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
runningTasks++;
|
||||||
|
var taskFn = (0, _wrapAsync2.default)(task[task.length - 1]);
|
||||||
|
if (task.length > 1) {
|
||||||
|
taskFn(results, taskCallback);
|
||||||
|
} else {
|
||||||
|
taskFn(taskCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkForDeadlocks() {
|
||||||
|
// Kahn's algorithm
|
||||||
|
// https://en.wikipedia.org/wiki/Topological_sorting#Kahn.27s_algorithm
|
||||||
|
// http://connalle.blogspot.com/2013/10/topological-sortingkahn-algorithm.html
|
||||||
|
var currentTask;
|
||||||
|
var counter = 0;
|
||||||
|
while (readyToCheck.length) {
|
||||||
|
currentTask = readyToCheck.pop();
|
||||||
|
counter++;
|
||||||
|
getDependents(currentTask).forEach(dependent => {
|
||||||
|
if (--uncheckedDependencies[dependent] === 0) {
|
||||||
|
readyToCheck.push(dependent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (counter !== numTasks) {
|
||||||
|
throw new Error('async.auto cannot execute tasks due to a recursive dependency');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDependents(taskName) {
|
||||||
|
var result = [];
|
||||||
|
Object.keys(tasks).forEach(key => {
|
||||||
|
const task = tasks[key];
|
||||||
|
if (Array.isArray(task) && task.indexOf(taskName) >= 0) {
|
||||||
|
result.push(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return callback[_promiseCallback.PROMISE_SYMBOL];
|
||||||
|
}
|
||||||
|
module.exports = exports.default;
|
||||||
182
node_modules/async/autoInject.js
generated
vendored
Normal file
182
node_modules/async/autoInject.js
generated
vendored
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.default = autoInject;
|
||||||
|
|
||||||
|
var _auto = require('./auto.js');
|
||||||
|
|
||||||
|
var _auto2 = _interopRequireDefault(_auto);
|
||||||
|
|
||||||
|
var _wrapAsync = require('./internal/wrapAsync.js');
|
||||||
|
|
||||||
|
var _wrapAsync2 = _interopRequireDefault(_wrapAsync);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
var FN_ARGS = /^(?:async\s)?(?:function)?\s*(?:\w+\s*)?\(([^)]+)\)(?:\s*{)/;
|
||||||
|
var ARROW_FN_ARGS = /^(?:async\s)?\s*(?:\(\s*)?((?:[^)=\s]\s*)*)(?:\)\s*)?=>/;
|
||||||
|
var FN_ARG_SPLIT = /,/;
|
||||||
|
var FN_ARG = /(=.+)?(\s*)$/;
|
||||||
|
|
||||||
|
function stripComments(string) {
|
||||||
|
let stripped = '';
|
||||||
|
let index = 0;
|
||||||
|
let endBlockComment = string.indexOf('*/');
|
||||||
|
while (index < string.length) {
|
||||||
|
if (string[index] === '/' && string[index + 1] === '/') {
|
||||||
|
// inline comment
|
||||||
|
let endIndex = string.indexOf('\n', index);
|
||||||
|
index = endIndex === -1 ? string.length : endIndex;
|
||||||
|
} else if (endBlockComment !== -1 && string[index] === '/' && string[index + 1] === '*') {
|
||||||
|
// block comment
|
||||||
|
let endIndex = string.indexOf('*/', index);
|
||||||
|
if (endIndex !== -1) {
|
||||||
|
index = endIndex + 2;
|
||||||
|
endBlockComment = string.indexOf('*/', index);
|
||||||
|
} else {
|
||||||
|
stripped += string[index];
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stripped += string[index];
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stripped;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseParams(func) {
|
||||||
|
const src = stripComments(func.toString());
|
||||||
|
let match = src.match(FN_ARGS);
|
||||||
|
if (!match) {
|
||||||
|
match = src.match(ARROW_FN_ARGS);
|
||||||
|
}
|
||||||
|
if (!match) throw new Error('could not parse args in autoInject\nSource:\n' + src);
|
||||||
|
let [, args] = match;
|
||||||
|
return args.replace(/\s/g, '').split(FN_ARG_SPLIT).map(arg => arg.replace(FN_ARG, '').trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dependency-injected version of the [async.auto]{@link module:ControlFlow.auto} function. Dependent
|
||||||
|
* tasks are specified as parameters to the function, after the usual callback
|
||||||
|
* parameter, with the parameter names matching the names of the tasks it
|
||||||
|
* depends on. This can provide even more readable task graphs which can be
|
||||||
|
* easier to maintain.
|
||||||
|
*
|
||||||
|
* If a final callback is specified, the task results are similarly injected,
|
||||||
|
* specified as named parameters after the initial error parameter.
|
||||||
|
*
|
||||||
|
* The autoInject function is purely syntactic sugar and its semantics are
|
||||||
|
* otherwise equivalent to [async.auto]{@link module:ControlFlow.auto}.
|
||||||
|
*
|
||||||
|
* @name autoInject
|
||||||
|
* @static
|
||||||
|
* @memberOf module:ControlFlow
|
||||||
|
* @method
|
||||||
|
* @see [async.auto]{@link module:ControlFlow.auto}
|
||||||
|
* @category Control Flow
|
||||||
|
* @param {Object} tasks - An object, each of whose properties is an {@link AsyncFunction} of
|
||||||
|
* the form 'func([dependencies...], callback). The object's key of a property
|
||||||
|
* serves as the name of the task defined by that property, i.e. can be used
|
||||||
|
* when specifying requirements for other tasks.
|
||||||
|
* * The `callback` parameter is a `callback(err, result)` which must be called
|
||||||
|
* when finished, passing an `error` (which can be `null`) and the result of
|
||||||
|
* the function's execution. The remaining parameters name other tasks on
|
||||||
|
* which the task is dependent, and the results from those tasks are the
|
||||||
|
* arguments of those parameters.
|
||||||
|
* @param {Function} [callback] - An optional callback which is called when all
|
||||||
|
* the tasks have been completed. It receives the `err` argument if any `tasks`
|
||||||
|
* pass an error to their callback, and a `results` object with any completed
|
||||||
|
* task results, similar to `auto`.
|
||||||
|
* @returns {Promise} a promise, if no callback is passed
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* // The example from `auto` can be rewritten as follows:
|
||||||
|
* async.autoInject({
|
||||||
|
* get_data: function(callback) {
|
||||||
|
* // async code to get some data
|
||||||
|
* callback(null, 'data', 'converted to array');
|
||||||
|
* },
|
||||||
|
* make_folder: function(callback) {
|
||||||
|
* // async code to create a directory to store a file in
|
||||||
|
* // this is run at the same time as getting the data
|
||||||
|
* callback(null, 'folder');
|
||||||
|
* },
|
||||||
|
* write_file: function(get_data, make_folder, callback) {
|
||||||
|
* // once there is some data and the directory exists,
|
||||||
|
* // write the data to a file in the directory
|
||||||
|
* callback(null, 'filename');
|
||||||
|
* },
|
||||||
|
* email_link: function(write_file, callback) {
|
||||||
|
* // once the file is written let's email a link to it...
|
||||||
|
* // write_file contains the filename returned by write_file.
|
||||||
|
* callback(null, {'file':write_file, 'email':'user@example.com'});
|
||||||
|
* }
|
||||||
|
* }, function(err, results) {
|
||||||
|
* console.log('err = ', err);
|
||||||
|
* console.log('email_link = ', results.email_link);
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // If you are using a JS minifier that mangles parameter names, `autoInject`
|
||||||
|
* // will not work with plain functions, since the parameter names will be
|
||||||
|
* // collapsed to a single letter identifier. To work around this, you can
|
||||||
|
* // explicitly specify the names of the parameters your task function needs
|
||||||
|
* // in an array, similar to Angular.js dependency injection.
|
||||||
|
*
|
||||||
|
* // This still has an advantage over plain `auto`, since the results a task
|
||||||
|
* // depends on are still spread into arguments.
|
||||||
|
* async.autoInject({
|
||||||
|
* //...
|
||||||
|
* write_file: ['get_data', 'make_folder', function(get_data, make_folder, callback) {
|
||||||
|
* callback(null, 'filename');
|
||||||
|
* }],
|
||||||
|
* email_link: ['write_file', function(write_file, callback) {
|
||||||
|
* callback(null, {'file':write_file, 'email':'user@example.com'});
|
||||||
|
* }]
|
||||||
|
* //...
|
||||||
|
* }, function(err, results) {
|
||||||
|
* console.log('err = ', err);
|
||||||
|
* console.log('email_link = ', results.email_link);
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
function autoInject(tasks, callback) {
|
||||||
|
var newTasks = {};
|
||||||
|
|
||||||
|
Object.keys(tasks).forEach(key => {
|
||||||
|
var taskFn = tasks[key];
|
||||||
|
var params;
|
||||||
|
var fnIsAsync = (0, _wrapAsync.isAsync)(taskFn);
|
||||||
|
var hasNoDeps = !fnIsAsync && taskFn.length === 1 || fnIsAsync && taskFn.length === 0;
|
||||||
|
|
||||||
|
if (Array.isArray(taskFn)) {
|
||||||
|
params = [...taskFn];
|
||||||
|
taskFn = params.pop();
|
||||||
|
|
||||||
|
newTasks[key] = params.concat(params.length > 0 ? newTask : taskFn);
|
||||||
|
} else if (hasNoDeps) {
|
||||||
|
// no dependencies, use the function as-is
|
||||||
|
newTasks[key] = taskFn;
|
||||||
|
} else {
|
||||||
|
params = parseParams(taskFn);
|
||||||
|
if (taskFn.length === 0 && !fnIsAsync && params.length === 0) {
|
||||||
|
throw new Error("autoInject task functions require explicit parameters.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove callback param
|
||||||
|
if (!fnIsAsync) params.pop();
|
||||||
|
|
||||||
|
newTasks[key] = params.concat(newTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
function newTask(results, taskCb) {
|
||||||
|
var newArgs = params.map(name => results[name]);
|
||||||
|
newArgs.push(taskCb);
|
||||||
|
(0, _wrapAsync2.default)(taskFn)(...newArgs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (0, _auto2.default)(newTasks, callback);
|
||||||
|
}
|
||||||
|
module.exports = exports.default;
|
||||||
17
node_modules/async/bower.json
generated
vendored
Normal file
17
node_modules/async/bower.json
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"name": "async",
|
||||||
|
"main": "dist/async.js",
|
||||||
|
"ignore": [
|
||||||
|
"bower_components",
|
||||||
|
"lib",
|
||||||
|
"test",
|
||||||
|
"node_modules",
|
||||||
|
"perf",
|
||||||
|
"support",
|
||||||
|
"**/.*",
|
||||||
|
"*.config.js",
|
||||||
|
"*.json",
|
||||||
|
"index.js",
|
||||||
|
"Makefile"
|
||||||
|
]
|
||||||
|
}
|
||||||
63
node_modules/async/cargo.js
generated
vendored
Normal file
63
node_modules/async/cargo.js
generated
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.default = cargo;
|
||||||
|
|
||||||
|
var _queue = require('./internal/queue.js');
|
||||||
|
|
||||||
|
var _queue2 = _interopRequireDefault(_queue);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a `cargo` object with the specified payload. Tasks added to the
|
||||||
|
* cargo will be processed altogether (up to the `payload` limit). If the
|
||||||
|
* `worker` is in progress, the task is queued until it becomes available. Once
|
||||||
|
* the `worker` has completed some tasks, each callback of those tasks is
|
||||||
|
* called. Check out [these](https://camo.githubusercontent.com/6bbd36f4cf5b35a0f11a96dcd2e97711ffc2fb37/68747470733a2f2f662e636c6f75642e6769746875622e636f6d2f6173736574732f313637363837312f36383130382f62626330636662302d356632392d313165322d393734662d3333393763363464633835382e676966) [animations](https://camo.githubusercontent.com/f4810e00e1c5f5f8addbe3e9f49064fd5d102699/68747470733a2f2f662e636c6f75642e6769746875622e636f6d2f6173736574732f313637363837312f36383130312f38346339323036362d356632392d313165322d383134662d3964336430323431336266642e676966)
|
||||||
|
* for how `cargo` and `queue` work.
|
||||||
|
*
|
||||||
|
* While [`queue`]{@link module:ControlFlow.queue} passes only one task to one of a group of workers
|
||||||
|
* at a time, cargo passes an array of tasks to a single worker, repeating
|
||||||
|
* when the worker is finished.
|
||||||
|
*
|
||||||
|
* @name cargo
|
||||||
|
* @static
|
||||||
|
* @memberOf module:ControlFlow
|
||||||
|
* @method
|
||||||
|
* @see [async.queue]{@link module:ControlFlow.queue}
|
||||||
|
* @category Control Flow
|
||||||
|
* @param {AsyncFunction} worker - An asynchronous function for processing an array
|
||||||
|
* of queued tasks. Invoked with `(tasks, callback)`.
|
||||||
|
* @param {number} [payload=Infinity] - An optional `integer` for determining
|
||||||
|
* how many tasks should be processed per round; if omitted, the default is
|
||||||
|
* unlimited.
|
||||||
|
* @returns {module:ControlFlow.QueueObject} A cargo object to manage the tasks. Callbacks can
|
||||||
|
* attached as certain properties to listen for specific events during the
|
||||||
|
* lifecycle of the cargo and inner queue.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* // create a cargo object with payload 2
|
||||||
|
* var cargo = async.cargo(function(tasks, callback) {
|
||||||
|
* for (var i=0; i<tasks.length; i++) {
|
||||||
|
* console.log('hello ' + tasks[i].name);
|
||||||
|
* }
|
||||||
|
* callback();
|
||||||
|
* }, 2);
|
||||||
|
*
|
||||||
|
* // add some items
|
||||||
|
* cargo.push({name: 'foo'}, function(err) {
|
||||||
|
* console.log('finished processing foo');
|
||||||
|
* });
|
||||||
|
* cargo.push({name: 'bar'}, function(err) {
|
||||||
|
* console.log('finished processing bar');
|
||||||
|
* });
|
||||||
|
* await cargo.push({name: 'baz'});
|
||||||
|
* console.log('finished processing baz');
|
||||||
|
*/
|
||||||
|
function cargo(worker, payload) {
|
||||||
|
return (0, _queue2.default)(worker, 1, payload);
|
||||||
|
}
|
||||||
|
module.exports = exports.default;
|
||||||
71
node_modules/async/cargoQueue.js
generated
vendored
Normal file
71
node_modules/async/cargoQueue.js
generated
vendored
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.default = cargo;
|
||||||
|
|
||||||
|
var _queue = require('./internal/queue.js');
|
||||||
|
|
||||||
|
var _queue2 = _interopRequireDefault(_queue);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a `cargoQueue` object with the specified payload. Tasks added to the
|
||||||
|
* cargoQueue will be processed together (up to the `payload` limit) in `concurrency` parallel workers.
|
||||||
|
* If the all `workers` are in progress, the task is queued until one becomes available. Once
|
||||||
|
* a `worker` has completed some tasks, each callback of those tasks is
|
||||||
|
* called. Check out [these](https://camo.githubusercontent.com/6bbd36f4cf5b35a0f11a96dcd2e97711ffc2fb37/68747470733a2f2f662e636c6f75642e6769746875622e636f6d2f6173736574732f313637363837312f36383130382f62626330636662302d356632392d313165322d393734662d3333393763363464633835382e676966) [animations](https://camo.githubusercontent.com/f4810e00e1c5f5f8addbe3e9f49064fd5d102699/68747470733a2f2f662e636c6f75642e6769746875622e636f6d2f6173736574732f313637363837312f36383130312f38346339323036362d356632392d313165322d383134662d3964336430323431336266642e676966)
|
||||||
|
* for how `cargo` and `queue` work.
|
||||||
|
*
|
||||||
|
* While [`queue`]{@link module:ControlFlow.queue} passes only one task to one of a group of workers
|
||||||
|
* at a time, and [`cargo`]{@link module:ControlFlow.cargo} passes an array of tasks to a single worker,
|
||||||
|
* the cargoQueue passes an array of tasks to multiple parallel workers.
|
||||||
|
*
|
||||||
|
* @name cargoQueue
|
||||||
|
* @static
|
||||||
|
* @memberOf module:ControlFlow
|
||||||
|
* @method
|
||||||
|
* @see [async.queue]{@link module:ControlFlow.queue}
|
||||||
|
* @see [async.cargo]{@link module:ControlFLow.cargo}
|
||||||
|
* @category Control Flow
|
||||||
|
* @param {AsyncFunction} worker - An asynchronous function for processing an array
|
||||||
|
* of queued tasks. Invoked with `(tasks, callback)`.
|
||||||
|
* @param {number} [concurrency=1] - An `integer` for determining how many
|
||||||
|
* `worker` functions should be run in parallel. If omitted, the concurrency
|
||||||
|
* defaults to `1`. If the concurrency is `0`, an error is thrown.
|
||||||
|
* @param {number} [payload=Infinity] - An optional `integer` for determining
|
||||||
|
* how many tasks should be processed per round; if omitted, the default is
|
||||||
|
* unlimited.
|
||||||
|
* @returns {module:ControlFlow.QueueObject} A cargoQueue object to manage the tasks. Callbacks can
|
||||||
|
* attached as certain properties to listen for specific events during the
|
||||||
|
* lifecycle of the cargoQueue and inner queue.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* // create a cargoQueue object with payload 2 and concurrency 2
|
||||||
|
* var cargoQueue = async.cargoQueue(function(tasks, callback) {
|
||||||
|
* for (var i=0; i<tasks.length; i++) {
|
||||||
|
* console.log('hello ' + tasks[i].name);
|
||||||
|
* }
|
||||||
|
* callback();
|
||||||
|
* }, 2, 2);
|
||||||
|
*
|
||||||
|
* // add some items
|
||||||
|
* cargoQueue.push({name: 'foo'}, function(err) {
|
||||||
|
* console.log('finished processing foo');
|
||||||
|
* });
|
||||||
|
* cargoQueue.push({name: 'bar'}, function(err) {
|
||||||
|
* console.log('finished processing bar');
|
||||||
|
* });
|
||||||
|
* cargoQueue.push({name: 'baz'}, function(err) {
|
||||||
|
* console.log('finished processing baz');
|
||||||
|
* });
|
||||||
|
* cargoQueue.push({name: 'boo'}, function(err) {
|
||||||
|
* console.log('finished processing boo');
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
function cargo(worker, concurrency, payload) {
|
||||||
|
return (0, _queue2.default)(worker, concurrency, payload);
|
||||||
|
}
|
||||||
|
module.exports = exports.default;
|
||||||
55
node_modules/async/compose.js
generated
vendored
Normal file
55
node_modules/async/compose.js
generated
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.default = compose;
|
||||||
|
|
||||||
|
var _seq = require('./seq.js');
|
||||||
|
|
||||||
|
var _seq2 = _interopRequireDefault(_seq);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a function which is a composition of the passed asynchronous
|
||||||
|
* functions. Each function consumes the return value of the function that
|
||||||
|
* follows. Composing functions `f()`, `g()`, and `h()` would produce the result
|
||||||
|
* of `f(g(h()))`, only this version uses callbacks to obtain the return values.
|
||||||
|
*
|
||||||
|
* If the last argument to the composed function is not a function, a promise
|
||||||
|
* is returned when you call it.
|
||||||
|
*
|
||||||
|
* Each function is executed with the `this` binding of the composed function.
|
||||||
|
*
|
||||||
|
* @name compose
|
||||||
|
* @static
|
||||||
|
* @memberOf module:ControlFlow
|
||||||
|
* @method
|
||||||
|
* @category Control Flow
|
||||||
|
* @param {...AsyncFunction} functions - the asynchronous functions to compose
|
||||||
|
* @returns {Function} an asynchronous function that is the composed
|
||||||
|
* asynchronous `functions`
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* function add1(n, callback) {
|
||||||
|
* setTimeout(function () {
|
||||||
|
* callback(null, n + 1);
|
||||||
|
* }, 10);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* function mul3(n, callback) {
|
||||||
|
* setTimeout(function () {
|
||||||
|
* callback(null, n * 3);
|
||||||
|
* }, 10);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* var add1mul3 = async.compose(mul3, add1);
|
||||||
|
* add1mul3(4, function (err, result) {
|
||||||
|
* // result now equals 15
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
function compose(...args) {
|
||||||
|
return (0, _seq2.default)(...args.reverse());
|
||||||
|
}
|
||||||
|
module.exports = exports.default;
|
||||||
115
node_modules/async/concat.js
generated
vendored
Normal file
115
node_modules/async/concat.js
generated
vendored
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _concatLimit = require('./concatLimit.js');
|
||||||
|
|
||||||
|
var _concatLimit2 = _interopRequireDefault(_concatLimit);
|
||||||
|
|
||||||
|
var _awaitify = require('./internal/awaitify.js');
|
||||||
|
|
||||||
|
var _awaitify2 = _interopRequireDefault(_awaitify);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies `iteratee` to each item in `coll`, concatenating the results. Returns
|
||||||
|
* the concatenated list. The `iteratee`s are called in parallel, and the
|
||||||
|
* results are concatenated as they return. The results array will be returned in
|
||||||
|
* the original order of `coll` passed to the `iteratee` function.
|
||||||
|
*
|
||||||
|
* @name concat
|
||||||
|
* @static
|
||||||
|
* @memberOf module:Collections
|
||||||
|
* @method
|
||||||
|
* @category Collection
|
||||||
|
* @alias flatMap
|
||||||
|
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
|
||||||
|
* @param {AsyncFunction} iteratee - A function to apply to each item in `coll`,
|
||||||
|
* which should use an array as its result. Invoked with (item, callback).
|
||||||
|
* @param {Function} [callback] - A callback which is called after all the
|
||||||
|
* `iteratee` functions have finished, or an error occurs. Results is an array
|
||||||
|
* containing the concatenated results of the `iteratee` function. Invoked with
|
||||||
|
* (err, results).
|
||||||
|
* @returns A Promise, if no callback is passed
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* // dir1 is a directory that contains file1.txt, file2.txt
|
||||||
|
* // dir2 is a directory that contains file3.txt, file4.txt
|
||||||
|
* // dir3 is a directory that contains file5.txt
|
||||||
|
* // dir4 does not exist
|
||||||
|
*
|
||||||
|
* let directoryList = ['dir1','dir2','dir3'];
|
||||||
|
* let withMissingDirectoryList = ['dir1','dir2','dir3', 'dir4'];
|
||||||
|
*
|
||||||
|
* // Using callbacks
|
||||||
|
* async.concat(directoryList, fs.readdir, function(err, results) {
|
||||||
|
* if (err) {
|
||||||
|
* console.log(err);
|
||||||
|
* } else {
|
||||||
|
* console.log(results);
|
||||||
|
* // [ 'file1.txt', 'file2.txt', 'file3.txt', 'file4.txt', file5.txt ]
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Error Handling
|
||||||
|
* async.concat(withMissingDirectoryList, fs.readdir, function(err, results) {
|
||||||
|
* if (err) {
|
||||||
|
* console.log(err);
|
||||||
|
* // [ Error: ENOENT: no such file or directory ]
|
||||||
|
* // since dir4 does not exist
|
||||||
|
* } else {
|
||||||
|
* console.log(results);
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Using Promises
|
||||||
|
* async.concat(directoryList, fs.readdir)
|
||||||
|
* .then(results => {
|
||||||
|
* console.log(results);
|
||||||
|
* // [ 'file1.txt', 'file2.txt', 'file3.txt', 'file4.txt', file5.txt ]
|
||||||
|
* }).catch(err => {
|
||||||
|
* console.log(err);
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Error Handling
|
||||||
|
* async.concat(withMissingDirectoryList, fs.readdir)
|
||||||
|
* .then(results => {
|
||||||
|
* console.log(results);
|
||||||
|
* }).catch(err => {
|
||||||
|
* console.log(err);
|
||||||
|
* // [ Error: ENOENT: no such file or directory ]
|
||||||
|
* // since dir4 does not exist
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Using async/await
|
||||||
|
* async () => {
|
||||||
|
* try {
|
||||||
|
* let results = await async.concat(directoryList, fs.readdir);
|
||||||
|
* console.log(results);
|
||||||
|
* // [ 'file1.txt', 'file2.txt', 'file3.txt', 'file4.txt', file5.txt ]
|
||||||
|
* } catch (err) {
|
||||||
|
* console.log(err);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // Error Handling
|
||||||
|
* async () => {
|
||||||
|
* try {
|
||||||
|
* let results = await async.concat(withMissingDirectoryList, fs.readdir);
|
||||||
|
* console.log(results);
|
||||||
|
* } catch (err) {
|
||||||
|
* console.log(err);
|
||||||
|
* // [ Error: ENOENT: no such file or directory ]
|
||||||
|
* // since dir4 does not exist
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function concat(coll, iteratee, callback) {
|
||||||
|
return (0, _concatLimit2.default)(coll, Infinity, iteratee, callback);
|
||||||
|
}
|
||||||
|
exports.default = (0, _awaitify2.default)(concat, 3);
|
||||||
|
module.exports = exports.default;
|
||||||
60
node_modules/async/concatLimit.js
generated
vendored
Normal file
60
node_modules/async/concatLimit.js
generated
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _wrapAsync = require('./internal/wrapAsync.js');
|
||||||
|
|
||||||
|
var _wrapAsync2 = _interopRequireDefault(_wrapAsync);
|
||||||
|
|
||||||
|
var _mapLimit = require('./mapLimit.js');
|
||||||
|
|
||||||
|
var _mapLimit2 = _interopRequireDefault(_mapLimit);
|
||||||
|
|
||||||
|
var _awaitify = require('./internal/awaitify.js');
|
||||||
|
|
||||||
|
var _awaitify2 = _interopRequireDefault(_awaitify);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The same as [`concat`]{@link module:Collections.concat} but runs a maximum of `limit` async operations at a time.
|
||||||
|
*
|
||||||
|
* @name concatLimit
|
||||||
|
* @static
|
||||||
|
* @memberOf module:Collections
|
||||||
|
* @method
|
||||||
|
* @see [async.concat]{@link module:Collections.concat}
|
||||||
|
* @category Collection
|
||||||
|
* @alias flatMapLimit
|
||||||
|
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
|
||||||
|
* @param {number} limit - The maximum number of async operations at a time.
|
||||||
|
* @param {AsyncFunction} iteratee - A function to apply to each item in `coll`,
|
||||||
|
* which should use an array as its result. Invoked with (item, callback).
|
||||||
|
* @param {Function} [callback] - A callback which is called after all the
|
||||||
|
* `iteratee` functions have finished, or an error occurs. Results is an array
|
||||||
|
* containing the concatenated results of the `iteratee` function. Invoked with
|
||||||
|
* (err, results).
|
||||||
|
* @returns A Promise, if no callback is passed
|
||||||
|
*/
|
||||||
|
function concatLimit(coll, limit, iteratee, callback) {
|
||||||
|
var _iteratee = (0, _wrapAsync2.default)(iteratee);
|
||||||
|
return (0, _mapLimit2.default)(coll, limit, (val, iterCb) => {
|
||||||
|
_iteratee(val, (err, ...args) => {
|
||||||
|
if (err) return iterCb(err);
|
||||||
|
return iterCb(err, args);
|
||||||
|
});
|
||||||
|
}, (err, mapResults) => {
|
||||||
|
var result = [];
|
||||||
|
for (var i = 0; i < mapResults.length; i++) {
|
||||||
|
if (mapResults[i]) {
|
||||||
|
result = result.concat(...mapResults[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return callback(err, result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.default = (0, _awaitify2.default)(concatLimit, 4);
|
||||||
|
module.exports = exports.default;
|
||||||
41
node_modules/async/concatSeries.js
generated
vendored
Normal file
41
node_modules/async/concatSeries.js
generated
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _concatLimit = require('./concatLimit.js');
|
||||||
|
|
||||||
|
var _concatLimit2 = _interopRequireDefault(_concatLimit);
|
||||||
|
|
||||||
|
var _awaitify = require('./internal/awaitify.js');
|
||||||
|
|
||||||
|
var _awaitify2 = _interopRequireDefault(_awaitify);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The same as [`concat`]{@link module:Collections.concat} but runs only a single async operation at a time.
|
||||||
|
*
|
||||||
|
* @name concatSeries
|
||||||
|
* @static
|
||||||
|
* @memberOf module:Collections
|
||||||
|
* @method
|
||||||
|
* @see [async.concat]{@link module:Collections.concat}
|
||||||
|
* @category Collection
|
||||||
|
* @alias flatMapSeries
|
||||||
|
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
|
||||||
|
* @param {AsyncFunction} iteratee - A function to apply to each item in `coll`.
|
||||||
|
* The iteratee should complete with an array an array of results.
|
||||||
|
* Invoked with (item, callback).
|
||||||
|
* @param {Function} [callback] - A callback which is called after all the
|
||||||
|
* `iteratee` functions have finished, or an error occurs. Results is an array
|
||||||
|
* containing the concatenated results of the `iteratee` function. Invoked with
|
||||||
|
* (err, results).
|
||||||
|
* @returns A Promise, if no callback is passed
|
||||||
|
*/
|
||||||
|
function concatSeries(coll, iteratee, callback) {
|
||||||
|
return (0, _concatLimit2.default)(coll, 1, iteratee, callback);
|
||||||
|
}
|
||||||
|
exports.default = (0, _awaitify2.default)(concatSeries, 3);
|
||||||
|
module.exports = exports.default;
|
||||||
14
node_modules/async/constant.js
generated
vendored
Normal file
14
node_modules/async/constant.js
generated
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.default = function (...args) {
|
||||||
|
return function (...ignoredArgs /*, callback*/) {
|
||||||
|
var callback = ignoredArgs.pop();
|
||||||
|
return callback(null, ...args);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = exports.default;
|
||||||
96
node_modules/async/detect.js
generated
vendored
Normal file
96
node_modules/async/detect.js
generated
vendored
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _createTester = require('./internal/createTester.js');
|
||||||
|
|
||||||
|
var _createTester2 = _interopRequireDefault(_createTester);
|
||||||
|
|
||||||
|
var _eachOf = require('./eachOf.js');
|
||||||
|
|
||||||
|
var _eachOf2 = _interopRequireDefault(_eachOf);
|
||||||
|
|
||||||
|
var _awaitify = require('./internal/awaitify.js');
|
||||||
|
|
||||||
|
var _awaitify2 = _interopRequireDefault(_awaitify);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first value in `coll` that passes an async truth test. The
|
||||||
|
* `iteratee` is applied in parallel, meaning the first iteratee to return
|
||||||
|
* `true` will fire the detect `callback` with that result. That means the
|
||||||
|
* result might not be the first item in the original `coll` (in terms of order)
|
||||||
|
* that passes the test.
|
||||||
|
|
||||||
|
* If order within the original `coll` is important, then look at
|
||||||
|
* [`detectSeries`]{@link module:Collections.detectSeries}.
|
||||||
|
*
|
||||||
|
* @name detect
|
||||||
|
* @static
|
||||||
|
* @memberOf module:Collections
|
||||||
|
* @method
|
||||||
|
* @alias find
|
||||||
|
* @category Collections
|
||||||
|
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
|
||||||
|
* @param {AsyncFunction} iteratee - A truth test to apply to each item in `coll`.
|
||||||
|
* The iteratee must complete with a boolean value as its result.
|
||||||
|
* Invoked with (item, callback).
|
||||||
|
* @param {Function} [callback] - A callback which is called as soon as any
|
||||||
|
* iteratee returns `true`, or after all the `iteratee` functions have finished.
|
||||||
|
* Result will be the first item in the array that passes the truth test
|
||||||
|
* (iteratee) or the value `undefined` if none passed. Invoked with
|
||||||
|
* (err, result).
|
||||||
|
* @returns {Promise} a promise, if a callback is omitted
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* // dir1 is a directory that contains file1.txt, file2.txt
|
||||||
|
* // dir2 is a directory that contains file3.txt, file4.txt
|
||||||
|
* // dir3 is a directory that contains file5.txt
|
||||||
|
*
|
||||||
|
* // asynchronous function that checks if a file exists
|
||||||
|
* function fileExists(file, callback) {
|
||||||
|
* fs.access(file, fs.constants.F_OK, (err) => {
|
||||||
|
* callback(null, !err);
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* async.detect(['file3.txt','file2.txt','dir1/file1.txt'], fileExists,
|
||||||
|
* function(err, result) {
|
||||||
|
* console.log(result);
|
||||||
|
* // dir1/file1.txt
|
||||||
|
* // result now equals the first file in the list that exists
|
||||||
|
* }
|
||||||
|
*);
|
||||||
|
*
|
||||||
|
* // Using Promises
|
||||||
|
* async.detect(['file3.txt','file2.txt','dir1/file1.txt'], fileExists)
|
||||||
|
* .then(result => {
|
||||||
|
* console.log(result);
|
||||||
|
* // dir1/file1.txt
|
||||||
|
* // result now equals the first file in the list that exists
|
||||||
|
* }).catch(err => {
|
||||||
|
* console.log(err);
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Using async/await
|
||||||
|
* async () => {
|
||||||
|
* try {
|
||||||
|
* let result = await async.detect(['file3.txt','file2.txt','dir1/file1.txt'], fileExists);
|
||||||
|
* console.log(result);
|
||||||
|
* // dir1/file1.txt
|
||||||
|
* // result now equals the file in the list that exists
|
||||||
|
* }
|
||||||
|
* catch (err) {
|
||||||
|
* console.log(err);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function detect(coll, iteratee, callback) {
|
||||||
|
return (0, _createTester2.default)(bool => bool, (res, item) => item)(_eachOf2.default, coll, iteratee, callback);
|
||||||
|
}
|
||||||
|
exports.default = (0, _awaitify2.default)(detect, 3);
|
||||||
|
module.exports = exports.default;
|
||||||
48
node_modules/async/detectLimit.js
generated
vendored
Normal file
48
node_modules/async/detectLimit.js
generated
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _createTester = require('./internal/createTester.js');
|
||||||
|
|
||||||
|
var _createTester2 = _interopRequireDefault(_createTester);
|
||||||
|
|
||||||
|
var _eachOfLimit = require('./internal/eachOfLimit.js');
|
||||||
|
|
||||||
|
var _eachOfLimit2 = _interopRequireDefault(_eachOfLimit);
|
||||||
|
|
||||||
|
var _awaitify = require('./internal/awaitify.js');
|
||||||
|
|
||||||
|
var _awaitify2 = _interopRequireDefault(_awaitify);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The same as [`detect`]{@link module:Collections.detect} but runs a maximum of `limit` async operations at a
|
||||||
|
* time.
|
||||||
|
*
|
||||||
|
* @name detectLimit
|
||||||
|
* @static
|
||||||
|
* @memberOf module:Collections
|
||||||
|
* @method
|
||||||
|
* @see [async.detect]{@link module:Collections.detect}
|
||||||
|
* @alias findLimit
|
||||||
|
* @category Collections
|
||||||
|
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
|
||||||
|
* @param {number} limit - The maximum number of async operations at a time.
|
||||||
|
* @param {AsyncFunction} iteratee - A truth test to apply to each item in `coll`.
|
||||||
|
* The iteratee must complete with a boolean value as its result.
|
||||||
|
* Invoked with (item, callback).
|
||||||
|
* @param {Function} [callback] - A callback which is called as soon as any
|
||||||
|
* iteratee returns `true`, or after all the `iteratee` functions have finished.
|
||||||
|
* Result will be the first item in the array that passes the truth test
|
||||||
|
* (iteratee) or the value `undefined` if none passed. Invoked with
|
||||||
|
* (err, result).
|
||||||
|
* @returns {Promise} a promise, if a callback is omitted
|
||||||
|
*/
|
||||||
|
function detectLimit(coll, limit, iteratee, callback) {
|
||||||
|
return (0, _createTester2.default)(bool => bool, (res, item) => item)((0, _eachOfLimit2.default)(limit), coll, iteratee, callback);
|
||||||
|
}
|
||||||
|
exports.default = (0, _awaitify2.default)(detectLimit, 4);
|
||||||
|
module.exports = exports.default;
|
||||||
47
node_modules/async/detectSeries.js
generated
vendored
Normal file
47
node_modules/async/detectSeries.js
generated
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _createTester = require('./internal/createTester.js');
|
||||||
|
|
||||||
|
var _createTester2 = _interopRequireDefault(_createTester);
|
||||||
|
|
||||||
|
var _eachOfLimit = require('./internal/eachOfLimit.js');
|
||||||
|
|
||||||
|
var _eachOfLimit2 = _interopRequireDefault(_eachOfLimit);
|
||||||
|
|
||||||
|
var _awaitify = require('./internal/awaitify.js');
|
||||||
|
|
||||||
|
var _awaitify2 = _interopRequireDefault(_awaitify);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The same as [`detect`]{@link module:Collections.detect} but runs only a single async operation at a time.
|
||||||
|
*
|
||||||
|
* @name detectSeries
|
||||||
|
* @static
|
||||||
|
* @memberOf module:Collections
|
||||||
|
* @method
|
||||||
|
* @see [async.detect]{@link module:Collections.detect}
|
||||||
|
* @alias findSeries
|
||||||
|
* @category Collections
|
||||||
|
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
|
||||||
|
* @param {AsyncFunction} iteratee - A truth test to apply to each item in `coll`.
|
||||||
|
* The iteratee must complete with a boolean value as its result.
|
||||||
|
* Invoked with (item, callback).
|
||||||
|
* @param {Function} [callback] - A callback which is called as soon as any
|
||||||
|
* iteratee returns `true`, or after all the `iteratee` functions have finished.
|
||||||
|
* Result will be the first item in the array that passes the truth test
|
||||||
|
* (iteratee) or the value `undefined` if none passed. Invoked with
|
||||||
|
* (err, result).
|
||||||
|
* @returns {Promise} a promise, if a callback is omitted
|
||||||
|
*/
|
||||||
|
function detectSeries(coll, iteratee, callback) {
|
||||||
|
return (0, _createTester2.default)(bool => bool, (res, item) => item)((0, _eachOfLimit2.default)(1), coll, iteratee, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.default = (0, _awaitify2.default)(detectSeries, 3);
|
||||||
|
module.exports = exports.default;
|
||||||
43
node_modules/async/dir.js
generated
vendored
Normal file
43
node_modules/async/dir.js
generated
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _consoleFunc = require('./internal/consoleFunc.js');
|
||||||
|
|
||||||
|
var _consoleFunc2 = _interopRequireDefault(_consoleFunc);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs the result of an [`async` function]{@link AsyncFunction} to the
|
||||||
|
* `console` using `console.dir` to display the properties of the resulting object.
|
||||||
|
* Only works in Node.js or in browsers that support `console.dir` and
|
||||||
|
* `console.error` (such as FF and Chrome).
|
||||||
|
* If multiple arguments are returned from the async function,
|
||||||
|
* `console.dir` is called on each argument in order.
|
||||||
|
*
|
||||||
|
* @name dir
|
||||||
|
* @static
|
||||||
|
* @memberOf module:Utils
|
||||||
|
* @method
|
||||||
|
* @category Util
|
||||||
|
* @param {AsyncFunction} function - The function you want to eventually apply
|
||||||
|
* all arguments to.
|
||||||
|
* @param {...*} arguments... - Any number of arguments to apply to the function.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* // in a module
|
||||||
|
* var hello = function(name, callback) {
|
||||||
|
* setTimeout(function() {
|
||||||
|
* callback(null, {hello: name});
|
||||||
|
* }, 1000);
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* // in the node repl
|
||||||
|
* node> async.dir(hello, 'world');
|
||||||
|
* {hello: 'world'}
|
||||||
|
*/
|
||||||
|
exports.default = (0, _consoleFunc2.default)('dir');
|
||||||
|
module.exports = exports.default;
|
||||||
6061
node_modules/async/dist/async.js
generated
vendored
Normal file
6061
node_modules/async/dist/async.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
1
node_modules/async/dist/async.min.js
generated
vendored
Normal file
1
node_modules/async/dist/async.min.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
5948
node_modules/async/dist/async.mjs
generated
vendored
Normal file
5948
node_modules/async/dist/async.mjs
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
68
node_modules/async/doDuring.js
generated
vendored
Normal file
68
node_modules/async/doDuring.js
generated
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _onlyOnce = require('./internal/onlyOnce.js');
|
||||||
|
|
||||||
|
var _onlyOnce2 = _interopRequireDefault(_onlyOnce);
|
||||||
|
|
||||||
|
var _wrapAsync = require('./internal/wrapAsync.js');
|
||||||
|
|
||||||
|
var _wrapAsync2 = _interopRequireDefault(_wrapAsync);
|
||||||
|
|
||||||
|
var _awaitify = require('./internal/awaitify.js');
|
||||||
|
|
||||||
|
var _awaitify2 = _interopRequireDefault(_awaitify);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The post-check version of [`whilst`]{@link module:ControlFlow.whilst}. To reflect the difference in
|
||||||
|
* the order of operations, the arguments `test` and `iteratee` are switched.
|
||||||
|
*
|
||||||
|
* `doWhilst` is to `whilst` as `do while` is to `while` in plain JavaScript.
|
||||||
|
*
|
||||||
|
* @name doWhilst
|
||||||
|
* @static
|
||||||
|
* @memberOf module:ControlFlow
|
||||||
|
* @method
|
||||||
|
* @see [async.whilst]{@link module:ControlFlow.whilst}
|
||||||
|
* @category Control Flow
|
||||||
|
* @param {AsyncFunction} iteratee - A function which is called each time `test`
|
||||||
|
* passes. Invoked with (callback).
|
||||||
|
* @param {AsyncFunction} test - asynchronous truth test to perform after each
|
||||||
|
* execution of `iteratee`. Invoked with (...args, callback), where `...args` are the
|
||||||
|
* non-error args from the previous callback of `iteratee`.
|
||||||
|
* @param {Function} [callback] - A callback which is called after the test
|
||||||
|
* function has failed and repeated execution of `iteratee` has stopped.
|
||||||
|
* `callback` will be passed an error and any arguments passed to the final
|
||||||
|
* `iteratee`'s callback. Invoked with (err, [results]);
|
||||||
|
* @returns {Promise} a promise, if no callback is passed
|
||||||
|
*/
|
||||||
|
function doWhilst(iteratee, test, callback) {
|
||||||
|
callback = (0, _onlyOnce2.default)(callback);
|
||||||
|
var _fn = (0, _wrapAsync2.default)(iteratee);
|
||||||
|
var _test = (0, _wrapAsync2.default)(test);
|
||||||
|
var results;
|
||||||
|
|
||||||
|
function next(err, ...args) {
|
||||||
|
if (err) return callback(err);
|
||||||
|
if (err === false) return;
|
||||||
|
results = args;
|
||||||
|
_test(...args, check);
|
||||||
|
}
|
||||||
|
|
||||||
|
function check(err, truth) {
|
||||||
|
if (err) return callback(err);
|
||||||
|
if (err === false) return;
|
||||||
|
if (!truth) return callback(null, ...results);
|
||||||
|
_fn(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
return check(null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.default = (0, _awaitify2.default)(doWhilst, 3);
|
||||||
|
module.exports = exports.default;
|
||||||
46
node_modules/async/doUntil.js
generated
vendored
Normal file
46
node_modules/async/doUntil.js
generated
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.default = doUntil;
|
||||||
|
|
||||||
|
var _doWhilst = require('./doWhilst.js');
|
||||||
|
|
||||||
|
var _doWhilst2 = _interopRequireDefault(_doWhilst);
|
||||||
|
|
||||||
|
var _wrapAsync = require('./internal/wrapAsync.js');
|
||||||
|
|
||||||
|
var _wrapAsync2 = _interopRequireDefault(_wrapAsync);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like ['doWhilst']{@link module:ControlFlow.doWhilst}, except the `test` is inverted. Note the
|
||||||
|
* argument ordering differs from `until`.
|
||||||
|
*
|
||||||
|
* @name doUntil
|
||||||
|
* @static
|
||||||
|
* @memberOf module:ControlFlow
|
||||||
|
* @method
|
||||||
|
* @see [async.doWhilst]{@link module:ControlFlow.doWhilst}
|
||||||
|
* @category Control Flow
|
||||||
|
* @param {AsyncFunction} iteratee - An async function which is called each time
|
||||||
|
* `test` fails. Invoked with (callback).
|
||||||
|
* @param {AsyncFunction} test - asynchronous truth test to perform after each
|
||||||
|
* execution of `iteratee`. Invoked with (...args, callback), where `...args` are the
|
||||||
|
* non-error args from the previous callback of `iteratee`
|
||||||
|
* @param {Function} [callback] - A callback which is called after the test
|
||||||
|
* function has passed and repeated execution of `iteratee` has stopped. `callback`
|
||||||
|
* will be passed an error and any arguments passed to the final `iteratee`'s
|
||||||
|
* callback. Invoked with (err, [results]);
|
||||||
|
* @returns {Promise} a promise, if no callback is passed
|
||||||
|
*/
|
||||||
|
function doUntil(iteratee, test, callback) {
|
||||||
|
const _test = (0, _wrapAsync2.default)(test);
|
||||||
|
return (0, _doWhilst2.default)(iteratee, (...args) => {
|
||||||
|
const cb = args.pop();
|
||||||
|
_test(...args, (err, truth) => cb(err, !truth));
|
||||||
|
}, callback);
|
||||||
|
}
|
||||||
|
module.exports = exports.default;
|
||||||
68
node_modules/async/doWhilst.js
generated
vendored
Normal file
68
node_modules/async/doWhilst.js
generated
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _onlyOnce = require('./internal/onlyOnce.js');
|
||||||
|
|
||||||
|
var _onlyOnce2 = _interopRequireDefault(_onlyOnce);
|
||||||
|
|
||||||
|
var _wrapAsync = require('./internal/wrapAsync.js');
|
||||||
|
|
||||||
|
var _wrapAsync2 = _interopRequireDefault(_wrapAsync);
|
||||||
|
|
||||||
|
var _awaitify = require('./internal/awaitify.js');
|
||||||
|
|
||||||
|
var _awaitify2 = _interopRequireDefault(_awaitify);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The post-check version of [`whilst`]{@link module:ControlFlow.whilst}. To reflect the difference in
|
||||||
|
* the order of operations, the arguments `test` and `iteratee` are switched.
|
||||||
|
*
|
||||||
|
* `doWhilst` is to `whilst` as `do while` is to `while` in plain JavaScript.
|
||||||
|
*
|
||||||
|
* @name doWhilst
|
||||||
|
* @static
|
||||||
|
* @memberOf module:ControlFlow
|
||||||
|
* @method
|
||||||
|
* @see [async.whilst]{@link module:ControlFlow.whilst}
|
||||||
|
* @category Control Flow
|
||||||
|
* @param {AsyncFunction} iteratee - A function which is called each time `test`
|
||||||
|
* passes. Invoked with (callback).
|
||||||
|
* @param {AsyncFunction} test - asynchronous truth test to perform after each
|
||||||
|
* execution of `iteratee`. Invoked with (...args, callback), where `...args` are the
|
||||||
|
* non-error args from the previous callback of `iteratee`.
|
||||||
|
* @param {Function} [callback] - A callback which is called after the test
|
||||||
|
* function has failed and repeated execution of `iteratee` has stopped.
|
||||||
|
* `callback` will be passed an error and any arguments passed to the final
|
||||||
|
* `iteratee`'s callback. Invoked with (err, [results]);
|
||||||
|
* @returns {Promise} a promise, if no callback is passed
|
||||||
|
*/
|
||||||
|
function doWhilst(iteratee, test, callback) {
|
||||||
|
callback = (0, _onlyOnce2.default)(callback);
|
||||||
|
var _fn = (0, _wrapAsync2.default)(iteratee);
|
||||||
|
var _test = (0, _wrapAsync2.default)(test);
|
||||||
|
var results;
|
||||||
|
|
||||||
|
function next(err, ...args) {
|
||||||
|
if (err) return callback(err);
|
||||||
|
if (err === false) return;
|
||||||
|
results = args;
|
||||||
|
_test(...args, check);
|
||||||
|
}
|
||||||
|
|
||||||
|
function check(err, truth) {
|
||||||
|
if (err) return callback(err);
|
||||||
|
if (err === false) return;
|
||||||
|
if (!truth) return callback(null, ...results);
|
||||||
|
_fn(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
return check(null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.default = (0, _awaitify2.default)(doWhilst, 3);
|
||||||
|
module.exports = exports.default;
|
||||||
78
node_modules/async/during.js
generated
vendored
Normal file
78
node_modules/async/during.js
generated
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _onlyOnce = require('./internal/onlyOnce.js');
|
||||||
|
|
||||||
|
var _onlyOnce2 = _interopRequireDefault(_onlyOnce);
|
||||||
|
|
||||||
|
var _wrapAsync = require('./internal/wrapAsync.js');
|
||||||
|
|
||||||
|
var _wrapAsync2 = _interopRequireDefault(_wrapAsync);
|
||||||
|
|
||||||
|
var _awaitify = require('./internal/awaitify.js');
|
||||||
|
|
||||||
|
var _awaitify2 = _interopRequireDefault(_awaitify);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repeatedly call `iteratee`, while `test` returns `true`. Calls `callback` when
|
||||||
|
* stopped, or an error occurs.
|
||||||
|
*
|
||||||
|
* @name whilst
|
||||||
|
* @static
|
||||||
|
* @memberOf module:ControlFlow
|
||||||
|
* @method
|
||||||
|
* @category Control Flow
|
||||||
|
* @param {AsyncFunction} test - asynchronous truth test to perform before each
|
||||||
|
* execution of `iteratee`. Invoked with (callback).
|
||||||
|
* @param {AsyncFunction} iteratee - An async function which is called each time
|
||||||
|
* `test` passes. Invoked with (callback).
|
||||||
|
* @param {Function} [callback] - A callback which is called after the test
|
||||||
|
* function has failed and repeated execution of `iteratee` has stopped. `callback`
|
||||||
|
* will be passed an error and any arguments passed to the final `iteratee`'s
|
||||||
|
* callback. Invoked with (err, [results]);
|
||||||
|
* @returns {Promise} a promise, if no callback is passed
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* var count = 0;
|
||||||
|
* async.whilst(
|
||||||
|
* function test(cb) { cb(null, count < 5); },
|
||||||
|
* function iter(callback) {
|
||||||
|
* count++;
|
||||||
|
* setTimeout(function() {
|
||||||
|
* callback(null, count);
|
||||||
|
* }, 1000);
|
||||||
|
* },
|
||||||
|
* function (err, n) {
|
||||||
|
* // 5 seconds have passed, n = 5
|
||||||
|
* }
|
||||||
|
* );
|
||||||
|
*/
|
||||||
|
function whilst(test, iteratee, callback) {
|
||||||
|
callback = (0, _onlyOnce2.default)(callback);
|
||||||
|
var _fn = (0, _wrapAsync2.default)(iteratee);
|
||||||
|
var _test = (0, _wrapAsync2.default)(test);
|
||||||
|
var results = [];
|
||||||
|
|
||||||
|
function next(err, ...rest) {
|
||||||
|
if (err) return callback(err);
|
||||||
|
results = rest;
|
||||||
|
if (err === false) return;
|
||||||
|
_test(check);
|
||||||
|
}
|
||||||
|
|
||||||
|
function check(err, truth) {
|
||||||
|
if (err) return callback(err);
|
||||||
|
if (err === false) return;
|
||||||
|
if (!truth) return callback(null, ...results);
|
||||||
|
_fn(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _test(check);
|
||||||
|
}
|
||||||
|
exports.default = (0, _awaitify2.default)(whilst, 3);
|
||||||
|
module.exports = exports.default;
|
||||||
129
node_modules/async/each.js
generated
vendored
Normal file
129
node_modules/async/each.js
generated
vendored
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _eachOf = require('./eachOf.js');
|
||||||
|
|
||||||
|
var _eachOf2 = _interopRequireDefault(_eachOf);
|
||||||
|
|
||||||
|
var _withoutIndex = require('./internal/withoutIndex.js');
|
||||||
|
|
||||||
|
var _withoutIndex2 = _interopRequireDefault(_withoutIndex);
|
||||||
|
|
||||||
|
var _wrapAsync = require('./internal/wrapAsync.js');
|
||||||
|
|
||||||
|
var _wrapAsync2 = _interopRequireDefault(_wrapAsync);
|
||||||
|
|
||||||
|
var _awaitify = require('./internal/awaitify.js');
|
||||||
|
|
||||||
|
var _awaitify2 = _interopRequireDefault(_awaitify);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the function `iteratee` to each item in `coll`, in parallel.
|
||||||
|
* The `iteratee` is called with an item from the list, and a callback for when
|
||||||
|
* it has finished. If the `iteratee` passes an error to its `callback`, the
|
||||||
|
* main `callback` (for the `each` function) is immediately called with the
|
||||||
|
* error.
|
||||||
|
*
|
||||||
|
* Note, that since this function applies `iteratee` to each item in parallel,
|
||||||
|
* there is no guarantee that the iteratee functions will complete in order.
|
||||||
|
*
|
||||||
|
* @name each
|
||||||
|
* @static
|
||||||
|
* @memberOf module:Collections
|
||||||
|
* @method
|
||||||
|
* @alias forEach
|
||||||
|
* @category Collection
|
||||||
|
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
|
||||||
|
* @param {AsyncFunction} iteratee - An async function to apply to
|
||||||
|
* each item in `coll`. Invoked with (item, callback).
|
||||||
|
* The array index is not passed to the iteratee.
|
||||||
|
* If you need the index, use `eachOf`.
|
||||||
|
* @param {Function} [callback] - A callback which is called when all
|
||||||
|
* `iteratee` functions have finished, or an error occurs. Invoked with (err).
|
||||||
|
* @returns {Promise} a promise, if a callback is omitted
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* // dir1 is a directory that contains file1.txt, file2.txt
|
||||||
|
* // dir2 is a directory that contains file3.txt, file4.txt
|
||||||
|
* // dir3 is a directory that contains file5.txt
|
||||||
|
* // dir4 does not exist
|
||||||
|
*
|
||||||
|
* const fileList = [ 'dir1/file2.txt', 'dir2/file3.txt', 'dir/file5.txt'];
|
||||||
|
* const withMissingFileList = ['dir1/file1.txt', 'dir4/file2.txt'];
|
||||||
|
*
|
||||||
|
* // asynchronous function that deletes a file
|
||||||
|
* const deleteFile = function(file, callback) {
|
||||||
|
* fs.unlink(file, callback);
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* // Using callbacks
|
||||||
|
* async.each(fileList, deleteFile, function(err) {
|
||||||
|
* if( err ) {
|
||||||
|
* console.log(err);
|
||||||
|
* } else {
|
||||||
|
* console.log('All files have been deleted successfully');
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Error Handling
|
||||||
|
* async.each(withMissingFileList, deleteFile, function(err){
|
||||||
|
* console.log(err);
|
||||||
|
* // [ Error: ENOENT: no such file or directory ]
|
||||||
|
* // since dir4/file2.txt does not exist
|
||||||
|
* // dir1/file1.txt could have been deleted
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Using Promises
|
||||||
|
* async.each(fileList, deleteFile)
|
||||||
|
* .then( () => {
|
||||||
|
* console.log('All files have been deleted successfully');
|
||||||
|
* }).catch( err => {
|
||||||
|
* console.log(err);
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Error Handling
|
||||||
|
* async.each(fileList, deleteFile)
|
||||||
|
* .then( () => {
|
||||||
|
* console.log('All files have been deleted successfully');
|
||||||
|
* }).catch( err => {
|
||||||
|
* console.log(err);
|
||||||
|
* // [ Error: ENOENT: no such file or directory ]
|
||||||
|
* // since dir4/file2.txt does not exist
|
||||||
|
* // dir1/file1.txt could have been deleted
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Using async/await
|
||||||
|
* async () => {
|
||||||
|
* try {
|
||||||
|
* await async.each(files, deleteFile);
|
||||||
|
* }
|
||||||
|
* catch (err) {
|
||||||
|
* console.log(err);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // Error Handling
|
||||||
|
* async () => {
|
||||||
|
* try {
|
||||||
|
* await async.each(withMissingFileList, deleteFile);
|
||||||
|
* }
|
||||||
|
* catch (err) {
|
||||||
|
* console.log(err);
|
||||||
|
* // [ Error: ENOENT: no such file or directory ]
|
||||||
|
* // since dir4/file2.txt does not exist
|
||||||
|
* // dir1/file1.txt could have been deleted
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function eachLimit(coll, iteratee, callback) {
|
||||||
|
return (0, _eachOf2.default)(coll, (0, _withoutIndex2.default)((0, _wrapAsync2.default)(iteratee)), callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.default = (0, _awaitify2.default)(eachLimit, 3);
|
||||||
|
module.exports = exports.default;
|
||||||
50
node_modules/async/eachLimit.js
generated
vendored
Normal file
50
node_modules/async/eachLimit.js
generated
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _eachOfLimit = require('./internal/eachOfLimit.js');
|
||||||
|
|
||||||
|
var _eachOfLimit2 = _interopRequireDefault(_eachOfLimit);
|
||||||
|
|
||||||
|
var _withoutIndex = require('./internal/withoutIndex.js');
|
||||||
|
|
||||||
|
var _withoutIndex2 = _interopRequireDefault(_withoutIndex);
|
||||||
|
|
||||||
|
var _wrapAsync = require('./internal/wrapAsync.js');
|
||||||
|
|
||||||
|
var _wrapAsync2 = _interopRequireDefault(_wrapAsync);
|
||||||
|
|
||||||
|
var _awaitify = require('./internal/awaitify.js');
|
||||||
|
|
||||||
|
var _awaitify2 = _interopRequireDefault(_awaitify);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The same as [`each`]{@link module:Collections.each} but runs a maximum of `limit` async operations at a time.
|
||||||
|
*
|
||||||
|
* @name eachLimit
|
||||||
|
* @static
|
||||||
|
* @memberOf module:Collections
|
||||||
|
* @method
|
||||||
|
* @see [async.each]{@link module:Collections.each}
|
||||||
|
* @alias forEachLimit
|
||||||
|
* @category Collection
|
||||||
|
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
|
||||||
|
* @param {number} limit - The maximum number of async operations at a time.
|
||||||
|
* @param {AsyncFunction} iteratee - An async function to apply to each item in
|
||||||
|
* `coll`.
|
||||||
|
* The array index is not passed to the iteratee.
|
||||||
|
* If you need the index, use `eachOfLimit`.
|
||||||
|
* Invoked with (item, callback).
|
||||||
|
* @param {Function} [callback] - A callback which is called when all
|
||||||
|
* `iteratee` functions have finished, or an error occurs. Invoked with (err).
|
||||||
|
* @returns {Promise} a promise, if a callback is omitted
|
||||||
|
*/
|
||||||
|
function eachLimit(coll, limit, iteratee, callback) {
|
||||||
|
return (0, _eachOfLimit2.default)(limit)(coll, (0, _withoutIndex2.default)((0, _wrapAsync2.default)(iteratee)), callback);
|
||||||
|
}
|
||||||
|
exports.default = (0, _awaitify2.default)(eachLimit, 4);
|
||||||
|
module.exports = exports.default;
|
||||||
185
node_modules/async/eachOf.js
generated
vendored
Normal file
185
node_modules/async/eachOf.js
generated
vendored
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _isArrayLike = require('./internal/isArrayLike.js');
|
||||||
|
|
||||||
|
var _isArrayLike2 = _interopRequireDefault(_isArrayLike);
|
||||||
|
|
||||||
|
var _breakLoop = require('./internal/breakLoop.js');
|
||||||
|
|
||||||
|
var _breakLoop2 = _interopRequireDefault(_breakLoop);
|
||||||
|
|
||||||
|
var _eachOfLimit = require('./eachOfLimit.js');
|
||||||
|
|
||||||
|
var _eachOfLimit2 = _interopRequireDefault(_eachOfLimit);
|
||||||
|
|
||||||
|
var _once = require('./internal/once.js');
|
||||||
|
|
||||||
|
var _once2 = _interopRequireDefault(_once);
|
||||||
|
|
||||||
|
var _onlyOnce = require('./internal/onlyOnce.js');
|
||||||
|
|
||||||
|
var _onlyOnce2 = _interopRequireDefault(_onlyOnce);
|
||||||
|
|
||||||
|
var _wrapAsync = require('./internal/wrapAsync.js');
|
||||||
|
|
||||||
|
var _wrapAsync2 = _interopRequireDefault(_wrapAsync);
|
||||||
|
|
||||||
|
var _awaitify = require('./internal/awaitify.js');
|
||||||
|
|
||||||
|
var _awaitify2 = _interopRequireDefault(_awaitify);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
// eachOf implementation optimized for array-likes
|
||||||
|
function eachOfArrayLike(coll, iteratee, callback) {
|
||||||
|
callback = (0, _once2.default)(callback);
|
||||||
|
var index = 0,
|
||||||
|
completed = 0,
|
||||||
|
{ length } = coll,
|
||||||
|
canceled = false;
|
||||||
|
if (length === 0) {
|
||||||
|
callback(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function iteratorCallback(err, value) {
|
||||||
|
if (err === false) {
|
||||||
|
canceled = true;
|
||||||
|
}
|
||||||
|
if (canceled === true) return;
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
} else if (++completed === length || value === _breakLoop2.default) {
|
||||||
|
callback(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; index < length; index++) {
|
||||||
|
iteratee(coll[index], index, (0, _onlyOnce2.default)(iteratorCallback));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// a generic version of eachOf which can handle array, object, and iterator cases.
|
||||||
|
function eachOfGeneric(coll, iteratee, callback) {
|
||||||
|
return (0, _eachOfLimit2.default)(coll, Infinity, iteratee, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like [`each`]{@link module:Collections.each}, except that it passes the key (or index) as the second argument
|
||||||
|
* to the iteratee.
|
||||||
|
*
|
||||||
|
* @name eachOf
|
||||||
|
* @static
|
||||||
|
* @memberOf module:Collections
|
||||||
|
* @method
|
||||||
|
* @alias forEachOf
|
||||||
|
* @category Collection
|
||||||
|
* @see [async.each]{@link module:Collections.each}
|
||||||
|
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
|
||||||
|
* @param {AsyncFunction} iteratee - A function to apply to each
|
||||||
|
* item in `coll`.
|
||||||
|
* The `key` is the item's key, or index in the case of an array.
|
||||||
|
* Invoked with (item, key, callback).
|
||||||
|
* @param {Function} [callback] - A callback which is called when all
|
||||||
|
* `iteratee` functions have finished, or an error occurs. Invoked with (err).
|
||||||
|
* @returns {Promise} a promise, if a callback is omitted
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* // dev.json is a file containing a valid json object config for dev environment
|
||||||
|
* // dev.json is a file containing a valid json object config for test environment
|
||||||
|
* // prod.json is a file containing a valid json object config for prod environment
|
||||||
|
* // invalid.json is a file with a malformed json object
|
||||||
|
*
|
||||||
|
* let configs = {}; //global variable
|
||||||
|
* let validConfigFileMap = {dev: 'dev.json', test: 'test.json', prod: 'prod.json'};
|
||||||
|
* let invalidConfigFileMap = {dev: 'dev.json', test: 'test.json', invalid: 'invalid.json'};
|
||||||
|
*
|
||||||
|
* // asynchronous function that reads a json file and parses the contents as json object
|
||||||
|
* function parseFile(file, key, callback) {
|
||||||
|
* fs.readFile(file, "utf8", function(err, data) {
|
||||||
|
* if (err) return calback(err);
|
||||||
|
* try {
|
||||||
|
* configs[key] = JSON.parse(data);
|
||||||
|
* } catch (e) {
|
||||||
|
* return callback(e);
|
||||||
|
* }
|
||||||
|
* callback();
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // Using callbacks
|
||||||
|
* async.forEachOf(validConfigFileMap, parseFile, function (err) {
|
||||||
|
* if (err) {
|
||||||
|
* console.error(err);
|
||||||
|
* } else {
|
||||||
|
* console.log(configs);
|
||||||
|
* // configs is now a map of JSON data, e.g.
|
||||||
|
* // { dev: //parsed dev.json, test: //parsed test.json, prod: //parsed prod.json}
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* //Error handing
|
||||||
|
* async.forEachOf(invalidConfigFileMap, parseFile, function (err) {
|
||||||
|
* if (err) {
|
||||||
|
* console.error(err);
|
||||||
|
* // JSON parse error exception
|
||||||
|
* } else {
|
||||||
|
* console.log(configs);
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Using Promises
|
||||||
|
* async.forEachOf(validConfigFileMap, parseFile)
|
||||||
|
* .then( () => {
|
||||||
|
* console.log(configs);
|
||||||
|
* // configs is now a map of JSON data, e.g.
|
||||||
|
* // { dev: //parsed dev.json, test: //parsed test.json, prod: //parsed prod.json}
|
||||||
|
* }).catch( err => {
|
||||||
|
* console.error(err);
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* //Error handing
|
||||||
|
* async.forEachOf(invalidConfigFileMap, parseFile)
|
||||||
|
* .then( () => {
|
||||||
|
* console.log(configs);
|
||||||
|
* }).catch( err => {
|
||||||
|
* console.error(err);
|
||||||
|
* // JSON parse error exception
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Using async/await
|
||||||
|
* async () => {
|
||||||
|
* try {
|
||||||
|
* let result = await async.forEachOf(validConfigFileMap, parseFile);
|
||||||
|
* console.log(configs);
|
||||||
|
* // configs is now a map of JSON data, e.g.
|
||||||
|
* // { dev: //parsed dev.json, test: //parsed test.json, prod: //parsed prod.json}
|
||||||
|
* }
|
||||||
|
* catch (err) {
|
||||||
|
* console.log(err);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* //Error handing
|
||||||
|
* async () => {
|
||||||
|
* try {
|
||||||
|
* let result = await async.forEachOf(invalidConfigFileMap, parseFile);
|
||||||
|
* console.log(configs);
|
||||||
|
* }
|
||||||
|
* catch (err) {
|
||||||
|
* console.log(err);
|
||||||
|
* // JSON parse error exception
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function eachOf(coll, iteratee, callback) {
|
||||||
|
var eachOfImplementation = (0, _isArrayLike2.default)(coll) ? eachOfArrayLike : eachOfGeneric;
|
||||||
|
return eachOfImplementation(coll, (0, _wrapAsync2.default)(iteratee), callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.default = (0, _awaitify2.default)(eachOf, 3);
|
||||||
|
module.exports = exports.default;
|
||||||
47
node_modules/async/eachOfLimit.js
generated
vendored
Normal file
47
node_modules/async/eachOfLimit.js
generated
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _eachOfLimit2 = require('./internal/eachOfLimit.js');
|
||||||
|
|
||||||
|
var _eachOfLimit3 = _interopRequireDefault(_eachOfLimit2);
|
||||||
|
|
||||||
|
var _wrapAsync = require('./internal/wrapAsync.js');
|
||||||
|
|
||||||
|
var _wrapAsync2 = _interopRequireDefault(_wrapAsync);
|
||||||
|
|
||||||
|
var _awaitify = require('./internal/awaitify.js');
|
||||||
|
|
||||||
|
var _awaitify2 = _interopRequireDefault(_awaitify);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The same as [`eachOf`]{@link module:Collections.eachOf} but runs a maximum of `limit` async operations at a
|
||||||
|
* time.
|
||||||
|
*
|
||||||
|
* @name eachOfLimit
|
||||||
|
* @static
|
||||||
|
* @memberOf module:Collections
|
||||||
|
* @method
|
||||||
|
* @see [async.eachOf]{@link module:Collections.eachOf}
|
||||||
|
* @alias forEachOfLimit
|
||||||
|
* @category Collection
|
||||||
|
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
|
||||||
|
* @param {number} limit - The maximum number of async operations at a time.
|
||||||
|
* @param {AsyncFunction} iteratee - An async function to apply to each
|
||||||
|
* item in `coll`. The `key` is the item's key, or index in the case of an
|
||||||
|
* array.
|
||||||
|
* Invoked with (item, key, callback).
|
||||||
|
* @param {Function} [callback] - A callback which is called when all
|
||||||
|
* `iteratee` functions have finished, or an error occurs. Invoked with (err).
|
||||||
|
* @returns {Promise} a promise, if a callback is omitted
|
||||||
|
*/
|
||||||
|
function eachOfLimit(coll, limit, iteratee, callback) {
|
||||||
|
return (0, _eachOfLimit3.default)(limit)(coll, (0, _wrapAsync2.default)(iteratee), callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.default = (0, _awaitify2.default)(eachOfLimit, 4);
|
||||||
|
module.exports = exports.default;
|
||||||
39
node_modules/async/eachOfSeries.js
generated
vendored
Normal file
39
node_modules/async/eachOfSeries.js
generated
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _eachOfLimit = require('./eachOfLimit.js');
|
||||||
|
|
||||||
|
var _eachOfLimit2 = _interopRequireDefault(_eachOfLimit);
|
||||||
|
|
||||||
|
var _awaitify = require('./internal/awaitify.js');
|
||||||
|
|
||||||
|
var _awaitify2 = _interopRequireDefault(_awaitify);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The same as [`eachOf`]{@link module:Collections.eachOf} but runs only a single async operation at a time.
|
||||||
|
*
|
||||||
|
* @name eachOfSeries
|
||||||
|
* @static
|
||||||
|
* @memberOf module:Collections
|
||||||
|
* @method
|
||||||
|
* @see [async.eachOf]{@link module:Collections.eachOf}
|
||||||
|
* @alias forEachOfSeries
|
||||||
|
* @category Collection
|
||||||
|
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
|
||||||
|
* @param {AsyncFunction} iteratee - An async function to apply to each item in
|
||||||
|
* `coll`.
|
||||||
|
* Invoked with (item, key, callback).
|
||||||
|
* @param {Function} [callback] - A callback which is called when all `iteratee`
|
||||||
|
* functions have finished, or an error occurs. Invoked with (err).
|
||||||
|
* @returns {Promise} a promise, if a callback is omitted
|
||||||
|
*/
|
||||||
|
function eachOfSeries(coll, iteratee, callback) {
|
||||||
|
return (0, _eachOfLimit2.default)(coll, 1, iteratee, callback);
|
||||||
|
}
|
||||||
|
exports.default = (0, _awaitify2.default)(eachOfSeries, 3);
|
||||||
|
module.exports = exports.default;
|
||||||
44
node_modules/async/eachSeries.js
generated
vendored
Normal file
44
node_modules/async/eachSeries.js
generated
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _eachLimit = require('./eachLimit.js');
|
||||||
|
|
||||||
|
var _eachLimit2 = _interopRequireDefault(_eachLimit);
|
||||||
|
|
||||||
|
var _awaitify = require('./internal/awaitify.js');
|
||||||
|
|
||||||
|
var _awaitify2 = _interopRequireDefault(_awaitify);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The same as [`each`]{@link module:Collections.each} but runs only a single async operation at a time.
|
||||||
|
*
|
||||||
|
* Note, that unlike [`each`]{@link module:Collections.each}, this function applies iteratee to each item
|
||||||
|
* in series and therefore the iteratee functions will complete in order.
|
||||||
|
|
||||||
|
* @name eachSeries
|
||||||
|
* @static
|
||||||
|
* @memberOf module:Collections
|
||||||
|
* @method
|
||||||
|
* @see [async.each]{@link module:Collections.each}
|
||||||
|
* @alias forEachSeries
|
||||||
|
* @category Collection
|
||||||
|
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
|
||||||
|
* @param {AsyncFunction} iteratee - An async function to apply to each
|
||||||
|
* item in `coll`.
|
||||||
|
* The array index is not passed to the iteratee.
|
||||||
|
* If you need the index, use `eachOfSeries`.
|
||||||
|
* Invoked with (item, callback).
|
||||||
|
* @param {Function} [callback] - A callback which is called when all
|
||||||
|
* `iteratee` functions have finished, or an error occurs. Invoked with (err).
|
||||||
|
* @returns {Promise} a promise, if a callback is omitted
|
||||||
|
*/
|
||||||
|
function eachSeries(coll, iteratee, callback) {
|
||||||
|
return (0, _eachLimit2.default)(coll, 1, iteratee, callback);
|
||||||
|
}
|
||||||
|
exports.default = (0, _awaitify2.default)(eachSeries, 3);
|
||||||
|
module.exports = exports.default;
|
||||||
67
node_modules/async/ensureAsync.js
generated
vendored
Normal file
67
node_modules/async/ensureAsync.js
generated
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.default = ensureAsync;
|
||||||
|
|
||||||
|
var _setImmediate = require('./internal/setImmediate.js');
|
||||||
|
|
||||||
|
var _setImmediate2 = _interopRequireDefault(_setImmediate);
|
||||||
|
|
||||||
|
var _wrapAsync = require('./internal/wrapAsync.js');
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap an async function and ensure it calls its callback on a later tick of
|
||||||
|
* the event loop. If the function already calls its callback on a next tick,
|
||||||
|
* no extra deferral is added. This is useful for preventing stack overflows
|
||||||
|
* (`RangeError: Maximum call stack size exceeded`) and generally keeping
|
||||||
|
* [Zalgo](http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony)
|
||||||
|
* contained. ES2017 `async` functions are returned as-is -- they are immune
|
||||||
|
* to Zalgo's corrupting influences, as they always resolve on a later tick.
|
||||||
|
*
|
||||||
|
* @name ensureAsync
|
||||||
|
* @static
|
||||||
|
* @memberOf module:Utils
|
||||||
|
* @method
|
||||||
|
* @category Util
|
||||||
|
* @param {AsyncFunction} fn - an async function, one that expects a node-style
|
||||||
|
* callback as its last argument.
|
||||||
|
* @returns {AsyncFunction} Returns a wrapped function with the exact same call
|
||||||
|
* signature as the function passed in.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* function sometimesAsync(arg, callback) {
|
||||||
|
* if (cache[arg]) {
|
||||||
|
* return callback(null, cache[arg]); // this would be synchronous!!
|
||||||
|
* } else {
|
||||||
|
* doSomeIO(arg, callback); // this IO would be asynchronous
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // this has a risk of stack overflows if many results are cached in a row
|
||||||
|
* async.mapSeries(args, sometimesAsync, done);
|
||||||
|
*
|
||||||
|
* // this will defer sometimesAsync's callback if necessary,
|
||||||
|
* // preventing stack overflows
|
||||||
|
* async.mapSeries(args, async.ensureAsync(sometimesAsync), done);
|
||||||
|
*/
|
||||||
|
function ensureAsync(fn) {
|
||||||
|
if ((0, _wrapAsync.isAsync)(fn)) return fn;
|
||||||
|
return function (...args /*, callback*/) {
|
||||||
|
var callback = args.pop();
|
||||||
|
var sync = true;
|
||||||
|
args.push((...innerArgs) => {
|
||||||
|
if (sync) {
|
||||||
|
(0, _setImmediate2.default)(() => callback(...innerArgs));
|
||||||
|
} else {
|
||||||
|
callback(...innerArgs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fn.apply(this, args);
|
||||||
|
sync = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
module.exports = exports.default;
|
||||||
119
node_modules/async/every.js
generated
vendored
Normal file
119
node_modules/async/every.js
generated
vendored
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _createTester = require('./internal/createTester.js');
|
||||||
|
|
||||||
|
var _createTester2 = _interopRequireDefault(_createTester);
|
||||||
|
|
||||||
|
var _eachOf = require('./eachOf.js');
|
||||||
|
|
||||||
|
var _eachOf2 = _interopRequireDefault(_eachOf);
|
||||||
|
|
||||||
|
var _awaitify = require('./internal/awaitify.js');
|
||||||
|
|
||||||
|
var _awaitify2 = _interopRequireDefault(_awaitify);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns `true` if every element in `coll` satisfies an async test. If any
|
||||||
|
* iteratee call returns `false`, the main `callback` is immediately called.
|
||||||
|
*
|
||||||
|
* @name every
|
||||||
|
* @static
|
||||||
|
* @memberOf module:Collections
|
||||||
|
* @method
|
||||||
|
* @alias all
|
||||||
|
* @category Collection
|
||||||
|
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
|
||||||
|
* @param {AsyncFunction} iteratee - An async truth test to apply to each item
|
||||||
|
* in the collection in parallel.
|
||||||
|
* The iteratee must complete with a boolean result value.
|
||||||
|
* Invoked with (item, callback).
|
||||||
|
* @param {Function} [callback] - A callback which is called after all the
|
||||||
|
* `iteratee` functions have finished. Result will be either `true` or `false`
|
||||||
|
* depending on the values of the async tests. Invoked with (err, result).
|
||||||
|
* @returns {Promise} a promise, if no callback provided
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* // dir1 is a directory that contains file1.txt, file2.txt
|
||||||
|
* // dir2 is a directory that contains file3.txt, file4.txt
|
||||||
|
* // dir3 is a directory that contains file5.txt
|
||||||
|
* // dir4 does not exist
|
||||||
|
*
|
||||||
|
* const fileList = ['dir1/file1.txt','dir2/file3.txt','dir3/file5.txt'];
|
||||||
|
* const withMissingFileList = ['file1.txt','file2.txt','file4.txt'];
|
||||||
|
*
|
||||||
|
* // asynchronous function that checks if a file exists
|
||||||
|
* function fileExists(file, callback) {
|
||||||
|
* fs.access(file, fs.constants.F_OK, (err) => {
|
||||||
|
* callback(null, !err);
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // Using callbacks
|
||||||
|
* async.every(fileList, fileExists, function(err, result) {
|
||||||
|
* console.log(result);
|
||||||
|
* // true
|
||||||
|
* // result is true since every file exists
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* async.every(withMissingFileList, fileExists, function(err, result) {
|
||||||
|
* console.log(result);
|
||||||
|
* // false
|
||||||
|
* // result is false since NOT every file exists
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Using Promises
|
||||||
|
* async.every(fileList, fileExists)
|
||||||
|
* .then( result => {
|
||||||
|
* console.log(result);
|
||||||
|
* // true
|
||||||
|
* // result is true since every file exists
|
||||||
|
* }).catch( err => {
|
||||||
|
* console.log(err);
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* async.every(withMissingFileList, fileExists)
|
||||||
|
* .then( result => {
|
||||||
|
* console.log(result);
|
||||||
|
* // false
|
||||||
|
* // result is false since NOT every file exists
|
||||||
|
* }).catch( err => {
|
||||||
|
* console.log(err);
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Using async/await
|
||||||
|
* async () => {
|
||||||
|
* try {
|
||||||
|
* let result = await async.every(fileList, fileExists);
|
||||||
|
* console.log(result);
|
||||||
|
* // true
|
||||||
|
* // result is true since every file exists
|
||||||
|
* }
|
||||||
|
* catch (err) {
|
||||||
|
* console.log(err);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* async () => {
|
||||||
|
* try {
|
||||||
|
* let result = await async.every(withMissingFileList, fileExists);
|
||||||
|
* console.log(result);
|
||||||
|
* // false
|
||||||
|
* // result is false since NOT every file exists
|
||||||
|
* }
|
||||||
|
* catch (err) {
|
||||||
|
* console.log(err);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function every(coll, iteratee, callback) {
|
||||||
|
return (0, _createTester2.default)(bool => !bool, res => !res)(_eachOf2.default, coll, iteratee, callback);
|
||||||
|
}
|
||||||
|
exports.default = (0, _awaitify2.default)(every, 3);
|
||||||
|
module.exports = exports.default;
|
||||||
46
node_modules/async/everyLimit.js
generated
vendored
Normal file
46
node_modules/async/everyLimit.js
generated
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _createTester = require('./internal/createTester.js');
|
||||||
|
|
||||||
|
var _createTester2 = _interopRequireDefault(_createTester);
|
||||||
|
|
||||||
|
var _eachOfLimit = require('./internal/eachOfLimit.js');
|
||||||
|
|
||||||
|
var _eachOfLimit2 = _interopRequireDefault(_eachOfLimit);
|
||||||
|
|
||||||
|
var _awaitify = require('./internal/awaitify.js');
|
||||||
|
|
||||||
|
var _awaitify2 = _interopRequireDefault(_awaitify);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The same as [`every`]{@link module:Collections.every} but runs a maximum of `limit` async operations at a time.
|
||||||
|
*
|
||||||
|
* @name everyLimit
|
||||||
|
* @static
|
||||||
|
* @memberOf module:Collections
|
||||||
|
* @method
|
||||||
|
* @see [async.every]{@link module:Collections.every}
|
||||||
|
* @alias allLimit
|
||||||
|
* @category Collection
|
||||||
|
* @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.
|
||||||
|
* @param {number} limit - The maximum number of async operations at a time.
|
||||||
|
* @param {AsyncFunction} iteratee - An async truth test to apply to each item
|
||||||
|
* in the collection in parallel.
|
||||||
|
* The iteratee must complete with a boolean result value.
|
||||||
|
* Invoked with (item, callback).
|
||||||
|
* @param {Function} [callback] - A callback which is called after all the
|
||||||
|
* `iteratee` functions have finished. Result will be either `true` or `false`
|
||||||
|
* depending on the values of the async tests. Invoked with (err, result).
|
||||||
|
* @returns {Promise} a promise, if no callback provided
|
||||||
|
*/
|
||||||
|
function everyLimit(coll, limit, iteratee, callback) {
|
||||||
|
return (0, _createTester2.default)(bool => !bool, res => !res)((0, _eachOfLimit2.default)(limit), coll, iteratee, callback);
|
||||||
|
}
|
||||||
|
exports.default = (0, _awaitify2.default)(everyLimit, 4);
|
||||||
|
module.exports = exports.default;
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue