@sentry/junior 0.96.0 → 0.97.0

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.
Files changed (135) hide show
  1. package/dist/{agent-hooks-5URSMY3O.js → agent-hooks-XSUIVRQS.js} +8 -6
  2. package/dist/{reporting → api}/conversations/activity.d.ts +1 -1
  3. package/dist/api/conversations/detail-projection.d.ts +10 -0
  4. package/dist/api/conversations/detail.d.ts +3 -0
  5. package/dist/api/conversations/detail.query.d.ts +3 -0
  6. package/dist/api/conversations/list.d.ts +3 -0
  7. package/dist/api/conversations/list.query.d.ts +94 -0
  8. package/dist/api/conversations/projection.d.ts +14 -0
  9. package/dist/api/conversations/schema.d.ts +683 -0
  10. package/dist/{reporting → api}/conversations/shared.d.ts +1 -5
  11. package/dist/api/conversations/stats.d.ts +3 -0
  12. package/dist/api/conversations/stats.query.d.ts +3 -0
  13. package/dist/api/conversations/subagent.d.ts +3 -0
  14. package/dist/{reporting → api}/conversations/transcript.d.ts +4 -4
  15. package/dist/api/people/list.d.ts +1 -2
  16. package/dist/api/people/list.query.d.ts +3 -4
  17. package/dist/api/people/profile.d.ts +1 -2
  18. package/dist/api/people/profile.query.d.ts +3 -4
  19. package/dist/api/people/schema.d.ts +193 -0
  20. package/dist/api/people/shared.d.ts +31 -9
  21. package/dist/api/schema.d.ts +20 -0
  22. package/dist/api/schema.js +50 -0
  23. package/dist/api-reference.d.ts +4 -3
  24. package/dist/api.d.ts +3 -0
  25. package/dist/api.js +156 -0
  26. package/dist/app.d.ts +1 -4
  27. package/dist/app.js +100 -366
  28. package/dist/build/virtual-config.d.ts +2 -2
  29. package/dist/{catalog-runtime-MSMAYYIK.js → catalog-runtime-PFHVMHDR.js} +3 -2
  30. package/dist/chat/agent/resume.d.ts +3 -1
  31. package/dist/chat/agent/tools.d.ts +2 -1
  32. package/dist/chat/config.d.ts +1 -8
  33. package/dist/chat/conversations/history.d.ts +241 -27
  34. package/dist/chat/conversations/legacy-advisor-session.d.ts +7 -0
  35. package/dist/chat/conversations/legacy-import.d.ts +2 -2
  36. package/dist/chat/conversations/projection.d.ts +19 -3
  37. package/dist/chat/conversations/sql/legacy-history-import.d.ts +1 -1
  38. package/dist/chat/conversations/sql/store.d.ts +15 -9
  39. package/dist/chat/conversations/store.d.ts +9 -4
  40. package/dist/chat/model-profile.d.ts +14 -0
  41. package/dist/chat/pi/transcript.d.ts +5 -3
  42. package/dist/chat/resource-events/store.d.ts +2 -2
  43. package/dist/chat/services/context-compaction.d.ts +15 -0
  44. package/dist/chat/services/turn-result.d.ts +1 -0
  45. package/dist/chat/services/turn-session-record.d.ts +6 -6
  46. package/dist/chat/state/turn-session.d.ts +16 -5
  47. package/dist/chat/task-execution/store.d.ts +2 -2
  48. package/dist/chat/tools/handoff/tool.d.ts +45 -0
  49. package/dist/chat/tools/types.d.ts +7 -2
  50. package/dist/chat/usage.d.ts +25 -25
  51. package/dist/{chunk-RITD7E5L.js → chunk-35BNLDOJ.js} +338 -577
  52. package/dist/{chunk-PMNN6ST4.js → chunk-3ECW2K2O.js} +7 -2
  53. package/dist/{chunk-5C6XTTFI.js → chunk-3F4OPKGF.js} +89 -55
  54. package/dist/chunk-3V7OFJZC.js +264 -0
  55. package/dist/chunk-4ZNGQH7C.js +25 -0
  56. package/dist/{chunk-S5KZ5LPG.js → chunk-5HLDPVYS.js} +14 -447
  57. package/dist/{chunk-DZENG5ML.js → chunk-6U26PYI5.js} +24 -14
  58. package/dist/chunk-AUUCAFDE.js +546 -0
  59. package/dist/{chunk-6APU57E6.js → chunk-B3IT5END.js} +41 -260
  60. package/dist/chunk-CEA3A3UA.js +86 -0
  61. package/dist/{chunk-YDT7SNCN.js → chunk-D5KZYS4T.js} +1 -1
  62. package/dist/{chunk-YDVEH3SB.js → chunk-EEZCY5WX.js} +78 -28
  63. package/dist/{chunk-2NAZZPPS.js → chunk-EOWXRY5E.js} +1 -1
  64. package/dist/{chunk-GQ3GK22G.js → chunk-FBHZJFHI.js} +2 -2
  65. package/dist/{chunk-CZEX6KIQ.js → chunk-G3E7SCME.js} +0 -23
  66. package/dist/chunk-GUAP3H7O.js +319 -0
  67. package/dist/{chunk-BYZWPRJA.js → chunk-H66UCBPW.js} +6 -4
  68. package/dist/{chunk-L7CDEIVJ.js → chunk-LMJPRYHP.js} +4 -2
  69. package/dist/chunk-LUAE4MGW.js +199 -0
  70. package/dist/{chunk-GY34VGP5.js → chunk-LVA4I4H7.js} +3 -1
  71. package/dist/chunk-LVUKF5CE.js +63 -0
  72. package/dist/{chunk-AAZXTVJT.js → chunk-NG2AV7IB.js} +2 -72
  73. package/dist/chunk-ON6YKIQ7.js +260 -0
  74. package/dist/chunk-PDO5BLNM.js +22 -0
  75. package/dist/chunk-QNVHH5PU.js +20 -0
  76. package/dist/{chunk-P3YFQLEN.js → chunk-T533JLM3.js} +8 -10
  77. package/dist/chunk-UAMKJPDF.js +11 -0
  78. package/dist/chunk-VFH4ZKZT.js +66 -0
  79. package/dist/{chunk-UKHVMPX5.js → chunk-WGXMTFNU.js} +15 -17
  80. package/dist/{chunk-MNTINMWR.js → chunk-WJOSZOON.js} +184 -300
  81. package/dist/chunk-WTYEKPXU.js +451 -0
  82. package/dist/{chunk-IYM2SIVG.js → chunk-XIRPQQVE.js} +605 -363
  83. package/dist/cli/chat.js +24 -21
  84. package/dist/cli/check.js +4 -3
  85. package/dist/cli/init.js +12 -0
  86. package/dist/cli/plugins.js +12 -9
  87. package/dist/cli/snapshot-warmup.js +8 -6
  88. package/dist/cli/upgrade.js +265 -137
  89. package/dist/db/db.d.ts +4 -0
  90. package/dist/db/schema/conversations.d.ts +128 -0
  91. package/dist/db/schema.d.ts +129 -60
  92. package/dist/{db-2D6RMG67.js → db-BWXYIKFQ.js} +3 -3
  93. package/dist/detail-GA6NXWQJ.js +43 -0
  94. package/dist/discovery-RMHYSRL7.js +43 -0
  95. package/dist/handlers/health.d.ts +3 -0
  96. package/dist/instrumentation.js +1 -1
  97. package/dist/{legacy-import-VZ2MEGP7.js → legacy-import-3NQJVGQL.js} +4 -4
  98. package/dist/{api/people/list.js → list-A3WFRIYX.js} +26 -16
  99. package/dist/list-UXJI5Q5X.js +27 -0
  100. package/dist/nitro.d.ts +1 -1
  101. package/dist/nitro.js +2 -1
  102. package/dist/plugin-conversations-DDNGC35A.js +36 -0
  103. package/dist/{api/people/profile.js → profile-4RYDXWTF.js} +33 -18
  104. package/dist/reporting/plugin-conversations.d.ts +3 -0
  105. package/dist/reporting-schema.d.ts +140 -0
  106. package/dist/reporting.d.ts +12 -81
  107. package/dist/{runner-JZBRQP4F.js → runner-JALSYH2P.js} +17 -12
  108. package/dist/skills-MAZXZ2KE.js +26 -0
  109. package/dist/stats-XKXH3PTY.js +217 -0
  110. package/dist/subagent-TZ55XY37.js +48 -0
  111. package/dist/usage-schema.d.ts +25 -0
  112. package/dist/{validation-6XOOQGME.js → validation-FMEAEKCY.js} +4 -3
  113. package/migrations/0000_initial.sql +115 -0
  114. package/migrations/0001_conversation_metrics.sql +4 -0
  115. package/migrations/README.md +12 -30
  116. package/migrations/meta/0000_snapshot.json +1 -33
  117. package/migrations/meta/0001_snapshot.json +972 -0
  118. package/migrations/meta/_journal.json +9 -2
  119. package/package.json +14 -18
  120. package/dist/api/people/types.d.ts +0 -85
  121. package/dist/chat/advisor-request.d.ts +0 -4
  122. package/dist/chat/conversations/sql/migrations.d.ts +0 -22
  123. package/dist/chat/tools/advisor/session-store.d.ts +0 -9
  124. package/dist/chat/tools/advisor/tool.d.ts +0 -72
  125. package/dist/chunk-RAIB6WSG.js +0 -142
  126. package/dist/chunk-WM6KHPBQ.js +0 -48
  127. package/dist/db/schema/migrations.d.ts +0 -58
  128. package/dist/reporting/conversations/context.d.ts +0 -8
  129. package/dist/reporting/conversations/details.d.ts +0 -6
  130. package/dist/reporting/conversations/index.d.ts +0 -4
  131. package/dist/reporting/conversations/statistics.d.ts +0 -4
  132. package/dist/reporting/conversations/summaries.d.ts +0 -20
  133. package/dist/reporting/conversations/types.d.ts +0 -191
  134. package/dist/reporting/conversations.d.ts +0 -1
  135. package/dist/reporting.js +0 -1155
