@a9n-shoji/rvw 0.4.1 → 0.4.3

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.
package/dist/cli.mjs CHANGED
@@ -18947,6 +18947,13 @@ function secureNewPath(targetPath, expectedMode, label, chmod = chmodSync) {
18947
18947
  }
18948
18948
  assertSecureExistingPath(targetPath, expectedMode, label);
18949
18949
  }
18950
+ var domainRevisionMetaKeys = {
18951
+ pullRequests: "revision_pull_requests",
18952
+ pullRequestContent: "revision_pull_request_content",
18953
+ comments: "revision_comments",
18954
+ walkthroughs: "revision_walkthroughs",
18955
+ structures: "revision_structures"
18956
+ };
18950
18957
  var RvwDatabase = class {
18951
18958
  filePath;
18952
18959
  configuredPath;
@@ -19090,10 +19097,69 @@ var RvwDatabase = class {
19090
19097
  ).run();
19091
19098
  return this.getChangeSequence();
19092
19099
  }
19100
+ incrementDomainRevisions(domains) {
19101
+ const uniqueDomains = [...new Set(domains)];
19102
+ this.incrementChangeSequence();
19103
+ const increment2 = this.database.prepare(
19104
+ "UPDATE app_meta SET value = CAST(CAST(value AS INTEGER) + 1 AS TEXT) WHERE key = ?"
19105
+ );
19106
+ for (const domain2 of uniqueDomains) increment2.run(domainRevisionMetaKeys[domain2]);
19107
+ return this.getChangeSequence();
19108
+ }
19093
19109
  getChangeSequence() {
19094
19110
  const row = this.database.prepare("SELECT value FROM app_meta WHERE key = 'change_sequence'").get();
19095
19111
  return Number(stringValue(row, "value"));
19096
19112
  }
19113
+ getRevisionSnapshot() {
19114
+ const rows = this.database.prepare(
19115
+ `SELECT key, value
19116
+ FROM app_meta
19117
+ WHERE key IN (
19118
+ 'change_sequence',
19119
+ 'revision_pull_requests',
19120
+ 'revision_pull_request_content',
19121
+ 'revision_comments',
19122
+ 'revision_walkthroughs',
19123
+ 'revision_structures'
19124
+ )`
19125
+ ).all();
19126
+ const values = new Map(rows.map((row) => [stringValue(row, "key"), stringValue(row, "value")]));
19127
+ const value = (key2) => {
19128
+ const raw3 = values.get(key2);
19129
+ if (raw3 === void 0) throw new RvwError("DATABASE_ERROR", `revision\u304C\u3042\u308A\u307E\u305B\u3093: ${key2}`);
19130
+ return Number(raw3);
19131
+ };
19132
+ return {
19133
+ changeSequence: value("change_sequence"),
19134
+ revisions: {
19135
+ pullRequests: value(domainRevisionMetaKeys.pullRequests),
19136
+ pullRequestContent: value(domainRevisionMetaKeys.pullRequestContent),
19137
+ comments: value(domainRevisionMetaKeys.comments),
19138
+ walkthroughs: value(domainRevisionMetaKeys.walkthroughs),
19139
+ structures: value(domainRevisionMetaKeys.structures)
19140
+ }
19141
+ };
19142
+ }
19143
+ getDomainRevisions() {
19144
+ return this.getRevisionSnapshot().revisions;
19145
+ }
19146
+ getPullRequestRevisionSnapshot(id) {
19147
+ this.database.exec("BEGIN");
19148
+ try {
19149
+ const result = {
19150
+ pullRequest: this.getPullRequest(id),
19151
+ revisionSnapshot: this.getRevisionSnapshot()
19152
+ };
19153
+ this.database.exec("COMMIT");
19154
+ return result;
19155
+ } catch (error51) {
19156
+ try {
19157
+ this.database.exec("ROLLBACK");
19158
+ } catch {
19159
+ }
19160
+ throw error51;
19161
+ }
19162
+ }
19097
19163
  getCommentWatchDatabaseId() {
19098
19164
  const row = this.database.prepare("SELECT value FROM app_meta WHERE key = 'comment_watch_database_id'").get();
19099
19165
  if (!row) throw new RvwError("DATABASE_ERROR", "comment watch\u306Edatabase ID\u304C\u3042\u308A\u307E\u305B\u3093\u3002");
@@ -19246,7 +19312,19 @@ var RvwDatabase = class {
19246
19312
  const statement = this.database.prepare(
19247
19313
  "UPDATE pull_requests SET github_state = ?, github_is_draft = ? WHERE id = ?"
19248
19314
  );
19315
+ let changed = false;
19249
19316
  for (const update of updates) {
19317
+ const current = this.getPullRequest(update.pullRequestId);
19318
+ if (!current) {
19319
+ throw new RvwError(
19320
+ "PR_NOT_FOUND",
19321
+ `Pull Request\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${update.pullRequestId}`,
19322
+ { status: 404 }
19323
+ );
19324
+ }
19325
+ if (current.githubState === update.state && current.githubIsDraft === update.isDraft) {
19326
+ continue;
19327
+ }
19250
19328
  const result = statement.run(update.state, update.isDraft ? 1 : 0, update.pullRequestId);
19251
19329
  if (Number(result.changes) !== 1) {
19252
19330
  throw new RvwError(
@@ -19255,18 +19333,24 @@ var RvwDatabase = class {
19255
19333
  { status: 404 }
19256
19334
  );
19257
19335
  }
19336
+ changed = true;
19258
19337
  }
19259
- this.incrementChangeSequence();
19338
+ if (changed) this.incrementDomainRevisions(["pullRequests"]);
19260
19339
  });
19261
19340
  }
