Skip to content

Commit f76f32e

Browse files
committed
fix(migrations): Defer state initialization
Require callers to select full migration or schema-bootstrap mode explicitly. Resolve the state context only when a pending TypeScript migration runs, so SQL-only upgrades do not depend on Redis. Align the migrations package with the current lockstep release version.
1 parent 1df1dca commit f76f32e

15 files changed

Lines changed: 147 additions & 100 deletions

File tree

packages/junior-migrations/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@sentry/junior-migrations",
3-
"version": "0.104.2",
3+
"version": "0.108.0",
44
"private": false,
55
"publishConfig": {
66
"access": "public"

packages/junior-migrations/src/runner.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ interface RunAllMigrationJournalOptions extends RunMigrationJournalBaseOptions {
7272
createContext: (args: {
7373
migration: ResolvedMigration;
7474
progress: MigrationContextV1["progress"];
75-
}) => MigrationContextV1;
75+
}) => MigrationContextV1 | Promise<MigrationContextV1>;
7676
loadTypeScript: TypeScriptMigrationLoader;
7777
mode?: "all";
7878
}
@@ -270,7 +270,10 @@ async function runTypeScriptMigration(args: {
270270
},
271271
};
272272
try {
273-
const context = args.createContext({ migration: args.migration, progress });
273+
const context = await args.createContext({
274+
migration: args.migration,
275+
progress,
276+
});
274277
const currentSource = await readFile(args.migration.path, "utf8");
275278
const currentHash = createHash("sha256")
276279
.update(currentSource)

packages/junior-migrations/tests/runner.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ describe("runMigrationJournal", () => {
167167
migrationsFolder: folder,
168168
migrationsTable: "__drizzle_test",
169169
loadTypeScript: async () => ({ default: migration }),
170-
createContext: ({ progress }) => ({
170+
createContext: async ({ progress }) => ({
171171
log: () => {},
172172
progress,
173173
database: executor,

packages/junior/src/chat/conversations/sql/migrations.ts

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -82,19 +82,21 @@ function assertSupportedMigrationState(state: CoreMigrationState): void {
8282
export type MigrateSchemaOptions =
8383
| { mode: "schema-bootstrap" }
8484
| {
85+
getStateContext: () => Promise<{
86+
redisStateAdapter?: RedisStateAdapter;
87+
stateAdapter: StateAdapter;
88+
}>;
8589
loadTypeScript: TypeScriptMigrationLoader;
8690
log?: MigrationContextV1["log"];
8791
mode: "all";
88-
redisStateAdapter?: RedisStateAdapter;
89-
stateAdapter: StateAdapter;
9092
};
9193

9294
export { schema };
9395

9496
/** Apply all migrations, or bootstrap an empty test database to the latest schema. */
9597
export async function migrateSchema(
9698
executor: JuniorSqlMigrationExecutor,
97-
options: MigrateSchemaOptions = { mode: "schema-bootstrap" },
99+
options: MigrateSchemaOptions,
98100
): Promise<MigrationRunResult> {
99101
const migrationsFolder = migrationFolder();
100102
const runAll = options.mode === "all";
@@ -114,22 +116,24 @@ export async function migrateSchema(
114116
}
115117
return await runMigrationJournal({
116118
...baseOptions,
117-
createContext: ({ progress }): MigrationContextV1 => ({
118-
database: executor,
119-
log: options.log ?? (() => {}),
120-
progress,
121-
...(options.redisStateAdapter
122-
? {
123-
redis: {
124-
sendCommand: async <T>(args: readonly string[]) =>
125-
await options
126-
.redisStateAdapter!.getClient()
127-
.sendCommand<T>([...args]),
128-
},
129-
}
130-
: {}),
131-
state: options.stateAdapter,
132-
}),
119+
createContext: async ({ progress }): Promise<MigrationContextV1> => {
120+
const { redisStateAdapter, stateAdapter } =
121+
await options.getStateContext();
122+
return {
123+
database: executor,
124+
log: options.log ?? (() => {}),
125+
progress,
126+
...(redisStateAdapter
127+
? {
128+
redis: {
129+
sendCommand: async <T>(args: readonly string[]) =>
130+
await redisStateAdapter.getClient().sendCommand<T>([...args]),
131+
},
132+
}
133+
: {}),
134+
state: stateAdapter,
135+
};
136+
},
133137
loadTypeScript: options.loadTypeScript,
134138
mode: "all",
135139
});

packages/junior/src/chat/plugins/migrations.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ type PluginMigrationResult = {
2626
type PluginMigrationOptions =
2727
| { mode: "schema-bootstrap" }
2828
| {
29+
getStateAdapter: () => Promise<StateAdapter>;
2930
loadTypeScript: TypeScriptMigrationLoader;
3031
log?: MigrationContextV1["log"];
3132
mode: "all";
32-
stateAdapter: StateAdapter;
3333
};
3434

3535
const LEGACY_SCHEDULER_BASELINE_HASH =
@@ -135,7 +135,7 @@ CREATE TABLE IF NOT EXISTS drizzle.${args.table} (
135135
export async function migratePluginSchemas(
136136
executor: JuniorSqlMigrationExecutor,
137137
roots: readonly PluginMigrationRoot[],
138-
options: PluginMigrationOptions = { mode: "schema-bootstrap" },
138+
options: PluginMigrationOptions,
139139
): Promise<PluginMigrationResult> {
140140
const result: PluginMigrationResult = {
141141
existing: 0,
@@ -165,13 +165,13 @@ export async function migratePluginSchemas(
165165
const pluginResult = runAll
166166
? await runMigrationJournal({
167167
...baseOptions,
168-
createContext: ({ progress }): MigrationContextV1 => ({
168+
createContext: async ({ progress }): Promise<MigrationContextV1> => ({
169169
database: executor,
170170
log: options.log ?? (() => {}),
171171
progress,
172172
state: createPluginMigrationStateV1(
173173
root.pluginName,
174-
options.stateAdapter,
174+
await options.getStateAdapter(),
175175
),
176176
}),
177177
loadTypeScript: options.loadTypeScript,

packages/junior/src/cli/upgrade.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { migratePluginJournals } from "./upgrade/migrations/plugin-journal";
99
import type {
1010
MigrationContext,
1111
MigrationResult,
12+
MigrationStateContext,
1213
UpgradeIo,
1314
} from "./upgrade/types";
1415
import { type JuniorPluginSet } from "@/plugins";
@@ -90,19 +91,21 @@ export async function runUpgrade(
9091
io: UpgradeIo = DEFAULT_IO,
9192
options: { pluginSet?: JuniorPluginSet | null } = {},
9293
): Promise<void> {
94+
let stateContext: Promise<MigrationStateContext> | undefined;
95+
const getStateContext = (): Promise<MigrationStateContext> => {
96+
stateContext ??= getConnectedStateContext();
97+
return stateContext;
98+
};
9399
try {
94-
const { redisStateAdapter, stateAdapter } =
95-
await getConnectedStateContext();
96100
const pluginSet =
97101
options.pluginSet === undefined
98102
? await resolveUpgradePluginSet()
99103
: (options.pluginSet ?? undefined);
100104
io.info("Running Junior upgrade migrations...");
101105
await runUpgradeMigrations({
106+
getStateContext,
102107
io,
103108
pluginSet,
104-
redisStateAdapter,
105-
stateAdapter,
106109
});
107110
io.info("Junior upgrade complete.");
108111
} finally {

packages/junior/src/cli/upgrade/migrations/core-journal.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,11 @@ export async function migrateCoreJournal(
1717
});
1818
try {
1919
const result = await migrateSchema(executor, {
20+
getStateContext: context.getStateContext,
2021
loadTypeScript: async (path) =>
2122
await migrationLoader.import<Record<string, unknown>>(path),
2223
log: context.io.info,
2324
mode: "all",
24-
redisStateAdapter: context.redisStateAdapter,
25-
stateAdapter: context.stateAdapter,
2625
});
2726
return {
2827
existing: result.existing,

packages/junior/src/cli/upgrade/migrations/plugin-journal.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@ export async function migratePluginJournals(
2424
executor,
2525
pluginCatalogRuntime.getMigrationRoots(),
2626
{
27+
getStateAdapter: async () =>
28+
(await context.getStateContext()).stateAdapter,
2729
loadTypeScript: async (path) =>
2830
await migrationLoader.import<Record<string, unknown>>(path),
2931
log: context.io.info,
3032
mode: "all",
31-
stateAdapter: context.stateAdapter,
3233
},
3334
);
3435
return {

packages/junior/src/cli/upgrade/types.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@ export interface UpgradeIo {
77
info: (line: string) => void;
88
}
99

10+
export interface MigrationStateContext {
11+
redisStateAdapter?: RedisStateAdapter;
12+
stateAdapter: StateAdapter;
13+
}
14+
1015
export interface MigrationContext {
16+
getStateContext: () => Promise<MigrationStateContext>;
1117
io: UpgradeIo;
1218
pluginCatalogConfig?: PluginCatalogConfig;
1319
pluginSet?: JuniorPluginSet;
14-
redisStateAdapter?: RedisStateAdapter;
15-
stateAdapter: StateAdapter;
1620
}
1721

1822
export type MigrationResult = {

packages/junior/tests/component/conversation-sql-store.test.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ describe("conversation SQL store", () => {
3636

3737
try {
3838
const store = createSqlStore(fixture.sql);
39-
await migrateSchema(fixture.sql);
39+
await migrateSchema(fixture.sql, { mode: "schema-bootstrap" });
4040

4141
await store.recordActivity({
4242
conversationId: CONVERSATION_ID,
@@ -169,7 +169,7 @@ describe("conversation SQL store", () => {
169169

170170
try {
171171
const store = createSqlStore(fixture.sql);
172-
await migrateSchema(fixture.sql);
172+
await migrateSchema(fixture.sql, { mode: "schema-bootstrap" });
173173

174174
const identity = await upsertIdentity(
175175
fixture.sql,
@@ -278,7 +278,7 @@ describe("conversation SQL store", () => {
278278

279279
try {
280280
const store = createSqlStore(fixture.sql);
281-
await migrateSchema(fixture.sql);
281+
await migrateSchema(fixture.sql, { mode: "schema-bootstrap" });
282282

283283
await store.recordActivity({
284284
conversationId: CONVERSATION_ID,
@@ -314,7 +314,7 @@ describe("conversation SQL store", () => {
314314
const fixture = await createLocalJuniorSqlFixture();
315315

316316
try {
317-
await migrateSchema(fixture.sql);
317+
await migrateSchema(fixture.sql, { mode: "schema-bootstrap" });
318318

319319
await fixture.sql
320320
.db()
@@ -387,7 +387,7 @@ describe("conversation SQL store", () => {
387387

388388
try {
389389
const store = createSqlStore(fixture.sql);
390-
await migrateSchema(fixture.sql);
390+
await migrateSchema(fixture.sql, { mode: "schema-bootstrap" });
391391

392392
await store.recordActivity({
393393
conversationId: CONVERSATION_ID,
@@ -434,7 +434,7 @@ describe("conversation SQL store", () => {
434434

435435
try {
436436
const store = createSqlStore(fixture.sql);
437-
await migrateSchema(fixture.sql);
437+
await migrateSchema(fixture.sql, { mode: "schema-bootstrap" });
438438
const destination = inboundMessage("visibility").destination;
439439

440440
// Slack reports this C-prefixed channel private (channel_type: group).
@@ -501,7 +501,7 @@ describe("conversation SQL store", () => {
501501

502502
try {
503503
const store = createSqlStore(fixture.sql);
504-
await migrateSchema(fixture.sql);
504+
await migrateSchema(fixture.sql, { mode: "schema-bootstrap" });
505505

506506
// A write without a live source signal fails closed to private even
507507
// though the channel id is C-prefixed.
@@ -531,7 +531,7 @@ describe("conversation SQL store", () => {
531531

532532
try {
533533
const store = createSqlStore(fixture.sql);
534-
await migrateSchema(fixture.sql);
534+
await migrateSchema(fixture.sql, { mode: "schema-bootstrap" });
535535
await fixture.sql.execute(
536536
`
537537
INSERT INTO junior_conversations (
@@ -591,7 +591,7 @@ INSERT INTO junior_conversations (
591591

592592
try {
593593
const store = createSqlStore(fixture.sql);
594-
await migrateSchema(fixture.sql);
594+
await migrateSchema(fixture.sql, { mode: "schema-bootstrap" });
595595

596596
await store.recordExecution({
597597
conversationId: CONVERSATION_ID,
@@ -644,7 +644,7 @@ INSERT INTO junior_conversations (
644644

645645
try {
646646
const store = createSqlStore(fixture.sql);
647-
await migrateSchema(fixture.sql);
647+
await migrateSchema(fixture.sql, { mode: "schema-bootstrap" });
648648
await store.recordExecution({
649649
conversationId: CONVERSATION_ID,
650650
createdAtMs: 1_000,
@@ -721,7 +721,7 @@ WHERE conversation_id = $1
721721

722722
try {
723723
const store = createSqlStore(fixture.sql);
724-
await migrateSchema(fixture.sql);
724+
await migrateSchema(fixture.sql, { mode: "schema-bootstrap" });
725725

726726
await store.recordExecution({
727727
conversationId: CONVERSATION_ID,
@@ -771,7 +771,7 @@ WHERE conversation_id = $1
771771

772772
try {
773773
const store = createSqlStore(fixture.sql);
774-
await migrateSchema(fixture.sql);
774+
await migrateSchema(fixture.sql, { mode: "schema-bootstrap" });
775775

776776
await store.recordActivity({
777777
conversationId: CONVERSATION_ID,
@@ -807,7 +807,7 @@ WHERE conversation_id = $1
807807
try {
808808
await disconnectStateAdapter();
809809
const store = createSqlStore(fixture.sql);
810-
await migrateSchema(fixture.sql);
810+
await migrateSchema(fixture.sql, { mode: "schema-bootstrap" });
811811
await store.recordExecution({
812812
conversationId: CONVERSATION_ID,
813813
createdAtMs: 1_000,
@@ -841,7 +841,7 @@ WHERE conversation_id = $1
841841
vi.useFakeTimers({ now: 302_000 });
842842
await disconnectStateAdapter();
843843
const store = createSqlStore(fixture.sql);
844-
await migrateSchema(fixture.sql);
844+
await migrateSchema(fixture.sql, { mode: "schema-bootstrap" });
845845
await store.recordExecution({
846846
conversationId: CONVERSATION_ID,
847847
createdAtMs: 1_000,
@@ -876,7 +876,7 @@ WHERE conversation_id = $1
876876
vi.useFakeTimers({ now: 2_000 });
877877
await disconnectStateAdapter();
878878
const store = createSqlStore(fixture.sql);
879-
await migrateSchema(fixture.sql);
879+
await migrateSchema(fixture.sql, { mode: "schema-bootstrap" });
880880
await store.recordExecution({
881881
conversationId: CONVERSATION_ID,
882882
createdAtMs: 1_000,
@@ -914,7 +914,7 @@ WHERE conversation_id = $1
914914
vi.useFakeTimers({ now: 600_000 });
915915
await disconnectStateAdapter();
916916
const store = createSqlStore(fixture.sql);
917-
await migrateSchema(fixture.sql);
917+
await migrateSchema(fixture.sql, { mode: "schema-bootstrap" });
918918
await store.recordExecution({
919919
conversationId: CONVERSATION_ID,
920920
createdAtMs: 1_000,
@@ -968,7 +968,7 @@ WHERE conversation_id = $1
968968
await disconnectStateAdapter();
969969
const state = getStateAdapter();
970970
const store = createSqlStore(fixture.sql);
971-
await migrateSchema(fixture.sql);
971+
await migrateSchema(fixture.sql, { mode: "schema-bootstrap" });
972972
await appendInboundMessage({
973973
message: inboundMessage("check-in"),
974974
conversationStore: store,
@@ -1021,7 +1021,7 @@ WHERE conversation_id = $1
10211021
await disconnectStateAdapter();
10221022
const state = getStateAdapter();
10231023
const store = createSqlStore(fixture.sql);
1024-
await migrateSchema(fixture.sql);
1024+
await migrateSchema(fixture.sql, { mode: "schema-bootstrap" });
10251025
await appendInboundMessage({
10261026
message: inboundMessage("drain-sql"),
10271027
conversationStore: store,

0 commit comments

Comments
 (0)