@@ -4,47 +4,50 @@ import {
4
4
  recordConversationActivity,
5
5
  recordConversationExecution,
6
6
  requestConversationWork
7
- } from "../chunk-GQ3GK22G.js";
7
+ } from "../chunk-FBHZJFHI.js";
8
8
  import {
9
9
  defineJuniorPlugins,
10
10
  pluginCatalogConfigFromEnv,
11
11
  pluginCatalogConfigFromPluginSet,
12
12
  pluginRuntimeRegistrationsFromPluginSet
13
13
  } from "../chunk-SG5WAA7H.js";
14
- import {
15
- coerceThreadConversationState
16
- } from "../chunk-RAIB6WSG.js";
17
14
  import {
18
15
  JUNIOR_THREAD_STATE_TTL_MS,
16
+ addAgentTurnUsage,
17
+ coerceThreadConversationState,
18
+ listAgentTurnSessionSummariesForConversations
19
+ } from "../chunk-WJOSZOON.js";
20
+ import "../chunk-GUAP3H7O.js";
21
+ import {
19
22
  importConversationFromLegacy
20
- } from "../chunk-5C6XTTFI.js";
23
+ } from "../chunk-3F4OPKGF.js";
24
+ import "../chunk-PDO5BLNM.js";
21
25
  import {
22
26
  loadAppPluginSet
23
27
  } from "../chunk-Y2CM7HXH.js";