19262
19341
  updateRepositoryLocation(id, repository) {
19342
+ const existing = this.getPullRequest(id);
19343
+ if (!existing) throw new RvwError("PR_NOT_FOUND", "Pull Request\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002");
19344
+ if (existing.localRepositoryPath === repository.localRepositoryPath && existing.gitCommonDir === repository.gitCommonDir) {
19345
+ return existing;
19346
+ }
19263
19347
  this.immediateTransaction(() => {
19264
19348
  const result = this.database.prepare(
19265
19349
  "UPDATE pull_requests SET local_repository_path = ?, git_common_dir = ?, updated_at = ? WHERE id = ?"
19266
19350
  ).run(repository.localRepositoryPath, repository.gitCommonDir, (/* @__PURE__ */ new Date()).toISOString(), id);
19267
19351
  if (Number(result.changes) === 0)
19268
19352
  throw new RvwError("PR_NOT_FOUND", "Pull Request\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002");
19269
- this.incrementChangeSequence();
19353
+ this.incrementDomainRevisions(["pullRequests"]);
19270
19354
  });
19271
19355
  const pullRequest = this.getPullRequest(id);
19272
19356
  if (!pullRequest) throw new RvwError("PR_NOT_FOUND", "Pull Request\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002");
@@ -19276,6 +19360,8 @@ var RvwDatabase = class {
19276
19360
  const now = (/* @__PURE__ */ new Date()).toISOString();
19277
19361
  const existing = this.findPullRequestByIdentity(github.owner, github.repository, github.number);
19278
19362
  const id = existing?.id ?? randomUUID();
19363
+ const contentChanged = !existing || existing.latestTitle !== github.title || existing.latestBody !== github.body;
19364
+ const semanticChanged = !existing || existing.url !== github.url || existing.latestAuthorLogin !== github.authorLogin || existing.latestHeadRepositoryOwner !== github.headRepositoryOwner || existing.latestHeadRepositoryName !== github.headRepositoryName || existing.localRepositoryPath !== repository.localRepositoryPath || existing.gitCommonDir !== repository.gitCommonDir || contentChanged || existing.latestBaseRefName !== github.baseRefName || existing.latestHeadRefName !== github.headRefName || existing.latestBaseOid !== github.baseOid || existing.latestComparisonBaseOid !== comparisonBaseOid || existing.latestHeadOid !== github.headOid || existing.githubCreatedAt !== github.createdAt || existing.githubUpdatedAt !== github.updatedAt || existing.githubState !== github.state || existing.githubIsDraft !== github.isDraft;
19279
19365
  this.database.prepare(
19280
19366
  `INSERT INTO pull_requests(
19281
19367
  id, host, owner, repository, number, github_url,
@@ -19329,16 +19415,19 @@ var RvwDatabase = class {
19329
19415
  github.isDraft ? 1 : 0,
19330
19416
  now,
19331
19417
  existing?.createdAt ?? now,
19332
- now,
19418
+ semanticChanged ? now : existing.updatedAt,
19333
19419
  comparisonBaseOid
19334
19420
  );
19335
- return id;
19421
+ return { id, semanticChanged, contentChanged };
19336
19422
  }
19337
19423
  upsertPullRequest(github, repository, comparisonBaseOid) {
19338
19424
  const id = this.immediateTransaction(() => {
19339
- const writtenId = this.writePullRequest(github, repository, comparisonBaseOid);
19340
- this.incrementChangeSequence();
19341
- return writtenId;
19425
+ const write = this.writePullRequest(github, repository, comparisonBaseOid);
19426
+ const domains = [];
19427
+ if (write.semanticChanged) domains.push("pullRequests");
19428
+ if (write.contentChanged) domains.push("pullRequestContent");
19429
+ if (domains.length > 0) this.incrementDomainRevisions(domains);
19430
+ return write.id;
19342
19431
  });
19343
19432
  const pullRequest = this.getPullRequest(id);
19344
19433
  if (!pullRequest)
@@ -19347,10 +19436,14 @@ var RvwDatabase = class {
19347
19436
  }
19348
19437
  syncPullRequestAndComments(github, repository, comparisonBaseOid, updates) {
19349
19438
  const id = this.immediateTransaction(() => {
19350
- const writtenId = this.writePullRequest(github, repository, comparisonBaseOid);
19351
- this.applyCommentUpdates(updates, github.headOid);
19352
- this.incrementChangeSequence();
19353
- return writtenId;
19439
+ const write = this.writePullRequest(github, repository, comparisonBaseOid);
19440
+ const commentsChanged = this.applyCommentUpdates(updates, github.headOid);
19441
+ const domains = [];
19442
+ if (write.semanticChanged) domains.push("pullRequests");
19443
+ if (write.contentChanged) domains.push("pullRequestContent");
19444
+ if (commentsChanged) domains.push("comments");
19445
+ if (domains.length > 0) this.incrementDomainRevisions(domains);
19446
+ return write.id;
19354
19447
  });
19355
19448
  const pullRequest = this.getPullRequest(id);
19356
19449
  if (!pullRequest)
@@ -19359,10 +19452,16 @@ var RvwDatabase = class {
19359
19452
  }
19360
19453
  resetPullRequest(github, repository, comparisonBaseOid) {
19361
19454
  const id = this.immediateTransaction(() => {
19362
- const writtenId = this.writePullRequest(github, repository, comparisonBaseOid);
19363
- this.deletePullRequestHistory(writtenId);
19364
- this.incrementChangeSequence();
19365
- return writtenId;
19455
+ const write = this.writePullRequest(github, repository, comparisonBaseOid);
19456
+ this.deletePullRequestHistory(write.id);
19457
+ this.incrementDomainRevisions([
19458
+ "pullRequests",
19459
+ "pullRequestContent",
19460
+ "comments",
19461
+ "walkthroughs",
19462
+ "structures"
19463
+ ]);
19464
+ return write.id;
19366
19465
  });
19367
19466
  const pullRequest = this.getPullRequest(id);
19368
19467
  if (!pullRequest)
@@ -19496,7 +19595,7 @@ var RvwDatabase = class {
19496
19595
  key_hash, request_hash, structure_id, created_at
19497
19596
  ) VALUES (?, ?, ?, ?)`
19498
19597
  ).run(keyHash, input.idempotencyRequestHash, id, now);
19499
- this.incrementChangeSequence();
19598
+ this.incrementDomainRevisions(["structures"]);
19500
19599
  structureId = id;
19501
19600
  });
19502
19601
  if (!structureId) {
@@ -19579,7 +19678,7 @@ var RvwDatabase = class {
19579
19678
  "INSERT OR IGNORE INTO structure_retired_edge_ids(structure_id, edge_id, retired_at) VALUES (?, ?, ?)"
19580
19679
  );
19581
19680
  for (const edgeId of retiredEdgesAtThisUpdate) retireEdge.run(id, edgeId, now);
19582
- this.incrementChangeSequence();
19681
+ this.incrementDomainRevisions(["structures"]);
19583
19682
  });
19584
19683
  const structure = this.getStructure(id);
19585
19684
  if (!structure) throw new RvwError("DATABASE_ERROR", "\u66F4\u65B0\u3057\u305FStructure\u3092\u8AAD\u307F\u51FA\u305B\u307E\u305B\u3093\u3002");
@@ -19619,7 +19718,7 @@ var RvwDatabase = class {
19619
19718
  { status: 409, details: { expectedUpdatedAt: expectedUpdatedAt2 } }
19620
19719
  );
19621
19720
  }
19622
- this.incrementChangeSequence();
19721
+ this.incrementDomainRevisions(["structures"]);
19623
19722
  return {
19624
19723
  id: structure.id,
19625
19724
  ref: structure.ref,
@@ -19715,7 +19814,7 @@ var RvwDatabase = class {
19715
19814
  now
19716
19815
  );
19717
19816
  this.insertCodeReferences("walkthrough", id, input.references);
19718
- this.incrementChangeSequence();
19817
+ this.incrementDomainRevisions(["walkthroughs"]);
19719
19818
  });
19720
19819
  const walkthrough = this.getWalkthrough(id);
19721
19820
  if (!walkthrough) {
@@ -19742,7 +19841,7 @@ var RvwDatabase = class {
19742
19841
  }
19743
19842
  this.database.prepare("DELETE FROM walkthrough_references WHERE walkthrough_id = ?").run(id);
19744
19843
  this.insertCodeReferences("walkthrough", id, input.references);
19745
- this.incrementChangeSequence();
19844
+ this.incrementDomainRevisions(["walkthroughs"]);
19746
19845
  });
19747
19846
  const walkthrough = this.getWalkthrough(id);
19748
19847
  if (!walkthrough) {
@@ -19778,7 +19877,9 @@ var RvwDatabase = class {
19778
19877
  WHERE id IN (SELECT comment_id FROM comment_targets WHERE walkthrough_id = ?)`
19779
19878
  ).run(id);
19780
19879
  this.database.prepare("DELETE FROM walkthroughs WHERE id = ?").run(id);
19781
- this.incrementChangeSequence();
19880
+ this.incrementDomainRevisions(
19881
+ counts.comments === 0 ? ["walkthroughs"] : ["walkthroughs", "comments"]
19882
+ );
19782
19883
  return {
19783
19884
  id: walkthrough.id,
19784
19885
  ref: walkthrough.ref,
@@ -19816,7 +19917,7 @@ var RvwDatabase = class {
19816
19917
  post_id, comment_ref, pull_request_url, created_at
19817
19918
  ) VALUES (?, ?, ?, ?)`
19818
19919
  ).run(postId, formatCommentUri(id), pullRequest.url, now);
19819
- this.incrementChangeSequence();
19920
+ this.incrementDomainRevisions(["comments"]);
19820
19921
  });
