pierre-review 0.1.6 → 0.1.7

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/README.md CHANGED
@@ -55,6 +55,7 @@ pierre-review [options]
55
55
  | `--no-open` | `NO_OPEN` | — | Don't open the browser on start. |
56
56
  | `--port <n>` | `PORT` | `4000` | Port to listen on. |
57
57
  | `--db <path>` | `DATABASE_URL` | `~/.pierre-review/pierre-review.sqlite` | SQLite DB path. |
58
+ | — | `PERSIST_BODIES` | `false` | Store full comment/PR text in the DB instead of loading it on demand (larger DB, but PR detail works fully offline). |
58
59
  | `-h`, `--help` | — | — | Show usage. |
59
60
 
60
61
  Examples:
@@ -83,6 +84,12 @@ repositories you want to watch from the in-app picker; the app syncs their PR
83
84
  activity (full backfill on first sync, incremental every few minutes thereafter)
84
85
  into your local DB and renders it as a timeline.
85
86
 
87
+ To keep the database small and backfills fast, bulky text — PR/comment/review
88
+ bodies and diff hunks — isn't stored; it's fetched from GitHub (using your `gh`
89
+ token) the first time you open a PR and cached in your browser, so re-opening an
90
+ unchanged PR is instant and offline. Set `PERSIST_BODIES=true` to store that text
91
+ locally instead — a larger database, but PR detail then works fully offline.
92
+
86
93
  ## License
87
94
 
88
95
  MIT
@@ -1,4 +1,5 @@
1
1
  import { getPrDetail, markPrViewed } from '../../db/queries.js';
2
+ import { hydratePrDetail } from '../../sync/hydrate-detail.js';
2
3
  import { accountIdOf } from '../plugins/auth.js';
