@portel/photon 1.32.3 → 1.32.4

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.
@@ -57,27 +57,34 @@ async function isSocketResponsive(target) {
57
57
  return false;
58
58
  return new Promise((resolve) => {
59
59
  const client = new net.Socket();
60
+ let done = false;
61
+ const finish = (alive) => {
62
+ if (done)
63
+ return;
64
+ done = true;
65
+ clearTimeout(timer);
66
+ if (alive) {
67
+ client.end();
68
+ }
69
+ else {
70
+ client.destroy();
71
+ }
72
+ resolve(alive);
73
+ };
60
74
  const timer = setTimeout(() => {
61
- client.destroy();
62
- resolve(false);
75
+ finish(false);
63
76
  }, 1000);
64
77
  client.on('error', () => {
65
- clearTimeout(timer);
66
- client.destroy();
67
- resolve(false);
78
+ finish(false);
68
79
  });
69
80
  client.on('connect', () => {
70
- clearTimeout(timer);
71
- client.destroy();
72
- resolve(true);
81
+ finish(true);
73
82
  });
74
83
  try {
75
84
  client.connect(target);
76
85
  }
77
86
  catch {
78
- clearTimeout(timer);
79
- client.destroy();
80
- resolve(false);
87
+ finish(false);
81
88
  }
82
89
  });
83
90
  }
@@ -286,6 +293,9 @@ const stateDirWatchers = new Map();
286
293
  // @scheduled cron changes and @webhook additions reach the declared-set
287
294
  // without a daemon restart. See watchBaseForProactiveMetadata().
288
295
  const baseDirWatchers = new Map();
296
+ // photonDir -> SafeWatcher: startup watcher for new .photon.ts files in each
297
+ // active base. Tracked separately so Bun poll timers are closed on shutdown.
298
+ const startupPhotonDirWatchers = new Map();
289
299
  import { compositeKey as _compositeKey, declaredKey as _declaredKey, webhookKey as _webhookKey, locationKey as _locationKey, findByPhoton, asScheduleKey, } from './registry-keys.js';
290
300
  /**
291
301
  * Create a composite key from photonName + workingDir for map lookups.
@@ -5155,15 +5165,33 @@ function startupWatchPhotons() {
5155
5165
  const defaultBase = getDefaultContext().baseDir;
5156
5166
  const bases = new Set([defaultBase]);
5157
5167
  for (const base of listActiveBases()) {
5168
+ if (!shouldStartupWatchBase(base.path, defaultBase))
5169
+ continue;
5158
5170
  bases.add(base.path);
5159
5171
  }
5160
5172
  for (const photonDir of bases) {
5161
5173
  startupWatchPhotonDir(photonDir, defaultBase);
5162
5174
  }
5163
5175
  }
5176
+ function shouldStartupWatchBase(basePath, defaultBase) {
5177
+ const resolved = path.resolve(basePath);
5178
+ if (resolved === path.resolve(defaultBase))
5179
+ return true;
5180
+ // Temporary directories are common in Beam and daemon regression tests. The
5181
+ // bases registry keeps them while the OS temp cleaner has not removed them,
5182
+ // but a production daemon should not spend long-lived watchers on them at
5183
+ // every startup. If a user actively invokes a temp photon later, the normal
5184
+ // request path still loads it and registers on-demand watchers.
5185
+ const tmpRoot = path.resolve(os.tmpdir());
5186
+ if (resolved === tmpRoot || resolved.startsWith(tmpRoot + path.sep))
5187
+ return false;
5188
+ return true;
5189
+ }
5164
5190
  function startupWatchPhotonDir(photonDir, defaultBase) {
5165
5191
  if (!fs.existsSync(photonDir))
5166
5192
  return;
5193
+ if (startupPhotonDirWatchers.has(photonDir))
5194
+ return;
5167
5195
  // Host mode: when the default base is host-disabled, skip the file
5168
5196
  // watcher, the eager onInitialize loader, and the directory watcher
5169
5197
  // entirely. They all activate background work that the marker is
@@ -5288,6 +5316,7 @@ function startupWatchPhotonDir(photonDir, defaultBase) {
5288
5316
  if (typeof dirWatcher.on === 'function') {
5289
5317
  dirWatcher.on('error', () => { }); // Non-fatal
5290
5318
  }
5319
+ startupPhotonDirWatchers.set(photonDir, dirWatcher);
5291
5320
  }
5292
5321
  catch {
5293
5322
  logger.warn('Failed to watch photon directory for new files', { dir: photonDir });
@@ -5335,7 +5364,13 @@ function startServer() {
5335
5364
  cleanupSocketSubscriptions(socket);
5336
5365
  });
5337
5366
  socket.on('error', (error) => {
5338
- logger.warn('Socket error', { error: getErrorMessage(error) });
5367
+ const code = error?.code;
5368
+ if (code === 'ECONNRESET' || code === 'EPIPE') {
5369
+ logger.debug('Socket closed by client', { error: getErrorMessage(error) });
5370
+ }
5371
+ else {
5372
+ logger.warn('Socket error', { error: getErrorMessage(error) });
5373
+ }
5339
5374
  connectedSockets.delete(socket);
5340
5375
  cleanupSocketSubscriptions(socket);
5341
5376
  });
@@ -5670,6 +5705,22 @@ function shutdown() {
5670
5705
  for (const photonPath of fileWatchers.keys()) {
5671
5706
  unwatchPhotonFile(photonPath);
5672
5707
  }
5708
+ for (const watcher of startupPhotonDirWatchers.values()) {
5709
+ watcher.close();
5710
+ }
5711
+ startupPhotonDirWatchers.clear();
5712
+ for (const watcher of baseDirWatchers.values()) {
5713
+ watcher.close();
5714
+ }
5715
+ baseDirWatchers.clear();
5716
+ for (const watcher of parentDirWatchers.values()) {
5717
+ watcher.close();
5718
+ }
5719
+ parentDirWatchers.clear();
5720
+ for (const watcher of stateDirWatchers.values()) {
5721
+ watcher.close();
5722
+ }
5723
+ stateDirWatchers.clear();
5673
5724
  // Clean up poll-based watchers (bun fallback)
5674
5725
  for (const timer of pollTimers) {
5675
5726
  clearInterval(timer);