19821
19922
  const comment3 = this.getComment(id);
19822
19923
  if (!comment3) throw new RvwError("DATABASE_ERROR", "\u4FDD\u5B58\u3057\u305F\u30B3\u30E1\u30F3\u30C8\u3092\u8AAD\u307F\u51FA\u305B\u307E\u305B\u3093\u3002");
@@ -19935,7 +20036,7 @@ var RvwDatabase = class {
19935
20036
  FROM comments c
19936
20037
  JOIN comment_targets t ON t.comment_id = c.id
19937
20038
  LEFT JOIN walkthroughs w ON w.id = t.walkthrough_id
19938
- WHERE c.pull_request_id = ?${where} ORDER BY c.updated_at DESC`
20039
+ WHERE c.pull_request_id = ?${where} ORDER BY c.updated_at DESC, c.id DESC`
19939
20040
  ).all(pullRequestId).map((row) => this.mapComment(row));
19940
20041
  }
19941
20042
  listCommentPage(pullRequestId, resolved, limit, offset) {
@@ -20061,7 +20162,7 @@ var RvwDatabase = class {
20061
20162
  ).run(idempotencyKeyHash, idempotencyRequestHash2, id, now);
20062
20163
  }
20063
20164
  this.database.prepare("UPDATE comments SET updated_at = ? WHERE id = ?").run(now, commentId);
20064
- if (incrementSequence) this.incrementChangeSequence();
20165
+ if (incrementSequence) this.incrementDomainRevisions(["comments"]);
20065
20166
  result = {
20066
20167
  id,
20067
20168
  commentId,
@@ -20096,7 +20197,7 @@ var RvwDatabase = class {
20096
20197
  this.database.prepare("DELETE FROM comment_post_references WHERE post_id = ?").run(postId);
20097
20198
  this.insertCodeReferences("comment-post", postId, references);
20098
20199
  this.database.prepare("UPDATE comments SET updated_at = ? WHERE id = ?").run(now, commentId);
20099
- this.incrementChangeSequence();
20200
+ this.incrementDomainRevisions(["comments"]);
20100
20201
  });
20101
20202
  const post = this.getCommentPost(postId);
20102
20203
  if (!post) {
@@ -20128,7 +20229,7 @@ var RvwDatabase = class {
20128
20229
  this.immediateTransaction(() => {
20129
20230
  this.database.prepare("DELETE FROM comment_posts WHERE id = ? AND comment_id = ?").run(postId, commentId);
20130
20231
  this.database.prepare("UPDATE comments SET updated_at = ? WHERE id = ?").run(now, commentId);
20131
- this.incrementChangeSequence();
20232
+ this.incrementDomainRevisions(["comments"]);
20132
20233
  });
20133
20234
  return { commentId, postId };
20134
20235
  }
@@ -20138,7 +20239,7 @@ var RvwDatabase = class {
20138
20239
  const result = this.database.prepare("UPDATE comments SET resolved_at = ?, updated_at = ? WHERE id = ?").run(resolved ? now : null, now, commentId);
20139
20240
  if (Number(result.changes) === 0)
20140
20241
  throw new RvwError("COMMENT_NOT_FOUND", "\u30B3\u30E1\u30F3\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002");
20141
- if (incrementSequence) this.incrementChangeSequence();
20242
+ if (incrementSequence) this.incrementDomainRevisions(["comments"]);
20142
20243
  };
20143
20244
  if (incrementSequence) this.immediateTransaction(write);
20144
20245
  else write();
@@ -20153,14 +20254,18 @@ var RvwDatabase = class {
20153
20254
  }
20154
20255
  this.immediateTransaction(() => {
20155
20256
  this.database.prepare("DELETE FROM comments WHERE id = ?").run(commentId);
20156
- this.incrementChangeSequence();
20257
+ this.incrementDomainRevisions(["comments"]);
20157
20258
  });
20158
20259
  return { id: comment3.id, ref: comment3.ref };
20159
20260
  }
