bun-dev-server 0.9.84 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,7 @@ export interface BuildEnv {
5
5
  finalConfig: BunDevServerConfig;
6
6
  destinationPath: string;
7
7
  buildCfg: BuildConfig;
8
- bunServer: Server;
8
+ bunServer: Server<any>;
9
9
  event: FileChangeInfo<any>;
10
10
  }
11
11
  export interface BunDevServerConfig extends Partial<BunServeConfig> {
@@ -117,13 +117,13 @@ export interface BunDevServerConfig extends Partial<BunServeConfig> {
117
117
  * Event listener to execute before Bun builds
118
118
  * @param env Supplied environment for the build
119
119
  */
120
- beforeBuild?: (env: BuildEnv) => void;
120
+ beforeBuild?: (env: BuildEnv) => Promise<void> | void;
121
121
  /**
122
122
  * Event listener to execute after Bun builds
123
123
  * @param output The output of the build
124
124
  * @param env Supplied environment for the build
125
125
  */
126
- afterBuild?: (output: BuildOutput, env: BuildEnv) => void;
126
+ afterBuild?: (output: BuildOutput, env: BuildEnv) => Promise<void> | void;
127
127
  }
128
128
  export interface BunServeConfig {
129
129
  port: number;
@@ -1,2 +1,2 @@
1
1
  import { type Server } from "bun";
2
- export declare function startTSWatcher(server: Server, watchDir: URL): Promise<void>;
2
+ export declare function startTSWatcher(server: Server<any>, watchDir: URL): Promise<void>;
package/dist/index.js CHANGED
@@ -1248,30 +1248,20 @@ var import__ = __toESM(require_eventemitter3(), 1);
1248
1248
 
1249
1249
  // node_modules/p-timeout/index.js
1250
1250
  class TimeoutError extends Error {
1251
- constructor(message) {
1252
- super(message);
1253
- this.name = "TimeoutError";
1251
+ name = "TimeoutError";
1252
+ constructor(message, options) {
1253
+ super(message, options);
1254
+ Error.captureStackTrace?.(this, TimeoutError);
1254
1255
  }
1255
1256
  }
1256
-
1257
- class AbortError extends Error {
1258
- constructor(message) {
1259
- super();
1260
- this.name = "AbortError";
1261
- this.message = message;
1262
- }
1263
- }
1264
- var getDOMException = (errorMessage) => globalThis.DOMException === undefined ? new AbortError(errorMessage) : new DOMException(errorMessage);
1265
- var getAbortedReason = (signal) => {
1266
- const reason = signal.reason === undefined ? getDOMException("This operation was aborted.") : signal.reason;
1267
- return reason instanceof Error ? reason : getDOMException(reason);
1268
- };
1257
+ var getAbortedReason = (signal) => signal.reason ?? new DOMException("This operation was aborted.", "AbortError");
1269
1258
  function pTimeout(promise, options) {
1270
1259
  const {
1271
1260
  milliseconds,
1272
1261
  fallback,
1273
1262
  message,
1274
- customTimers = { setTimeout, clearTimeout }
1263
+ customTimers = { setTimeout, clearTimeout },
1264
+ signal
1275
1265
  } = options;
1276
1266
  let timer;
1277
1267
  let abortHandler;
@@ -1279,18 +1269,18 @@ function pTimeout(promise, options) {
1279
1269
  if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
1280
1270
  throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
1281
1271
  }
1282
- if (options.signal) {
1283
- const { signal } = options;
1284
- if (signal.aborted) {
1285
- reject(getAbortedReason(signal));
1286
- }
1272
+ if (signal?.aborted) {
1273
+ reject(getAbortedReason(signal));
1274
+ return;
1275
+ }
1276
+ if (signal) {
1287
1277
  abortHandler = () => {
1288
1278
  reject(getAbortedReason(signal));
1289
1279
  };
1290
1280
  signal.addEventListener("abort", abortHandler, { once: true });
1291
1281
  }
1282
+ promise.then(resolve, reject);
1292
1283
  if (milliseconds === Number.POSITIVE_INFINITY) {
1293
- promise.then(resolve, reject);
1294
1284
  return;
1295
1285
  }
1296
1286
  const timeoutError = new TimeoutError;
@@ -1315,18 +1305,11 @@ function pTimeout(promise, options) {
1315
1305
  reject(timeoutError);
1316
1306
  }
1317
1307
  }, milliseconds);
1318
- (async () => {
1319
- try {
1320
- resolve(await promise);
1321
- } catch (error) {
1322
- reject(error);
1323
- }
1324
- })();
1325
1308
  });
1326
1309
  const cancelablePromise = wrappedPromise.finally(() => {
1327
1310
  cancelablePromise.clear();
1328
- if (abortHandler && options.signal) {
1329
- options.signal.removeEventListener("abort", abortHandler);
1311
+ if (abortHandler && signal) {
1312
+ signal.removeEventListener("abort", abortHandler);
1330
1313
  }
1331
1314
  });
1332
1315
  cancelablePromise.clear = () => {
@@ -1357,16 +1340,13 @@ function lowerBound(array, value, comparator) {
1357
1340
  class PriorityQueue {
1358
1341
  #queue = [];
1359
1342
  enqueue(run, options) {
1360
- options = {
1361
- priority: 0,
1362
- ...options
1363
- };
1343
+ const { priority = 0, id } = options ?? {};
1364
1344
  const element = {
1365
- priority: options.priority,
1366
- id: options.id,
1345
+ priority,
1346
+ id,
1367
1347
  run
1368
1348
  };
1369
- if (this.size === 0 || this.#queue[this.size - 1].priority >= options.priority) {
1349
+ if (this.size === 0 || this.#queue[this.size - 1].priority >= priority) {
1370
1350
  this.#queue.push(element);
1371
1351
  return;
1372
1352
  }
@@ -1395,12 +1375,15 @@ class PriorityQueue {
1395
1375
 
1396
1376
  // node_modules/p-queue/dist/index.js
1397
1377
  class PQueue extends import__.default {
1398
- #carryoverConcurrencyCount;
1378
+ #carryoverIntervalCount;
1399
1379
  #isIntervalIgnored;
1400
1380
  #intervalCount = 0;
1401
1381
  #intervalCap;
1382
+ #rateLimitedInInterval = false;
1383
+ #rateLimitFlushScheduled = false;
1402
1384
  #interval;
1403
1385
  #intervalEnd = 0;
1386
+ #lastExecutionTime = 0;
1404
1387
  #intervalId;
1405
1388
  #timeoutId;
1406
1389
  #queue;
@@ -1408,13 +1391,13 @@ class PQueue extends import__.default {
1408
1391
  #pending = 0;
1409
1392
  #concurrency;
1410
1393
  #isPaused;
1411
- #throwOnTimeout;
1412
1394
  #idAssigner = 1n;
1395
+ #runningTasks = new Map;
1413
1396
  timeout;
1414
1397
  constructor(options) {
1415
1398
  super();
1416
1399
  options = {
1417
- carryoverConcurrencyCount: false,
1400
+ carryoverIntervalCount: false,
1418
1401
  intervalCap: Number.POSITIVE_INFINITY,
1419
1402
  interval: 0,
1420
1403
  concurrency: Number.POSITIVE_INFINITY,
@@ -1428,16 +1411,19 @@ class PQueue extends import__.default {
1428
1411
  if (options.interval === undefined || !(Number.isFinite(options.interval) && options.interval >= 0)) {
1429
1412
  throw new TypeError(`Expected \`interval\` to be a finite number >= 0, got \`${options.interval?.toString() ?? ""}\` (${typeof options.interval})`);
1430
1413
  }
1431
- this.#carryoverConcurrencyCount = options.carryoverConcurrencyCount;
1414
+ this.#carryoverIntervalCount = options.carryoverIntervalCount ?? options.carryoverConcurrencyCount ?? false;
1432
1415
  this.#isIntervalIgnored = options.intervalCap === Number.POSITIVE_INFINITY || options.interval === 0;
1433
1416
  this.#intervalCap = options.intervalCap;
1434
1417
  this.#interval = options.interval;
1435
1418
  this.#queue = new options.queueClass;
1436
1419
  this.#queueClass = options.queueClass;
1437
1420
  this.concurrency = options.concurrency;
1421
+ if (options.timeout !== undefined && !(Number.isFinite(options.timeout) && options.timeout > 0)) {
1422
+ throw new TypeError(`Expected \`timeout\` to be a positive finite number, got \`${options.timeout}\` (${typeof options.timeout})`);
1423
+ }
1438
1424
  this.timeout = options.timeout;
1439
- this.#throwOnTimeout = options.throwOnTimeout === true;
1440
1425
  this.#isPaused = options.autoStart === false;
1426
+ this.#setupRateLimitTracking();
1441
1427
  }
1442
1428
  get #doesIntervalAllowAnother() {
1443
1429
  return this.#isIntervalIgnored || this.#intervalCount < this.#intervalCap;
@@ -1447,6 +1433,9 @@ class PQueue extends import__.default {
1447
1433
  }
1448
1434
  #next() {
1449
1435
  this.#pending--;
1436
+ if (this.#pending === 0) {
1437
+ this.emit("pendingZero");
1438
+ }
1450
1439
  this.#tryToStartAnother();
1451
1440
  this.emit("next");
1452
1441
  }
@@ -1460,46 +1449,70 @@ class PQueue extends import__.default {
1460
1449
  if (this.#intervalId === undefined) {
1461
1450
  const delay = this.#intervalEnd - now;
1462
1451
  if (delay < 0) {
1463
- this.#intervalCount = this.#carryoverConcurrencyCount ? this.#pending : 0;
1464
- } else {
1465
- if (this.#timeoutId === undefined) {
1466
- this.#timeoutId = setTimeout(() => {
1467
- this.#onResumeInterval();
1468
- }, delay);
1452
+ if (this.#lastExecutionTime > 0) {
1453
+ const timeSinceLastExecution = now - this.#lastExecutionTime;
1454
+ if (timeSinceLastExecution < this.#interval) {
1455
+ this.#createIntervalTimeout(this.#interval - timeSinceLastExecution);
1456
+ return true;
1457
+ }
1469
1458
  }
1459
+ this.#intervalCount = this.#carryoverIntervalCount ? this.#pending : 0;
1460
+ } else {
1461
+ this.#createIntervalTimeout(delay);
1470
1462
  return true;
1471
1463
  }
1472
1464
  }
1473
1465
  return false;
1474
1466
  }
1467
+ #createIntervalTimeout(delay) {
1468
+ if (this.#timeoutId !== undefined) {
1469
+ return;
1470
+ }
1471
+ this.#timeoutId = setTimeout(() => {
1472
+ this.#onResumeInterval();
1473
+ }, delay);
1474
+ }
1475
+ #clearIntervalTimer() {
1476
+ if (this.#intervalId) {
1477
+ clearInterval(this.#intervalId);
1478
+ this.#intervalId = undefined;
1479
+ }
1480
+ }
1481
+ #clearTimeoutTimer() {
1482
+ if (this.#timeoutId) {
1483
+ clearTimeout(this.#timeoutId);
1484
+ this.#timeoutId = undefined;
1485
+ }
1486
+ }
1475
1487
  #tryToStartAnother() {
1476
1488
  if (this.#queue.size === 0) {
1477
- if (this.#intervalId) {
1478
- clearInterval(this.#intervalId);
1479
- }
1480
- this.#intervalId = undefined;
1489
+ this.#clearIntervalTimer();
1481
1490
  this.emit("empty");
1482
1491
  if (this.#pending === 0) {
1492
+ this.#clearTimeoutTimer();
1483
1493
  this.emit("idle");
1484
1494
  }
1485
1495
  return false;
1486
1496
  }
1497
+ let taskStarted = false;
1487
1498
  if (!this.#isPaused) {
1488
1499
  const canInitializeInterval = !this.#isIntervalPaused;
1489
1500
  if (this.#doesIntervalAllowAnother && this.#doesConcurrentAllowAnother) {
1490
1501
  const job = this.#queue.dequeue();
1491
- if (!job) {
1492
- return false;
1502
+ if (!this.#isIntervalIgnored) {
1503
+ this.#intervalCount++;
1504
+ this.#scheduleRateLimitUpdate();
1493
1505
  }
1494
1506
  this.emit("active");
1507
+ this.#lastExecutionTime = Date.now();
1495
1508
  job();
1496
1509
  if (canInitializeInterval) {
1497
1510
  this.#initializeIntervalIfNeeded();
1498
1511
  }
1499
- return true;
1512
+ taskStarted = true;
1500
1513
  }
1501
1514
  }
1502
- return false;
1515
+ return taskStarted;
1503
1516
  }
1504
1517
  #initializeIntervalIfNeeded() {
1505
1518
  if (this.#isIntervalIgnored || this.#intervalId !== undefined) {
@@ -1512,11 +1525,11 @@ class PQueue extends import__.default {
1512
1525
  }
1513
1526
  #onInterval() {
1514
1527
  if (this.#intervalCount === 0 && this.#pending === 0 && this.#intervalId) {
1515
- clearInterval(this.#intervalId);
1516
- this.#intervalId = undefined;
1528
+ this.#clearIntervalTimer();
1517
1529
  }
1518
- this.#intervalCount = this.#carryoverConcurrencyCount ? this.#pending : 0;
1530
+ this.#intervalCount = this.#carryoverIntervalCount ? this.#pending : 0;
1519
1531
  this.#processQueue();
1532
+ this.#scheduleRateLimitUpdate();
1520
1533
  }
1521
1534
  #processQueue() {
1522
1535
  while (this.#tryToStartAnother()) {
@@ -1540,24 +1553,43 @@ class PQueue extends import__.default {
1540
1553
  });
1541
1554
  }
1542
1555
  setPriority(id, priority) {
1556
+ if (typeof priority !== "number" || !Number.isFinite(priority)) {
1557
+ throw new TypeError(`Expected \`priority\` to be a finite number, got \`${priority}\` (${typeof priority})`);
1558
+ }
1543
1559
  this.#queue.setPriority(id, priority);
1544
1560
  }
1545
1561
  async add(function_, options = {}) {
1546
1562
  options.id ??= (this.#idAssigner++).toString();
1547
1563
  options = {
1548
1564
  timeout: this.timeout,
1549
- throwOnTimeout: this.#throwOnTimeout,
1550
1565
  ...options
1551
1566
  };
1552
1567
  return new Promise((resolve, reject) => {
1568
+ const taskSymbol = Symbol(`task-${options.id}`);
1553
1569
  this.#queue.enqueue(async () => {
1554
1570
  this.#pending++;
1555
- this.#intervalCount++;
1571
+ this.#runningTasks.set(taskSymbol, {
1572
+ id: options.id,
1573
+ priority: options.priority ?? 0,
1574
+ startTime: Date.now(),
1575
+ timeout: options.timeout
1576
+ });
1556
1577
  try {
1557
- options.signal?.throwIfAborted();
1578
+ try {
1579
+ options.signal?.throwIfAborted();
1580
+ } catch (error) {
1581
+ if (!this.#isIntervalIgnored) {
1582
+ this.#intervalCount--;
1583
+ }
1584
+ this.#runningTasks.delete(taskSymbol);
1585
+ throw error;
1586
+ }
1558
1587
  let operation = function_({ signal: options.signal });
1559
1588
  if (options.timeout) {
1560
- operation = pTimeout(Promise.resolve(operation), { milliseconds: options.timeout });
1589
+ operation = pTimeout(Promise.resolve(operation), {
1590
+ milliseconds: options.timeout,
1591
+ message: `Task timed out after ${options.timeout}ms (queue has ${this.#pending} running, ${this.#queue.size} waiting)`
1592
+ });
1561
1593
  }
1562
1594
  if (options.signal) {
1563
1595
  operation = Promise.race([operation, this.#throwOnAbort(options.signal)]);
@@ -1566,14 +1598,13 @@ class PQueue extends import__.default {
1566
1598
  resolve(result);
1567
1599
  this.emit("completed", result);
1568
1600
  } catch (error) {
1569
- if (error instanceof TimeoutError && !options.throwOnTimeout) {
1570
- resolve();
1571
- return;
1572
- }
1573
1601
  reject(error);
1574
1602
  this.emit("error", error);
1575
1603
  } finally {
1576
- this.#next();
1604
+ this.#runningTasks.delete(taskSymbol);
1605
+ queueMicrotask(() => {
1606
+ this.#next();
1607
+ });
1577
1608
  }
1578
1609
  }, options);
1579
1610
  this.emit("add");
@@ -1596,6 +1627,7 @@ class PQueue extends import__.default {
1596
1627
  }
1597
1628
  clear() {
1598
1629
  this.#queue = new this.#queueClass;
1630
+ this.#updateRateLimitState();
1599
1631
  }
1600
1632
  async onEmpty() {
1601
1633
  if (this.#queue.size === 0) {
@@ -1615,6 +1647,33 @@ class PQueue extends import__.default {
1615
1647
  }
1616
1648
  await this.#onEvent("idle");
1617
1649
  }
1650
+ async onPendingZero() {
1651
+ if (this.#pending === 0) {
1652
+ return;
1653
+ }
1654
+ await this.#onEvent("pendingZero");
1655
+ }
1656
+ async onRateLimit() {
1657
+ if (this.isRateLimited) {
1658
+ return;
1659
+ }
1660
+ await this.#onEvent("rateLimit");
1661
+ }
1662
+ async onRateLimitCleared() {
1663
+ if (!this.isRateLimited) {
1664
+ return;
1665
+ }
1666
+ await this.#onEvent("rateLimitCleared");
1667
+ }
1668
+ async onError() {
1669
+ return new Promise((_resolve, reject) => {
1670
+ const handleError = (error) => {
1671
+ this.off("error", handleError);
1672
+ reject(error);
1673
+ };
1674
+ this.on("error", handleError);
1675
+ });
1676
+ }
1618
1677
  async#onEvent(event, filter) {
1619
1678
  return new Promise((resolve) => {
1620
1679
  const listener = () => {
@@ -1639,6 +1698,46 @@ class PQueue extends import__.default {
1639
1698
  get isPaused() {
1640
1699
  return this.#isPaused;
1641
1700
  }
1701
+ #setupRateLimitTracking() {
1702
+ if (this.#isIntervalIgnored) {
1703
+ return;
1704
+ }
1705
+ this.on("add", () => {
1706
+ if (this.#queue.size > 0) {
1707
+ this.#scheduleRateLimitUpdate();
1708
+ }
1709
+ });
1710
+ this.on("next", () => {
1711
+ this.#scheduleRateLimitUpdate();
1712
+ });
1713
+ }
1714
+ #scheduleRateLimitUpdate() {
1715
+ if (this.#isIntervalIgnored || this.#rateLimitFlushScheduled) {
1716
+ return;
1717
+ }
1718
+ this.#rateLimitFlushScheduled = true;
1719
+ queueMicrotask(() => {
1720
+ this.#rateLimitFlushScheduled = false;
1721
+ this.#updateRateLimitState();
1722
+ });
1723
+ }
1724
+ #updateRateLimitState() {
1725
+ const previous = this.#rateLimitedInInterval;
1726
+ const shouldBeRateLimited = !this.#isIntervalIgnored && this.#intervalCount >= this.#intervalCap && this.#queue.size > 0;
1727
+ if (shouldBeRateLimited !== previous) {
1728
+ this.#rateLimitedInInterval = shouldBeRateLimited;
1729
+ this.emit(shouldBeRateLimited ? "rateLimit" : "rateLimitCleared");
1730
+ }
1731
+ }
1732
+ get isRateLimited() {
1733
+ return this.#rateLimitedInInterval;
1734
+ }
1735
+ get isSaturated() {
1736
+ return this.#pending === this.#concurrency && this.#queue.size > 0 || this.isRateLimited && this.#queue.size > 0;
1737
+ }
1738
+ get runningTasks() {
1739
+ return [...this.#runningTasks.values()].map((task) => ({ ...task }));
1740
+ }
1642
1741
  }
1643
1742
 
1644
1743
  // src/server.ts
@@ -1777,7 +1876,7 @@ async function cleanBuildAndNotify(importerMeta, finalConfig, destinationPath, b
1777
1876
  bunServer,
1778
1877
  event
1779
1878
  };
1780
- finalConfig.beforeBuild?.(buildEnv);
1879
+ await finalConfig.beforeBuild?.(buildEnv);
1781
1880
  try {
1782
1881
  const output = await build(buildCfg);
1783
1882
  publishOutputLogs(bunServer, output, finalConfig, event);
@@ -1787,7 +1886,7 @@ async function cleanBuildAndNotify(importerMeta, finalConfig, destinationPath, b
1787
1886
  if (finalConfig.writeManifest) {
1788
1887
  writeManifest(output, destinationPath, finalConfig.manifestWithHash, finalConfig.manifestName);
1789
1888
  }
1790
- finalConfig.afterBuild?.(output, buildEnv);
1889
+ await finalConfig.afterBuild?.(output, buildEnv);
1791
1890
  if (finalConfig.reloadOnChange && !finalConfig.waitForTSCSuccessBeforeReload) {
1792
1891
  bunServer.publish("message", JSON.stringify({ type: "reload" }));
1793
1892
  }
package/package.json CHANGED
@@ -24,7 +24,7 @@
24
24
  "exports": {
25
25
  ".": "./dist/index.js"
26
26
  },
27
- "version": "0.9.84",
27
+ "version": "1.0.0",
28
28
  "module": "index.ts",
29
29
  "type": "module",
30
30
  "license": "MIT",
@@ -32,15 +32,15 @@
32
32
  "build": "bun run ./build.ts"
33
33
  },
34
34
  "devDependencies": {
35
- "@types/bun": "^1.2.8"
35
+ "@types/bun": "^1.3.0"
36
36
  },
37
37
  "peerDependencies": {
38
- "typescript": "^5.8.3"
38
+ "typescript": "^5.9.3"
39
39
  },
40
40
  "dependencies": {
41
41
  "@types/ejs": "^3.1.5",
42
42
  "ejs": "^3.1.10",
43
- "p-queue": "^8.1.0",
43
+ "p-queue": "^9.0.0",
44
44
  "picocolors": "^1.1.1"
45
45
  }
46
46
  }