3
4
  const idParamSchema = {
4
5
  params: {
@@ -18,12 +19,15 @@ const markViewedSchema = {
18
19
  export async function prRoutes(app) {
19
20
  app.get('/api/prs/:id', { schema: idParamSchema }, async (req, reply) => {
20
21
  const { id } = req.params;
21
- const pr = await getPrDetail(id, accountIdOf(req));
22
+ const accountId = accountIdOf(req);
23
+ const pr = await getPrDetail(id, accountId);
22
24
  if (!pr) {
23
25
  reply.status(404);
24
26
  return { error: 'NotFound', message: `PR ${id} not found` };
25
27
  }
26
- return pr;
28
+ // Cloud lean mode: fill in bulky text from GitHub (no-op in local). The client
29
+ // caches the result in IndexedDB keyed by updatedAt so unchanged PRs don't refetch.
30
+ return hydratePrDetail(pr, accountId);
27
31
  });
28
32
  // Record that the local user has seen this PR up to `sha` (defaults to the
29
33
  // current head). Clears "new since last viewed" badges.
@@ -1,4 +1,5 @@
1
1
  import { getThreadDetail } from '../../db/queries.js';
2
+ import { hydrateThreadDetail } from '../../sync/hydrate-detail.js';
2
3
  import { accountIdOf } from '../plugins/auth.js';
3
4
  const idParamSchema = {
4
5
  params: {
@@ -10,12 +11,13 @@ const idParamSchema = {
10
11
  export async function threadRoutes(app) {
11
12
  app.get('/api/threads/:id', { schema: idParamSchema }, async (req, reply) => {
12
13
  const { id } = req.params;
13
- const thread = await getThreadDetail(id, accountIdOf(req));
14
+ const accountId = accountIdOf(req);
15
+ const thread = await getThreadDetail(id, accountId);
14
16
  if (!thread) {
15
17
  reply.status(404);
16
18
  return { error: 'NotFound', message: `Thread ${id} not found` };
17
19
  }
18
- return thread;
20
+ return hydrateThreadDetail(thread, accountId);
19
21
  });
20
22
  }
21
23
  //# sourceMappingURL=threads.js.map
package/dist/config.js CHANGED
@@ -52,6 +52,18 @@ export const config = {
52
52
  isCloud,
53
53
  // Drives the DB driver + schema selection in client.ts.
54
54
  dbDialect: (isCloud ? 'postgres' : 'sqlite'),
55
+ // ---- Lean storage (both modes by default) ----
56
+ // When false (the default), sync does NOT persist bulky user-authored text —
57
+ // comment / review / PR bodies, review-comment diff hunks, commit messages, and
58
+ // the per-job checkRuns JSON (the ci_status summary enum is kept). That text is
59
+ // regenerable from GitHub (and duplicated per tenant in cloud), so it's the
60
+ // dominant storage cost; it's hydrated on demand when a PR/thread is opened
61
+ // (sync/hydrate-detail.ts, via the gh-CLI token locally or the OAuth token in
62
+ // cloud) and cached in the browser. Not storing it also shrinks the DB and the
63
+ // sync payload, speeding up an initial backfill. Set PERSIST_BODIES=true to store
64
+ // full bodies instead — instant, fully-offline detail at the cost of a larger DB
65
+ // (e.g. a local instance used without network).
66
+ persistBodies: process.env.PERSIST_BODIES === 'true',
55
67
  // Postgres connection string (cloud only). Empty in local mode.
56
68
  databaseUrl: process.env.DATABASE_URL ?? '',
57
69
  port: intFromEnv('PORT', 4000),
@@ -1,5 +1,6 @@
1
1
  import { and, eq, inArray, isNull, or, sql } from 'drizzle-orm';
2
2
  import { db, schema } from './client.js';
3
+ import { config } from '../config.js';
3
4
  // One-time / idempotent maintenance run at startup.
4
5
  //
5
6
  // GitHub wraps inline-only reviews in an empty "commented" review, which the
@@ -12,6 +13,14 @@ import { db, schema } from './client.js';
12
13
  // Written as portable drizzle (no raw better-sqlite3 / db.execute) so it runs on
13
14
  // both dialects; the count comes from `.returning().length` (dialect-neutral).
14
15
  export async function cleanupRedundantReviewEvents() {
16
+ // In cloud "lean storage" mode (config.persistBodies false) reviews.body is
17
+ // always null, so the body-emptiness signal this relies on is unavailable — and
18
+ // unnecessary: persistPr already only emits review_submitted for *substantive*
19
+ // reviews (it checks the in-memory GraphQL body, not the stored one), and cloud
20
+ // starts empty, so there are no wrapper events to remove. Skipping here avoids
21
+ // wrongly deleting markers for substantive "commented" reviews.
22
+ if (!config.persistBodies)
23
+ return 0;
15
24
  const { events, reviews } = schema;
16
25
  const emptyCommentedReviews = db
17
26
  .select({ id: reviews.id })
@@ -0,0 +1,13 @@
1
+ -- Lean cloud storage groundwork (additive).
2
+ --
3
+ -- Adds review_comments.excerpt: a short (~160 char) preview of the comment body,
4
+ -- kept even when the full body is dropped in cloud "lean storage" mode so the
5
+ -- triage path (getMyTurn / getThreadsAwaiting) and graceful UI degradation work
6
+ -- without a network round trip. Local mode still stores the full body.
7
+ --
8
+ -- Note: in the Drizzle schema review_comments.body / pr_comments.body are now
9
+ -- nullable (to allow cloud's lean Postgres rows to omit them). SQLite cannot drop
10
+ -- a NOT NULL via ALTER, but local mode always persists bodies (config.persistBodies
11
+ -- is true whenever the driver is sqlite), so the live NOT NULL constraint is never
12
+ -- exercised — only the additive column below is needed here.
13
+ ALTER TABLE `review_comments` ADD `excerpt` text;
@@ -0,0 +1,56 @@
1
+ -- Make review_comments.body and pr_comments.body NULLABLE for lean storage
2
+ -- (config.persistBodies false, now the default in both modes): the full body is
3
+ -- dropped from storage and hydrated on demand, leaving review_comments.excerpt
4
+ -- (from 0009) as the kept preview. SQLite can't drop a NOT NULL via ALTER, so the
5
+ -- two tables are rebuilt. This is safe: no other table has a foreign key pointing
6
+ -- at them, and the copied data already satisfies their own (unchanged) outbound
7
+ -- FKs. The rebuilt tables keep the excerpt column and every current index.
8
+ CREATE TABLE `__new_review_comments` (
9
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
10
+ `github_node_id` text NOT NULL,
11
+ `thread_id` integer NOT NULL,
12
+ `pr_id` integer NOT NULL,
13
+ `author_id` integer,
14
+ `body` text,
15
+ `excerpt` text,
16
+ `diff_hunk` text,
17
+ `database_id` text,
18
+ `created_at` integer NOT NULL,
19
+ FOREIGN KEY (`thread_id`) REFERENCES `review_threads`(`id`) ON UPDATE no action ON DELETE no action,
20
+ FOREIGN KEY (`pr_id`) REFERENCES `pull_requests`(`id`) ON UPDATE no action ON DELETE no action,
21
+ FOREIGN KEY (`author_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
22
+ );
23
+ --> statement-breakpoint
24
+ INSERT INTO `__new_review_comments` (`id`, `github_node_id`, `thread_id`, `pr_id`, `author_id`, `body`, `excerpt`, `diff_hunk`, `database_id`, `created_at`)
25
+ SELECT `id`, `github_node_id`, `thread_id`, `pr_id`, `author_id`, `body`, `excerpt`, `diff_hunk`, `database_id`, `created_at` FROM `review_comments`;
26
+ --> statement-breakpoint
27
+ DROP TABLE `review_comments`;
28
+ --> statement-breakpoint
29
+ ALTER TABLE `__new_review_comments` RENAME TO `review_comments`;
30
+ --> statement-breakpoint
31
+ CREATE INDEX `rc_thread_idx` ON `review_comments` (`thread_id`);
32
+ --> statement-breakpoint
33
+ CREATE UNIQUE INDEX `rc_pr_node` ON `review_comments` (`pr_id`,`github_node_id`);
34
+ --> statement-breakpoint
35
+ CREATE TABLE `__new_pr_comments` (
36
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
37
+ `github_node_id` text NOT NULL,
38
+ `pr_id` integer NOT NULL,
39
+ `author_id` integer,
40
+ `body` text,
41
+ `database_id` text,
42
+ `created_at` integer NOT NULL,
43
+ FOREIGN KEY (`pr_id`) REFERENCES `pull_requests`(`id`) ON UPDATE no action ON DELETE no action,
44
+ FOREIGN KEY (`author_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
45
+ );
46
+ --> statement-breakpoint
47
+ INSERT INTO `__new_pr_comments` (`id`, `github_node_id`, `pr_id`, `author_id`, `body`, `database_id`, `created_at`)
48
+ SELECT `id`, `github_node_id`, `pr_id`, `author_id`, `body`, `database_id`, `created_at` FROM `pr_comments`;
49
+ --> statement-breakpoint
50
+ DROP TABLE `pr_comments`;
51
+ --> statement-breakpoint
52
+ ALTER TABLE `__new_pr_comments` RENAME TO `pr_comments`;
53
+ --> statement-breakpoint
54
+ CREATE INDEX `prc_pr_idx` ON `pr_comments` (`pr_id`);
55
+ --> statement-breakpoint
56
+ CREATE UNIQUE INDEX `prc_pr_node` ON `pr_comments` (`pr_id`,`github_node_id`);
@@ -64,6 +64,20 @@
64
64
  "when": 1780700000000,
65
65
  "tag": "0008_multitenant_accounts",
66
66
  "breakpoints": true
67
+ },
68
+ {
69
+ "idx": 9,
70
+ "version": "6",
71
+ "when": 1780800000000,
72
+ "tag": "0009_lean_bodies",
73
+ "breakpoints": true
74
+ },
75
+ {
76
+ "idx": 10,
77
+ "version": "6",
78
+ "when": 1780800000001,
79
+ "tag": "0010_lean_bodies_nullable",
80
+ "breakpoints": true
67
81
  }
68
82
  ]
69
83
  }
@@ -0,0 +1,3 @@
1
+ ALTER TABLE "pr_comments" ALTER COLUMN "body" DROP NOT NULL;--> statement-breakpoint
2
+ ALTER TABLE "review_comments" ALTER COLUMN "body" DROP NOT NULL;--> statement-breakpoint
3
+ ALTER TABLE "review_comments" ADD COLUMN "excerpt" text;