20160
20261
  applyCommentUpdates(updates, relatedCommitOid) {
20262
+ let changed = false;
20161
20263
  for (const update of updates) {
20264
+ const current = this.getComment(update.commentId);
20265
+ if (!current) throw new RvwError("COMMENT_NOT_FOUND", "\u30B3\u30E1\u30F3\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002");
20162
20266
  if (update.reply.trim().length > 0) {
20163
- this.insertReply(
20267
+ const existingPostIds = new Set(current.posts.map(({ id }) => id));
20268
+ const reply = this.insertReply(
20164
20269
  update.commentId,
20165
20270
  {
20166
20271
  body: update.reply,
@@ -20173,11 +20278,14 @@ var RvwDatabase = class {
20173
20278
  },
20174
20279
  false
20175
20280
  );
20176
- } else if (!this.getComment(update.commentId)) {
20177
- throw new RvwError("COMMENT_NOT_FOUND", "\u30B3\u30E1\u30F3\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002");
20281
+ if (!existingPostIds.has(reply.id)) changed = true;
20282
+ }
20283
+ if (update.resolve && current.resolvedAt === null) {
20284
+ this.setCommentResolved(update.commentId, true, false);
20285
+ changed = true;
20178
20286
  }
20179
- if (update.resolve) this.setCommentResolved(update.commentId, true, false);
20180
20287
  }
20288
+ return changed;
20181
20289
  }
20182
20290
  };
20183
20291
 
@@ -20187,12 +20295,13 @@ import path5 from "node:path";
20187
20295
  import { TextDecoder as TextDecoder2 } from "node:util";
20188
20296
 
20189
20297
  // src/shared/constants.ts
20190
- var APP_VERSION = "0.4.1";
20298
+ var APP_VERSION = "0.4.3";
20191
20299
  var PROTOCOL_VERSION = 4;
20192
20300
  var VIEWER_ID_HEADER = "x-rvw-viewer-id";
20193
20301
  var VIEWER_OPEN_LEASE_HEADER = "x-rvw-viewer-open-lease";
20194
20302
  var VIEWER_OPEN_LEASE_QUERY = "viewerLease";
20195
20303
  var GIT_OBJECT_ID_PATTERN = /^[0-9a-f]{40,64}$/i;
20304
+ var CONTENT_FINGERPRINT_PATTERN = /^[0-9a-f]{64}$/;
20196
20305
  var MAX_TEXT_DOCUMENT_BYTES = 1024 * 1024;
20197
20306
  var MAX_MARKDOWN_ASSET_BYTES = 5 * 1024 * 1024;
20198
20307
  var MAX_GITHUB_ATTACHMENT_BYTES = 10 * 1024 * 1024;
@@ -28386,6 +28495,9 @@ ${normalizeLf(body3)}`;
28386
28495
  function hashDocument(value) {
28387
28496
  return createHash2("sha256").update(normalizeLf(value), "utf8").digest("hex");
28388
28497
  }
28498
+ function pullRequestContentFingerprint(title, body3) {
28499
+ return hashDocument(buildPullRequestMarkdown(title, body3));
28500
+ }
28389
28501
  function selectedLineText(value, startLine, endLine) {
28390
28502
  const lines2 = normalizeLf(value).split("\n");
28391
28503
  if (startLine < 1 || endLine < startLine || endLine > lines2.length) return null;
@@ -41706,6 +41818,20 @@ function assertCodeReferencePath(filePath) {
41706
41818
  throw new RvwError("INVALID_INPUT", `code reference\u306Epath\u304C\u4E0D\u6B63\u3067\u3059: ${filePath}`);
41707
41819
  }
41708
41820
  }
41821
+ async function mapWithConcurrency(values, concurrency, mapper) {
41822
+ const results = new Array(values.length);
41823
+ let nextIndex = 0;
41824
+ const worker = async () => {
41825
+ while (nextIndex < values.length) {
41826
+ const index2 = nextIndex++;
41827
+ results[index2] = await mapper(values[index2], index2);
41828
+ }
41829
+ };
41830
+ await Promise.all(
41831
+ Array.from({ length: Math.min(concurrency, values.length) }, async () => await worker())
41832
+ );
41833
+ return results;
41834
+ }
41709
41835
  var RvwService = class {
41710
41836
  constructor(database, git, github) {
41711
41837
  this.database = database;
@@ -42093,7 +42219,10 @@ var RvwService = class {
42093
42219
  });
42094
42220
  }
42095
42221
  async getPullRequestView(id) {
42096
- const pullRequest = this.getPullRequest(id);
42222
+ const { pullRequest, revisionSnapshot } = this.database.getPullRequestRevisionSnapshot(id);
42223
+ if (!pullRequest) {
42224
+ throw new RvwError("PR_NOT_FOUND", "Pull Request\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002", { status: 404 });
42225
+ }
42097
42226
  const commits = await this.git.commits(
42098
42227
  pullRequest.localRepositoryPath,
42099
42228
  pullRequest.latestComparisonBaseOid,
@@ -42101,11 +42230,30 @@ var RvwService = class {
42101
42230
  );
42102
42231
  return {
42103
42232
  pullRequest,
42233
+ pullRequestContentFingerprint: pullRequestContentFingerprint(
42234
+ pullRequest.latestTitle,
42235
+ pullRequest.latestBody
42236
+ ),
42104
42237
  comparisonBaseOid: pullRequest.latestComparisonBaseOid,
42105
42238
  headOid: pullRequest.latestHeadOid,
42106
- commits
42239
+ commits,
42240
+ ...revisionSnapshot
42107
42241
  };
42108
42242
  }
42243
+ assertPullRequestContentFingerprint(pullRequest, expectedFingerprint) {
42244
+ const actualFingerprint = pullRequestContentFingerprint(
42245
+ pullRequest.latestTitle,
42246
+ pullRequest.latestBody
42247
+ );
42248
+ if (expectedFingerprint !== void 0 && expectedFingerprint !== actualFingerprint) {
42249
+ throw new RvwError("STALE_CONTENT", "Pull Request\u672C\u6587\u304C\u66F4\u65B0\u3055\u308C\u3066\u3044\u307E\u3059\u3002", {
42250
+ status: 409,
42251
+ details: { expectedFingerprint, actualFingerprint },
42252
+ suggestions: ["\u6700\u65B0\u306EPull Request\u672C\u6587\u3092\u518D\u53D6\u5F97\u3057\u3066\u304F\u3060\u3055\u3044\u3002"]
42253
+ });
42254
+ }
42255
+ return actualFingerprint;
42256
+ }
42109
42257
  async assertCommitAvailable(pullRequest, oid) {
42110
42258
  if (!GIT_OBJECT_ID_PATTERN.test(oid) || !await this.git.hasObject(pullRequest.localRepositoryPath, oid)) {
42111
42259
  throw new RvwError("COMMIT_NOT_FOUND", `Git commit\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${oid}`, { status: 404 });
@@ -42162,6 +42310,26 @@ var RvwService = class {
42162
42310
  );
42163
42311
  return { ref, ...content3 };
42164
42312
  }
42313
+ async getDocumentWithContentFingerprint(ref, expectedFingerprint) {
42314
+ if (ref.kind !== "pull-request-markdown") {
42315
+ return { document: await this.getDocument(ref), pullRequestContentFingerprint: null };
42316
+ }
42317
+ const pullRequest = this.getPullRequest(ref.pullRequestId);
42318
+ const fingerprint = this.assertPullRequestContentFingerprint(pullRequest, expectedFingerprint);
42319
+ const text5 = buildPullRequestMarkdown(pullRequest.latestTitle, pullRequest.latestBody);
42320
+ return {
42321
+ document: {
42322
+ ref,
42323
+ availability: "available",
42324
+ text: text5,
42325
+ byteLength: Buffer.byteLength(text5, "utf8"),
42326
+ entryKind: "virtual",
42327
+ normalizedLineEndings: false,
42328
+ oid: null
42329
+ },
42330
+ pullRequestContentFingerprint: fingerprint
42331
+ };
42332
+ }
42165
42333
  async getRepositoryAsset(pullRequestId, sourceOid, filePath) {
42166
42334
  const pullRequest = this.getPullRequest(pullRequestId);
42167
42335
  await this.assertCommitAvailable(pullRequest, sourceOid);
@@ -42189,7 +42357,7 @@ var RvwService = class {
42189
42357
  ]);
42190
42358
  return { ref, old, new: next2 };
42191
42359
  }
42192
- async search(pullRequestId, oid, query, options) {
42360
+ async search(pullRequestId, oid, query, options, expectedPullRequestContentFingerprint) {
42193
42361
  const queryBytes = Buffer.byteLength(query, "utf8");
42194
42362
  if (queryBytes === 0 || queryBytes > MAX_SEARCH_QUERY_BYTES || query.includes("\n") || query.includes("\r")) {
42195
42363
  throw new RvwError(
@@ -42198,6 +42366,10 @@ var RvwService = class {
42198
42366
  );
42199
42367
  }
42200
42368
  const pullRequest = this.getPullRequest(pullRequestId);
42369
+ const contentFingerprint = this.assertPullRequestContentFingerprint(
42370
+ pullRequest,
42371
+ expectedPullRequestContentFingerprint
42372
+ );
42201
42373
  await this.assertCommitAvailable(pullRequest, oid);
42202
42374
  const prMarkdown = buildPullRequestMarkdown(pullRequest.latestTitle, pullRequest.latestBody);
42203
42375
  const results = [];
@@ -42223,6 +42395,7 @@ var RvwService = class {
42223
42395
  });
42224
42396
  }
42225
42397
  return {
42398
+ pullRequestContentFingerprint: contentFingerprint,
42226
42399
  results,
42227
42400
  matchCount: results.reduce((count, result) => count + result.matches.length, 0),
42228
42401
  truncated: gitResult.truncated || results.length >= MAX_SEARCH_RESULTS && (gitResult.results.length > 0 || prMarkdown.split("\n").some((line) => findFixedStringMatches(line, query, options).length > 0)),
@@ -42550,19 +42723,22 @@ var RvwService = class {
42550
42723
  return this.database.listStructures(pullRequestId);
42551
42724
  }
42552
42725
  async listFileStructureReferences(pullRequestId, targetSourceOid, targetPath) {
42553
- const pullRequest = this.getPullRequest(pullRequestId);
42554
42726
  assertCodeReferencePath(targetPath);
42727
+ const index2 = await this.listFileStructureReferenceIndex(pullRequestId, targetSourceOid);
42728
+ return index2.entries.find(({ path: candidate }) => candidate === targetPath)?.references ?? [];
42729
+ }
42730
+ async listFileStructureReferenceIndex(pullRequestId, targetSourceOid) {
42731
+ const pullRequest = this.getPullRequest(pullRequestId);
42555
42732
  const structures = this.database.listStructures(pullRequestId).flatMap((summary) => {
42556
42733
  const structure = this.database.getStructure(summary.id);
42557
42734
  return structure?.pullRequestId === pullRequestId ? [structure] : [];
42558
42735
  });
42736
+ if (structures.length === 0) return { sourceOid: targetSourceOid, entries: [] };
42559
42737
  await this.assertCommitAvailable(pullRequest, targetSourceOid);
42560
- const targetPaths = new Set(
42561
- (await this.git.tree(pullRequest.localRepositoryPath, targetSourceOid)).map(
42562
- (entry) => entry.path
42563
- )
42738
+ const treePaths = (await this.git.tree(pullRequest.localRepositoryPath, targetSourceOid)).map(
42739
+ (entry) => entry.path
42564
42740
  );
42565
- if (!targetPaths.has(targetPath)) return [];
42741
+ const targetPaths = new Set(treePaths);
42566
42742
  const successorIndexes = /* @__PURE__ */ new Map();
42567
42743
  const successorIndex = (sourceOid) => {
42568
42744
  const key2 = JSON.stringify([sourceOid, targetSourceOid]);
@@ -42593,26 +42769,37 @@ var RvwService = class {
42593
42769
  return targetPaths.has(successor) ? successor : null;
42594
42770
  });
42595
42771
  };
42596
- const references = [];
42772
+ const referencesByPath = /* @__PURE__ */ new Map();
42597
42773
  for (const structure of structures) {
42598
- const matchingNodeIds = /* @__PURE__ */ new Set();
42599
- await Promise.all(
42600
- structure.nodes.map(async (node2) => {
42601
- if (!node2.anchor) return;
42602
- const resolvedPath = await resolvePath(structure.sourceOid, node2.anchor.path);
42603
- if (resolvedPath === targetPath) matchingNodeIds.add(node2.id);
42604
- })
42605
- );
42606
- const targetNode = closestMatchingStructureNode(structure, matchingNodeIds);
42607
- if (!targetNode) continue;
42608
- references.push({
42609
- structure: structureSummary(structure),
42610
- targetNodeId: targetNode.id,
42611
- targetNodeLabel: targetNode.label,
42612
- matchingNodeCount: matchingNodeIds.size
42613
- });
42774
+ const matchingNodeIdsByPath = /* @__PURE__ */ new Map();
42775
+ for (const node2 of structure.nodes) {
42776
+ if (!node2.anchor) continue;
42777
+ const resolvedPath = await resolvePath(structure.sourceOid, node2.anchor.path);
42778
+ if (!resolvedPath) continue;
42779
+ const nodeIds = matchingNodeIdsByPath.get(resolvedPath) ?? /* @__PURE__ */ new Set();
42780
+ nodeIds.add(node2.id);
42781
+ matchingNodeIdsByPath.set(resolvedPath, nodeIds);
42782
+ }
42783
+ for (const [resolvedPath, matchingNodeIds] of matchingNodeIdsByPath) {
42784
+ const targetNode = closestMatchingStructureNode(structure, matchingNodeIds);
42785
+ if (!targetNode) continue;
42786
+ const references = referencesByPath.get(resolvedPath) ?? [];
42787
+ references.push({
42788
+ structure: structureSummary(structure),
42789
+ targetNodeId: targetNode.id,
42790
+ targetNodeLabel: targetNode.label,
42791
+ matchingNodeCount: matchingNodeIds.size
42792
+ });
42793
+ referencesByPath.set(resolvedPath, references);
42794
+ }
42614
42795
  }
42615
- return references;
42796
+ return {
42797
+ sourceOid: targetSourceOid,
42798
+ entries: treePaths.flatMap((path11) => {
42799
+ const references = referencesByPath.get(path11);
42800
+ return references ? [{ path: path11, references }] : [];
42801
+ })
42802
+ };
42616
42803
  }
42617
42804
  listStructuresByReference(reference) {
42618
42805
  const pullRequest = this.resolveStoredPullRequest(reference);
@@ -43318,40 +43505,54 @@ var RvwService = class {
43318
43505
  }
43319
43506
  return { ...placeMutableDocumentComment(comment3.target, walkthrough.body), path: null };
43320
43507
  }
43321
- async resolveRepositoryCommentPath(pullRequest, source, destinationOid) {
43508
+ async resolveRepositoryCommentPath(pullRequest, source, destinationOid, context) {
43322
43509
  if (source.sourceOid === destinationOid) {
43323
43510
  return { path: source.path, deleted: false };
43324
43511
  }
43325
- const changes = await this.git.changedFiles(
43326
- pullRequest.localRepositoryPath,
43327
- source.sourceOid,
43328
- destinationOid
43329
- );
43512
+ const key2 = JSON.stringify([source.sourceOid, destinationOid]);
43513
+ let changesPromise = context.changedFiles.get(key2);
43514
+ if (!changesPromise) {
43515
+ changesPromise = this.git.changedFiles(
43516
+ pullRequest.localRepositoryPath,
43517
+ source.sourceOid,
43518
+ destinationOid
43519
+ );
43520
+ context.changedFiles.set(key2, changesPromise);
43521
+ }
43522
+ const changes = await changesPromise;
43330
43523
  const change = changes.find((candidate) => candidate.oldPath === source.path);
43331
43524
  if (change?.kind === "deleted" || change && change.newPath === null) {
43332
43525
  return { path: source.path, deleted: true };
43333
43526
  }
43334
43527
  return { path: change?.newPath ?? source.path, deleted: false };
43335
43528
  }
43336
- async placeRepositoryCommentAtPath(pullRequest, source, destinationOid, destinationPath) {
43337
- const destinationContent = await this.getDocument({
43338
- kind: "repository-file",
43339
- pullRequestId: pullRequest.id,
43340
- sourceOid: destinationOid,
43341
- path: destinationPath
43342
- });
43529
+ async placeRepositoryCommentAtPath(pullRequest, source, destinationOid, destinationPath, context) {
43530
+ const destinationContent = await this.getPlacementDocument(
43531
+ pullRequest,
43532
+ {
43533
+ kind: "repository-file",
43534
+ pullRequestId: pullRequest.id,
43535
+ sourceOid: destinationOid,
43536
+ path: destinationPath
43537
+ },
43538
+ context
43539
+ );
43343
43540
  if (source.startLine === null || source.endLine === null) {
43344
43541
  return destinationContent.availability === "missing" ? { outdated: true, range: null, path: destinationPath } : { outdated: false, range: null, path: destinationPath };
43345
43542
  }
43346
43543
  if (destinationContent.availability !== "available") {
43347
43544
  return { outdated: true, range: null, path: destinationPath };
43348
43545
  }
43349
- const sourceContent = await this.getDocument({
43350
- kind: "repository-file",
43351
- pullRequestId: pullRequest.id,
43352
- sourceOid: source.sourceOid,
43353
- path: source.path
43354
- });
43546
+ const sourceContent = await this.getPlacementDocument(
43547
+ pullRequest,
43548
+ {
43549
+ kind: "repository-file",
43550
+ pullRequestId: pullRequest.id,
43551
+ sourceOid: source.sourceOid,
43552
+ path: source.path
43553
+ },
43554
+ context
43555
+ );
43355
43556
  if (sourceContent.availability !== "available") {
43356
43557
  return { outdated: true, range: null, path: destinationPath };
43357
43558
  }
@@ -43364,55 +43565,171 @@ var RvwService = class {
43364
43565
  return range ? { outdated: false, range, path: destinationPath } : { outdated: true, range: null, path: destinationPath };
43365
43566
  }
43366
43567
  async placeComment(comment3, destination) {
43367
- if (comment3.pullRequestId !== destination.pullRequestId) {
43568
+ const pullRequest = this.getPullRequest(comment3.pullRequestId);
43569
+ return await this.placeCommentForDestination(
43570
+ comment3,
43571
+ pullRequest,
43572
+ { kind: "document", ref: destination },
43573
+ this.createCommentPlacementRequestContext()
43574
+ );
43575
+ }
43576
+ createCommentPlacementRequestContext() {
43577
+ return {
43578
+ commitAvailability: /* @__PURE__ */ new Map(),
43579
+ changedFiles: /* @__PURE__ */ new Map(),
43580
+ documents: /* @__PURE__ */ new Map()
43581
+ };
43582
+ }
43583
+ async assertPlacementCommitAvailable(pullRequest, oid, context) {
43584
+ let available = context.commitAvailability.get(oid);
43585
+ if (!available) {
43586
+ available = this.assertCommitAvailable(pullRequest, oid);
43587
+ context.commitAvailability.set(oid, available);
43588
+ }
43589
+ await available;
43590
+ }
43591
+ async getPlacementDocument(pullRequest, ref, context) {
43592
+ const key2 = JSON.stringify(ref);
43593
+ let document3 = context.documents.get(key2);
43594
+ if (!document3) {
43595
+ document3 = (async () => {
43596
+ if (ref.kind === "pull-request-markdown") {
43597
+ const text5 = buildPullRequestMarkdown(pullRequest.latestTitle, pullRequest.latestBody);
43598
+ return {
43599
+ ref,
43600
+ availability: "available",
43601
+ text: text5,
43602
+ byteLength: Buffer.byteLength(text5, "utf8"),
43603
+ entryKind: "virtual",
43604
+ normalizedLineEndings: false,
43605
+ oid: null
43606
+ };
43607
+ }
43608
+ await this.assertPlacementCommitAvailable(pullRequest, ref.sourceOid, context);
43609
+ const content3 = await this.git.readDocument(
43610
+ pullRequest.localRepositoryPath,
43611
+ ref.sourceOid,
43612
+ ref.path
43613
+ );
43614
+ return { ref, ...content3 };
43615
+ })();
43616
+ context.documents.set(key2, document3);
43617
+ }
43618
+ return await document3;
43619
+ }
43620
+ async placeCommentForDestination(comment3, pullRequest, destination, context) {
43621
+ if (destination.kind === "walkthrough") {
43622
+ return this.placeWalkthroughComment(comment3, destination.walkthroughId);
43623
+ }
43624
+ if (destination.kind === "commit") {
43625
+ await this.assertPlacementCommitAvailable(pullRequest, destination.oid, context);
43626
+ }
43627
+ const documentDestination = destination.kind === "document" ? destination.ref : null;
43628
+ const destinationOid = destination.kind === "commit" ? destination.oid : documentDestination?.kind === "repository-file" ? documentDestination.sourceOid : null;
43629
+ if (documentDestination !== null && comment3.pullRequestId !== documentDestination.pullRequestId) {
43368
43630
  return { outdated: true, range: null, path: null };
43369
43631
  }
43370
43632
  if (comment3.target.kind === "pull-request") return { outdated: false, range: null, path: null };
43371
43633
  if (comment3.target.kind === "walkthrough") {
43372
43634
  return this.placeWalkthroughComment(comment3);
43373
43635
  }
43374
- const pullRequest = this.getPullRequest(comment3.pullRequestId);
43375
43636
  if (comment3.target.documentKind === "pull-request-markdown") {
43376
- return destination.kind === "pull-request-markdown" ? this.placePullRequestMarkdownComment(comment3, pullRequest) : { outdated: true, range: null, path: null };
43637
+ return destination.kind === "commit" || documentDestination?.kind === "pull-request-markdown" ? this.placePullRequestMarkdownComment(comment3, pullRequest) : { outdated: true, range: null, path: null };
43377
43638
  }
43378
- if (destination.kind !== "repository-file") return { outdated: true, range: null, path: null };
43639
+ if (destinationOid === null) return { outdated: true, range: null, path: null };
43379
43640
  const source = comment3.target;
43641
+ await this.assertPlacementCommitAvailable(pullRequest, source.sourceOid, context);
43380
43642
  const resolved = await this.resolveRepositoryCommentPath(
43381
43643
  pullRequest,
43382
43644
  source,
43383
- destination.sourceOid
43645
+ destinationOid,
43646
+ context
43384
43647
  );
43385
43648
  if (resolved.deleted) return { outdated: true, range: null, path: source.path };
43386
- if (resolved.path !== destination.path) {
43649
+ if (documentDestination?.kind === "repository-file" && resolved.path !== documentDestination.path) {
43387
43650
  return { outdated: true, range: null, path: resolved.path };
43388
43651
  }
43389
43652
  return await this.placeRepositoryCommentAtPath(
43390
43653
  pullRequest,
43391
43654
  source,
43392
- destination.sourceOid,
43393
- resolved.path
43655
+ destinationOid,
43656
+ resolved.path,
43657
+ context
43394
43658
  );
43395
43659
  }
43396
43660
  async placeCommentAtCommit(comment3, destinationOid) {
43397
43661
  const pullRequest = this.getPullRequest(comment3.pullRequestId);
43398
- await this.assertCommitAvailable(pullRequest, destinationOid);
43399
- if (comment3.target.kind === "pull-request") return { outdated: false, range: null, path: null };
43400
- if (comment3.target.kind === "walkthrough") {
43401
- return this.placeWalkthroughComment(comment3);
43402
- }
43403
- if (comment3.target.documentKind === "pull-request-markdown") {
43404
- return this.placePullRequestMarkdownComment(comment3, pullRequest);
43405
- }
43406
- const source = comment3.target;
43407
- const resolved = await this.resolveRepositoryCommentPath(pullRequest, source, destinationOid);
43408
- if (resolved.deleted) return { outdated: true, range: null, path: source.path };
43409
- return await this.placeRepositoryCommentAtPath(
43662
+ return await this.placeCommentForDestination(
43663
+ comment3,
43410
43664
  pullRequest,
43411
- source,
43412
- destinationOid,
43413
- resolved.path
43665
+ { kind: "commit", oid: destinationOid },
43666
+ this.createCommentPlacementRequestContext()
43414
43667
  );
43415
43668
  }
43669
+ async resolveCommentPlacements(pullRequestId, commentIds, destinations, expectedPullRequestContentFingerprint) {
43670
+ const pullRequest = this.getPullRequest(pullRequestId);
43671
+ const contentFingerprint = this.assertPullRequestContentFingerprint(
43672
+ pullRequest,
43673
+ expectedPullRequestContentFingerprint
43674
+ );
43675
+ const uniqueCommentIds = [...new Set(commentIds)];
43676
+ const comments = [];
43677
+ const missingCommentIds = [];
43678
+ for (const commentId of uniqueCommentIds) {
43679
+ const comment3 = this.database.getComment(commentId);
43680
+ if (!comment3) {
43681
+ missingCommentIds.push(commentId);
43682
+ continue;
43683
+ }
43684
+ if (comment3.pullRequestId !== pullRequestId) {
43685
+ throw new RvwError(
43686
+ "INVALID_INPUT",
43687
+ `\u5225\u306EPull Request\u306E\u30B3\u30E1\u30F3\u30C8\u306F\u4E00\u62EC\u89E3\u6C7A\u3067\u304D\u307E\u305B\u3093: ${commentId}`
43688
+ );
43689
+ }
43690
+ comments.push(comment3);
43691
+ }
43692
+ const context = this.createCommentPlacementRequestContext();
43693
+ if (comments.length > 0) {
43694
+ for (const destination of destinations) {
43695
+ if (destination.kind === "commit") {
43696
+ await this.assertPlacementCommitAvailable(pullRequest, destination.oid, context);
43697
+ } else if (destination.kind === "document" && destination.ref.kind === "repository-file") {
43698
+ await this.assertPlacementCommitAvailable(
43699
+ pullRequest,
43700
+ destination.ref.sourceOid,
43701
+ context
43702
+ );
43703
+ }
43704
+ }
43705
+ }
43706
+ return {
43707
+ pullRequestContentFingerprint: contentFingerprint,
43708
+ comments: await mapWithConcurrency(comments, 4, async (comment3) => {
43709
+ const placements = [];
43710
+ const failures = [];
43711
+ for (const destination of destinations) {
43712
+ try {
43713
+ placements.push({
43714
+ destination,
43715
+ placement: await this.placeCommentForDestination(
43716
+ comment3,
43717
+ pullRequest,
43718
+ destination,
43719
+ context
43720
+ )
43721
+ });
43722
+ } catch (error51) {
43723
+ const placementError = asRvwError(error51);
43724
+ if (placementError.code !== "COMMIT_NOT_FOUND") throw error51;
43725
+ failures.push({ destination, error: placementError.toJSON() });
43726
+ }
43727
+ }
43728
+ return { commentId: comment3.id, placements, failures };
43729
+ }),
43730
+ missingCommentIds
43731
+ };
43732
+ }
43416
43733
  async getResetPreview(pullRequestId) {
43417
43734
  const pullRequest = this.getPullRequest(pullRequestId);
43418
43735
  const refs = await this.git.listRefsByPrefix(
@@ -47710,9 +48027,30 @@ var editCommentPostSchema = external_exports.object({
47710
48027
  body: external_exports.string().min(1).refine((value) => Buffer.byteLength(value, "utf8") <= MAX_COMMENT_BODY_BYTES),
47711
48028
  references: external_exports.array(codeReferenceInputSchema).optional()
47712
48029
  });
48030
+ var placementDocumentRefSchema = external_exports.discriminatedUnion("kind", [
48031
+ external_exports.object({ kind: external_exports.literal("pull-request-markdown"), pullRequestId: external_exports.uuid() }),
48032
+ external_exports.object({
48033
+ kind: external_exports.literal("repository-file"),
48034
+ pullRequestId: external_exports.uuid(),
48035
+ sourceOid: external_exports.string().regex(GIT_OBJECT_ID_PATTERN),
48036
+ path: external_exports.string().min(1)
48037
+ })
48038
+ ]);
48039
+ var resolveCommentPlacementsSchema = external_exports.object({
48040
+ commentIds: external_exports.array(external_exports.uuid()),
48041
+ expectedPullRequestContentFingerprint: external_exports.string().regex(CONTENT_FINGERPRINT_PATTERN).optional(),
48042
+ destinations: external_exports.array(
48043
+ external_exports.union([
48044
+ external_exports.object({ kind: external_exports.literal("document"), ref: placementDocumentRefSchema }),
48045
+ external_exports.object({ kind: external_exports.literal("commit"), oid: external_exports.string().regex(GIT_OBJECT_ID_PATTERN) }),
48046
+ external_exports.object({ kind: external_exports.literal("walkthrough"), walkthroughId: external_exports.uuid() })
48047
+ ])
48048
+ ).min(1).max(4)
48049
+ });
47713
48050
 
47714
48051
  // src/server/app.ts
47715
48052
  var oidSchema = external_exports.string().regex(GIT_OBJECT_ID_PATTERN);
48053
+ var contentFingerprintSchema = external_exports.string().regex(CONTENT_FINGERPRINT_PATTERN);
47716
48054
  var nonnegativeIndexSchema = external_exports.coerce.number().int().nonnegative();
47717
48055
  var svgAssetContentSecurityPolicy = "default-src 'none'; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'; sandbox";
47718
48056
  function requiredQuery(value, name) {
@@ -47722,6 +48060,9 @@ function requiredQuery(value, name) {
47722
48060
  function oidQuery(value, name) {
47723
48061
  return oidSchema.parse(requiredQuery(value, name));
47724
48062
  }
48063
+ function optionalContentFingerprint(value) {
48064
+ return value === void 0 ? void 0 : contentFingerprintSchema.parse(value);
48065
+ }
47725
48066
  function structureSourceLocator(query) {
47726
48067
  if (query.locatorKind === "node") {
47727
48068
  return { kind: "node", nodeId: requiredQuery(query.nodeId, "nodeId") };
@@ -47831,7 +48172,7 @@ function createApp(service, options) {
47831
48172
  rawViewerLease === void 0 ? void 0 : viewerIdSchema.parse(rawViewerLease)
47832
48173
  );
47833
48174
  }
47834
- return context.json({ ok: true, changeSequence: service.database.getChangeSequence() });
48175
+ return context.json({ ok: true, ...service.database.getRevisionSnapshot() });
47835
48176
  });
47836
48177
  app.post("/api/meta/viewers/release", async (context) => {
47837
48178
  const input = viewerReleaseSchema.parse(await context.req.json());
@@ -47872,10 +48213,10 @@ function createApp(service, options) {
47872
48213
  ...await service.openPullRequest(input.reference, input.cwd)
47873
48214
  });
47874
48215
  });
47875
- app.post(
47876
- "/api/pull-requests/:id/refresh",
47877
- async (context) => context.json({ ok: true, ...await service.refreshPullRequest(context.req.param("id")) })
47878
- );
48216
+ app.post("/api/pull-requests/:id/refresh", async (context) => {
48217
+ const result = await service.refreshPullRequest(context.req.param("id"));
48218
+ return context.json({ ok: true, ...result });
48219
+ });
47879
48220
  app.post("/api/pull-requests/:id/reset", async (context) => {
47880
48221
  const input = resetSchema.parse(await context.req.json());
47881
48222
  if (!input.yes) {
@@ -47913,7 +48254,13 @@ function createApp(service, options) {
47913
48254
  });
47914
48255
  app.get("/api/pull-requests/:id/document", async (context) => {
47915
48256
  const ref = documentRefFromQuery(context.req.param("id"), context.req.query());
47916
- return context.json({ ok: true, document: await service.getDocument(ref) });
48257
+ return context.json({
48258
+ ok: true,
48259
+ ...await service.getDocumentWithContentFingerprint(
48260
+ ref,
48261
+ optionalContentFingerprint(context.req.query("expectedPullRequestContentFingerprint"))
48262
+ )
48263
+ });
47917
48264
  });
47918
48265
  app.on(["GET", "HEAD"], "/api/pull-requests/:id/markdown-asset", async (context) => {
47919
48266
  const sourceOid = oidQuery(context.req.query("sourceOid"), "sourceOid");
@@ -47972,10 +48319,16 @@ function createApp(service, options) {
47972
48319
  const wholeWord = parseBooleanQuery(context.req.query("wholeWord"), "wholeWord");
47973
48320
  return context.json({
47974
48321
  ok: true,
47975
- ...await service.search(context.req.param("id"), oid, query, {
47976
- matchCase,
47977
- wholeWord
47978
- })
48322
+ ...await service.search(
48323
+ context.req.param("id"),
48324
+ oid,
48325
+ query,
48326
+ {
48327
+ matchCase,
48328
+ wholeWord
48329
+ },
48330
+ optionalContentFingerprint(context.req.query("expectedPullRequestContentFingerprint"))
48331
+ )
47979
48332
  });
47980
48333
  });
47981
48334
  app.get("/api/pull-requests/:id/comments", (context) => {
@@ -48042,6 +48395,13 @@ function createApp(service, options) {
48042
48395
  )
48043
48396
  });
48044
48397
  });
48398
+ app.get("/api/pull-requests/:id/structure-reference-index", async (context) => {
48399
+ const sourceOid = oidQuery(context.req.query("sourceOid"), "sourceOid");
48400
+ return context.json({
48401
+ ok: true,
48402
+ index: await service.listFileStructureReferenceIndex(context.req.param("id"), sourceOid)
48403
+ });
48404
+ });
48045
48405
  app.get(
48046
48406
  "/api/pull-requests/:id/structures/:structureId",
48047
48407
  (context) => context.json({
@@ -48095,43 +48455,51 @@ function createApp(service, options) {
48095
48455
  });
48096
48456
  app.post("/api/comments/:id/posts", async (context) => {
48097
48457
  const input = replySchema.parse(await context.req.json());
48458
+ const commentId = context.req.param("id");
48459
+ const post = await service.replyToComment(commentId, {
48460
+ body: input.body,
48461
+ ...input.relatedCommitOid === void 0 ? {} : { relatedCommitOid: input.relatedCommitOid },
48462
+ ...input.authorLabel === void 0 ? {} : { authorLabel: input.authorLabel },
48463
+ lastModifiedBy: "human",
48464
+ ...input.references === void 0 ? {} : { references: input.references }
48465
+ });
48098
48466
  return context.json(
48099
48467
  {
48100
48468
  ok: true,
48101
- post: await service.replyToComment(context.req.param("id"), {
48102
- body: input.body,
48103
- ...input.relatedCommitOid === void 0 ? {} : { relatedCommitOid: input.relatedCommitOid },
48104
- ...input.authorLabel === void 0 ? {} : { authorLabel: input.authorLabel },
48105
- lastModifiedBy: "human",
48106
- ...input.references === void 0 ? {} : { references: input.references }
48107
- })
48469
+ post,
48470
+ comment: service.database.getComment(commentId)
48108
48471
  },
48109
48472
  201
48110
48473
  );
48111
48474
  });
48112
48475
  app.patch("/api/comments/:id/posts/:postId", async (context) => {
48113
48476
  const input = editCommentPostSchema.parse(await context.req.json());
48477
+ const commentId = context.req.param("id");
48478
+ const post = input.references === void 0 ? await service.updateCommentPost(
48479
+ commentId,
48480
+ context.req.param("postId"),
48481
+ input.body,
48482
+ "human"
48483
+ ) : await service.editCommentPost(commentId, context.req.param("postId"), {
48484
+ body: input.body,
48485
+ references: input.references,
48486
+ lastModifiedBy: "human"
48487
+ });
48114
48488
  return context.json({
48115
48489
  ok: true,
48116
- post: input.references === void 0 ? await service.updateCommentPost(
48117
- context.req.param("id"),
48118
- context.req.param("postId"),
48119
- input.body,
48120
- "human"
48121
- ) : await service.editCommentPost(context.req.param("id"), context.req.param("postId"), {
48122
- body: input.body,
48123
- references: input.references,
48124
- lastModifiedBy: "human"
48125
- })
48490
+ post,
48491
+ comment: service.database.getComment(commentId)
48126
48492
  });
48127
48493
  });
48128
- app.delete(
48129
- "/api/comments/:id/posts/:postId",
48130
- (context) => context.json({
48494
+ app.delete("/api/comments/:id/posts/:postId", (context) => {
48495
+ const commentId = context.req.param("id");
48496
+ const deleted = service.deleteReply(commentId, context.req.param("postId"));
48497
+ return context.json({
48131
48498
  ok: true,
48132
- deleted: service.deleteReply(context.req.param("id"), context.req.param("postId"))
48133
- })
48134
- );
48499
+ deleted,
48500
+ comment: service.database.getComment(commentId)
48501
+ });
48502
+ });
48135
48503
  app.post(
48136
48504
  "/api/comments/:id/resolve",
48137
48505
  (context) => context.json({ ok: true, comment: service.setCommentResolved(context.req.param("id"), true) })
@@ -48144,6 +48512,18 @@ function createApp(service, options) {
48144
48512
  "/api/comments/:id",
48145
48513
  (context) => context.json({ ok: true, deleted: service.deleteComment(context.req.param("id")) })
48146
48514
  );
48515
+ app.post("/api/pull-requests/:pullRequestId/comment-placements/resolve", async (context) => {
48516
+ const input = resolveCommentPlacementsSchema.parse(await context.req.json());
48517
+ return context.json({
48518
+ ok: true,
48519
+ ...await service.resolveCommentPlacements(
48520
+ context.req.param("pullRequestId"),
48521
+ input.commentIds,
48522
+ input.destinations,
48523
+ input.expectedPullRequestContentFingerprint
48524
+ )
48525
+ });
48526
+ });
48147
48527
  app.get("/api/comments/:id/placement", async (context) => {
48148
48528
  const comment3 = service.database.getComment(context.req.param("id"));
48149
48529
  if (!comment3)
@@ -48155,22 +48535,21 @@ function createApp(service, options) {
48155
48535
  placement: { outdated: true, range: null, path: null }
48156
48536
  });
48157
48537
  }
48538
+ let destination;
48158
48539
  if (context.req.query("kind") === "commit") {
48159
48540
  const oid = oidQuery(context.req.query("oid"), "oid");
48160
- return context.json({
48161
- ok: true,
48162
- placement: await service.placeCommentAtCommit(comment3, oid)
48163
- });
48164
- }
48165
- if (context.req.query("kind") === "walkthrough") {
48541
+ destination = { kind: "commit", oid };
48542
+ } else if (context.req.query("kind") === "walkthrough") {
48166
48543
  const walkthroughId = requiredQuery(context.req.query("walkthroughId"), "walkthroughId");
48167
- return context.json({
48168
- ok: true,
48169
- placement: service.placeWalkthroughComment(comment3, walkthroughId)
48170
- });
48544
+ destination = { kind: "walkthrough", walkthroughId };
48545
+ } else {
48546
+ destination = {
48547
+ kind: "document",
48548
+ ref: documentRefFromQuery(pullRequestId, context.req.query())
48549
+ };
48171
48550
  }
48172
- const destination = documentRefFromQuery(pullRequestId, context.req.query());
48173
- return context.json({ ok: true, placement: await service.placeComment(comment3, destination) });
48551
+ const placement = destination.kind === "commit" ? await service.placeCommentAtCommit(comment3, destination.oid) : destination.kind === "walkthrough" ? service.placeWalkthroughComment(comment3, destination.walkthroughId) : await service.placeComment(comment3, destination.ref);
48552
+ return context.json({ ok: true, placement });
48174
48553
  });
48175
48554
  if (options.staticDirectory && existsSync4(options.staticDirectory)) {
48176
48555
  app.use("*", serveStatic({ root: options.staticDirectory }));