@camstack/server 1.2.48 → 1.2.49

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.
@@ -54,6 +54,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
54
54
  exports.acceptsBrotli = acceptsBrotli;
55
55
  exports.isCompressibleAsset = isCompressibleAsset;
56
56
  exports.resolvePrecompressedAsset = resolvePrecompressedAsset;
57
+ exports.warmPrecompressedAssets = warmPrecompressedAssets;
57
58
  exports.resetPrecompressedWarning = resetPrecompressedWarning;
58
59
  exports.precompressedSiblingExists = precompressedSiblingExists;
59
60
  const fs = __importStar(require("node:fs"));
@@ -166,6 +167,50 @@ async function resolvePrecompressedAsset(filePath, acceptEncoding, log) {
166
167
  inFlight.delete(filePath);
167
168
  }
168
169
  }
170
+ /**
171
+ * Compress a dist's assets in the background, so no CLIENT pays for the first
172
+ * one.
173
+ *
174
+ * Measured on the live hub: the viewer's 5 MB bundle takes 6.7 s at quality 11.
175
+ * Every request after it is served in 14 ms — but without this, one unlucky
176
+ * operator right after a deploy waits those 6.7 s, which is WORSE than the
177
+ * 0.6 s the per-request compression used to cost. Warming turns a cliff into a
178
+ * bit of boot-time CPU nobody is waiting on.
179
+ *
180
+ * Deliberately not awaited by the caller, and deliberately serial: this runs
181
+ * while the hub is booting the rest of itself, and saturating the CPU to make
182
+ * a page load faster would be a bad trade. Failures are silent here — the
183
+ * request path logs them if it ever hits one.
184
+ */
185
+ function warmPrecompressedAssets(root, log) {
186
+ void (async () => {
187
+ let warmed = 0;
188
+ for (const file of await listCompressible(root)) {
189
+ const resolved = await resolvePrecompressedAsset(file, 'br').catch(() => null);
190
+ if (resolved?.encoding === 'br')
191
+ warmed += 1;
192
+ }
193
+ if (warmed > 0)
194
+ log?.('warmed the precompressed asset cache', { root, files: warmed });
195
+ })();
196
+ }
197
+ async function listCompressible(dir, out = []) {
198
+ let entries;
199
+ try {
200
+ entries = await fsp.readdir(dir, { withFileTypes: true });
201
+ }
202
+ catch {
203
+ return out;
204
+ }
205
+ for (const entry of entries) {
206
+ const full = path.join(dir, entry.name);
207
+ if (entry.isDirectory())
208
+ await listCompressible(full, out);
209
+ else if (isCompressibleAsset(entry.name) && !entry.name.endsWith('.br'))
210
+ out.push(full);
211
+ }
212
+ return out;
213
+ }
169
214
  /** Test seam — the "read-only dist" warning is once per process. */
170
215
  function resetPrecompressedWarning() {
171
216
  warnedUnwritable = false;
package/dist/main.js CHANGED
@@ -1136,6 +1136,10 @@ async function bootstrap() {
1136
1136
  reply.header('cache-control', 'no-cache, must-revalidate');
1137
1137
  return reply.type('text/html').send(fs.createReadStream(spaIndexHtml));
1138
1138
  });
1139
+ // Compress the dist in the background so the first CLIENT does not pay
1140
+ // for it — a 5 MB bundle takes ~6.7s at quality 11, and that is a worse
1141
+ // first load than the per-request compression this replaced.
1142
+ (0, precompressed_asset_js_1.warmPrecompressedAssets)(staticDir, (msg, meta) => console.log(`[static] ${msg} ${JSON.stringify(meta)}`));
1139
1143
  const { version } = await adminUI.getVersion();
1140
1144
  console.log(`[bootstrap] Admin UI served from: ${staticDir} (v${version})`);
1141
1145
  }
@@ -1215,6 +1219,7 @@ async function bootstrap() {
1215
1219
  reply.header('cache-control', 'no-cache, must-revalidate');
1216
1220
  return reply.type('text/html').send(fs.createReadStream(indexPath));
1217
1221
  });
1222
+ (0, precompressed_asset_js_1.warmPrecompressedAssets)(staticDir, (msg, meta) => console.log(`[static] ${msg} ${JSON.stringify(meta)}`));
1218
1223
  const { version } = await viewerUI.getVersion();
1219
1224
  console.log(`[bootstrap] Viewer UI served from: ${staticDir} at ${VIEWER_MOUNT} (v${version})`);
1220
1225
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.2.48",
3
+ "version": "1.2.49",
4
4
  "private": false,
5
5
  "files": [
6
6
  "dist",
@@ -39,7 +39,7 @@
39
39
  "@camstack/addon-decoder-nodeav": "1.2.9",
40
40
  "@camstack/addon-notifiers": "1.2.12",
41
41
  "@camstack/addon-pipeline": "1.2.40",
42
- "@camstack/addon-pipeline-orchestrator": "1.2.21",
42
+ "@camstack/addon-pipeline-orchestrator": "1.2.22",
43
43
  "@camstack/addon-post-analysis": "1.2.31",
44
44
  "@camstack/sdk": "1.2.9",
45
45
  "@camstack/shm-ring": "1.1.9",