@sentry/junior-memory 0.131.0 → 0.133.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.
@@ -0,0 +1,4 @@
1
+ CREATE EXTENSION IF NOT EXISTS btree_gin;--> statement-breakpoint
2
+ DROP INDEX "junior_memory_memories_search_idx";--> statement-breakpoint
3
+ ALTER TABLE "junior_memory_memories" ADD COLUMN "search_vector" "tsvector" GENERATED ALWAYS AS (to_tsvector('english', "content")) STORED;--> statement-breakpoint
4
+ CREATE INDEX "junior_memory_memories_search_idx" ON "junior_memory_memories" USING gin ("scope","scope_key","search_vector") WHERE "junior_memory_memories"."archived_at_ms" IS NULL AND "junior_memory_memories"."superseded_at_ms" IS NULL AND "junior_memory_memories"."superseded_by_id" IS NULL;
@@ -0,0 +1,386 @@
1
+ {
2
+ "id": "f18fa309-6485-40f3-921a-5d65552fbdf6",
3
+ "prevId": "6948ea3f-2e44-49ce-aa94-8a993ce61cec",
4
+ "version": "7",
5
+ "dialect": "postgresql",
6
+ "tables": {
7
+ "public.junior_memory_embeddings": {
8
+ "name": "junior_memory_embeddings",
9
+ "schema": "",
10
+ "columns": {
11
+ "memory_id": {
12
+ "name": "memory_id",
13
+ "type": "text",
14
+ "primaryKey": true,
15
+ "notNull": true
16
+ },
17
+ "provider": {
18
+ "name": "provider",
19
+ "type": "text",
20
+ "primaryKey": false,
21
+ "notNull": true
22
+ },
23
+ "model": {
24
+ "name": "model",
25
+ "type": "text",
26
+ "primaryKey": false,
27
+ "notNull": true
28
+ },
29
+ "dimensions": {
30
+ "name": "dimensions",
31
+ "type": "integer",
32
+ "primaryKey": false,
33
+ "notNull": true
34
+ },
35
+ "metric": {
36
+ "name": "metric",
37
+ "type": "text",
38
+ "primaryKey": false,
39
+ "notNull": true
40
+ },
41
+ "content_hash": {
42
+ "name": "content_hash",
43
+ "type": "text",
44
+ "primaryKey": false,
45
+ "notNull": true
46
+ },
47
+ "embedding": {
48
+ "name": "embedding",
49
+ "type": "vector(1536)",
50
+ "primaryKey": false,
51
+ "notNull": true
52
+ },
53
+ "created_at_ms": {
54
+ "name": "created_at_ms",
55
+ "type": "bigint",
56
+ "primaryKey": false,
57
+ "notNull": true
58
+ }
59
+ },
60
+ "indexes": {
61
+ "junior_memory_embeddings_model_idx": {
62
+ "name": "junior_memory_embeddings_model_idx",
63
+ "columns": [
64
+ {
65
+ "expression": "provider",
66
+ "isExpression": false,
67
+ "asc": true,
68
+ "nulls": "last"
69
+ },
70
+ {
71
+ "expression": "model",
72
+ "isExpression": false,
73
+ "asc": true,
74
+ "nulls": "last"
75
+ },
76
+ {
77
+ "expression": "dimensions",
78
+ "isExpression": false,
79
+ "asc": true,
80
+ "nulls": "last"
81
+ },
82
+ {
83
+ "expression": "metric",
84
+ "isExpression": false,
85
+ "asc": true,
86
+ "nulls": "last"
87
+ }
88
+ ],
89
+ "isUnique": false,
90
+ "concurrently": false,
91
+ "method": "btree",
92
+ "with": {}
93
+ }
94
+ },
95
+ "foreignKeys": {
96
+ "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk": {
97
+ "name": "junior_memory_embeddings_memory_id_junior_memory_memories_id_fk",
98
+ "tableFrom": "junior_memory_embeddings",
99
+ "tableTo": "junior_memory_memories",
100
+ "columnsFrom": ["memory_id"],
101
+ "columnsTo": ["id"],
102
+ "onDelete": "cascade",
103
+ "onUpdate": "no action"
104
+ }
105
+ },
106
+ "compositePrimaryKeys": {},
107
+ "uniqueConstraints": {},
108
+ "policies": {},
109
+ "checkConstraints": {
110
+ "junior_memory_embeddings_metric_check": {
111
+ "name": "junior_memory_embeddings_metric_check",
112
+ "value": "\"junior_memory_embeddings\".\"metric\" IN ('cosine')"
113
+ },
114
+ "junior_memory_embeddings_dimensions_check": {
115
+ "name": "junior_memory_embeddings_dimensions_check",
116
+ "value": "\"junior_memory_embeddings\".\"dimensions\" = 1536"
117
+ }
118
+ },
119
+ "isRLSEnabled": false
120
+ },
121
+ "public.junior_memory_memories": {
122
+ "name": "junior_memory_memories",
123
+ "schema": "",
124
+ "columns": {
125
+ "id": {
126
+ "name": "id",
127
+ "type": "text",
128
+ "primaryKey": true,
129
+ "notNull": true
130
+ },
131
+ "scope": {
132
+ "name": "scope",
133
+ "type": "text",
134
+ "primaryKey": false,
135
+ "notNull": true
136
+ },
137
+ "scope_key": {
138
+ "name": "scope_key",
139
+ "type": "text",
140
+ "primaryKey": false,
141
+ "notNull": true
142
+ },
143
+ "type": {
144
+ "name": "type",
145
+ "type": "text",
146
+ "primaryKey": false,
147
+ "notNull": true
148
+ },
149
+ "subject_type": {
150
+ "name": "subject_type",
151
+ "type": "text",
152
+ "primaryKey": false,
153
+ "notNull": true
154
+ },
155
+ "subject_key": {
156
+ "name": "subject_key",
157
+ "type": "text",
158
+ "primaryKey": false,
159
+ "notNull": false
160
+ },
161
+ "content": {
162
+ "name": "content",
163
+ "type": "text",
164
+ "primaryKey": false,
165
+ "notNull": true
166
+ },
167
+ "search_vector": {
168
+ "name": "search_vector",
169
+ "type": "tsvector",
170
+ "primaryKey": false,
171
+ "notNull": false,
172
+ "generated": {
173
+ "as": "to_tsvector('english', \"content\")",
174
+ "type": "stored"
175
+ }
176
+ },
177
+ "source_platform": {
178
+ "name": "source_platform",
179
+ "type": "text",
180
+ "primaryKey": false,
181
+ "notNull": true
182
+ },
183
+ "source_key": {
184
+ "name": "source_key",
185
+ "type": "text",
186
+ "primaryKey": false,
187
+ "notNull": true
188
+ },
189
+ "idempotency_key": {
190
+ "name": "idempotency_key",
191
+ "type": "text",
192
+ "primaryKey": false,
193
+ "notNull": false
194
+ },
195
+ "observed_at_ms": {
196
+ "name": "observed_at_ms",
197
+ "type": "bigint",
198
+ "primaryKey": false,
199
+ "notNull": true
200
+ },
201
+ "created_at_ms": {
202
+ "name": "created_at_ms",
203
+ "type": "bigint",
204
+ "primaryKey": false,
205
+ "notNull": true
206
+ },
207
+ "expires_at_ms": {
208
+ "name": "expires_at_ms",
209
+ "type": "bigint",
210
+ "primaryKey": false,
211
+ "notNull": false
212
+ },
213
+ "superseded_at_ms": {
214
+ "name": "superseded_at_ms",
215
+ "type": "bigint",
216
+ "primaryKey": false,
217
+ "notNull": false
218
+ },
219
+ "superseded_by_id": {
220
+ "name": "superseded_by_id",
221
+ "type": "text",
222
+ "primaryKey": false,
223
+ "notNull": false
224
+ },
225
+ "archived_at_ms": {
226
+ "name": "archived_at_ms",
227
+ "type": "bigint",
228
+ "primaryKey": false,
229
+ "notNull": false
230
+ },
231
+ "archive_reason": {
232
+ "name": "archive_reason",
233
+ "type": "text",
234
+ "primaryKey": false,
235
+ "notNull": false
236
+ }
237
+ },
238
+ "indexes": {
239
+ "junior_memory_memories_visible_idx": {
240
+ "name": "junior_memory_memories_visible_idx",
241
+ "columns": [
242
+ {
243
+ "expression": "scope",
244
+ "isExpression": false,
245
+ "asc": true,
246
+ "nulls": "last"
247
+ },
248
+ {
249
+ "expression": "scope_key",
250
+ "isExpression": false,
251
+ "asc": true,
252
+ "nulls": "last"
253
+ },
254
+ {
255
+ "expression": "created_at_ms",
256
+ "isExpression": false,
257
+ "asc": false,
258
+ "nulls": "last"
259
+ },
260
+ {
261
+ "expression": "id",
262
+ "isExpression": false,
263
+ "asc": true,
264
+ "nulls": "last"
265
+ }
266
+ ],
267
+ "isUnique": false,
268
+ "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL",
269
+ "concurrently": false,
270
+ "method": "btree",
271
+ "with": {}
272
+ },
273
+ "junior_memory_memories_expiration_idx": {
274
+ "name": "junior_memory_memories_expiration_idx",
275
+ "columns": [
276
+ {
277
+ "expression": "expires_at_ms",
278
+ "isExpression": false,
279
+ "asc": true,
280
+ "nulls": "last"
281
+ }
282
+ ],
283
+ "isUnique": false,
284
+ "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"expires_at_ms\" IS NOT NULL",
285
+ "concurrently": false,
286
+ "method": "btree",
287
+ "with": {}
288
+ },
289
+ "junior_memory_memories_search_idx": {
290
+ "name": "junior_memory_memories_search_idx",
291
+ "columns": [
292
+ {
293
+ "expression": "scope",
294
+ "isExpression": false,
295
+ "asc": true,
296
+ "nulls": "last"
297
+ },
298
+ {
299
+ "expression": "scope_key",
300
+ "isExpression": false,
301
+ "asc": true,
302
+ "nulls": "last"
303
+ },
304
+ {
305
+ "expression": "search_vector",
306
+ "isExpression": false,
307
+ "asc": true,
308
+ "nulls": "last"
309
+ }
310
+ ],
311
+ "isUnique": false,
312
+ "where": "\"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL",
313
+ "concurrently": false,
314
+ "method": "gin",
315
+ "with": {}
316
+ },
317
+ "junior_memory_memories_idempotency_idx": {
318
+ "name": "junior_memory_memories_idempotency_idx",
319
+ "columns": [
320
+ {
321
+ "expression": "scope",
322
+ "isExpression": false,
323
+ "asc": true,
324
+ "nulls": "last"
325
+ },
326
+ {
327
+ "expression": "scope_key",
328
+ "isExpression": false,
329
+ "asc": true,
330
+ "nulls": "last"
331
+ },
332
+ {
333
+ "expression": "idempotency_key",
334
+ "isExpression": false,
335
+ "asc": true,
336
+ "nulls": "last"
337
+ }
338
+ ],
339
+ "isUnique": true,
340
+ "where": "\"junior_memory_memories\".\"idempotency_key\" IS NOT NULL AND \"junior_memory_memories\".\"archived_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_at_ms\" IS NULL AND \"junior_memory_memories\".\"superseded_by_id\" IS NULL",
341
+ "concurrently": false,
342
+ "method": "btree",
343
+ "with": {}
344
+ }
345
+ },
346
+ "foreignKeys": {},
347
+ "compositePrimaryKeys": {},
348
+ "uniqueConstraints": {},
349
+ "policies": {},
350
+ "checkConstraints": {
351
+ "junior_memory_memories_scope_check": {
352
+ "name": "junior_memory_memories_scope_check",
353
+ "value": "\"junior_memory_memories\".\"scope\" IN ('personal', 'conversation')"
354
+ },
355
+ "junior_memory_memories_kind_check": {
356
+ "name": "junior_memory_memories_kind_check",
357
+ "value": "\"junior_memory_memories\".\"type\" IN (\n 'preference',\n 'procedure',\n 'knowledge'\n )"
358
+ },
359
+ "junior_memory_memories_subject_type_check": {
360
+ "name": "junior_memory_memories_subject_type_check",
361
+ "value": "\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation', 'general')"
362
+ },
363
+ "junior_memory_memories_subject_key_check": {
364
+ "name": "junior_memory_memories_subject_key_check",
365
+ "value": "(\"junior_memory_memories\".\"subject_type\" = 'general' AND \"junior_memory_memories\".\"subject_key\" IS NULL) OR (\"junior_memory_memories\".\"subject_type\" IN ('user', 'conversation') AND \"junior_memory_memories\".\"subject_key\" IS NOT NULL AND length(\"junior_memory_memories\".\"subject_key\") > 0)"
366
+ },
367
+ "junior_memory_memories_source_platform_check": {
368
+ "name": "junior_memory_memories_source_platform_check",
369
+ "value": "\"junior_memory_memories\".\"source_platform\" IN ('slack', 'local')"
370
+ }
371
+ },
372
+ "isRLSEnabled": false
373
+ }
374
+ },
375
+ "enums": {},
376
+ "schemas": {},
377
+ "sequences": {},
378
+ "roles": {},
379
+ "policies": {},
380
+ "views": {},
381
+ "_meta": {
382
+ "columns": {},
383
+ "schemas": {},
384
+ "tables": {}
385
+ }
386
+ }
@@ -43,6 +43,13 @@
43
43
  "when": 1785293874649,
