@catalyst-cloud/schema 0.1.12 → 0.1.14
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/package.json +1 -1
- package/src/index.ts +9 -0
- package/src/migrations.generated.ts +18 -0
- package/src/mirror.ts +49 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@catalyst-cloud/schema",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
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",
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
issue_history,
|
|
19
19
|
projects,
|
|
20
20
|
cycles,
|
|
21
|
+
workflow_states,
|
|
21
22
|
initiatives,
|
|
22
23
|
project_initiatives,
|
|
23
24
|
comments,
|
|
@@ -40,6 +41,7 @@ import {
|
|
|
40
41
|
pr_commits,
|
|
41
42
|
pr_events,
|
|
42
43
|
pr_ancillary_backfill,
|
|
44
|
+
push_subscriptions,
|
|
43
45
|
} from "./mirror.js";
|
|
44
46
|
|
|
45
47
|
export * from "./mirror.js";
|
|
@@ -76,6 +78,7 @@ export const mirrorSchema = {
|
|
|
76
78
|
issue_history,
|
|
77
79
|
projects,
|
|
78
80
|
cycles,
|
|
81
|
+
workflow_states,
|
|
79
82
|
initiatives,
|
|
80
83
|
project_initiatives,
|
|
81
84
|
comments,
|
|
@@ -110,6 +113,9 @@ export const mirrorSchema = {
|
|
|
110
113
|
pr_commits,
|
|
111
114
|
pr_events,
|
|
112
115
|
pr_ancillary_backfill,
|
|
116
|
+
// CTC-619: a member's Web Push subscription. Rides the change feed like any other entity — a
|
|
117
|
+
// member-facing settings UI can reflect subscribe/unsubscribe reactively.
|
|
118
|
+
push_subscriptions,
|
|
113
119
|
} as const;
|
|
114
120
|
|
|
115
121
|
/**
|
|
@@ -136,6 +142,7 @@ export const mirrorEntityTables = {
|
|
|
136
142
|
issue_history,
|
|
137
143
|
projects,
|
|
138
144
|
cycles,
|
|
145
|
+
workflow_states,
|
|
139
146
|
initiatives,
|
|
140
147
|
project_initiatives,
|
|
141
148
|
comments,
|
|
@@ -154,6 +161,8 @@ export const mirrorEntityTables = {
|
|
|
154
161
|
pr_conversation_comments,
|
|
155
162
|
pr_commits,
|
|
156
163
|
pr_events,
|
|
164
|
+
// CTC-619: a member's Web Push subscription.
|
|
165
|
+
push_subscriptions,
|
|
157
166
|
} as const;
|
|
158
167
|
|
|
159
168
|
/** The wire `entity` name of a change-feed-carried table (keys of mirrorEntityTables). */
|
|
@@ -165,6 +165,20 @@ export const MIRROR_MIGRATIONS = {
|
|
|
165
165
|
tag: "0021_worried_annihilus",
|
|
166
166
|
breakpoints: true,
|
|
167
167
|
},
|
|
168
|
+
{
|
|
169
|
+
idx: 22,
|
|
170
|
+
version: "6",
|
|
171
|
+
when: 1786928302892,
|
|
172
|
+
tag: "0022_chilly_gorgon",
|
|
173
|
+
breakpoints: true,
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
idx: 23,
|
|
177
|
+
version: "6",
|
|
178
|
+
when: 1786935258669,
|
|
179
|
+
tag: "0023_ambiguous_micromax",
|
|
180
|
+
breakpoints: true,
|
|
181
|
+
},
|
|
168
182
|
],
|
|
169
183
|
},
|
|
170
184
|
migrations: {
|
|
@@ -210,5 +224,9 @@ export const MIRROR_MIGRATIONS = {
|
|
|
210
224
|
"CREATE TABLE `pr_review_backfill` (\n\t`repo_id` text NOT NULL,\n\t`pr_number` integer NOT NULL,\n\t`backfilled_at` integer NOT NULL,\n\tPRIMARY KEY(`repo_id`, `pr_number`)\n);\n--> statement-breakpoint\nALTER TABLE `reviews` ADD `body` text;",
|
|
211
225
|
"0021_worried_annihilus":
|
|
212
226
|
"CREATE TABLE `pr_ancillary_backfill` (\n\t`repo_id` text NOT NULL,\n\t`pr_number` integer NOT NULL,\n\t`backfilled_at` integer NOT NULL,\n\tPRIMARY KEY(`repo_id`, `pr_number`)\n);\n--> statement-breakpoint\nCREATE TABLE `pr_commits` (\n\t`repo_id` text NOT NULL,\n\t`pr_number` integer NOT NULL,\n\t`sha` text NOT NULL,\n\t`message` text,\n\t`author_login` text,\n\t`author_name` text,\n\t`author_email` text,\n\t`authored_at` integer,\n\t`committed_at` integer,\n\tPRIMARY KEY(`repo_id`, `pr_number`, `sha`)\n);\n--> statement-breakpoint\nCREATE TABLE `pr_conversation_comments` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`repo_id` text NOT NULL,\n\t`pr_number` integer NOT NULL,\n\t`author_id` text,\n\t`body` text,\n\t`created_at` integer,\n\t`updated_at` integer,\n\t`removed_at` integer\n);\n--> statement-breakpoint\nCREATE INDEX `idx_pr_conversation_comments_pr` ON `pr_conversation_comments` (`repo_id`,`pr_number`);--> statement-breakpoint\nCREATE TABLE `pr_events` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`repo_id` text NOT NULL,\n\t`pr_number` integer NOT NULL,\n\t`action` text,\n\t`actor_id` text,\n\t`created_at` integer,\n\t`updated_at` integer,\n\t`raw` text\n);\n--> statement-breakpoint\nCREATE INDEX `idx_pr_events_pr` ON `pr_events` (`repo_id`,`pr_number`,`created_at`);--> statement-breakpoint\nCREATE TABLE `pr_review_comments` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`repo_id` text NOT NULL,\n\t`pr_number` integer NOT NULL,\n\t`review_id` text,\n\t`commit_id` text,\n\t`path` text,\n\t`line` integer,\n\t`diff_hunk` text,\n\t`in_reply_to_id` text,\n\t`author_id` text,\n\t`body` text,\n\t`created_at` integer,\n\t`updated_at` integer,\n\t`removed_at` integer\n);\n--> statement-breakpoint\nCREATE INDEX `idx_pr_review_comments_pr` ON `pr_review_comments` (`repo_id`,`pr_number`);",
|
|
227
|
+
"0022_chilly_gorgon":
|
|
228
|
+
"CREATE TABLE `workflow_states` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`team_id` text,\n\t`name` text,\n\t`type` text,\n\t`position` real,\n\t`color` text,\n\t`archived_at` integer,\n\t`updated_at` integer\n);\n--> statement-breakpoint\nCREATE INDEX `idx_workflow_states_team` ON `workflow_states` (`team_id`);--> statement-breakpoint\nALTER TABLE `issues` ADD `state_type` text;--> statement-breakpoint\nALTER TABLE `issues` ADD `state_position` real;",
|
|
229
|
+
"0023_ambiguous_micromax":
|
|
230
|
+
"CREATE TABLE `push_subscriptions` (\n\t`endpoint` text PRIMARY KEY NOT NULL,\n\t`member_id` text NOT NULL,\n\t`p256dh` text NOT NULL,\n\t`auth` text NOT NULL,\n\t`created_at` integer,\n\t`last_used_at` integer\n);\n--> statement-breakpoint\nCREATE INDEX `idx_push_subscriptions_member` ON `push_subscriptions` (`member_id`);",
|
|
213
231
|
},
|
|
214
232
|
} as const;
|
package/src/mirror.ts
CHANGED
|
@@ -39,6 +39,11 @@ export const issues = sqliteTable(
|
|
|
39
39
|
// CTC-148: the workflow state id behind the `state` NAME — lets a consumer key on state without
|
|
40
40
|
// string-matching the renameable name.
|
|
41
41
|
state_id: text("state_id"),
|
|
42
|
+
// CTC-308: the state's bucket (triage|backlog|unstarted|started|completed|canceled) + its manual
|
|
43
|
+
// column order — denormalized off `state` (the `workflow_states` FK, below) so a board can order
|
|
44
|
+
// its columns without a join on the hot path. Null on a partial webhook, exactly like state_id.
|
|
45
|
+
state_type: text("state_type"),
|
|
46
|
+
state_position: real("state_position"),
|
|
42
47
|
assignee: text("assignee"),
|
|
43
48
|
// CTC-USERS: the assignee's user id (FK → users.id), so the row JOINs to a name + avatar. The
|
|
44
49
|
// legacy `assignee` name string is KEPT for back-compat (the SSE list row still carries it).
|
|
@@ -308,6 +313,28 @@ export const cycles = sqliteTable("cycles", {
|
|
|
308
313
|
updated_at: integer("updated_at"),
|
|
309
314
|
});
|
|
310
315
|
|
|
316
|
+
// Linear workflow states (CTC-308) — the id/type/position behind an issue's `state` NAME, mirrored as
|
|
317
|
+
// its own entity table so a board can order columns by (type, position) and key a column across teams
|
|
318
|
+
// (the prerequisite CTC-277's kanban board blocks on). Small, workspace-wide set — full sweep each
|
|
319
|
+
// pass, same discipline as `labels`/`cycles`. No `removed_at`: Linear ARCHIVES a workflow state rather
|
|
320
|
+
// than deleting it (`archived_at`), same convention `issues.archived_at` already uses.
|
|
321
|
+
export const workflow_states = sqliteTable(
|
|
322
|
+
"workflow_states",
|
|
323
|
+
{
|
|
324
|
+
id: text("id").primaryKey(),
|
|
325
|
+
team_id: text("team_id"),
|
|
326
|
+
name: text("name"),
|
|
327
|
+
// The state's bucket: triage | backlog | unstarted | started | completed | canceled.
|
|
328
|
+
type: text("type"),
|
|
329
|
+
// Manual sort rank within its team's workflow — a Float, Linear's own column order.
|
|
330
|
+
position: real("position"),
|
|
331
|
+
color: text("color"),
|
|
332
|
+
archived_at: integer("archived_at"),
|
|
333
|
+
updated_at: integer("updated_at"),
|
|
334
|
+
},
|
|
335
|
+
(t) => [index("idx_workflow_states_team").on(t.team_id)],
|
|
336
|
+
);
|
|
337
|
+
|
|
311
338
|
// Linear initiatives (CTC-HIERARCHY) — the top of the HIERARCHY (initiative → project → cycle →
|
|
312
339
|
// issue). PK `id` (Linear node id). `status` is the InitiativeStatus enum string; `target_date` is a
|
|
313
340
|
// TimelessDate ("YYYY-MM-DD") stored AS-IS as TEXT (the trap). `owner_id` is the FK → users.id.
|
|
@@ -824,3 +851,25 @@ export const pr_ancillary_backfill = sqliteTable(
|
|
|
824
851
|
},
|
|
825
852
|
(t) => [primaryKey({ columns: [t.repo_id, t.pr_number] })],
|
|
826
853
|
);
|
|
854
|
+
|
|
855
|
+
// CTC-619 — a member's Web Push subscription (one browser/device). PK is the subscription `endpoint`
|
|
856
|
+
// itself — a push endpoint URL is unique per browser install by construction, so keying on it is what
|
|
857
|
+
// makes "a duplicate subscribe for the same endpoint does not create a second row" (the ticket's own
|
|
858
|
+
// AC) a plain upsert, no synthetic id needed. `member_id` is the Linear user id (FK-shaped to
|
|
859
|
+
// users.id, same as issues.assignee_id) — the ingest trigger's own correlation key. HARD delete on
|
|
860
|
+
// unsubscribe (no removed_at): a stale/revoked push subscription has no historical value, matching
|
|
861
|
+
// `users`' own "HARD-UPSERT, no soft delete" precedent for similarly overwrite-in-place data.
|
|
862
|
+
export const push_subscriptions = sqliteTable(
|
|
863
|
+
"push_subscriptions",
|
|
864
|
+
{
|
|
865
|
+
endpoint: text("endpoint").primaryKey(),
|
|
866
|
+
member_id: text("member_id").notNull(),
|
|
867
|
+
p256dh: text("p256dh").notNull(),
|
|
868
|
+
auth: text("auth").notNull(),
|
|
869
|
+
created_at: integer("created_at"),
|
|
870
|
+
last_used_at: integer("last_used_at"),
|
|
871
|
+
},
|
|
872
|
+
// The ingest trigger's own read pattern: "every subscription for this member" (member_id, NOT the
|
|
873
|
+
// PK), keyed off issues.assignee_id — the same shape idx_workflow_states_team indexes on team_id.
|
|
874
|
+
(t) => [index("idx_push_subscriptions_member").on(t.member_id)],
|
|
875
|
+
);
|