24
28
  import {
25
29
  createPluginLogger,
26
30
  createPluginState
27
- } from "../chunk-2NAZZPPS.js";
31
+ } from "../chunk-EOWXRY5E.js";
28
32
  import {
29
33
  createJuniorSqlExecutor,
30
34
  createSqlConversationMessageStore,
31
35
  createSqlStore,
32
- getDb,
33
- migrateSchema
34
- } from "../chunk-RITD7E5L.js";
36
+ getDb
37
+ } from "../chunk-35BNLDOJ.js";
35
38
  import {
36
39
  disconnectStateAdapter,
37
40
  getChatConfig,
38
41
  getConnectedStateContext
39
- } from "../chunk-YDVEH3SB.js";
40
- import "../chunk-GY34VGP5.js";
42
+ } from "../chunk-EEZCY5WX.js";
43
+ import "../chunk-LVA4I4H7.js";
41
44
  import {
42
45
  parseDestination,
43
46
  sameDestination
44
47
  } from "../chunk-B7PUBQQM.js";
45
48
  import {
46
49
  pluginCatalogRuntime
47
- } from "../chunk-L7CDEIVJ.js";
50
+ } from "../chunk-LMJPRYHP.js";
48
51
  import "../chunk-I35CTGPO.js";
49
52
  import {
50
53
  isRecord,
@@ -52,28 +55,14 @@ import {
52
55
  toOptionalString
53
56
  } from "../chunk-OB42YVAE.js";
54
57
  import "../chunk-6GWA276C.js";
55
- import "../chunk-6APU57E6.js";
58
+ import "../chunk-B3IT5END.js";
56
59
  import "../chunk-7FBGKXPE.js";
60
+ import "../chunk-3V7OFJZC.js";
57
61
  import "../chunk-MLKGABMK.js";
58
62
 
59
63
  // src/cli/upgrade.ts
60
64
  import { createJiti } from "jiti";
61
65
 
62
- // src/chat/conversations/sql/backfill.ts
63
- async function backfillToSql(args) {
64
- const limit = Math.max(0, args.limit ?? 500);
65
- const conversations = await args.source.listByActivity({
66
- limit
67
- });
68
- await args.target.migrate();
69
- for (const conversation of conversations) {
70
- await args.target.backfillConversation(conversation);
71
- }
72
- return {
73
- copiedCount: conversations.length
74
- };
75
- }
76
-
77
66
  // src/chat/conversations/state.ts
78
67
  function createStateConversationStore(state) {
79
68
  return {
@@ -81,8 +70,8 @@ function createStateConversationStore(state) {
81
70
  // Task-execution state has no destination records, so visibility is never
82
71
  // source-confirmed here and cross-context reads fail closed to private.
83
72
  getDestinationVisibility: async () => void 0,
84
- // Subagent child conversations are a SQL-only concept; the advisor always
85
- // links its child through the SQL store, never this legacy metadata path.
73
+ // Child conversations are a SQL-only concept and never use this legacy
74
+ // metadata path.
86
75
  ensureChildConversation: async () => void 0,
87
76
  recordActivity: (args) => recordConversationActivity({ ...args, state }),
88
77
  recordExecution: (args) => recordConversationExecution({ ...args, state }),
@@ -107,16 +96,58 @@ async function migrateConversationsToSql(context, options = {}) {
107
96
  }
108
97
  const limit = Math.max(1, options.batchSize ?? CONVERSATION_BACKFILL_LIMIT);
109
98
  try {
110
- const result = await backfillToSql({
111
- limit,
112
- source,
113
- target
114
- });
99
+ const [stateConversations, sqlConversations] = await Promise.all([
100
+ source.listByActivity({ limit }),
101
+ target.listByActivity({ limit })
102
+ ]);
103
+ const byId = new Map(
104
+ sqlConversations.map((conversation) => [
105
+ conversation.conversationId,
106
+ conversation
107
+ ])
108
+ );
109
+ for (const conversation of stateConversations) {
110
+ const existing = byId.get(conversation.conversationId);
111
+ const existingExecutionAt = existing?.execution.updatedAtMs ?? existing?.updatedAtMs ?? 0;
112
+ const stateExecutionAt = conversation.execution.updatedAtMs ?? conversation.updatedAtMs;
113
+ if (!existing || stateExecutionAt >= existingExecutionAt) {
114
+ byId.set(conversation.conversationId, conversation);
115
+ }
116
+ }
117
+ const conversations = [...byId.values()].sort(
118
+ (left, right) => right.lastActivityAtMs - left.lastActivityAtMs || left.conversationId.localeCompare(right.conversationId)
119
+ ).slice(0, limit);
120
+ const summaries = await listAgentTurnSessionSummariesForConversations(
121
+ context.stateAdapter,
122
+ conversations.map((conversation) => conversation.conversationId)
123
+ );
124
+ for (const conversation of conversations) {
125
+ const conversationSummaries = summaries.get(conversation.conversationId) ?? [];
126
+ const executionSummary = conversation.execution.runId ? conversationSummaries.find(
127
+ (summary) => summary.sessionId === conversation.execution.runId
128
+ ) : void 0;
129
+ await target.backfillConversation(
130
+ conversation,
131
+ conversationSummaries.length > 0 ? {
132
+ durationMs: conversationSummaries.reduce(
133
+ (total, summary) => total + summary.cumulativeDurationMs,
134
+ 0
135
+ ),
136
+ usage: addAgentTurnUsage(
137
+ ...conversationSummaries.map(
138
+ (summary) => summary.cumulativeUsage
139
+ )
140
+ ),
141
+ executionDurationMs: executionSummary?.cumulativeDurationMs ?? 0,
142
+ executionUsage: executionSummary?.cumulativeUsage
143
+ } : void 0
144
+ );
145
+ }
115
146
  return {
116
147
  existing: 0,
117
- migrated: result.copiedCount,
148
+ migrated: conversations.length,
118
149
  missing: 0,
119
- scanned: result.copiedCount
150
+ scanned: conversations.length
120
151
  };
121
152
  } finally {
122
153
  await closeTarget?.();
@@ -127,6 +158,107 @@ var sqlConversationMigration = {
127
158
  run: migrateConversationsToSql
128
159
  };
129
160
 
161
+ // src/chat/conversations/sql/migrations.ts
162
+ import { basename, dirname, join, resolve } from "path";
163
+ import { fileURLToPath } from "url";
164
+ import { readMigrationFiles } from "drizzle-orm/migrator";
165
+ var LEGACY_CORE_MIGRATION_IDS = [
166
+ "0001_conversation_core",
167
+ "0002_slack_destination_visibility_backfill",
168
+ "0003_user_identities",
169
+ "0004_actor_cutover",
170
+ "0005_conversation_transcripts"
171
+ ];
172
+ var LEGACY_METRICS_MIGRATION_ID = "0006_conversation_metrics";
173
+ var MIGRATIONS_TABLE = "__drizzle_junior_core";
174
+ function migrationFolder() {
175
+ const moduleDir = dirname(fileURLToPath(import.meta.url));
176
+ const packageRoot = basename(moduleDir) === "dist" ? dirname(moduleDir) : basename(dirname(moduleDir)) === "dist" ? resolve(moduleDir, "../..") : resolve(moduleDir, "../../../..");
177
+ return join(packageRoot, "migrations");
178
+ }
179
+ async function adoptLegacyMigrationState(executor, migrationsFolder) {
180
+ const [tables] = await executor.query(`
181
+ SELECT
182
+ to_regclass('drizzle.__drizzle_junior_core')::text AS "drizzleTable",
183
+ to_regclass('public.junior_schema_migrations')::text AS "legacyTable"
184
+ `);
185
+ if (!tables?.legacyTable || tables.drizzleTable) {
186
+ return;
187
+ }
188
+ const migrations = readMigrationFiles({ migrationsFolder });
189
+ const [metrics] = await executor.query(`
190
+ SELECT count(*) = 4 AS complete
191
+ FROM information_schema.columns
192
+ WHERE table_schema = 'public'
193
+ AND table_name = 'junior_conversations'
194
+ AND column_name IN (
195
+ 'duration_ms',
196
+ 'usage_json',
197
+ 'execution_duration_ms',
198
+ 'execution_usage_json'
199
+ )
200
+ `);
201
+ const legacyRecords = await executor.query("SELECT id, checksum FROM junior_schema_migrations");
202
+ const expectedIds = metrics?.complete ? [...LEGACY_CORE_MIGRATION_IDS, LEGACY_METRICS_MIGRATION_ID] : [...LEGACY_CORE_MIGRATION_IDS];
203
+ const validIds = new Set(
204
+ legacyRecords.filter((record) => record.checksum.trim().length > 0).map((record) => record.id)
205
+ );
206
+ const missingIds = expectedIds.filter((id) => !validIds.has(id));
207
+ if (missingIds.length > 0) {
208
+ throw new Error(
209
+ `Cannot adopt partial legacy core migration state; missing: ${missingIds.join(", ")}`
210
+ );
211
+ }
212
+ const migration = metrics?.complete ? migrations.at(-1) : migrations[0];
213
+ if (!migration) {
214
+ throw new Error("No core Drizzle migrations were packaged");
215
+ }
216
+ await executor.transaction(async () => {
217
+ await executor.execute("CREATE SCHEMA IF NOT EXISTS drizzle");
218
+ await executor.execute(`
219
+ CREATE TABLE IF NOT EXISTS drizzle.__drizzle_junior_core (
220
+ id SERIAL PRIMARY KEY,
221
+ hash TEXT NOT NULL,
222
+ created_at BIGINT
223
+ )
224
+ `);
225
+ await executor.execute(
226
+ `INSERT INTO drizzle.__drizzle_junior_core (hash, created_at)
227
+ VALUES ($1, $2)`,
228
+ [migration.hash, migration.folderMillis]
229
+ );
230
+ });
231
+ }
232
+ async function migrateSchema(executor) {
233
+ const migrationsFolder = migrationFolder();
234
+ await executor.withMigrationLock(MIGRATIONS_TABLE, async () => {
235
+ await adoptLegacyMigrationState(executor, migrationsFolder);
236
+ await executor.migrate({
237
+ migrationsFolder,
238
+ migrationsTable: MIGRATIONS_TABLE
239
+ });
240
+ });
241
+ }
242
+
243
+ // src/cli/upgrade/migrations/core-sql.ts
244
+ async function migrateCoreSqlSchema(_context) {
245
+ const { sql } = getChatConfig();
246
+ const executor = createJuniorSqlExecutor({
247
+ connectionString: sql.databaseUrl,
248
+ driver: sql.driver
249
+ });
250
+ try {
251
+ await migrateSchema(executor);
252
+ return { existing: 0, migrated: 0, missing: 0, scanned: 0 };
253
+ } finally {
254
+ await executor.close();
255
+ }
256
+ }
257
+ var coreSqlSchemaMigration = {
258
+ name: "core-sql-schema",
259
+ run: migrateCoreSqlSchema
260
+ };
261
+
130
262
  // src/cli/upgrade/migrations/conversations-history-sql.ts
131
263
  var HISTORY_BACKFILL_LIMIT = 1e4;
132
264
  async function migrateConversationHistoryToSql(context, options = {}) {
@@ -143,7 +275,6 @@ async function migrateConversationHistoryToSql(context, options = {}) {
143
275
  }
144
276
  const limit = Math.max(1, options.batchSize ?? HISTORY_BACKFILL_LIMIT);
145
277
  try {
146
- await migrateSchema(executor);
147
278
  const messageStore = createSqlConversationMessageStore(executor);
148
279
  const conversations = await source.listByActivity({ limit });
149
280
  let migrated = 0;
@@ -313,122 +444,116 @@ var pluginStorageMigration = {
313
444
  run: runPluginStorageMigrations
314
445
  };
315
446
 
316
- // src/chat/plugins/db.ts
447
+ // src/chat/plugins/migrations.ts
317
448
  import { createHash } from "crypto";
318
- import { readdirSync, readFileSync, statSync } from "fs";
319
- import path from "path";
320
- import { z } from "zod";
321
- var PLUGIN_SCHEMA_LOCK_NAME = "junior_plugin_schema";
322
- var MIGRATION_FILENAME_RE = /^[0-9]{4}_[a-z0-9_]+\.sql$/;
323
- var migrationRecordSchema = z.object({
324
- id: z.string().min(1),
325
- checksum: z.string().min(1)
326
- }).strict();
327
- function checksumSql(sql) {
328
- return createHash("sha256").update(sql).digest("hex");
449
+ import { readMigrationFiles as readMigrationFiles2 } from "drizzle-orm/migrator";
450
+ function migrationTable(pluginName) {
451
+ const label = pluginName.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 32);
452
+ const hash = createHash("sha256").update(pluginName).digest("hex").slice(0, 8);
453
+ return `__drizzle_${label}_${hash}`;
329
454
  }
330
- function parseStoredMigrationRecord(value) {
331
- return migrationRecordSchema.parse(value);
455
+ async function appliedMigrationTime(executor, table) {
456
+ const [exists] = await executor.query(
457
+ `SELECT to_regclass($1)::text AS "tableName"`,
458
+ [`drizzle.${table}`]
459
+ );
460
+ if (!exists?.tableName) {
461
+ return void 0;
462
+ }
463
+ const [row] = await executor.query(
464
+ `SELECT created_at::text AS "createdAt"
465
+ FROM drizzle.${table}
466
+ ORDER BY created_at DESC
467
+ LIMIT 1`
468
+ );
469
+ return row?.createdAt === null || row?.createdAt === void 0 ? void 0 : Number(row.createdAt);
332
470
  }
333
- function assertMigrationFilename(filename) {
334
- if (!filename || filename !== path.basename(filename) || !MIGRATION_FILENAME_RE.test(filename)) {
335
- throw new Error(`Plugin migration filename "${filename}" is invalid`);
471
+ async function legacyMigrationHashes(executor, pluginName) {
472
+ const [exists] = await executor.query(
473
+ `SELECT to_regclass('public.junior_schema_migrations')::text AS "tableName"`
474
+ );
475
+ if (!exists?.tableName) {
476
+ return /* @__PURE__ */ new Set();
336
477
  }
478
+ const rows = await executor.query(
479
+ `SELECT checksum
480
+ FROM junior_schema_migrations
481
+ WHERE id LIKE $1
482
+ ORDER BY id ASC`,
483
+ [`plugin:${pluginName}/%`]
484
+ );
485
+ return new Set(rows.map((row) => row.checksum));
337
486
  }
338
- function assertUniqueMigrationIds(migrations) {
339
- const seen = /* @__PURE__ */ new Set();
487
+ function adoptedMigration(migrations, legacyHashes) {
488
+ let adopted;
340
489
  for (const migration of migrations) {
341
- if (seen.has(migration.id)) {
342
- throw new Error(`Duplicate plugin migration id ${migration.id}`);
490
+ if (!legacyHashes.has(migration.hash)) {
491
+ break;
343
492
  }
344
- seen.add(migration.id);
493
+ adopted = migration;
345
494
  }
495
+ if (!adopted && migrations.length === 1 && legacyHashes.size > 0) {
496
+ return migrations[0];
497
+ }
498
+ return adopted;
346
499
  }
347
- function migrationId(pluginName, filename) {
348
- return `plugin:${pluginName}/${filename}`;
349
- }
350
- function createMigrationTableSql() {
351
- return `
352
- CREATE TABLE IF NOT EXISTS junior_schema_migrations (
353
- id TEXT PRIMARY KEY,
354
- checksum TEXT NOT NULL,
355
- applied_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
356
- )
357
- `;
358
- }
359
- async function listAppliedMigrations(executor) {
360
- const rows = await executor.query(
361
- "SELECT id, checksum FROM junior_schema_migrations ORDER BY id ASC"
500
+ async function adoptLegacyMigrationState2(args) {
501
+ const legacyHashes = await legacyMigrationHashes(
502
+ args.executor,
503
+ args.pluginName
362
504
  );
363
- const records = /* @__PURE__ */ new Map();
364
- for (const row of rows) {
365
- const record = parseStoredMigrationRecord(row);
366
- records.set(record.id, record);
505
+ const migration = adoptedMigration(args.migrations, legacyHashes);
506
+ if (!migration) {
507
+ return void 0;
367
508
  }
368
- return records;
369
- }
370
- async function applyPluginMigration(executor, migration) {
371
- await executor.transaction(async () => {
372
- await executor.execute(migration.sql);
373
- await executor.execute(
374
- "INSERT INTO junior_schema_migrations (id, checksum) VALUES ($1, $2)",
375
- [migration.id, migration.checksum]
509
+ await args.executor.transaction(async () => {
510
+ await args.executor.execute("CREATE SCHEMA IF NOT EXISTS drizzle");
511
+ await args.executor.execute(`
512
+ CREATE TABLE IF NOT EXISTS drizzle.${args.table} (
513
+ id SERIAL PRIMARY KEY,
514
+ hash TEXT NOT NULL,
515
+ created_at BIGINT
516
+ )
517
+ `);
518
+ await args.executor.execute(
519
+ `INSERT INTO drizzle.${args.table} (hash, created_at) VALUES ($1, $2)`,
520
+ [migration.hash, migration.folderMillis]
376
521
  );
377
522
  });
523
+ return migration.folderMillis;
378
524
  }
379
- function readPluginMigrations(root) {
380
- const migrationsDir = root.dir;
381
- let stat;
382
- try {
383
- stat = statSync(migrationsDir);
384
- } catch {
385
- return [];
386
- }
387
- if (!stat.isDirectory()) {
388
- throw new Error(
389
- `Plugin "${root.pluginName}" migrations path is not a directory`
390
- );
391
- }
392
- return readdirSync(migrationsDir).filter((filename) => filename.endsWith(".sql")).sort((left, right) => left.localeCompare(right)).map((filename) => {
393
- assertMigrationFilename(filename);
394
- const sql = readFileSync(path.join(migrationsDir, filename), "utf8");
395
- if (!sql.trim()) {
396
- throw new Error(
397
- `Plugin "${root.pluginName}" migration "${filename}" is empty`
398
- );
399
- }
400
- return {
401
- checksum: checksumSql(sql),
402
- filename,
403
- id: migrationId(root.pluginName, filename),
404
- pluginName: root.pluginName,
405
- sql
406
- };
407
- });
525
+ function appliedCount(migrations, createdAt) {
526
+ return createdAt === void 0 ? 0 : migrations.filter((migration) => migration.folderMillis <= createdAt).length;
408
527
  }
409
- async function migratePluginSchemas(executor, migrations) {
410
- assertUniqueMigrationIds(migrations);
528
+ async function migratePluginSchemas(executor, roots) {
411
529
  const result = {
412
530
  existing: 0,
413
531
  migrated: 0,
414
- scanned: migrations.length
532
+ scanned: 0
415
533
  };
416
- await executor.withLock(PLUGIN_SCHEMA_LOCK_NAME, async () => {
417
- await executor.execute(createMigrationTableSql());
418
- const applied = await listAppliedMigrations(executor);
419
- for (const migration of migrations) {
420
- const existing = applied.get(migration.id);
421
- if (existing) {
422
- if (existing.checksum !== migration.checksum) {
423
- throw new Error(`Plugin migration ${migration.id} checksum changed`);
424
- }
425
- result.existing++;
426
- continue;
427
- }
428
- await applyPluginMigration(executor, migration);
429
- result.migrated++;
430
- }
431
- });
534
+ const orderedRoots = [...roots].sort(
535
+ (left, right) => left.pluginName.localeCompare(right.pluginName)
536
+ );
537
+ for (const root of orderedRoots) {
538
+ const migrations = readMigrationFiles2({ migrationsFolder: root.dir });
539
+ const table = migrationTable(root.pluginName);
540
+ await executor.withMigrationLock(table, async () => {
541
+ const currentTime = await appliedMigrationTime(executor, table) ?? await adoptLegacyMigrationState2({
542
+ executor,
543
+ migrations,
544
+ pluginName: root.pluginName,
545
+ table
546
+ });
547
+ const existing = appliedCount(migrations, currentTime);
548
+ await executor.migrate({
549
+ migrationsFolder: root.dir,
550
+ migrationsTable: table
551
+ });
552
+ result.scanned += migrations.length;
553
+ result.existing += existing;
554
+ result.migrated += migrations.length - existing;
555
+ });
556
+ }
432
557
  return result;
433
558
  }
434
559
 
@@ -442,8 +567,10 @@ async function migratePluginsToSql(context) {
442
567
  driver: sql.driver
443
568
  });
444
569
  try {
445
- const migrations = pluginCatalogRuntime.getMigrationRoots().flatMap((root) => readPluginMigrations(root));
446
- const result = await migratePluginSchemas(executor, migrations);
570
+ const result = await migratePluginSchemas(
571
+ executor,
572
+ pluginCatalogRuntime.getMigrationRoots()
573
+ );
447
574
  return {
448
575
  existing: result.existing,
449
576
  migrated: result.migrated,
@@ -999,6 +1126,7 @@ var DEFAULT_IO = {
999
1126
  var localPluginLoader = createJiti(import.meta.url, { moduleCache: false });
1000
1127
  var MIGRATIONS = [
1001
1128
  redisConversationStateMigration,
1129
+ coreSqlSchemaMigration,
1002
1130
  sqlConversationMigration,
1003
1131
  sqlConversationHistoryMigration,
1004
1132
  sqlPluginMigration,
package/dist/db/db.d.ts CHANGED
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import type { PgDatabase } from "drizzle-orm/pg-core";
9
9
  import type { PgQueryResultHKT } from "drizzle-orm/pg-core/session";
10
+ import type { MigrationConfig } from "drizzle-orm/migrator";
10
11
  import type { juniorSqlSchema } from "./schema";
11
12
  export type JuniorDatabase = PgDatabase<PgQueryResultHKT, typeof juniorSqlSchema>;
12
13
  export interface JuniorSqlDatabase {
@@ -16,7 +17,10 @@ export interface JuniorSqlDatabase {
16
17
  }
17
18
  export interface JuniorSqlMigrationExecutor extends JuniorSqlDatabase {
18
19
  execute(statement: string, params?: readonly unknown[]): Promise<void>;
20
+ migrate(config: MigrationConfig): Promise<void>;
19
21
  query<T = unknown>(statement: string, params?: readonly unknown[]): Promise<T[]>;
22
+ /** Serialize adoption and migration for one Drizzle journal. */
23
+ withMigrationLock<T>(migrationTable: string, callback: () => Promise<T>): Promise<T>;
20
24
  }
21
25
  export interface JuniorSqlExecutor extends JuniorSqlMigrationExecutor {
22
26
  close(): Promise<void>;
@@ -447,6 +447,134 @@ export declare const juniorConversations: import("drizzle-orm/pg-core").PgTableW
447
447
  identity: undefined;
448
448
  generated: undefined;
449
449
  }, {}, {}>;
450
+ durationMs: import("drizzle-orm/pg-core").PgColumn<{
451
+ name: "duration_ms";
452
+ tableName: "junior_conversations";
453
+ dataType: "number";
454
+ columnType: "PgInteger";
455
+ data: number;
456
+ driverParam: string | number;
457
+ notNull: true;
458
+ hasDefault: true;
459
+ isPrimaryKey: false;
460
+ isAutoincrement: false;
461
+ hasRuntimeDefault: false;
462
+ enumValues: undefined;
463
+ baseColumn: never;
464
+ identity: undefined;
465
+ generated: undefined;
466
+ }, {}, {}>;
467
+ usage: import("drizzle-orm/pg-core").PgColumn<{
468
+ name: "usage_json";
469
+ tableName: "junior_conversations";
470
+ dataType: "json";
471
+ columnType: "PgJsonb";
472
+ data: {
473
+ inputTokens?: number | undefined;
474
+ outputTokens?: number | undefined;
475
+ cachedInputTokens?: number | undefined;
476
+ cacheCreationTokens?: number | undefined;
477
+ reasoningTokens?: number | undefined;
478
+ totalTokens?: number | undefined;
479
+ cost?: {
480
+ input?: number | undefined;
481
+ output?: number | undefined;
482
+ cacheRead?: number | undefined;
483
+ cacheWrite?: number | undefined;
484
+ total?: number | undefined;
485
+ } | undefined;
486
+ };
487
+ driverParam: unknown;
488
+ notNull: false;
489
+ hasDefault: false;
490
+ isPrimaryKey: false;
491
+ isAutoincrement: false;
492
+ hasRuntimeDefault: false;
493
+ enumValues: undefined;
494
+ baseColumn: never;
495
+ identity: undefined;
496
+ generated: undefined;
497
+ }, {}, {
498
+ $type: {
499
+ inputTokens?: number | undefined;
500
+ outputTokens?: number | undefined;
501
+ cachedInputTokens?: number | undefined;
502
+ cacheCreationTokens?: number | undefined;
503
+ reasoningTokens?: number | undefined;
504
+ totalTokens?: number | undefined;
505
+ cost?: {
506
+ input?: number | undefined;
507
+ output?: number | undefined;
508
+ cacheRead?: number | undefined;
509
+ cacheWrite?: number | undefined;
510
+ total?: number | undefined;
511
+ } | undefined;
512
+ };
513
+ }>;
514
+ executionDurationMs: import("drizzle-orm/pg-core").PgColumn<{
515
+ name: "execution_duration_ms";
516
+ tableName: "junior_conversations";
517
+ dataType: "number";
518
+ columnType: "PgInteger";
519
+ data: number;
520
+ driverParam: string | number;
521
+ notNull: true;
522
+ hasDefault: true;
523
+ isPrimaryKey: false;
524
+ isAutoincrement: false;
525
+ hasRuntimeDefault: false;
526
+ enumValues: undefined;
527
+ baseColumn: never;
528
+ identity: undefined;
529
+ generated: undefined;
530
+ }, {}, {}>;
531
+ executionUsage: import("drizzle-orm/pg-core").PgColumn<{
532
+ name: "execution_usage_json";
533
+ tableName: "junior_conversations";
534
+ dataType: "json";
535
+ columnType: "PgJsonb";
536
+ data: {
537
+ inputTokens?: number | undefined;
538
+ outputTokens?: number | undefined;
539
+ cachedInputTokens?: number | undefined;
540
+ cacheCreationTokens?: number | undefined;
541
+ reasoningTokens?: number | undefined;
542
+ totalTokens?: number | undefined;
543
+ cost?: {
544
+ input?: number | undefined;
545
+ output?: number | undefined;
546
+ cacheRead?: number | undefined;
547
+ cacheWrite?: number | undefined;
548
+ total?: number | undefined;
549
+ } | undefined;
550
+ };
551
+ driverParam: unknown;
552
+ notNull: false;
553
+ hasDefault: false;
554
+ isPrimaryKey: false;
555
+ isAutoincrement: false;
556
+ hasRuntimeDefault: false;
557
+ enumValues: undefined;
558
+ baseColumn: never;
559
+ identity: undefined;
560
+ generated: undefined;
561
+ }, {}, {
562
+ $type: {
563
+ inputTokens?: number | undefined;
564
+ outputTokens?: number | undefined;
565
+ cachedInputTokens?: number | undefined;
566
+ cacheCreationTokens?: number | undefined;
567
+ reasoningTokens?: number | undefined;
568
+ totalTokens?: number | undefined;
569
+ cost?: {
570
+ input?: number | undefined;
571
+ output?: number | undefined;
572
+ cacheRead?: number | undefined;
573
+ cacheWrite?: number | undefined;
574
+ total?: number | undefined;
575
+ } | undefined;
576
+ };
577
+ }>;
450
578
  };
451
579
  dialect: "pg";
452
580
  }>;