44
44
  "tag": "0005_short_jamie_braddock",
45
45
  "breakpoints": true
46
+ },
47
+ {
48
+ "idx": 6,
49
+ "version": "7",
50
+ "when": 1785878952301,
51
+ "tag": "0006_friendly_hydra",
52
+ "breakpoints": true
46
53
  }
47
54
  ]
48
55
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior-memory",
3
- "version": "0.131.0",
3
+ "version": "0.133.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -27,7 +27,7 @@
27
27
  "commander": "^14.0.3",
28
28
  "drizzle-orm": "^0.45.2",
29
29
  "zod": "^4.4.3",
30
- "@sentry/junior-plugin-api": "0.131.0"
30
+ "@sentry/junior-plugin-api": "0.133.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^25.9.1",
package/src/db/schema.ts CHANGED
@@ -8,6 +8,7 @@ import { sql } from "drizzle-orm";
8
8
  import {
9
9
  bigint,
10
10
  check,
11
+ customType,
11
12
  index,
12
13
  integer,
13
14
  pgTable,
@@ -24,6 +25,12 @@ import {
24
25
  MEMORY_KINDS,
25
26
  } from "../types";
26
27
 
28
+ const tsvector = customType<{ data: string }>({
29
+ dataType() {
30
+ return "tsvector";
31
+ },
32
+ });
33
+
27
34
  export const juniorMemoryMemories = pgTable(
28
35
  "junior_memory_memories",
29
36
  {
@@ -34,6 +41,9 @@ export const juniorMemoryMemories = pgTable(
34
41
  subjectType: text("subject_type", { enum: MEMORY_SUBJECT_TYPES }).notNull(),
35
42
  subjectKey: text("subject_key"),
36
43
  content: text("content").notNull(),
44
+ searchVector: tsvector("search_vector").generatedAlwaysAs(
45
+ sql`to_tsvector('english', "content")`,
46
+ ),
37
47
  sourcePlatform: text("source_platform", {
38
48
  enum: MEMORY_SOURCE_PLATFORMS,
39
49
  }).notNull(),
@@ -59,7 +69,7 @@ export const juniorMemoryMemories = pgTable(
59
69
  sql`${table.archivedAtMs} IS NULL AND ${table.expiresAtMs} IS NOT NULL`,
60
70
  ),
61
71
  index("junior_memory_memories_search_idx")
62
- .using("gin", sql`to_tsvector('english', ${table.content})`)
72
+ .using("gin", table.scope, table.scopeKey, table.searchVector)
63
73
  .where(
64
74
  sql`${table.archivedAtMs} IS NULL AND ${table.supersededAtMs} IS NULL AND ${table.supersededById} IS NULL`,
65
75
  ),
package/src/store.ts CHANGED
@@ -52,6 +52,8 @@ const DEFAULT_EXPIRED_ARCHIVE_LIMIT = 100;
52
52
  const PREFERENCE_ADJUDICATION_CANDIDATE_LIMIT = 10;
53
53
  const PREFERENCE_ADJUDICATION_VECTOR_LIMIT = 5;
54
54
  const VECTOR_SEARCH_OVERFETCH = 4;
55
+ const LEXICAL_RANK_OVERFETCH = 4;
56
+ const MAX_LEXICAL_RANK_CANDIDATES = 1_000;
55
57
  const MAX_MEMORY_CONTENT_CHARS = 4_000;
56
58
  const EMBEDDING_METRIC = "cosine";
57
59
 
@@ -128,6 +130,7 @@ const memoryRowSchema = z
128
130
  id: z.string().min(1),
129
131
  idempotencyKey: optionalStringSchema,
130
132
  observedAtMs: z.coerce.number(),
133
+ searchVector: z.string().optional(),
131
134
  scope: z.enum(MEMORY_SCOPES),
132
135
  scopeKey: z.string().min(1),
133
136
  sourceKey: z.string().min(1),
@@ -946,7 +949,7 @@ async function listVisibleMemories(args: {
946
949
  return rows.map(parseMemoryRow);
947
950
  }
948
951
 
949
- /** Search active visible records with PostgreSQL full-text ranking. */
952
+ /** Search a bounded active candidate set with PostgreSQL full-text ranking. */
950
953
  async function searchVisibleLexicalMemories(args: {
951
954
  db: MemoryDb;
952
955
  limit: number;
@@ -966,20 +969,49 @@ async function searchVisibleLexicalMemories(args: {
966
969
  )
967
970
  FROM unnest(tsvector_to_array(${queryVector})) AS query_terms(term)
968
971
  )`;
969
- const textsearch = sql`to_tsvector('english', ${juniorMemoryMemories.content})`;
970
- const textRank = sql<number>`ts_rank_cd(${textsearch}, ${tsquery})`;
971
- const rows = await args.db
972
- .select({
973
- memory: juniorMemoryMemories,
974
- textRank,
975
- })
972
+ const candidateLimit = Math.min(
973
+ MAX_LEXICAL_RANK_CANDIDATES,
974
+ args.limit * LEXICAL_RANK_OVERFETCH,
975
+ );
976
+ const candidates = args.db
977
+ .select()
976
978
  .from(juniorMemoryMemories)
977
- .where(and(predicate, sql`${textsearch} @@ ${tsquery}`))
979
+ .where(
980
+ and(predicate, sql`${juniorMemoryMemories.searchVector} @@ ${tsquery}`),
981
+ )
978
982
  .orderBy(
979
- desc(textRank),
980
983
  desc(juniorMemoryMemories.observedAtMs),
981
984
  asc(juniorMemoryMemories.id),
982
985
  )
986
+ .limit(candidateLimit)
987
+ .as("lexical_candidates");
988
+ const textRank = sql<number>`ts_rank_cd(${candidates.searchVector}, ${tsquery})`;
989
+ const rows = await args.db
990
+ .select({
991
+ memory: {
992
+ archiveReason: candidates.archiveReason,
993
+ archivedAtMs: candidates.archivedAtMs,
994
+ content: candidates.content,
995
+ createdAtMs: candidates.createdAtMs,
996
+ expiresAtMs: candidates.expiresAtMs,
997
+ id: candidates.id,
998
+ idempotencyKey: candidates.idempotencyKey,
999
+ kind: candidates.kind,
1000
+ observedAtMs: candidates.observedAtMs,
1001
+ scope: candidates.scope,
1002
+ scopeKey: candidates.scopeKey,
1003
+ searchVector: candidates.searchVector,
1004
+ sourceKey: candidates.sourceKey,
1005
+ sourcePlatform: candidates.sourcePlatform,
1006
+ subjectKey: candidates.subjectKey,
1007
+ subjectType: candidates.subjectType,
1008
+ supersededAtMs: candidates.supersededAtMs,
1009
+ supersededById: candidates.supersededById,
1010
+ },
1011
+ textRank,
1012
+ })
1013
+ .from(candidates)
1014
+ .orderBy(desc(textRank), desc(candidates.observedAtMs), asc(candidates.id))
983
1015
  .limit(args.limit);
984
1016
  const ranks = denseRanks(rows, (row) => Number(row.textRank));
985
1017
  return rows.map((row, index) => ({