@catalyst-cloud/schema 0.1.0 → 0.1.1
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/index.d.ts +6782 -0
- package/dist/index.js +72 -0
- package/dist/migrate.d.ts +36 -0
- package/{src/migrate.ts → dist/migrate.js} +48 -91
- package/dist/migrations.generated.d.ts +65 -0
- package/dist/migrations.generated.js +81 -0
- package/dist/mirror.d.ts +3517 -0
- package/{src/mirror.ts → dist/mirror.js} +88 -148
- package/package.json +8 -3
- package/src/index.ts +0 -116
- package/src/migrations.generated.ts +0 -90
|
@@ -15,14 +15,9 @@
|
|
|
15
15
|
// • `relations` join on issues.identifier (issue_identifier), NOT issues.id.
|
|
16
16
|
// • `check_runs` join a PR by head_sha, NOT PR number.
|
|
17
17
|
// • PR mergeable / mergeable_state are nullable until a reconcile resolves them.
|
|
18
|
-
|
|
19
18
|
import { sqliteTable, text, integer, real, primaryKey, index } from "drizzle-orm/sqlite-core";
|
|
20
|
-
|
|
21
19
|
// ── Linear ───────────────────────────────────────────────────────────────────────────────────────
|
|
22
|
-
|
|
23
|
-
export const issues = sqliteTable(
|
|
24
|
-
"issues",
|
|
25
|
-
{
|
|
20
|
+
export const issues = sqliteTable("issues", {
|
|
26
21
|
id: text("id").primaryKey(),
|
|
27
22
|
identifier: text("identifier"),
|
|
28
23
|
title: text("title"),
|
|
@@ -62,8 +57,7 @@ export const issues = sqliteTable(
|
|
|
62
57
|
archived_at: integer("archived_at"),
|
|
63
58
|
removed_at: integer("removed_at"),
|
|
64
59
|
raw: text("raw"),
|
|
65
|
-
|
|
66
|
-
(t) => [
|
|
60
|
+
}, (t) => [
|
|
67
61
|
index("idx_issues_team").on(t.team_id),
|
|
68
62
|
// CTC-59: the list query — `WHERE removed_at IS NULL ORDER BY updated_at DESC` (buildIssuesView).
|
|
69
63
|
// The composite lets SQLite seek the live rows then walk updated_at in order (no temp b-tree).
|
|
@@ -74,17 +68,14 @@ export const issues = sqliteTable(
|
|
|
74
68
|
// Drizzle SSOT ONLY (NOT schema.sql) — migrate.ts swallows a duplicate CREATE INDEX on replay
|
|
75
69
|
// (CTC-59), so the generated `CREATE INDEX` is replay-safe; schema.sql would break the adoption test.
|
|
76
70
|
index("idx_issues_sort_order").on(t.sort_order),
|
|
77
|
-
|
|
78
|
-
);
|
|
79
|
-
|
|
71
|
+
]);
|
|
80
72
|
export const labels = sqliteTable("labels", {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
73
|
+
id: text("id").primaryKey(),
|
|
74
|
+
name: text("name"),
|
|
75
|
+
color: text("color"),
|
|
76
|
+
updated_at: integer("updated_at"),
|
|
77
|
+
removed_at: integer("removed_at"),
|
|
86
78
|
});
|
|
87
|
-
|
|
88
79
|
// Teammate identities (CTC-USERS): resolves issue assignees + delegate AGENTS (and comment/PR
|
|
89
80
|
// authors/reviewers) to a name + avatar so the UI never renders a gray "Unknown" chip. AGENT
|
|
90
81
|
// identities are first-class: `type` is human | agent | bot | app. Populated three ways — a ROSTER
|
|
@@ -95,38 +86,27 @@ export const labels = sqliteTable("labels", {
|
|
|
95
86
|
// in place, never tombstoned (an avatar/name that goes missing just stops being referenced). `id` is
|
|
96
87
|
// the source-native id: Linear's user node id, the bot actor id, or a `github:<login>` synthetic id.
|
|
97
88
|
export const users = sqliteTable("users", {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
89
|
+
id: text("id").primaryKey(),
|
|
90
|
+
name: text("name"),
|
|
91
|
+
display_name: text("display_name"),
|
|
92
|
+
avatar_url: text("avatar_url"),
|
|
93
|
+
type: text("type"), // human | agent | bot | app
|
|
94
|
+
source: text("source"), // linear | github
|
|
95
|
+
updated_at: integer("updated_at"),
|
|
105
96
|
});
|
|
106
|
-
|
|
107
|
-
export const issue_labels = sqliteTable(
|
|
108
|
-
"issue_labels",
|
|
109
|
-
{
|
|
97
|
+
export const issue_labels = sqliteTable("issue_labels", {
|
|
110
98
|
issue_id: text("issue_id").notNull(),
|
|
111
99
|
label_id: text("label_id").notNull(),
|
|
112
|
-
|
|
113
|
-
(t) => [primaryKey({ columns: [t.issue_id, t.label_id] })],
|
|
114
|
-
);
|
|
115
|
-
|
|
100
|
+
}, (t) => [primaryKey({ columns: [t.issue_id, t.label_id] })]);
|
|
116
101
|
// FULL-poll-and-diff every reconcile pass (Linear fires NO issueRelations webhooks). Delete-absent is
|
|
117
102
|
// load-bearing — and there is intentionally NO removed_at: a stale edge is HARD-deleted, never soft.
|
|
118
|
-
export const relations = sqliteTable(
|
|
119
|
-
"relations",
|
|
120
|
-
{
|
|
103
|
+
export const relations = sqliteTable("relations", {
|
|
121
104
|
id: text("id").primaryKey(),
|
|
122
105
|
type: text("type"), // blocks | duplicate | related
|
|
123
106
|
issue_identifier: text("issue_identifier"),
|
|
124
107
|
related_identifier: text("related_identifier"),
|
|
125
108
|
updated_at: integer("updated_at"),
|
|
126
|
-
|
|
127
|
-
(t) => [index("idx_relations_issue").on(t.issue_identifier)],
|
|
128
|
-
);
|
|
129
|
-
|
|
109
|
+
}, (t) => [index("idx_relations_issue").on(t.issue_identifier)]);
|
|
130
110
|
// Linear IssueHistory events (CTC-14) — the per-issue change timeline. Captured INLINE on the issues
|
|
131
111
|
// delta sweep (a nested history(last:100) connection), NOT a separate leg and NOT a webhook path. Each
|
|
132
112
|
// node is IMMUTABLE with a stable `id`; HARD upsert keyed on `id` (NO removed_at — a history row never
|
|
@@ -135,9 +115,7 @@ export const relations = sqliteTable(
|
|
|
135
115
|
// name — no states table). from_due_date/to_due_date are TimelessDate "YYYY-MM-DD" stored AS-IS as
|
|
136
116
|
// TEXT (the trap), never a timestamp. added_label_ids/removed_label_ids are JSON array strings; the
|
|
137
117
|
// boolean columns are the mirror's 0/1 ints. raw is the full node JSON (full fidelity / forward-compat).
|
|
138
|
-
export const issue_history = sqliteTable(
|
|
139
|
-
"issue_history",
|
|
140
|
-
{
|
|
118
|
+
export const issue_history = sqliteTable("issue_history", {
|
|
141
119
|
id: text("id").primaryKey(),
|
|
142
120
|
issue_id: text("issue_id").notNull(), // FK → issues.id
|
|
143
121
|
actor_id: text("actor_id"), // FK → users.id; nullable (system events have no actor)
|
|
@@ -171,79 +149,67 @@ export const issue_history = sqliteTable(
|
|
|
171
149
|
auto_closed: integer("auto_closed"), // 0/1
|
|
172
150
|
trashed: integer("trashed"), // 0/1
|
|
173
151
|
raw: text("raw"), // JSON.stringify(node) — full fidelity / forward-compat (like issues.raw)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
);
|
|
180
|
-
|
|
152
|
+
},
|
|
153
|
+
// The timeline index lives in the Drizzle SSOT ONLY (NOT schema.sql) — migrate.ts makes the generated
|
|
154
|
+
// CREATE INDEX replay-safe (same as the issues read-path indexes); putting it in schema.sql would
|
|
155
|
+
// break the adoption test.
|
|
156
|
+
(t) => [index("idx_issue_history_issue_created").on(t.issue_id, t.created_at)]);
|
|
181
157
|
// CTC-HIERARCHY enriches the project row with the lead (FK → users.id), description, lifecycle dates,
|
|
182
158
|
// progress, health, and presentation (icon/color). `target_date` / `start_date` are Linear
|
|
183
159
|
// TimelessDates ("YYYY-MM-DD") — stored AS-IS as TEXT, NEVER through isoToMs (the same trap as
|
|
184
160
|
// issues.due_date). `progress` is a Float (0..1).
|
|
185
161
|
export const projects = sqliteTable("projects", {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
162
|
+
id: text("id").primaryKey(),
|
|
163
|
+
name: text("name"),
|
|
164
|
+
state: text("state"),
|
|
165
|
+
description: text("description"),
|
|
166
|
+
lead_id: text("lead_id"), // FK → users.id (CTC-HIERARCHY); upserted from the project lead.
|
|
167
|
+
target_date: text("target_date"), // TimelessDate "YYYY-MM-DD" — TEXT AS-IS (the trap), not a ts.
|
|
168
|
+
start_date: text("start_date"), // TimelessDate "YYYY-MM-DD" — TEXT AS-IS (the trap), not a ts.
|
|
169
|
+
progress: real("progress"),
|
|
170
|
+
health: text("health"), // onTrack | atRisk | offTrack (null until a project update reports it).
|
|
171
|
+
icon: text("icon"),
|
|
172
|
+
color: text("color"),
|
|
173
|
+
updated_at: integer("updated_at"),
|
|
174
|
+
removed_at: integer("removed_at"),
|
|
199
175
|
});
|
|
200
|
-
|
|
201
176
|
// CTC-HIERARCHY enriches the cycle row with its display name + progress + current scope/issue counts.
|
|
202
177
|
// Linear exposes scope/issue counts only as weekly history arrays (completedScopeHistory etc.); we
|
|
203
178
|
// store the CURRENT value (the last array element) as a scalar — not the whole history.
|
|
204
179
|
export const cycles = sqliteTable("cycles", {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
180
|
+
id: text("id").primaryKey(),
|
|
181
|
+
number: integer("number"),
|
|
182
|
+
name: text("name"),
|
|
183
|
+
progress: real("progress"),
|
|
184
|
+
completed_scope: real("completed_scope"), // last(completedScopeHistory) — current completed points.
|
|
185
|
+
scope: real("scope"), // last(scopeHistory) — current total scope points.
|
|
186
|
+
completed_issue_count: integer("completed_issue_count"), // last(completedIssueCountHistory).
|
|
187
|
+
issue_count: integer("issue_count"), // last(issueCountHistory).
|
|
188
|
+
starts_at: integer("starts_at"),
|
|
189
|
+
ends_at: integer("ends_at"),
|
|
190
|
+
updated_at: integer("updated_at"),
|
|
216
191
|
});
|
|
217
|
-
|
|
218
192
|
// Linear initiatives (CTC-HIERARCHY) — the top of the HIERARCHY (initiative → project → cycle →
|
|
219
193
|
// issue). PK `id` (Linear node id). `status` is the InitiativeStatus enum string; `target_date` is a
|
|
220
194
|
// TimelessDate ("YYYY-MM-DD") stored AS-IS as TEXT (the trap). `owner_id` is the FK → users.id.
|
|
221
195
|
// HARD upsert — NO removed_at (a vanished initiative just stops being referenced by the join table).
|
|
222
196
|
export const initiatives = sqliteTable("initiatives", {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
197
|
+
id: text("id").primaryKey(),
|
|
198
|
+
name: text("name"),
|
|
199
|
+
description: text("description"),
|
|
200
|
+
status: text("status"),
|
|
201
|
+
target_date: text("target_date"), // TimelessDate "YYYY-MM-DD" — TEXT AS-IS (the trap), not a ts.
|
|
202
|
+
owner_id: text("owner_id"), // FK → users.id (CTC-HIERARCHY); upserted from the initiative owner.
|
|
203
|
+
updated_at: integer("updated_at"),
|
|
230
204
|
});
|
|
231
|
-
|
|
232
205
|
// project↔initiative join (CTC-HIERARCHY) — Linear models this as many:many (a project belongs to
|
|
233
206
|
// many initiatives), so it's an edge table modeled on issue_labels: composite PK, no updated_at, full
|
|
234
207
|
// poll-and-diff per project leg in the reconcile (Linear fires no membership webhook for it).
|
|
235
|
-
export const project_initiatives = sqliteTable(
|
|
236
|
-
"project_initiatives",
|
|
237
|
-
{
|
|
208
|
+
export const project_initiatives = sqliteTable("project_initiatives", {
|
|
238
209
|
project_id: text("project_id").notNull(),
|
|
239
210
|
initiative_id: text("initiative_id").notNull(),
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
);
|
|
243
|
-
|
|
244
|
-
export const comments = sqliteTable(
|
|
245
|
-
"comments",
|
|
246
|
-
{
|
|
211
|
+
}, (t) => [primaryKey({ columns: [t.project_id, t.initiative_id] })]);
|
|
212
|
+
export const comments = sqliteTable("comments", {
|
|
247
213
|
id: text("id").primaryKey(),
|
|
248
214
|
issue_id: text("issue_id"),
|
|
249
215
|
body: text("body"),
|
|
@@ -256,17 +222,12 @@ export const comments = sqliteTable(
|
|
|
256
222
|
created_at: integer("created_at"),
|
|
257
223
|
updated_at: integer("updated_at"),
|
|
258
224
|
removed_at: integer("removed_at"),
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
);
|
|
264
|
-
|
|
225
|
+
},
|
|
226
|
+
// CTC-59: the detail comments fetch — `WHERE issue_id = ? AND removed_at IS NULL ORDER BY updated_at`.
|
|
227
|
+
// Composite covers the filter AND the sort, so SQLite seeks instead of building a temp b-tree.
|
|
228
|
+
(t) => [index("idx_comments_issue").on(t.issue_id, t.removed_at, t.updated_at)]);
|
|
265
229
|
// ── GitHub ───────────────────────────────────────────────────────────────────────────────────────
|
|
266
|
-
|
|
267
|
-
export const pull_requests = sqliteTable(
|
|
268
|
-
"pull_requests",
|
|
269
|
-
{
|
|
230
|
+
export const pull_requests = sqliteTable("pull_requests", {
|
|
270
231
|
repo_id: text("repo_id").notNull(),
|
|
271
232
|
number: integer("number").notNull(),
|
|
272
233
|
node_id: text("node_id"),
|
|
@@ -293,17 +254,12 @@ export const pull_requests = sqliteTable(
|
|
|
293
254
|
milestone_title: text("milestone_title"),
|
|
294
255
|
updated_at: integer("updated_at"),
|
|
295
256
|
synced_at: integer("synced_at"),
|
|
296
|
-
|
|
297
|
-
(t) => [
|
|
257
|
+
}, (t) => [
|
|
298
258
|
primaryKey({ columns: [t.repo_id, t.number] }),
|
|
299
259
|
// CTC-59: the pulls list — `ORDER BY updated_at DESC` (buildPullsView).
|
|
300
260
|
index("idx_pulls_updated").on(t.updated_at),
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
export const check_runs = sqliteTable(
|
|
305
|
-
"check_runs",
|
|
306
|
-
{
|
|
261
|
+
]);
|
|
262
|
+
export const check_runs = sqliteTable("check_runs", {
|
|
307
263
|
repo_id: text("repo_id").notNull(),
|
|
308
264
|
check_run_id: text("check_run_id").primaryKey(),
|
|
309
265
|
head_sha: text("head_sha"),
|
|
@@ -312,57 +268,41 @@ export const check_runs = sqliteTable(
|
|
|
312
268
|
conclusion: text("conclusion"),
|
|
313
269
|
check_suite_id: text("check_suite_id"),
|
|
314
270
|
updated_at: integer("updated_at"),
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
);
|
|
318
|
-
|
|
319
|
-
export const commit_statuses = sqliteTable(
|
|
320
|
-
"commit_statuses",
|
|
321
|
-
{
|
|
271
|
+
}, (t) => [index("idx_check_runs_sha").on(t.head_sha)]);
|
|
272
|
+
export const commit_statuses = sqliteTable("commit_statuses", {
|
|
322
273
|
repo_id: text("repo_id").notNull(),
|
|
323
274
|
sha: text("sha").notNull(),
|
|
324
275
|
context: text("context").notNull(),
|
|
325
276
|
state: text("state"),
|
|
326
277
|
target_url: text("target_url"),
|
|
327
278
|
updated_at: integer("updated_at"),
|
|
328
|
-
|
|
329
|
-
(t) => [primaryKey({ columns: [t.repo_id, t.sha, t.context] })],
|
|
330
|
-
);
|
|
331
|
-
|
|
279
|
+
}, (t) => [primaryKey({ columns: [t.repo_id, t.sha, t.context] })]);
|
|
332
280
|
export const reviews = sqliteTable("reviews", {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
281
|
+
repo_id: text("repo_id").notNull(),
|
|
282
|
+
pr_number: integer("pr_number").notNull(),
|
|
283
|
+
review_id: text("review_id").primaryKey(),
|
|
284
|
+
user_id: text("user_id"),
|
|
285
|
+
state: text("state"),
|
|
286
|
+
submitted_at: integer("submitted_at"),
|
|
339
287
|
});
|
|
340
|
-
|
|
341
288
|
// ── Mirror infra (not domain entities — never ride the change-feed) ─────────────────────────────────
|
|
342
|
-
|
|
343
289
|
export const processed_events = sqliteTable("processed_events", {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
290
|
+
delivery_id: text("delivery_id").primaryKey(),
|
|
291
|
+
source: text("source"),
|
|
292
|
+
event_type: text("event_type"),
|
|
293
|
+
received_at: integer("received_at"),
|
|
348
294
|
});
|
|
349
|
-
|
|
350
295
|
export const change_log = sqliteTable("change_log", {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
296
|
+
seq: integer("seq").primaryKey({ autoIncrement: true }),
|
|
297
|
+
entity: text("entity"),
|
|
298
|
+
entity_id: text("entity_id"),
|
|
299
|
+
op: text("op"), // upsert | delete
|
|
300
|
+
payload: text("payload"),
|
|
301
|
+
ts: integer("ts"),
|
|
357
302
|
});
|
|
358
|
-
|
|
359
|
-
export const sync_cursors = sqliteTable(
|
|
360
|
-
"sync_cursors",
|
|
361
|
-
{
|
|
303
|
+
export const sync_cursors = sqliteTable("sync_cursors", {
|
|
362
304
|
source: text("source").notNull(),
|
|
363
305
|
entity: text("entity").notNull(),
|
|
364
306
|
cursor: text("cursor"),
|
|
365
307
|
last_run_at: integer("last_run_at"),
|
|
366
|
-
|
|
367
|
-
(t) => [primaryKey({ columns: [t.source, t.entity] })],
|
|
368
|
-
);
|
|
308
|
+
}, (t) => [primaryKey({ columns: [t.source, t.entity] })]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@catalyst-cloud/schema",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Typed Drizzle schema = single source of truth for the per-tenant Mirror DO SQLite store (CTC-13 / ADR-0002). Shared by the mirror Worker, the host-sync replica, and the browser OPFS replica.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -10,19 +10,24 @@
|
|
|
10
10
|
"directory": "packages/schema"
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
|
-
"
|
|
13
|
+
"dist"
|
|
14
14
|
],
|
|
15
15
|
"publishConfig": {
|
|
16
16
|
"access": "public"
|
|
17
17
|
},
|
|
18
18
|
"exports": {
|
|
19
|
-
".":
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
}
|
|
20
24
|
},
|
|
21
25
|
"scripts": {
|
|
22
26
|
"typecheck": "tsc --noEmit",
|
|
23
27
|
"test": "vitest run",
|
|
24
28
|
"test:coverage": "vitest run --coverage",
|
|
25
29
|
"generate": "drizzle-kit generate",
|
|
30
|
+
"build": "tsc -p tsconfig.build.json",
|
|
26
31
|
"build:bundle": "bun scripts/build-bundle.ts && prettier --write src/migrations.generated.ts"
|
|
27
32
|
},
|
|
28
33
|
"dependencies": {
|
package/src/index.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
// @catalyst-cloud/schema — the typed Drizzle SSOT for the Mirror DO store (CTC-13 / ADR-0002).
|
|
2
|
-
//
|
|
3
|
-
// Re-exports every table plus two consumption surfaces:
|
|
4
|
-
// • `mirrorSchema` — all tables, to pass as `drizzle(storage, { schema: mirrorSchema })` in the DO
|
|
5
|
-
// (and `drizzle(bunSqlite, …)` in the host-sync replica, `drizzle(sqliteProxy, …)` in the browser).
|
|
6
|
-
// • `MIRROR_TABLE_META` — per-ENTITY `{ pk, softDelete }` DERIVED from the schema via getTableConfig.
|
|
7
|
-
// This is what packages/replicate's applyDelta consumes to fold a change_log delta into a replica
|
|
8
|
-
// — replacing the hand-maintained `TABLES` map in apps/host-sync/src/apply.ts (the ADR-0002 "4th
|
|
9
|
-
// hand-copy" this schema deletes). Because it is derived, it can never drift from the table defs.
|
|
10
|
-
|
|
11
|
-
import { getTableConfig, type SQLiteTable } from "drizzle-orm/sqlite-core";
|
|
12
|
-
import {
|
|
13
|
-
issues,
|
|
14
|
-
labels,
|
|
15
|
-
users,
|
|
16
|
-
issue_labels,
|
|
17
|
-
relations,
|
|
18
|
-
issue_history,
|
|
19
|
-
projects,
|
|
20
|
-
cycles,
|
|
21
|
-
initiatives,
|
|
22
|
-
project_initiatives,
|
|
23
|
-
comments,
|
|
24
|
-
pull_requests,
|
|
25
|
-
check_runs,
|
|
26
|
-
commit_statuses,
|
|
27
|
-
reviews,
|
|
28
|
-
processed_events,
|
|
29
|
-
change_log,
|
|
30
|
-
sync_cursors,
|
|
31
|
-
} from "./mirror";
|
|
32
|
-
|
|
33
|
-
export * from "./mirror";
|
|
34
|
-
|
|
35
|
-
// The committed cross-surface migration bundle + the shared runner (CTC-41). Re-exported here so
|
|
36
|
-
// every consumer imports from the one package entrypoint: `applyMigrations(adapter, MIRROR_MIGRATIONS)`.
|
|
37
|
-
export { MIRROR_MIGRATIONS } from "./migrations.generated";
|
|
38
|
-
export {
|
|
39
|
-
applyMigrations,
|
|
40
|
-
type MigrationDb,
|
|
41
|
-
type MigrationBundle,
|
|
42
|
-
type MigrationJournalEntry,
|
|
43
|
-
} from "./migrate";
|
|
44
|
-
|
|
45
|
-
/** Every table in the Mirror DO store — pass as `drizzle(storage, { schema: mirrorSchema })`. */
|
|
46
|
-
export const mirrorSchema = {
|
|
47
|
-
issues,
|
|
48
|
-
labels,
|
|
49
|
-
users,
|
|
50
|
-
issue_labels,
|
|
51
|
-
relations,
|
|
52
|
-
issue_history,
|
|
53
|
-
projects,
|
|
54
|
-
cycles,
|
|
55
|
-
initiatives,
|
|
56
|
-
project_initiatives,
|
|
57
|
-
comments,
|
|
58
|
-
pull_requests,
|
|
59
|
-
check_runs,
|
|
60
|
-
commit_statuses,
|
|
61
|
-
reviews,
|
|
62
|
-
processed_events,
|
|
63
|
-
change_log,
|
|
64
|
-
sync_cursors,
|
|
65
|
-
} as const;
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* The domain-entity tables that flow over the change-feed (the keys are the wire `entity` names). The
|
|
69
|
-
* infra tables — processed_events / change_log / sync_cursors — are deliberately excluded: they never
|
|
70
|
-
* ride a delta.
|
|
71
|
-
*/
|
|
72
|
-
export const mirrorEntityTables = {
|
|
73
|
-
issues,
|
|
74
|
-
labels,
|
|
75
|
-
users,
|
|
76
|
-
issue_labels,
|
|
77
|
-
relations,
|
|
78
|
-
issue_history,
|
|
79
|
-
projects,
|
|
80
|
-
cycles,
|
|
81
|
-
initiatives,
|
|
82
|
-
project_initiatives,
|
|
83
|
-
comments,
|
|
84
|
-
pull_requests,
|
|
85
|
-
check_runs,
|
|
86
|
-
commit_statuses,
|
|
87
|
-
reviews,
|
|
88
|
-
} as const;
|
|
89
|
-
|
|
90
|
-
/** The wire `entity` name of a change-feed-carried table (keys of mirrorEntityTables). */
|
|
91
|
-
export type MirrorEntityName = keyof typeof mirrorEntityTables;
|
|
92
|
-
|
|
93
|
-
/** Per-table replication metadata — the PK columns and whether deletes are soft (removed_at) vs hard. */
|
|
94
|
-
export interface TableMeta {
|
|
95
|
-
/** PK column SQL names, in declared order (composite PKs yield more than one). */
|
|
96
|
-
pk: string[];
|
|
97
|
-
/** True iff the table carries a soft-delete `removed_at` column (delete sets it; else hard-delete). */
|
|
98
|
-
softDelete: boolean;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** Derive a table's PK columns + soft-delete posture from the Drizzle definition (no hand-maintenance). */
|
|
102
|
-
export function tableMeta(table: SQLiteTable): TableMeta {
|
|
103
|
-
const cfg = getTableConfig(table);
|
|
104
|
-
// A single-column PK (`.primaryKey()`) marks the column `.primary`; a composite PK
|
|
105
|
-
// (`primaryKey({ columns })`) instead lands in cfg.primaryKeys. Cover both.
|
|
106
|
-
const singlePk = cfg.columns.filter((c) => c.primary).map((c) => c.name);
|
|
107
|
-
const compositePk = cfg.primaryKeys.flatMap((pk) => pk.columns.map((c) => c.name));
|
|
108
|
-
const pk = singlePk.length > 0 ? singlePk : compositePk;
|
|
109
|
-
const softDelete = cfg.columns.some((c) => c.name === "removed_at");
|
|
110
|
-
return { pk, softDelete };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/** Schema-derived replication metadata for every change-feed entity, keyed by wire `entity` name. */
|
|
114
|
-
export const MIRROR_TABLE_META: Record<MirrorEntityName, TableMeta> = Object.fromEntries(
|
|
115
|
-
Object.entries(mirrorEntityTables).map(([name, table]) => [name, tableMeta(table)]),
|
|
116
|
-
) as Record<MirrorEntityName, TableMeta>;
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
// AUTO-GENERATED by scripts/build-bundle.ts — DO NOT EDIT BY HAND.
|
|
2
|
-
//
|
|
3
|
-
// The committed Mirror migration bundle (CTC-41): the drizzle-kit journal + every migration SQL as a
|
|
4
|
-
// string literal (NO `.sql`-as-import), so the SAME module imports cleanly in the DO (Workers), the
|
|
5
|
-
// bun daemon, node tests, and the browser. Regenerate via `bun run --cwd packages/schema build:bundle`
|
|
6
|
-
// (or the root `migration:generate`). The CI drift gate (`migration:check`) fails if this file is
|
|
7
|
-
// stale vs drizzle/.
|
|
8
|
-
|
|
9
|
-
export const MIRROR_MIGRATIONS = {
|
|
10
|
-
journal: {
|
|
11
|
-
version: "7",
|
|
12
|
-
dialect: "sqlite",
|
|
13
|
-
entries: [
|
|
14
|
-
{
|
|
15
|
-
idx: 0,
|
|
16
|
-
version: "6",
|
|
17
|
-
when: 1782051267561,
|
|
18
|
-
tag: "0000_baseline",
|
|
19
|
-
breakpoints: true,
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
idx: 1,
|
|
23
|
-
version: "6",
|
|
24
|
-
when: 1782110588498,
|
|
25
|
-
tag: "0001_short_glorian",
|
|
26
|
-
breakpoints: true,
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
idx: 2,
|
|
30
|
-
version: "6",
|
|
31
|
-
when: 1782179798312,
|
|
32
|
-
tag: "0002_many_captain_marvel",
|
|
33
|
-
breakpoints: true,
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
idx: 3,
|
|
37
|
-
version: "6",
|
|
38
|
-
when: 1782182056315,
|
|
39
|
-
tag: "0003_brainy_silver_samurai",
|
|
40
|
-
breakpoints: true,
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
idx: 4,
|
|
44
|
-
version: "6",
|
|
45
|
-
when: 1782183082087,
|
|
46
|
-
tag: "0004_fat_mindworm",
|
|
47
|
-
breakpoints: true,
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
idx: 5,
|
|
51
|
-
version: "6",
|
|
52
|
-
when: 1782184123941,
|
|
53
|
-
tag: "0005_blue_black_queen",
|
|
54
|
-
breakpoints: true,
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
idx: 6,
|
|
58
|
-
version: "6",
|
|
59
|
-
when: 1782186590417,
|
|
60
|
-
tag: "0006_lively_vector",
|
|
61
|
-
breakpoints: true,
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
idx: 7,
|
|
65
|
-
version: "6",
|
|
66
|
-
when: 1782229820186,
|
|
67
|
-
tag: "0007_rapid_logan",
|
|
68
|
-
breakpoints: true,
|
|
69
|
-
},
|
|
70
|
-
],
|
|
71
|
-
},
|
|
72
|
-
migrations: {
|
|
73
|
-
"0000_baseline":
|
|
74
|
-
"CREATE TABLE IF NOT EXISTS `change_log` (\n\t`seq` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`entity` text,\n\t`entity_id` text,\n\t`op` text,\n\t`payload` text,\n\t`ts` integer\n);\n--> statement-breakpoint\nCREATE TABLE IF NOT EXISTS `check_runs` (\n\t`repo_id` text NOT NULL,\n\t`check_run_id` text PRIMARY KEY NOT NULL,\n\t`head_sha` text,\n\t`name` text,\n\t`status` text,\n\t`conclusion` text,\n\t`check_suite_id` text,\n\t`updated_at` integer\n);\n--> statement-breakpoint\nCREATE INDEX IF NOT EXISTS `idx_check_runs_sha` ON `check_runs` (`head_sha`);--> statement-breakpoint\nCREATE TABLE IF NOT EXISTS `comments` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`issue_id` text,\n\t`body` text,\n\t`updated_at` integer,\n\t`removed_at` integer\n);\n--> statement-breakpoint\nCREATE TABLE IF NOT EXISTS `commit_statuses` (\n\t`repo_id` text NOT NULL,\n\t`sha` text NOT NULL,\n\t`context` text NOT NULL,\n\t`state` text,\n\t`target_url` text,\n\t`updated_at` integer,\n\tPRIMARY KEY(`repo_id`, `sha`, `context`)\n);\n--> statement-breakpoint\nCREATE TABLE IF NOT EXISTS `cycles` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`number` integer,\n\t`starts_at` integer,\n\t`ends_at` integer,\n\t`updated_at` integer\n);\n--> statement-breakpoint\nCREATE TABLE IF NOT EXISTS `issue_labels` (\n\t`issue_id` text NOT NULL,\n\t`label_id` text NOT NULL,\n\tPRIMARY KEY(`issue_id`, `label_id`)\n);\n--> statement-breakpoint\nCREATE TABLE IF NOT EXISTS `issues` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`identifier` text,\n\t`title` text,\n\t`state` text,\n\t`assignee` text,\n\t`priority` integer,\n\t`estimate` real,\n\t`project_id` text,\n\t`cycle_id` text,\n\t`team_id` text,\n\t`updated_at` integer,\n\t`archived_at` integer,\n\t`removed_at` integer,\n\t`raw` text\n);\n--> statement-breakpoint\nCREATE INDEX IF NOT EXISTS `idx_issues_team` ON `issues` (`team_id`);--> statement-breakpoint\nCREATE TABLE IF NOT EXISTS `labels` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`name` text,\n\t`updated_at` integer,\n\t`removed_at` integer\n);\n--> statement-breakpoint\nCREATE TABLE IF NOT EXISTS `processed_events` (\n\t`delivery_id` text PRIMARY KEY NOT NULL,\n\t`source` text,\n\t`event_type` text,\n\t`received_at` integer\n);\n--> statement-breakpoint\nCREATE TABLE IF NOT EXISTS `projects` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`name` text,\n\t`state` text,\n\t`updated_at` integer,\n\t`removed_at` integer\n);\n--> statement-breakpoint\nCREATE TABLE IF NOT EXISTS `pull_requests` (\n\t`repo_id` text NOT NULL,\n\t`number` integer NOT NULL,\n\t`node_id` text,\n\t`state` text,\n\t`draft` integer,\n\t`merged` integer,\n\t`merged_at` integer,\n\t`head_sha` text,\n\t`base_ref` text,\n\t`mergeable` integer,\n\t`mergeable_state` text,\n\t`auto_merge` integer,\n\t`updated_at` integer,\n\t`synced_at` integer,\n\tPRIMARY KEY(`repo_id`, `number`)\n);\n--> statement-breakpoint\nCREATE TABLE IF NOT EXISTS `relations` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`type` text,\n\t`issue_identifier` text,\n\t`related_identifier` text,\n\t`updated_at` integer\n);\n--> statement-breakpoint\nCREATE INDEX IF NOT EXISTS `idx_relations_issue` ON `relations` (`issue_identifier`);--> statement-breakpoint\nCREATE TABLE IF NOT EXISTS `reviews` (\n\t`repo_id` text NOT NULL,\n\t`pr_number` integer NOT NULL,\n\t`review_id` text PRIMARY KEY NOT NULL,\n\t`user_id` text,\n\t`state` text,\n\t`submitted_at` integer\n);\n--> statement-breakpoint\nCREATE TABLE IF NOT EXISTS `sync_cursors` (\n\t`source` text NOT NULL,\n\t`entity` text NOT NULL,\n\t`cursor` text,\n\t`last_run_at` integer,\n\tPRIMARY KEY(`source`, `entity`)\n);\n",
|
|
75
|
-
"0001_short_glorian":
|
|
76
|
-
"ALTER TABLE `comments` ADD `author_id` text;--> statement-breakpoint\nALTER TABLE `comments` ADD `author_name` text;--> statement-breakpoint\nALTER TABLE `comments` ADD `author_avatar_url` text;--> statement-breakpoint\nALTER TABLE `comments` ADD `is_bot` integer;--> statement-breakpoint\nALTER TABLE `comments` ADD `parent_id` text;--> statement-breakpoint\nALTER TABLE `issues` ADD `description` text;--> statement-breakpoint\nALTER TABLE `issues` ADD `delegate_id` text;--> statement-breakpoint\nALTER TABLE `issues` ADD `delegate_name` text;--> statement-breakpoint\nALTER TABLE `issues` ADD `bot_actor_name` text;--> statement-breakpoint\nALTER TABLE `issues` ADD `bot_actor_type` text;--> statement-breakpoint\nALTER TABLE `issues` ADD `bot_actor_sub_type` text;--> statement-breakpoint\nALTER TABLE `issues` ADD `parent_id` text;--> statement-breakpoint\nALTER TABLE `issues` ADD `parent_identifier` text;--> statement-breakpoint\nALTER TABLE `labels` ADD `color` text;",
|
|
77
|
-
"0002_many_captain_marvel":
|
|
78
|
-
"CREATE INDEX `idx_comments_issue` ON `comments` (`issue_id`,`removed_at`,`updated_at`);--> statement-breakpoint\nCREATE INDEX `idx_issues_updated` ON `issues` (`removed_at`,`updated_at`);--> statement-breakpoint\nCREATE INDEX `idx_issues_identifier` ON `issues` (`identifier`);--> statement-breakpoint\nCREATE INDEX `idx_pulls_updated` ON `pull_requests` (`updated_at`);",
|
|
79
|
-
"0003_brainy_silver_samurai":
|
|
80
|
-
"ALTER TABLE `pull_requests` ADD `title` text;--> statement-breakpoint\nALTER TABLE `pull_requests` ADD `body` text;--> statement-breakpoint\nALTER TABLE `pull_requests` ADD `author_login` text;--> statement-breakpoint\nALTER TABLE `pull_requests` ADD `author_avatar_url` text;--> statement-breakpoint\nALTER TABLE `pull_requests` ADD `created_at` integer;--> statement-breakpoint\nALTER TABLE `pull_requests` ADD `closed_at` integer;--> statement-breakpoint\nALTER TABLE `pull_requests` ADD `comment_count` integer;--> statement-breakpoint\nALTER TABLE `pull_requests` ADD `milestone_title` text;",
|
|
81
|
-
"0004_fat_mindworm":
|
|
82
|
-
"ALTER TABLE `comments` ADD `created_at` integer;--> statement-breakpoint\nALTER TABLE `issues` ADD `started_at` integer;--> statement-breakpoint\nALTER TABLE `issues` ADD `completed_at` integer;--> statement-breakpoint\nALTER TABLE `issues` ADD `canceled_at` integer;--> statement-breakpoint\nALTER TABLE `issues` ADD `created_at` integer;--> statement-breakpoint\nALTER TABLE `issues` ADD `due_date` text;--> statement-breakpoint\nALTER TABLE `issues` ADD `snoozed_until_at` integer;--> statement-breakpoint\nALTER TABLE `issues` ADD `url` text;--> statement-breakpoint\nALTER TABLE `issues` ADD `priority_label` text;--> statement-breakpoint\nALTER TABLE `issues` ADD `sort_order` real;--> statement-breakpoint\nALTER TABLE `issues` ADD `branch_name` text;--> statement-breakpoint\nCREATE INDEX `idx_issues_sort_order` ON `issues` (`sort_order`);",
|
|
83
|
-
"0005_blue_black_queen":
|
|
84
|
-
"CREATE TABLE `users` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`name` text,\n\t`display_name` text,\n\t`avatar_url` text,\n\t`type` text,\n\t`source` text,\n\t`updated_at` integer\n);\n--> statement-breakpoint\nALTER TABLE `issues` ADD `assignee_id` text;",
|
|
85
|
-
"0006_lively_vector":
|
|
86
|
-
"CREATE TABLE `initiatives` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`name` text,\n\t`description` text,\n\t`status` text,\n\t`target_date` text,\n\t`owner_id` text,\n\t`updated_at` integer\n);\n--> statement-breakpoint\nCREATE TABLE `project_initiatives` (\n\t`project_id` text NOT NULL,\n\t`initiative_id` text NOT NULL,\n\tPRIMARY KEY(`project_id`, `initiative_id`)\n);\n--> statement-breakpoint\nALTER TABLE `cycles` ADD `name` text;--> statement-breakpoint\nALTER TABLE `cycles` ADD `progress` real;--> statement-breakpoint\nALTER TABLE `cycles` ADD `completed_scope` real;--> statement-breakpoint\nALTER TABLE `cycles` ADD `scope` real;--> statement-breakpoint\nALTER TABLE `cycles` ADD `completed_issue_count` integer;--> statement-breakpoint\nALTER TABLE `cycles` ADD `issue_count` integer;--> statement-breakpoint\nALTER TABLE `projects` ADD `description` text;--> statement-breakpoint\nALTER TABLE `projects` ADD `lead_id` text;--> statement-breakpoint\nALTER TABLE `projects` ADD `target_date` text;--> statement-breakpoint\nALTER TABLE `projects` ADD `start_date` text;--> statement-breakpoint\nALTER TABLE `projects` ADD `progress` real;--> statement-breakpoint\nALTER TABLE `projects` ADD `health` text;--> statement-breakpoint\nALTER TABLE `projects` ADD `icon` text;--> statement-breakpoint\nALTER TABLE `projects` ADD `color` text;",
|
|
87
|
-
"0007_rapid_logan":
|
|
88
|
-
"CREATE TABLE `issue_history` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`issue_id` text NOT NULL,\n\t`actor_id` text,\n\t`created_at` integer,\n\t`updated_at` integer,\n\t`from_state` text,\n\t`to_state` text,\n\t`from_assignee_id` text,\n\t`to_assignee_id` text,\n\t`from_priority` integer,\n\t`to_priority` integer,\n\t`from_estimate` real,\n\t`to_estimate` real,\n\t`from_title` text,\n\t`to_title` text,\n\t`from_cycle_id` text,\n\t`to_cycle_id` text,\n\t`from_project_id` text,\n\t`to_project_id` text,\n\t`from_parent_id` text,\n\t`to_parent_id` text,\n\t`from_team_id` text,\n\t`to_team_id` text,\n\t`from_due_date` text,\n\t`to_due_date` text,\n\t`added_label_ids` text,\n\t`removed_label_ids` text,\n\t`updated_description` integer,\n\t`archived` integer,\n\t`auto_archived` integer,\n\t`auto_closed` integer,\n\t`trashed` integer,\n\t`raw` text\n);\n--> statement-breakpoint\nCREATE INDEX `idx_issue_history_issue_created` ON `issue_history` (`issue_id`,`created_at`);",
|
|
89
|
-
},
|
|
90
|
-
} as const;
|