@0x1f320.sh/why-did-you-render-mcp 1.0.0-dev.7 → 1.0.0-dev.8

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.
@@ -123,11 +123,13 @@ function toResult(stored) {
123
123
  //#endregion
124
124
  //#region src/server/store/render-store.ts
125
125
  const FLUSH_DELAY_MS = 200;
126
+ const NOCOMMIT = "nocommit";
126
127
  var RenderStore = class {
127
128
  dir;
128
129
  buffers = /* @__PURE__ */ new Map();
129
130
  timers = /* @__PURE__ */ new Map();
130
131
  dicts = /* @__PURE__ */ new Map();
132
+ bufferMeta = /* @__PURE__ */ new Map();
131
133
  constructor(dir) {
132
134
  this.dir = dir ?? join(homedir(), ".wdyr-mcp", "renders");
133
135
  mkdirSync(this.dir, { recursive: true });
@@ -138,46 +140,53 @@ var RenderStore = class {
138
140
  projectId,
139
141
  ...commitId != null && { commitId }
140
142
  };
141
- let buf = this.buffers.get(projectId);
143
+ const bk = this.bufferKey(projectId, commitId);
144
+ let buf = this.buffers.get(bk);
142
145
  if (!buf) {
143
146
  buf = [];
144
- this.buffers.set(projectId, buf);
147
+ this.buffers.set(bk, buf);
148
+ this.bufferMeta.set(bk, {
149
+ projectId,
150
+ commitId
151
+ });
145
152
  }
146
153
  buf.push(stored);
147
- const existing = this.timers.get(projectId);
154
+ const existing = this.timers.get(bk);
148
155
  if (existing) clearTimeout(existing);
149
- this.timers.set(projectId, setTimeout(() => {
150
- this.flushAsync(projectId).catch((err) => console.error(`[wdyr-mcp] flush error for ${projectId}:`, err));
156
+ this.timers.set(bk, setTimeout(() => {
157
+ this.flushAsync(projectId, commitId).catch((err) => console.error(`[wdyr-mcp] flush error for ${bk}:`, err));
151
158
  }, FLUSH_DELAY_MS));
152
159
  }
153
- async flushAsync(projectId) {
160
+ async flushAsync(projectId, commitId) {
154
161
  await ensureReady();
155
- if (projectId) this.flushProject(projectId);
156
- else for (const id of this.buffers.keys()) this.flushProject(id);
162
+ this.flush(projectId, commitId);
157
163
  }
158
- flush(projectId) {
159
- if (projectId) this.flushProject(projectId);
160
- else for (const id of this.buffers.keys()) this.flushProject(id);
164
+ flush(projectId, commitId) {
165
+ if (projectId != null && commitId !== void 0) this.flushBuffer(this.bufferKey(projectId, commitId));
166
+ else if (projectId != null) for (const bk of this.bufferKeysForProject(projectId)) this.flushBuffer(bk);
167
+ else for (const bk of [...this.buffers.keys()]) this.flushBuffer(bk);
161
168
  }
162
- flushProject(projectId) {
163
- const buf = this.buffers.get(projectId);
169
+ flushBuffer(bk) {
170
+ const buf = this.buffers.get(bk);
164
171
  if (!buf || buf.length === 0) return;
165
- let dict = this.dicts.get(projectId);
172
+ const meta = this.bufferMeta.get(bk);
173
+ if (!meta) return;
174
+ let dict = this.dicts.get(bk);
166
175
  if (!dict) {
167
176
  dict = {};
168
- this.dicts.set(projectId, dict);
177
+ this.dicts.set(bk, dict);
169
178
  }
170
179
  const dehydrated = buf.map((r) => dehydrate(r, dict));
171
- const file = this.projectFile(projectId);
180
+ const file = this.commitFile(meta.projectId, meta.commitId);
172
181
  const existingLines = this.readDataLines(file);
173
182
  const newLines = dehydrated.map((r) => JSON.stringify(r));
174
183
  const allDataLines = [...existingLines, ...newLines];
175
184
  writeFileSync(file, `${(Object.keys(dict).length > 0 ? [JSON.stringify({ [DICT_KEY]: dict }), ...allDataLines] : allDataLines).join("\n")}\n`);
176
185
  buf.length = 0;
177
- const timer = this.timers.get(projectId);
186
+ const timer = this.timers.get(bk);
178
187
  if (timer) {
179
188
  clearTimeout(timer);
180
- this.timers.delete(projectId);
189
+ this.timers.delete(bk);
181
190
  }
182
191
  }
183
192
  readDataLines(file) {
@@ -190,7 +199,7 @@ var RenderStore = class {
190
199
  }
191
200
  getAllRenders(projectId) {
192
201
  this.flush(projectId);
193
- if (projectId) return readJsonl(this.projectFile(projectId)).map(toResult);
202
+ if (projectId) return this.projectFiles(projectId).flatMap((f) => readJsonl(join(this.dir, f)).map(toResult));
194
203
  return this.jsonlFiles().flatMap((f) => readJsonl(join(this.dir, f)).map(toResult));
195
204
  }
196
205
  getRendersByComponent(componentName, projectId) {
@@ -198,44 +207,64 @@ var RenderStore = class {
198
207
  }
199
208
  clearRenders(projectId) {
200
209
  if (projectId) {
201
- this.buffers.delete(projectId);
202
- this.dicts.delete(projectId);
203
- const timer = this.timers.get(projectId);
204
- if (timer) {
205
- clearTimeout(timer);
206
- this.timers.delete(projectId);
210
+ for (const bk of this.bufferKeysForProject(projectId)) {
211
+ this.buffers.delete(bk);
212
+ this.dicts.delete(bk);
213
+ this.bufferMeta.delete(bk);
214
+ const timer = this.timers.get(bk);
215
+ if (timer) {
216
+ clearTimeout(timer);
217
+ this.timers.delete(bk);
218
+ }
207
219
  }
208
- const file = this.projectFile(projectId);
209
- if (existsSync(file)) unlinkSync(file);
220
+ for (const f of this.projectFiles(projectId)) unlinkSync(join(this.dir, f));
210
221
  } else {
211
- for (const [id, timer] of this.timers) clearTimeout(timer);
222
+ for (const [, timer] of this.timers) clearTimeout(timer);
212
223
  this.buffers.clear();
213
224
  this.timers.clear();
214
225
  this.dicts.clear();
226
+ this.bufferMeta.clear();
215
227
  for (const f of this.jsonlFiles()) unlinkSync(join(this.dir, f));
216
228
  }
217
229
  }
218
230
  getProjects() {
219
231
  this.flush();
220
232
  const projects = /* @__PURE__ */ new Set();
233
+ const seen = /* @__PURE__ */ new Set();
221
234
  for (const f of this.jsonlFiles()) {
235
+ const parsed = this.parseFilename(f);
236
+ if (!parsed) continue;
237
+ if (seen.has(parsed.projectSanitized)) continue;
238
+ seen.add(parsed.projectSanitized);
222
239
  const lines = readFileSync(join(this.dir, f), "utf-8").split("\n");
223
240
  for (const line of lines) {
224
241
  if (!line) continue;
225
- const parsed = JSON.parse(line);
226
- if ("@@dict" in parsed) continue;
227
- projects.add(parsed.projectId);
242
+ const obj = JSON.parse(line);
243
+ if ("@@dict" in obj) continue;
244
+ projects.add(obj.projectId);
228
245
  break;
229
246
  }
230
247
  }
231
248
  return [...projects];
232
249
  }
233
250
  getCommitIds(projectId) {
234
- const renders = this.getAllRenders(projectId);
235
- return [...new Set(renders.map((r) => r.commitId).filter((id) => id != null))];
251
+ this.flush(projectId);
252
+ const files = projectId ? this.projectFiles(projectId) : this.jsonlFiles();
253
+ const ids = /* @__PURE__ */ new Set();
254
+ for (const f of files) {
255
+ const parsed = this.parseFilename(f);
256
+ if (parsed?.commitId != null) ids.add(parsed.commitId);
257
+ }
258
+ return [...ids].sort((a, b) => a - b);
236
259
  }
237
260
  getRendersByCommit(commitId, projectId) {
238
- return this.getAllRenders(projectId).filter((r) => r.commitId === commitId);
261
+ if (projectId) {
262
+ this.flush(projectId, commitId);
263
+ return readJsonl(this.commitFile(projectId, commitId)).map(toResult);
264
+ }
265
+ this.flush();
266
+ const suffix = `_commit_${commitId}.jsonl`;
267
+ return this.jsonlFiles().filter((f) => f.endsWith(suffix)).flatMap((f) => readJsonl(join(this.dir, f)).map(toResult));
239
268
  }
240
269
  getSummary(projectId) {
241
270
  const renders = this.getAllRenders(projectId);
@@ -247,8 +276,33 @@ var RenderStore = class {
247
276
  }
248
277
  return summary;
249
278
  }
250
- projectFile(projectId) {
251
- return join(this.dir, `${sanitizeProjectId(projectId)}.jsonl`);
279
+ bufferKey(projectId, commitId) {
280
+ return `${projectId}\0${commitId ?? NOCOMMIT}`;
281
+ }
282
+ bufferKeysForProject(projectId) {
283
+ const prefix = `${projectId}\0`;
284
+ return [...this.buffers.keys()].filter((bk) => bk.startsWith(prefix));
285
+ }
286
+ commitFile(projectId, commitId) {
287
+ const sanitized = sanitizeProjectId(projectId);
288
+ const suffix = commitId != null ? `_commit_${commitId}` : `_${NOCOMMIT}`;
289
+ return join(this.dir, `${sanitized}${suffix}.jsonl`);
290
+ }
291
+ projectFiles(projectId) {
292
+ const prefix = sanitizeProjectId(projectId);
293
+ return readdirSync(this.dir).filter((f) => f.startsWith(prefix) && f.endsWith(".jsonl"));
294
+ }
295
+ parseFilename(filename) {
296
+ if (!filename.endsWith(".jsonl")) return null;
297
+ const base = filename.slice(0, -6);
298
+ const commitMatch = base.match(/^(.+)_commit_(\d+)$/);
299
+ if (commitMatch) return {
300
+ projectSanitized: commitMatch[1],
301
+ commitId: Number(commitMatch[2])
302
+ };
303
+ const nocommitMatch = base.match(/^(.+)_nocommit$/);
304
+ if (nocommitMatch) return { projectSanitized: nocommitMatch[1] };
305
+ return { projectSanitized: base };
252
306
  }
253
307
  jsonlFiles() {
254
308
  return readdirSync(this.dir).filter((f) => f.endsWith(".jsonl"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0x1f320.sh/why-did-you-render-mcp",
3
- "version": "1.0.0-dev.7",
3
+ "version": "1.0.0-dev.8",
4
4
  "type": "module",
5
5
  "description": "MCP server that collects why-did-you-render data from browser and exposes it to coding agents",
6
6
  "license": "MIT",