@dxos/index-core 0.8.4-main.9735255 → 0.8.4-main.abd8ff62ef

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 (37) hide show
  1. package/dist/lib/{node-esm → neutral}/index.mjs +200 -47
  2. package/dist/lib/neutral/index.mjs.map +7 -0
  3. package/dist/lib/neutral/meta.json +1 -0
  4. package/dist/types/src/index-engine.d.ts +44 -8
  5. package/dist/types/src/index-engine.d.ts.map +1 -1
  6. package/dist/types/src/index-tracker.d.ts +3 -3
  7. package/dist/types/src/index-tracker.d.ts.map +1 -1
  8. package/dist/types/src/index.d.ts +2 -1
  9. package/dist/types/src/index.d.ts.map +1 -1
  10. package/dist/types/src/indexes/fts-index.d.ts +2 -1
  11. package/dist/types/src/indexes/fts-index.d.ts.map +1 -1
  12. package/dist/types/src/indexes/interface.d.ts +4 -0
  13. package/dist/types/src/indexes/interface.d.ts.map +1 -1
  14. package/dist/types/src/indexes/object-meta-index.d.ts +46 -4
  15. package/dist/types/src/indexes/object-meta-index.d.ts.map +1 -1
  16. package/dist/types/src/indexes/reverse-ref-index.d.ts.map +1 -1
  17. package/dist/types/src/utils.d.ts +1 -1
  18. package/dist/types/src/utils.d.ts.map +1 -1
  19. package/dist/types/tsconfig.tsbuildinfo +1 -1
  20. package/package.json +18 -19
  21. package/src/index-engine.test.ts +155 -4
  22. package/src/index-engine.ts +125 -29
  23. package/src/index-tracker.ts +9 -0
  24. package/src/index.ts +8 -1
  25. package/src/indexes/fts-index.test.ts +131 -3
  26. package/src/indexes/fts-index.ts +25 -8
  27. package/src/indexes/interface.ts +5 -0
  28. package/src/indexes/object-meta-index.test.ts +143 -13
  29. package/src/indexes/object-meta-index.ts +192 -15
  30. package/src/indexes/reverse-ref-index.test.ts +9 -2
  31. package/src/indexes/reverse-ref-index.ts +0 -1
  32. package/src/utils.ts +1 -1
  33. package/dist/lib/browser/index.mjs +0 -630
  34. package/dist/lib/browser/index.mjs.map +0 -7
  35. package/dist/lib/browser/meta.json +0 -1
  36. package/dist/lib/node-esm/index.mjs.map +0 -7
  37. package/dist/lib/node-esm/meta.json +0 -1
@@ -1,7 +1,6 @@
1
- import { createRequire } from 'node:module';const require = createRequire(import.meta.url);
2
-
3
1
  // src/index-engine.ts
4
2
  import * as Effect5 from "effect/Effect";
3
+ import { ATTR_TYPE as ATTR_TYPE2 } from "@dxos/echo/internal";
5
4
  import * as SqlTransaction from "@dxos/sql-sqlite/SqlTransaction";
6
5
 
7
6
  // src/index-tracker.ts
@@ -33,6 +32,9 @@ var IndexCursor = Schema.Struct({
33
32
  */
34
33
  cursor: Schema.Union(Schema.Number, Schema.String)
35
34
  });
35
+ var DEPRECATED_INDEX_NAMES = [
36
+ "fts"
37
+ ];
36
38
  var IndexTracker = class {
37
39
  migrate = Effect.fn("IndexTracker.migrate")(function* () {
38
40
  const sql = yield* SqlClient.SqlClient;
@@ -44,6 +46,9 @@ var IndexTracker = class {
44
46
  cursor,
45
47
  PRIMARY KEY (indexName, spaceId, sourceName, resourceId)
46
48
  )`;
49
+ yield* Effect.forEach(DEPRECATED_INDEX_NAMES, (indexName) => {
50
+ return sql`DELETE FROM indexCursor WHERE indexName = ${indexName}`;
51
+ });
47
52
  });
48
53
  queryCursors = Effect.fn("IndexTracker.queryCursors")((query) => Effect.gen(function* () {
49
54
  const sql = yield* SqlClient.SqlClient;
@@ -84,6 +89,7 @@ var IndexTracker = class {
84
89
  // src/indexes/fts-index.ts
85
90
  import * as SqlClient3 from "@effect/sql/SqlClient";
86
91
  import * as Effect2 from "effect/Effect";
92
+ var SQL_CHUNK_SIZE = 500;
87
93
  var escapeFts5Query = (text) => {
88
94
  return text.split(/\s+/).filter(Boolean).map((term) => `"${term.replace(/"/g, '""')}"`).join(" ");
89
95
  };
@@ -145,6 +151,7 @@ var FtsIndex = class {
145
151
  /**
146
152
  * Query snapshots by recordIds.
147
153
  * Returns the parsed JSON snapshots for queue objects.
154
+ * RecordIds not present in the FTS index are silently omitted from the result.
148
155
  */
149
156
  querySnapshotsJSON(recordIds) {
150
157
  return Effect2.gen(function* () {
@@ -152,11 +159,21 @@ var FtsIndex = class {
152
159
  return [];
153
160
  }
154
161
  const sql = yield* SqlClient3.SqlClient;
155
- const results = yield* sql`SELECT rowid, snapshot FROM ftsIndex WHERE rowid IN ${sql.in(recordIds)}`;
156
- return results.map((r) => ({
157
- recordId: r.rowid,
158
- snapshot: JSON.parse(r.snapshot)
159
- }));
162
+ const chunks = [];
163
+ for (let i = 0; i < recordIds.length; i += SQL_CHUNK_SIZE) {
164
+ chunks.push(recordIds.slice(i, i + SQL_CHUNK_SIZE));
165
+ }
166
+ const allResults = [];
167
+ for (const chunk of chunks) {
168
+ const rows = yield* sql`SELECT rowid, snapshot FROM ftsIndex WHERE rowid IN ${sql.in(chunk)}`;
169
+ for (const r of rows) {
170
+ allResults.push({
171
+ recordId: r.rowid,
172
+ snapshot: JSON.parse(r.snapshot)
173
+ });
174
+ }
175
+ }
176
+ return allResults;
160
177
  });
161
178
  }
162
179
  update = Effect2.fn("FtsIndex.update")((objects) => Effect2.gen(function* () {
@@ -182,7 +199,7 @@ var FtsIndex = class {
182
199
  import * as SqlClient5 from "@effect/sql/SqlClient";
183
200
  import * as Effect3 from "effect/Effect";
184
201
  import * as Schema2 from "effect/Schema";
185
- import { ATTR_DELETED, ATTR_RELATION_SOURCE, ATTR_RELATION_TARGET, ATTR_TYPE } from "@dxos/echo/internal";
202
+ import { ATTR_DELETED, ATTR_PARENT, ATTR_RELATION_SOURCE, ATTR_RELATION_TARGET, ATTR_TYPE } from "@dxos/echo/internal";
186
203
  import { DXN } from "@dxos/keys";
187
204
  var _escapeLikePrefix = (prefix) => {
188
205
  const escaped = prefix.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
@@ -200,9 +217,32 @@ var ObjectMeta = Schema2.Struct({
200
217
  deleted: Schema2.Boolean,
201
218
  source: Schema2.NullOr(Schema2.String),
202
219
  target: Schema2.NullOr(Schema2.String),
220
+ /** Parent object id (nullable). */
221
+ parent: Schema2.NullOr(Schema2.String),
203
222
  /** Monotonically increasing sequence number assigned on insert/update for tracking indexing order. */
204
- version: Schema2.Number
223
+ version: Schema2.Number,
224
+ /** Unix ms timestamp when the object was first indexed. */
225
+ createdAt: Schema2.NullOr(Schema2.Number),
226
+ /** Unix ms timestamp when the object was last re-indexed. */
227
+ updatedAt: Schema2.NullOr(Schema2.Number)
205
228
  });
229
+ var buildSourceCondition = (sql, spaceIds, includeAllQueues, queueIds) => {
230
+ const conditions = [];
231
+ if (spaceIds.length > 0) {
232
+ if (includeAllQueues) {
233
+ conditions.push(sql`${sql.in("spaceId", spaceIds)}`);
234
+ } else {
235
+ conditions.push(sql`(${sql.in("spaceId", spaceIds)} AND queueId = '')`);
236
+ }
237
+ }
238
+ if (queueIds && queueIds.length > 0) {
239
+ conditions.push(sql`${sql.in("queueId", queueIds)}`);
240
+ }
241
+ if (conditions.length === 0) {
242
+ return sql`1 = 0`;
243
+ }
244
+ return sql.or(conditions);
245
+ };
206
246
  var ObjectMetaIndex = class {
207
247
  migrate = Effect3.fn("ObjectMetaIndex.runMigrations")(function* () {
208
248
  const sql = yield* SqlClient5.SqlClient;
@@ -217,13 +257,22 @@ var ObjectMetaIndex = class {
217
257
  deleted INTEGER NOT NULL,
218
258
  source TEXT,
219
259
  target TEXT,
220
- version INTEGER NOT NULL
260
+ parent TEXT,
261
+ version INTEGER NOT NULL,
262
+ createdAt INTEGER,
263
+ updatedAt INTEGER
221
264
  )`;
265
+ yield* Effect3.catchAll(sql`ALTER TABLE objectMeta ADD COLUMN parent TEXT`, () => Effect3.void);
266
+ yield* Effect3.catchAll(sql`ALTER TABLE objectMeta ADD COLUMN createdAt INTEGER`, () => Effect3.void);
267
+ yield* Effect3.catchAll(sql`ALTER TABLE objectMeta ADD COLUMN updatedAt INTEGER`, () => Effect3.void);
222
268
  yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_objectId ON objectMeta(spaceId, objectId)`;
223
269
  yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_typeDxn ON objectMeta(spaceId, typeDxn)`;
224
270
  yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_version ON objectMeta(version)`;
271
+ yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_parent ON objectMeta(spaceId, parent)`;
272
+ yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_updatedAt ON objectMeta(updatedAt)`;
273
+ yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_createdAt ON objectMeta(createdAt)`;
225
274
  });
226
- query = Effect3.fn("ObjectMetaIndex.queryType")((query) => Effect3.gen(function* () {
275
+ query = Effect3.fn("ObjectMetaIndex.query")((query) => Effect3.gen(function* () {
227
276
  const sql = yield* SqlClient5.SqlClient;
228
277
  const parsedType = DXN.tryParse(query.typeDxn)?.asTypeDXN();
229
278
  const rows = parsedType && parsedType.version === void 0 ? yield* sql`SELECT * FROM objectMeta WHERE spaceId = ${query.spaceId} AND (typeDxn = ${query.typeDxn} OR typeDxn LIKE ${_escapeLikePrefix(query.typeDxn)} ESCAPE '\\')` : yield* sql`SELECT * FROM objectMeta WHERE spaceId = ${query.spaceId} AND typeDxn = ${query.typeDxn}`;
@@ -233,18 +282,19 @@ var ObjectMetaIndex = class {
233
282
  }));
234
283
  }));
235
284
  queryAll = Effect3.fn("ObjectMetaIndex.queryAll")((query) => Effect3.gen(function* () {
236
- if (query.spaceIds.length === 0) {
285
+ if (query.spaceIds.length === 0 && (!query.queueIds || query.queueIds.length === 0)) {
237
286
  return [];
238
287
  }
239
288
  const sql = yield* SqlClient5.SqlClient;
240
- const rows = yield* sql`SELECT * FROM objectMeta WHERE ${sql.in("spaceId", query.spaceIds)}`;
289
+ const sourceCondition = buildSourceCondition(sql, query.spaceIds, query.includeAllQueues ?? false, query.queueIds ?? null);
290
+ const rows = yield* sql`SELECT * FROM objectMeta WHERE ${sourceCondition}`;
241
291
  return rows.map((row) => ({
242
292
  ...row,
243
293
  deleted: !!row.deleted
244
294
  }));
245
295
  }));
246
- queryTypes = Effect3.fn("ObjectMetaIndex.queryTypes")(({ spaceIds, typeDxns, inverted = false }) => Effect3.gen(function* () {
247
- if (spaceIds.length === 0) {
296
+ queryTypes = Effect3.fn("ObjectMetaIndex.queryTypes")(({ spaceIds, typeDxns, inverted = false, includeAllQueues = false, queueIds = null }) => Effect3.gen(function* () {
297
+ if (spaceIds.length === 0 && (!queueIds || queueIds.length === 0)) {
248
298
  return [];
249
299
  }
250
300
  if (typeDxns.length === 0) {
@@ -252,14 +302,15 @@ var ObjectMetaIndex = class {
252
302
  return [];
253
303
  }
254
304
  const sql2 = yield* SqlClient5.SqlClient;
255
- const rows2 = yield* sql2`SELECT * FROM objectMeta WHERE ${sql2.in("spaceId", spaceIds)}`;
305
+ const sourceCondition2 = buildSourceCondition(sql2, spaceIds, includeAllQueues, queueIds);
306
+ const rows2 = yield* sql2`SELECT * FROM objectMeta WHERE ${sourceCondition2}`;
256
307
  return rows2.map((row) => ({
257
308
  ...row,
258
309
  deleted: !!row.deleted
259
310
  }));
260
311
  }
261
312
  const sql = yield* SqlClient5.SqlClient;
262
- const spaceWhere = sql.in("spaceId", spaceIds);
313
+ const sourceCondition = buildSourceCondition(sql, spaceIds, includeAllQueues, queueIds);
263
314
  const typeWhere = sql.or(typeDxns.map((typeDxn) => {
264
315
  const parsedType = DXN.tryParse(typeDxn)?.asTypeDXN();
265
316
  return parsedType && parsedType.version === void 0 ? sql.or([
@@ -267,7 +318,7 @@ var ObjectMetaIndex = class {
267
318
  sql`typeDxn LIKE ${_escapeLikePrefix(typeDxn)} ESCAPE '\\'`
268
319
  ]) : sql`typeDxn = ${typeDxn}`;
269
320
  }));
270
- const rows = inverted ? yield* sql`SELECT * FROM objectMeta WHERE ${spaceWhere} AND NOT ${typeWhere}` : yield* sql`SELECT * FROM objectMeta WHERE ${spaceWhere} AND ${typeWhere}`;
321
+ const rows = inverted ? yield* sql`SELECT * FROM objectMeta WHERE ${sourceCondition} AND NOT ${typeWhere}` : yield* sql`SELECT * FROM objectMeta WHERE ${sourceCondition} AND ${typeWhere}`;
271
322
  return rows.map((row) => ({
272
323
  ...row,
273
324
  deleted: !!row.deleted
@@ -308,6 +359,8 @@ var ObjectMetaIndex = class {
308
359
  const deleted = castData[ATTR_DELETED] ? 1 : 0;
309
360
  const source = entityKind === "relation" ? castData[ATTR_RELATION_SOURCE] ?? null : null;
310
361
  const target = entityKind === "relation" ? castData[ATTR_RELATION_TARGET] ?? null : null;
362
+ const parent = castData[ATTR_PARENT] ?? null;
363
+ const sourceTimestamp = object.updatedAt;
311
364
  if (existing.length > 0) {
312
365
  yield* sql`
313
366
  UPDATE objectMeta SET
@@ -316,18 +369,22 @@ var ObjectMetaIndex = class {
316
369
  typeDxn = ${typeDxn},
317
370
  deleted = ${deleted},
318
371
  source = ${source},
319
- target = ${target}
372
+ target = ${target},
373
+ parent = ${parent},
374
+ updatedAt = ${sourceTimestamp}
320
375
  WHERE recordId = ${existing[0].recordId}
321
376
  `;
322
377
  } else {
323
378
  yield* sql`
324
379
  INSERT INTO objectMeta (
325
380
  objectId, queueId, spaceId, documentId,
326
- entityKind, typeDxn, deleted, source, target, version
381
+ entityKind, typeDxn, deleted, source, target, parent, version,
382
+ createdAt, updatedAt
327
383
  ) VALUES (
328
384
  ${objectId}, ${queueId ?? ""}, ${spaceId}, ${documentId ?? ""},
329
385
  ${entityKind}, ${typeDxn}, ${deleted},
330
- ${source}, ${target}, ${version}
386
+ ${source}, ${target}, ${parent}, ${version},
387
+ ${sourceTimestamp}, ${sourceTimestamp}
331
388
  )
332
389
  `;
333
390
  }
@@ -353,7 +410,7 @@ var ObjectMetaIndex = class {
353
410
  result = [];
354
411
  }
355
412
  if (result.length === 0) {
356
- yield* Effect3.die(new Error(`Object not found in ObjectMetaIndex: ${spaceId}/${documentId ?? queueId}/${objectId}`));
413
+ return yield* Effect3.die(new Error(`Object not found in ObjectMetaIndex: ${spaceId}/${documentId ?? queueId}/${objectId}`));
357
414
  }
358
415
  object.recordId = result[0].recordId;
359
416
  }
@@ -372,6 +429,67 @@ var ObjectMetaIndex = class {
372
429
  deleted: !!row.deleted
373
430
  }));
374
431
  }));
432
+ /**
433
+ * Look up object metadata by objectId, spaceId, and queueId.
434
+ */
435
+ lookupByObjectId = Effect3.fn("ObjectMetaIndex.lookupByObjectId")((query) => Effect3.gen(function* () {
436
+ const sql = yield* SqlClient5.SqlClient;
437
+ const rows = yield* sql`SELECT * FROM objectMeta WHERE spaceId = ${query.spaceId} AND queueId = ${query.queueId} AND objectId = ${query.objectId} LIMIT 1`;
438
+ if (rows.length === 0) {
439
+ return null;
440
+ }
441
+ return {
442
+ ...rows[0],
443
+ deleted: !!rows[0].deleted
444
+ };
445
+ }));
446
+ /**
447
+ * Query objects by timestamp range.
448
+ */
449
+ queryByTimeRange = Effect3.fn("ObjectMetaIndex.queryByTimeRange")((query) => Effect3.gen(function* () {
450
+ if (query.spaceIds.length === 0 && (!query.queueIds || query.queueIds.length === 0)) {
451
+ return [];
452
+ }
453
+ const sql = yield* SqlClient5.SqlClient;
454
+ const sourceCondition = buildSourceCondition(sql, query.spaceIds, query.includeAllQueues ?? false, query.queueIds ?? null);
455
+ const timeConditions = [];
456
+ if (query.updatedAfter != null) {
457
+ timeConditions.push(sql`updatedAt >= ${query.updatedAfter}`);
458
+ }
459
+ if (query.updatedBefore != null) {
460
+ timeConditions.push(sql`updatedAt <= ${query.updatedBefore}`);
461
+ }
462
+ if (query.createdAfter != null) {
463
+ timeConditions.push(sql`createdAt >= ${query.createdAfter}`);
464
+ }
465
+ if (query.createdBefore != null) {
466
+ timeConditions.push(sql`createdAt <= ${query.createdBefore}`);
467
+ }
468
+ const rows = timeConditions.length > 0 ? yield* sql`SELECT * FROM objectMeta WHERE ${sourceCondition} AND ${sql.and(timeConditions)}` : yield* sql`SELECT * FROM objectMeta WHERE ${sourceCondition}`;
469
+ return rows.map((row) => ({
470
+ ...row,
471
+ deleted: !!row.deleted
472
+ }));
473
+ }));
474
+ /**
475
+ * Query children by parent object ids.
476
+ * Matches both:
477
+ * - Objects whose `parent` field references one of the given parent ids (standard parent/child hierarchy).
478
+ * - Queue items whose `queueId` equals one of the parent ids (e.g. items inside a Feed, since a feed's queue
479
+ * DXN uses the feed's object id as its queue id — see `Feed.getQueueDxn`).
480
+ */
481
+ queryChildren = Effect3.fn("ObjectMetaIndex.queryChildren")((query) => Effect3.gen(function* () {
482
+ if (query.parentIds.length === 0) {
483
+ return [];
484
+ }
485
+ const sql = yield* SqlClient5.SqlClient;
486
+ const parentDxns = query.parentIds.map((id) => DXN.fromLocalObjectId(id).toString());
487
+ const rows = yield* sql`SELECT * FROM objectMeta WHERE ${sql.in("spaceId", query.spaceId)} AND (${sql.in("parent", parentDxns)} OR ${sql.in("queueId", query.parentIds)})`;
488
+ return rows.map((row) => ({
489
+ ...row,
490
+ deleted: !!row.deleted
491
+ }));
492
+ }));
375
493
  };
376
494
 
377
495
  // src/indexes/reverse-ref-index.ts
@@ -395,15 +513,7 @@ var EscapedPropPath = class extends Schema3.String.annotations({
395
513
  let current = "";
396
514
  for (let i = 0; i < path.length; i++) {
397
515
  if (path[i] === "\\") {
398
- invariant(i + 1 < path.length && (path[i + 1] === "." || path[i + 1] === "\\"), "Malformed escaping.", {
399
- F: __dxlog_file,
400
- L: 34,
401
- S: this,
402
- A: [
403
- "i + 1 < path.length && (path[i + 1] === '.' || path[i + 1] === '\\\\')",
404
- "'Malformed escaping.'"
405
- ]
406
- });
516
+ invariant(i + 1 < path.length && (path[i + 1] === "." || path[i + 1] === "\\"), "Malformed escaping.", { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 25, S: this, A: ["i + 1 < path.length && (path[i + 1] === '.' || path[i + 1] === '\\\\')", "'Malformed escaping.'"] });
407
517
  current = current + path[i + 1];
408
518
  i++;
409
519
  } else if (path[i] === ".") {
@@ -503,6 +613,33 @@ var ReverseRefIndex = class {
503
613
  };
504
614
 
505
615
  // src/index-engine.ts
616
+ var makeEmptyIndexingResult = () => ({
617
+ updated: 0,
618
+ done: true,
619
+ spaces: /* @__PURE__ */ new Set(),
620
+ queues: /* @__PURE__ */ new Set(),
621
+ documents: /* @__PURE__ */ new Set(),
622
+ types: /* @__PURE__ */ new Set(),
623
+ objects: /* @__PURE__ */ new Set()
624
+ });
625
+ var accumulateIndexingResult = (acc, objects) => {
626
+ for (const obj of objects) {
627
+ acc.spaces.add(obj.spaceId);
628
+ if (obj.queueId) {
629
+ acc.queues.add(obj.queueId);
630
+ }
631
+ if (obj.documentId) {
632
+ acc.documents.add(obj.documentId);
633
+ }
634
+ const t = obj.data[ATTR_TYPE2];
635
+ if (t) {
636
+ acc.types.add(String(t));
637
+ }
638
+ if (obj.data.id) {
639
+ acc.objects.add(obj.data.id);
640
+ }
641
+ }
642
+ };
506
643
  var IndexEngine = class {
507
644
  #tracker;
508
645
  #objectMetaIndex;
@@ -546,34 +683,47 @@ var IndexEngine = class {
546
683
  queryType(query) {
547
684
  return this.#objectMetaIndex.query(query);
548
685
  }
686
+ /**
687
+ * Query children by parent object ids.
688
+ */
689
+ queryChildren(query) {
690
+ return this.#objectMetaIndex.queryChildren(query);
691
+ }
549
692
  queryTypes(query) {
550
693
  return this.#objectMetaIndex.queryTypes(query);
551
694
  }
695
+ queryByTimeRange(query) {
696
+ return this.#objectMetaIndex.queryByTimeRange(query);
697
+ }
552
698
  queryRelations(query) {
553
699
  return this.#objectMetaIndex.queryRelations(query);
554
700
  }
555
701
  lookupByRecordIds(recordIds) {
556
702
  return this.#objectMetaIndex.lookupByRecordIds(recordIds);
557
703
  }
558
- update(dataSource, opts) {
704
+ lookupByObjectId(query) {
705
+ return this.#objectMetaIndex.lookupByObjectId(query);
706
+ }
707
+ update(ctx, dataSource, opts) {
559
708
  return Effect5.gen(this, function* () {
560
- let updated = 0;
561
- const { updated: updatedFtsIndex, done: doneFtsIndex } = yield* this.#update(this.#ftsIndex, dataSource, {
562
- indexName: "fts",
709
+ const result = makeEmptyIndexingResult();
710
+ const { updated: updatedFtsIndex, done: doneFtsIndex, objects: ftsObjects } = yield* this.#update(ctx, this.#ftsIndex, dataSource, {
711
+ indexName: "fts5",
563
712
  spaceId: opts.spaceId,
564
713
  limit: opts.limit
565
714
  });
566
- updated += updatedFtsIndex;
567
- const { updated: updatedReverseRefIndex, done: doneReverseRefIndex } = yield* this.#update(this.#reverseRefIndex, dataSource, {
715
+ result.updated += updatedFtsIndex;
716
+ result.done = result.done && doneFtsIndex;
717
+ accumulateIndexingResult(result, ftsObjects);
718
+ const { updated: updatedReverseRefIndex, done: doneReverseRefIndex, objects: reverseRefObjects } = yield* this.#update(ctx, this.#reverseRefIndex, dataSource, {
568
719
  indexName: "reverseRef",
569
720
  spaceId: opts.spaceId,
570
721
  limit: opts.limit
571
722
  });
572
- updated += updatedReverseRefIndex;
573
- return {
574
- updated,
575
- done: doneFtsIndex && doneReverseRefIndex
576
- };
723
+ result.updated += updatedReverseRefIndex;
724
+ result.done = result.done && doneReverseRefIndex;
725
+ accumulateIndexingResult(result, reverseRefObjects);
726
+ return result;
577
727
  }).pipe(Effect5.withSpan("IndexEngine.update"));
578
728
  }
579
729
  /**
@@ -585,7 +735,7 @@ var IndexEngine = class {
585
735
  * 4. Enriches objects with recordIds.
586
736
  * 5. Updates the dependent index.
587
737
  */
588
- #update(index, source, opts) {
738
+ #update(ctx, index, source, opts) {
589
739
  return Effect5.gen(this, function* () {
590
740
  const sqlTransaction = yield* SqlTransaction.SqlTransaction;
591
741
  return yield* sqlTransaction.withTransaction(Effect5.gen(this, function* () {
@@ -595,13 +745,14 @@ var IndexEngine = class {
595
745
  // Pass undefined to get all cursors when spaceId is null.
596
746
  spaceId: opts.spaceId ?? void 0
597
747
  });
598
- const { objects, cursors: updatedCursors } = yield* source.getChangedObjects(cursors, {
748
+ const { objects, cursors: updatedCursors } = yield* source.getChangedObjects(ctx, cursors, {
599
749
  limit: opts.limit
600
750
  });
601
751
  if (objects.length === 0) {
602
752
  return {
603
753
  updated: 0,
604
- done: true
754
+ done: true,
755
+ objects: []
605
756
  };
606
757
  }
607
758
  yield* this.#objectMetaIndex.update(objects);
@@ -616,13 +767,15 @@ var IndexEngine = class {
616
767
  })));
617
768
  return {
618
769
  updated: objects.length,
619
- done: false
770
+ done: false,
771
+ objects
620
772
  };
621
773
  }));
622
- }).pipe(Effect5.withSpan("IndexEngine.#updateDependentIndex"));
774
+ }).pipe(Effect5.withSpan("IndexEngine.#update"));
623
775
  }
624
776
  };
625
777
  export {
778
+ EscapedPropPath,
626
779
  FtsIndex,
627
780
  IndexEngine,
628
781
  IndexTracker,
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/index-engine.ts", "../../../src/index-tracker.ts", "../../../src/indexes/fts-index.ts", "../../../src/indexes/object-meta-index.ts", "../../../src/indexes/reverse-ref-index.ts", "../../../src/utils.ts"],
4
+ "sourcesContent": ["//\n// Copyright 2026 DXOS.org\n//\n\nimport type * as SqlClient from '@effect/sql/SqlClient';\nimport type * as SqlError from '@effect/sql/SqlError';\nimport * as Effect from 'effect/Effect';\n\nimport { type Context } from '@dxos/context';\nimport { ATTR_TYPE } from '@dxos/echo/internal';\nimport type { ObjectId, SpaceId } from '@dxos/keys';\nimport * as SqlTransaction from '@dxos/sql-sqlite/SqlTransaction';\n\nimport { type IndexCursor, IndexTracker } from './index-tracker';\nimport {\n FtsIndex,\n type FtsQuery,\n type FtsQueryResult,\n type Index,\n type IndexerObject,\n type ObjectMeta,\n ObjectMetaIndex,\n ReverseRefIndex,\n type ReverseRefQuery,\n} from './indexes';\n\n/**\n * Result of a single indexing pass over a data source.\n * Carries enough metadata for callers to build targeted invalidation hints.\n */\nexport type IndexingResult = {\n updated: number;\n done: boolean;\n spaces: ReadonlySet<SpaceId>;\n queues: ReadonlySet<ObjectId>;\n documents: ReadonlySet<string>;\n types: ReadonlySet<string>;\n objects: ReadonlySet<ObjectId>;\n};\n\ntype MutableIndexingResult = {\n updated: number;\n done: boolean;\n spaces: Set<SpaceId>;\n queues: Set<ObjectId>;\n documents: Set<string>;\n types: Set<string>;\n objects: Set<ObjectId>;\n};\n\nconst makeEmptyIndexingResult = (): MutableIndexingResult => ({\n updated: 0,\n done: true,\n spaces: new Set(),\n queues: new Set(),\n documents: new Set(),\n types: new Set(),\n objects: new Set(),\n});\n\nconst accumulateIndexingResult = (acc: MutableIndexingResult, objects: readonly IndexerObject[]) => {\n for (const obj of objects) {\n acc.spaces.add(obj.spaceId);\n if (obj.queueId) {\n acc.queues.add(obj.queueId);\n }\n if (obj.documentId) {\n acc.documents.add(obj.documentId);\n }\n const t = (obj.data as Record<string, unknown>)[ATTR_TYPE];\n if (t) {\n acc.types.add(String(t));\n }\n if (obj.data.id) {\n acc.objects.add(obj.data.id as ObjectId);\n }\n }\n};\n\n/**\n * Cursor into indexable data-source.\n */\nexport interface DataSourceCursor {\n spaceId: SpaceId | null;\n\n /**\n * documentId or queueNamespace.\n */\n resourceId: string | null;\n\n /**\n * heads or queue position.\n */\n cursor: number | string;\n}\n\nexport interface IndexDataSource {\n readonly sourceName: string; // e.g. queue, automerge, etc.\n\n getChangedObjects(\n ctx: Context,\n cursors: DataSourceCursor[],\n opts?: { limit?: number },\n ): Effect.Effect<{ objects: IndexerObject[]; cursors: DataSourceCursor[] }>;\n}\n\nexport interface IndexEngineParams {\n tracker: IndexTracker;\n objectMetaIndex: ObjectMetaIndex;\n ftsIndex: FtsIndex;\n reverseRefIndex: ReverseRefIndex;\n}\n\nexport class IndexEngine {\n readonly #tracker: IndexTracker;\n readonly #objectMetaIndex: ObjectMetaIndex;\n readonly #ftsIndex: FtsIndex;\n readonly #reverseRefIndex: ReverseRefIndex;\n\n constructor(params?: IndexEngineParams) {\n this.#tracker = params?.tracker ?? new IndexTracker();\n this.#objectMetaIndex = params?.objectMetaIndex ?? new ObjectMetaIndex();\n this.#ftsIndex = params?.ftsIndex ?? new FtsIndex();\n this.#reverseRefIndex = params?.reverseRefIndex ?? new ReverseRefIndex();\n }\n\n migrate() {\n return Effect.gen(this, function* () {\n yield* this.#tracker.migrate();\n yield* this.#objectMetaIndex.migrate();\n yield* this.#ftsIndex.migrate();\n yield* this.#reverseRefIndex.migrate();\n });\n }\n\n /**\n * Query text index and return full object metadata with rank.\n */\n queryText(query: FtsQuery): Effect.Effect<readonly FtsQueryResult[], SqlError.SqlError, SqlClient.SqlClient> {\n return Effect.gen(this, function* () {\n return yield* this.#ftsIndex.query(query);\n });\n }\n\n queryReverseRef(query: ReverseRefQuery) {\n // TODO(mykola): Join with metadata table here.\n return this.#reverseRefIndex.query(query);\n }\n\n queryAll(query: {\n spaceIds: readonly SpaceId[];\n includeAllQueues?: boolean;\n queueIds?: readonly string[] | null;\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {\n return this.#objectMetaIndex.queryAll(query);\n }\n\n /**\n * Query snapshots by recordIds.\n * Used to load queue objects from indexed snapshots.\n */\n querySnapshotsJSON(recordIds: number[]) {\n return this.#ftsIndex.querySnapshotsJSON(recordIds);\n }\n\n queryType(\n query: Pick<ObjectMeta, 'spaceId' | 'typeDxn'>,\n ): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {\n return this.#objectMetaIndex.query(query);\n }\n\n /**\n * Query children by parent object ids.\n */\n queryChildren(query: {\n spaceId: SpaceId[];\n parentIds: ObjectId[];\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {\n return this.#objectMetaIndex.queryChildren(query);\n }\n\n queryTypes(query: {\n spaceIds: readonly SpaceId[];\n typeDxns: readonly ObjectMeta['typeDxn'][];\n inverted?: boolean;\n includeAllQueues?: boolean;\n queueIds?: readonly string[] | null;\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {\n return this.#objectMetaIndex.queryTypes(query);\n }\n queryByTimeRange(query: {\n spaceIds: readonly string[];\n updatedAfter?: number;\n updatedBefore?: number;\n createdAfter?: number;\n createdBefore?: number;\n includeAllQueues?: boolean;\n queueIds?: readonly string[] | null;\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {\n return this.#objectMetaIndex.queryByTimeRange(query);\n }\n\n queryRelations(query: {\n endpoint: 'source' | 'target';\n anchorDxns: readonly string[];\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {\n return this.#objectMetaIndex.queryRelations(query);\n }\n lookupByRecordIds(recordIds: number[]): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {\n return this.#objectMetaIndex.lookupByRecordIds(recordIds);\n }\n\n lookupByObjectId(query: {\n objectId: string;\n spaceId: string;\n queueId: string;\n }): Effect.Effect<ObjectMeta | null, SqlError.SqlError, SqlClient.SqlClient> {\n return this.#objectMetaIndex.lookupByObjectId(query);\n }\n\n update(\n ctx: Context,\n dataSource: IndexDataSource,\n opts: { spaceId: SpaceId | null; limit?: number },\n ): Effect.Effect<IndexingResult, SqlError.SqlError, SqlTransaction.SqlTransaction | SqlClient.SqlClient> {\n return Effect.gen(this, function* () {\n const result = makeEmptyIndexingResult();\n\n const {\n updated: updatedFtsIndex,\n done: doneFtsIndex,\n objects: ftsObjects,\n } = yield* this.#update(ctx, this.#ftsIndex, dataSource, {\n indexName: 'fts5',\n spaceId: opts.spaceId,\n limit: opts.limit,\n });\n result.updated += updatedFtsIndex;\n result.done = result.done && doneFtsIndex;\n accumulateIndexingResult(result, ftsObjects);\n\n const {\n updated: updatedReverseRefIndex,\n done: doneReverseRefIndex,\n objects: reverseRefObjects,\n } = yield* this.#update(ctx, this.#reverseRefIndex, dataSource, {\n indexName: 'reverseRef',\n spaceId: opts.spaceId,\n limit: opts.limit,\n });\n result.updated += updatedReverseRefIndex;\n result.done = result.done && doneReverseRefIndex;\n accumulateIndexingResult(result, reverseRefObjects);\n\n return result as IndexingResult;\n }).pipe(Effect.withSpan('IndexEngine.update'));\n }\n\n /**\n * Update a dependent index that requires recordId enrichment.\n * This method:\n * 1. Gets changed objects from the source.\n * 2. Ensures those objects exist in ObjectMetaIndex.\n * 3. Looks up recordIds for those objects.\n * 4. Enriches objects with recordIds.\n * 5. Updates the dependent index.\n */\n #update(\n ctx: Context,\n index: Index,\n source: IndexDataSource,\n opts: { indexName: string; spaceId: SpaceId | null; limit?: number },\n ): Effect.Effect<\n { updated: number; done: boolean; objects: readonly IndexerObject[] },\n SqlError.SqlError,\n SqlTransaction.SqlTransaction | SqlClient.SqlClient\n > {\n return Effect.gen(this, function* () {\n const sqlTransaction = yield* SqlTransaction.SqlTransaction;\n\n return yield* sqlTransaction.withTransaction(\n Effect.gen(this, function* () {\n const cursors = yield* this.#tracker.queryCursors({\n indexName: opts.indexName,\n sourceName: source.sourceName,\n // Pass undefined to get all cursors when spaceId is null.\n spaceId: opts.spaceId ?? undefined,\n });\n const { objects, cursors: updatedCursors } = yield* source.getChangedObjects(ctx, cursors, {\n limit: opts.limit,\n });\n if (objects.length === 0) {\n return { updated: 0, done: true, objects: [] as readonly IndexerObject[] };\n }\n\n // Ensure objects exist in ObjectMetaIndex.\n yield* this.#objectMetaIndex.update(objects);\n\n // Look up recordIds for the objects.\n yield* this.#objectMetaIndex.lookupRecordIds(objects);\n\n yield* index.update(objects);\n yield* this.#tracker.updateCursors(\n updatedCursors.map(\n (_): IndexCursor => ({\n indexName: opts.indexName,\n spaceId: _.spaceId,\n sourceName: source.sourceName,\n resourceId: _.resourceId,\n cursor: _.cursor,\n }),\n ),\n );\n return { updated: objects.length, done: false, objects };\n }),\n );\n }).pipe(Effect.withSpan('IndexEngine.#update'));\n }\n}\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as SqlClient from '@effect/sql/SqlClient';\nimport type * as SqlError from '@effect/sql/SqlError';\nimport * as Effect from 'effect/Effect';\nimport * as Schema from 'effect/Schema';\n\nimport { SpaceId } from '@dxos/keys';\n\nexport const IndexCursor = Schema.Struct({\n /**\n * Name of the index owning this cursor.\n */\n indexName: Schema.String,\n /**\n * Space id.\n */\n spaceId: Schema.NullOr(SpaceId),\n /**\n * Source name.\n * 'automerge' / 'queue' / 'index' (for secondary indexes)\n */\n sourceName: Schema.String,\n /**\n * Document id or queue id.\n * doc_id, queue_id, '' <empty string> (if indexing entire namespace)\n */\n resourceId: Schema.NullOr(Schema.String),\n /**\n * Heads, queue position, version.\n */\n cursor: Schema.Union(Schema.Number, Schema.String),\n});\nexport interface IndexCursor extends Schema.Schema.Type<typeof IndexCursor> {}\n\n/**\n * Deprecated index names that are no longer used. Will be cleaned up on migration.\n */\nconst DEPRECATED_INDEX_NAMES = ['fts'];\n\nexport class IndexTracker {\n migrate = Effect.fn('IndexTracker.migrate')(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n // For automerge: last-indexed heads of the document\n // For queue: the position of the item that was indexed last\n yield* sql`CREATE TABLE IF NOT EXISTS indexCursor (\n indexName TEXT NOT NULL,\n spaceId TEXT NOT NULL DEFAULT '',\n sourceName TEXT NOT NULL,\n resourceId TEXT NOT NULL DEFAULT '',\n cursor,\n PRIMARY KEY (indexName, spaceId, sourceName, resourceId)\n )`;\n\n yield* Effect.forEach(DEPRECATED_INDEX_NAMES, (indexName) => {\n return sql`DELETE FROM indexCursor WHERE indexName = ${indexName}`;\n });\n });\n\n queryCursors = Effect.fn('IndexTracker.queryCursors')(\n (\n query: Pick<IndexCursor, 'indexName'> & Partial<Pick<IndexCursor, 'sourceName' | 'resourceId' | 'spaceId'>>,\n ): Effect.Effect<IndexCursor[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n const spaceIdParam = query.spaceId === undefined ? null : (query.spaceId ?? '');\n const sourceNameParam = query.sourceName === undefined ? null : query.sourceName;\n const resourceIdParam = query.resourceId === undefined ? null : (query.resourceId ?? '');\n\n const rows = yield* sql<IndexCursor>`\n SELECT * FROM indexCursor \n WHERE indexName = ${query.indexName}\n AND (${spaceIdParam} IS NULL OR spaceId = ${spaceIdParam})\n AND (${sourceNameParam} IS NULL OR sourceName = ${sourceNameParam})\n AND (${resourceIdParam} IS NULL OR resourceId = ${resourceIdParam})\n `;\n\n return rows.map(\n (row): IndexCursor => ({\n indexName: row.indexName,\n spaceId: row.spaceId === '' ? null : Schema.decodeSync(SpaceId)(row.spaceId!),\n sourceName: row.sourceName,\n resourceId: row.resourceId === '' ? null : row.resourceId,\n cursor: row.cursor,\n }),\n );\n }),\n );\n\n updateCursors = Effect.fn('IndexTracker.updateCursors')(\n (cursors: IndexCursor[]): Effect.Effect<void, SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n yield* Effect.forEach(\n cursors,\n (cursor) => {\n const spaceId = cursor.spaceId ?? '';\n const resourceId = cursor.resourceId ?? '';\n return sql`\n INSERT INTO indexCursor (indexName, spaceId, sourceName, resourceId, cursor)\n VALUES (${cursor.indexName}, ${spaceId}, ${cursor.sourceName}, ${resourceId}, ${cursor.cursor})\n ON CONFLICT(indexName, spaceId, sourceName, resourceId) DO UPDATE SET cursor = excluded.cursor\n `;\n },\n { discard: true },\n );\n }),\n );\n}\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as SqlClient from '@effect/sql/SqlClient';\nimport type * as SqlError from '@effect/sql/SqlError';\nimport type * as Statement from '@effect/sql/Statement';\nimport * as Effect from 'effect/Effect';\n\nimport type { Obj } from '@dxos/echo';\nimport type { ObjectId, SpaceId } from '@dxos/keys';\n\nimport type { Index, IndexerObject } from './interface';\nimport type { ObjectMeta } from './object-meta-index';\n\n// SQLite bound-variable limit (SQLITE_LIMIT_VARIABLE_NUMBER) is 999 in most builds.\n// Use 500 as a safe chunk size for IN (...) clauses.\nconst SQL_CHUNK_SIZE = 500;\n\n/**\n * The space and queue constrains are combined together using a logical OR.\n */\nexport interface FtsQuery {\n /**\n * Text to search.\n */\n query: string;\n\n /**\n * Space ID to search within.\n */\n spaceId: readonly SpaceId[] | null;\n\n /**\n * If true, include all queues in the spaces specified by `spaceId`.\n */\n includeAllQueues: boolean;\n\n /**\n * Queue IDs to search within.\n */\n queueIds: readonly ObjectId[] | null;\n}\n\n/**\n * Result of FTS query including the indexed snapshot data.\n */\nexport interface FtsResult extends ObjectMeta {\n /**\n * The indexed snapshot data (JSON string).\n * Used to load queue objects without going through document loading.\n */\n snapshot: string;\n}\n\n/**\n * Result of FTS query with rank.\n */\nexport interface FtsQueryResult extends ObjectMeta {\n /**\n * Relevance rank from FTS5.\n * Higher values indicate better matches.\n * Uses BM25 algorithm when available, falls back to 1 for non-BM25 queries.\n */\n rank: number;\n}\n\n/**\n * Escapes user input for safe FTS5 queries.\n *\n * FTS5 has special syntax characters that can cause errors or unexpected behavior:\n * - `*` suffix for prefix matching (e.g., `prog*` matches \"program\", \"programming\")\n * - `\"...\"` for phrase queries\n * - `.` for column specification\n * - `AND`, `OR`, `NOT` boolean operators\n * - `+`, `-` for required/excluded terms\n *\n * This function wraps each whitespace-separated term in double quotes, treating all\n * characters as literals. Double quotes within terms are escaped by doubling (`\"\"`).\n *\n * Example: `prog* AND test.` becomes `\"prog*\" \"AND\" \"test.\"`.\n */\nconst escapeFts5Query = (text: string): string => {\n return text\n .split(/\\s+/)\n .filter(Boolean)\n .map((term) => `\"${term.replace(/\"/g, '\"\"')}\"`)\n .join(' ');\n};\n\nexport class FtsIndex implements Index {\n migrate = Effect.fn('FtsIndex.migrate')(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n // https://sqlite.org/fts5.html#the_trigram_tokenizer\n // FTS5 tables are created as virtual tables; they implicitly have a `rowid`.\n // Trigram tokenizer enables substring matching (e.g., \"rog\" matches \"programming\").\n //\n // Data structure: inverted index mapping trigrams to document IDs.\n // \"hello\" → trigrams [\"hel\", \"ell\", \"llo\"] → B-tree entries: \"hel\"→[1], \"ell\"→[1], \"llo\"→[1].\n // Query \"ell\" → O(log n) B-tree lookup → returns [1].\n // Posting lists are compressed, so index size scales well with document count.\n yield* sql`CREATE VIRTUAL TABLE IF NOT EXISTS ftsIndex USING fts5(snapshot, tokenize='trigram')`;\n });\n\n query({\n query,\n spaceId,\n includeAllQueues,\n queueIds,\n }: FtsQuery): Effect.Effect<readonly FtsQueryResult[], SqlError.SqlError, SqlClient.SqlClient> {\n return Effect.gen(function* () {\n const trimmed = query.trim();\n if (trimmed.length === 0) {\n return [];\n }\n\n const sql = yield* SqlClient.SqlClient;\n\n // Trigram tokenizer requires at least 3 characters per term.\n // Check if ALL terms are at least 3 chars; otherwise use LIKE fallback.\n const terms = trimmed.split(/\\s+/).filter(Boolean);\n const minTermLength = Math.min(...terms.map((t) => t.length));\n\n // Use BM25 ranking for FTS5 MATCH queries, fall back to rank 1 for LIKE queries.\n // BM25 returns negative values where lower (more negative) means better match,\n // so we negate it to get higher = better.\n const useBm25 = minTermLength >= 3;\n\n const conditions =\n minTermLength < 3\n ? // LIKE fallback - scan the entire table, AND all terms.\n terms.map((term) => sql`f.snapshot LIKE ${'%' + term + '%'}`)\n : // MATCH - fast index lookup.\n [sql`f.snapshot MATCH ${escapeFts5Query(trimmed)}`];\n\n // Space and queue constraints are combined with OR.\n const sourceConditions: Statement.Statement<{}>[] = [];\n\n if (spaceId && spaceId.length > 0) {\n if (includeAllQueues) {\n // All items from these spaces (both space objects and queue objects).\n sourceConditions.push(sql`m.spaceId IN ${sql.in(spaceId)}`);\n } else {\n // Only space objects (not queue objects) from these spaces.\n sourceConditions.push(sql`(m.spaceId IN ${sql.in(spaceId)} AND m.queueId = '')`);\n }\n }\n\n if (queueIds && queueIds.length > 0) {\n // Items from specific queues.\n sourceConditions.push(sql`m.queueId IN ${sql.in(queueIds)}`);\n }\n\n if (sourceConditions.length > 0) {\n conditions.push(sql`(${sql.or(sourceConditions)})`);\n }\n\n if (useBm25) {\n // Use BM25 ranking for FTS5 MATCH queries.\n // BM25 returns negative values, negate to get higher = better match.\n // Order by rank descending so best matches come first.\n // Note: bm25() requires the actual table name, not an alias.\n const rows = yield* sql<ObjectMeta & { rank: number }>`\n SELECT m.*, -bm25(ftsIndex) AS rank \n FROM ftsIndex AS f \n JOIN objectMeta AS m ON f.rowid = m.recordId \n WHERE ${sql.and(conditions)}\n ORDER BY rank DESC\n `;\n return rows;\n } else {\n // LIKE fallback - no ranking available, default to 1.\n const rows = yield* sql<ObjectMeta>`\n SELECT m.* \n FROM ftsIndex AS f \n JOIN objectMeta AS m ON f.rowid = m.recordId \n WHERE ${sql.and(conditions)}\n `;\n return rows.map((row) => ({ ...row, rank: 1 }));\n }\n });\n }\n\n /**\n * Query snapshots by recordIds.\n * Returns the parsed JSON snapshots for queue objects.\n * RecordIds not present in the FTS index are silently omitted from the result.\n */\n querySnapshotsJSON(\n recordIds: number[],\n ): Effect.Effect<readonly { recordId: number; snapshot: Obj.JSON }[], SqlError.SqlError, SqlClient.SqlClient> {\n return Effect.gen(function* () {\n if (recordIds.length === 0) {\n return [];\n }\n const sql = yield* SqlClient.SqlClient;\n\n // Chunk to avoid SQLite bound-variable limit (SQLITE_LIMIT_VARIABLE_NUMBER,\n // typically 999 in wasm builds). 500 gives a safe margin.\n const chunks: number[][] = [];\n for (let i = 0; i < recordIds.length; i += SQL_CHUNK_SIZE) {\n chunks.push(recordIds.slice(i, i + SQL_CHUNK_SIZE));\n }\n\n const allResults: { recordId: number; snapshot: Obj.JSON }[] = [];\n for (const chunk of chunks) {\n const rows = yield* sql<{\n rowid: number;\n snapshot: string;\n }>`SELECT rowid, snapshot FROM ftsIndex WHERE rowid IN ${sql.in(chunk)}`;\n for (const r of rows) {\n allResults.push({ recordId: r.rowid, snapshot: JSON.parse(r.snapshot) });\n }\n }\n\n return allResults;\n });\n }\n\n update = Effect.fn('FtsIndex.update')(\n (objects: IndexerObject[]): Effect.Effect<void, SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n yield* Effect.forEach(\n objects,\n (object) =>\n Effect.gen(function* () {\n const { recordId, data } = object;\n if (recordId === null) {\n return yield* Effect.die(new Error('FtsIndex.update requires recordId to be set'));\n }\n\n const snapshot = JSON.stringify(data);\n\n // FTS5 doesn't support UPDATE, need DELETE + INSERT for upsert.\n const existing = yield* sql<{ rowid: number }>`SELECT rowid FROM ftsIndex WHERE rowid = ${recordId}`;\n if (existing.length > 0) {\n yield* sql`DELETE FROM ftsIndex WHERE rowid = ${recordId}`;\n }\n\n yield* sql`INSERT INTO ftsIndex (rowid, snapshot) VALUES (${recordId}, ${snapshot})`;\n }),\n { discard: true },\n );\n }),\n );\n}\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as SqlClient from '@effect/sql/SqlClient';\nimport type * as SqlError from '@effect/sql/SqlError';\nimport type * as Statement from '@effect/sql/Statement';\nimport * as Effect from 'effect/Effect';\nimport * as Schema from 'effect/Schema';\n\nimport { ATTR_DELETED, ATTR_PARENT, ATTR_RELATION_SOURCE, ATTR_RELATION_TARGET, ATTR_TYPE } from '@dxos/echo/internal';\nimport { DXN, type ObjectId, type SpaceId } from '@dxos/keys';\n\nimport type { IndexerObject } from './interface';\nimport type { Index } from './interface';\n\nconst _escapeLikePrefix = (prefix: string) => {\n // Escape LIKE metacharacters in the *literal* prefix (we still append a wildcard for the version suffix).\n // Backslash is used as the ESCAPE character.\n // See: https://www.sqlite.org/lang_expr.html#like\n const escaped = prefix.replaceAll('\\\\', '\\\\\\\\').replaceAll('%', '\\\\%').replaceAll('_', '\\\\_');\n return `${escaped}:%`;\n};\n\nexport const ObjectMeta = Schema.Struct({\n recordId: Schema.Number,\n objectId: Schema.String,\n queueId: Schema.String,\n spaceId: Schema.String,\n documentId: Schema.String,\n entityKind: Schema.String,\n /** The versioned DXN of the type of the object. */\n typeDxn: Schema.String,\n deleted: Schema.Boolean,\n source: Schema.NullOr(Schema.String),\n target: Schema.NullOr(Schema.String),\n /** Parent object id (nullable). */\n parent: Schema.NullOr(Schema.String),\n /** Monotonically increasing sequence number assigned on insert/update for tracking indexing order. */\n version: Schema.Number,\n /** Unix ms timestamp when the object was first indexed. */\n createdAt: Schema.NullOr(Schema.Number),\n /** Unix ms timestamp when the object was last re-indexed. */\n updatedAt: Schema.NullOr(Schema.Number),\n});\nexport interface ObjectMeta extends Schema.Schema.Type<typeof ObjectMeta> {}\n\n/**\n * Builds a SQL condition for filtering by space and queue source.\n * When `includeAllQueues` is false and no `queueIds`, only non-queue objects are returned.\n */\nconst buildSourceCondition = (\n sql: SqlClient.SqlClient,\n spaceIds: readonly string[],\n includeAllQueues: boolean,\n queueIds: readonly string[] | null,\n): Statement.Fragment => {\n const conditions: Statement.Fragment[] = [];\n\n if (spaceIds.length > 0) {\n if (includeAllQueues) {\n conditions.push(sql`${sql.in('spaceId', spaceIds)}`);\n } else {\n conditions.push(sql`(${sql.in('spaceId', spaceIds)} AND queueId = '')`);\n }\n }\n\n if (queueIds && queueIds.length > 0) {\n conditions.push(sql`${sql.in('queueId', queueIds)}`);\n }\n\n if (conditions.length === 0) {\n return sql`1 = 0`;\n }\n\n return sql.or(conditions);\n};\n\nexport class ObjectMetaIndex implements Index {\n migrate = Effect.fn('ObjectMetaIndex.runMigrations')(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n yield* sql`CREATE TABLE IF NOT EXISTS objectMeta (\n recordId INTEGER PRIMARY KEY AUTOINCREMENT,\n objectId TEXT NOT NULL,\n queueId TEXT NOT NULL DEFAULT '',\n spaceId TEXT NOT NULL,\n documentId TEXT NOT NULL DEFAULT '',\n entityKind TEXT NOT NULL,\n typeDxn TEXT NOT NULL,\n deleted INTEGER NOT NULL,\n source TEXT,\n target TEXT,\n parent TEXT,\n version INTEGER NOT NULL,\n createdAt INTEGER,\n updatedAt INTEGER\n )`;\n\n // Add `parent` column for tables created before it was introduced.\n yield* Effect.catchAll(sql`ALTER TABLE objectMeta ADD COLUMN parent TEXT`, () => Effect.void);\n // Add timestamp columns for tables created before they were introduced.\n yield* Effect.catchAll(sql`ALTER TABLE objectMeta ADD COLUMN createdAt INTEGER`, () => Effect.void);\n yield* Effect.catchAll(sql`ALTER TABLE objectMeta ADD COLUMN updatedAt INTEGER`, () => Effect.void);\n\n yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_objectId ON objectMeta(spaceId, objectId)`;\n yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_typeDxn ON objectMeta(spaceId, typeDxn)`;\n yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_version ON objectMeta(version)`;\n yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_parent ON objectMeta(spaceId, parent)`;\n yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_updatedAt ON objectMeta(updatedAt)`;\n yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_createdAt ON objectMeta(createdAt)`;\n });\n\n query = Effect.fn('ObjectMetaIndex.query')(\n (\n query: Pick<ObjectMeta, 'spaceId' | 'typeDxn'>,\n ): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n const parsedType = DXN.tryParse(query.typeDxn)?.asTypeDXN();\n\n // SQLite stores booleans as integers, so we need to specify the raw row type.\n const rows =\n parsedType && parsedType.version === undefined\n ? yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE spaceId = ${query.spaceId} AND (typeDxn = ${\n query.typeDxn\n } OR typeDxn LIKE ${_escapeLikePrefix(query.typeDxn)} ESCAPE '\\\\')`\n : yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE spaceId = ${query.spaceId} AND typeDxn = ${query.typeDxn}`;\n return rows.map((row) => ({\n ...row,\n deleted: !!row.deleted,\n }));\n }),\n );\n\n queryAll = Effect.fn('ObjectMetaIndex.queryAll')(\n (query: {\n spaceIds: readonly ObjectMeta['spaceId'][];\n includeAllQueues?: boolean;\n queueIds?: readonly string[] | null;\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n if (query.spaceIds.length === 0 && (!query.queueIds || query.queueIds.length === 0)) {\n return [];\n }\n\n const sql = yield* SqlClient.SqlClient;\n const sourceCondition = buildSourceCondition(\n sql,\n query.spaceIds,\n query.includeAllQueues ?? false,\n query.queueIds ?? null,\n );\n const rows = yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition}`;\n return rows.map((row) => ({\n ...row,\n deleted: !!row.deleted,\n }));\n }),\n );\n\n queryTypes = Effect.fn('ObjectMetaIndex.queryTypes')(\n ({\n spaceIds,\n typeDxns,\n inverted = false,\n includeAllQueues = false,\n queueIds = null,\n }: {\n spaceIds: readonly ObjectMeta['spaceId'][];\n typeDxns: readonly ObjectMeta['typeDxn'][];\n inverted?: boolean;\n includeAllQueues?: boolean;\n queueIds?: readonly string[] | null;\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n if (spaceIds.length === 0 && (!queueIds || queueIds.length === 0)) {\n return [];\n }\n\n if (typeDxns.length === 0) {\n if (!inverted) {\n return [];\n }\n\n const sql = yield* SqlClient.SqlClient;\n const sourceCondition = buildSourceCondition(sql, spaceIds, includeAllQueues, queueIds);\n const rows = yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition}`;\n return rows.map((row) => ({\n ...row,\n deleted: !!row.deleted,\n }));\n }\n const sql = yield* SqlClient.SqlClient;\n const sourceCondition = buildSourceCondition(sql, spaceIds, includeAllQueues, queueIds);\n const typeWhere = sql.or(\n typeDxns.map((typeDxn) => {\n const parsedType = DXN.tryParse(typeDxn)?.asTypeDXN();\n return parsedType && parsedType.version === undefined\n ? sql.or([sql`typeDxn = ${typeDxn}`, sql`typeDxn LIKE ${_escapeLikePrefix(typeDxn)} ESCAPE '\\\\'`])\n : sql`typeDxn = ${typeDxn}`;\n }),\n );\n const rows = inverted\n ? yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition} AND NOT ${typeWhere}`\n : yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition} AND ${typeWhere}`;\n return rows.map((row) => ({\n ...row,\n deleted: !!row.deleted,\n }));\n }),\n );\n\n queryRelations = Effect.fn('ObjectMetaIndex.queryRelations')(\n ({\n endpoint,\n anchorDxns,\n }: {\n endpoint: 'source' | 'target';\n anchorDxns: readonly string[];\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n if (anchorDxns.length === 0) {\n return [];\n }\n const sql = yield* SqlClient.SqlClient;\n const column = endpoint === 'source' ? 'source' : 'target';\n const rows = yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE entityKind = 'relation' AND ${sql.in(\n column,\n anchorDxns,\n )}`;\n return rows.map((row) => ({\n ...row,\n deleted: !!row.deleted,\n }));\n }),\n );\n\n // TODO(dmaretskyi): Update recordId on objects so that we don't need to look it up separately.\n update = Effect.fn('ObjectMetaIndex.update')(\n (objects: IndexerObject[]): Effect.Effect<void, SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n yield* Effect.forEach(\n objects,\n (object) =>\n Effect.gen(function* () {\n const { spaceId, queueId, documentId, data } = object;\n\n // Extract metadata (Logic emulating Echo APIs as strict imports are unavailable).\n const castData = data;\n const objectId = castData.id;\n\n // Check for existing record by (spaceId, queueId) or (spaceId, documentId).\n let existing: readonly { recordId: number }[];\n if (documentId) {\n existing = yield* sql<{\n recordId: number;\n }>`SELECT recordId FROM objectMeta WHERE spaceId = ${spaceId} AND documentId = ${documentId} AND objectId = ${objectId} LIMIT 1`;\n } else if (queueId) {\n existing = yield* sql<{\n recordId: number;\n }>`SELECT recordId FROM objectMeta WHERE spaceId = ${spaceId} AND queueId = ${queueId} AND objectId = ${objectId} LIMIT 1`;\n } else {\n // Should not happen based on IndexerObject definition (one must be present ideally), but handle gracefully.\n existing = [];\n }\n\n // Get max version + 1.\n const result = yield* sql<{ v: number | null }>`SELECT MAX(version) as v FROM objectMeta`;\n const [{ v }] = result;\n const version = (v ?? 0) + 1;\n\n // Extract metadata.\n const entityKind = castData[ATTR_RELATION_SOURCE] ? 'relation' : 'object';\n const typeDxn = castData[ATTR_TYPE] ? String(castData[ATTR_TYPE]) : 'type';\n const deleted = castData[ATTR_DELETED] ? 1 : 0;\n // Relations.\n const source = entityKind === 'relation' ? (castData[ATTR_RELATION_SOURCE] ?? null) : null;\n const target = entityKind === 'relation' ? (castData[ATTR_RELATION_TARGET] ?? null) : null;\n // Parent (nullable).\n const parent = castData[ATTR_PARENT] ?? null;\n\n const sourceTimestamp = object.updatedAt;\n\n if (existing.length > 0) {\n yield* sql`\n UPDATE objectMeta SET\n version = ${version},\n entityKind = ${entityKind},\n typeDxn = ${typeDxn},\n deleted = ${deleted},\n source = ${source},\n target = ${target},\n parent = ${parent},\n updatedAt = ${sourceTimestamp}\n WHERE recordId = ${existing[0].recordId}\n `;\n } else {\n yield* sql`\n INSERT INTO objectMeta (\n objectId, queueId, spaceId, documentId, \n entityKind, typeDxn, deleted, source, target, parent, version,\n createdAt, updatedAt\n ) VALUES (\n ${objectId}, ${queueId ?? ''}, ${spaceId}, ${documentId ?? ''}, \n ${entityKind}, ${typeDxn}, ${deleted}, \n ${source}, ${target}, ${parent}, ${version},\n ${sourceTimestamp}, ${sourceTimestamp}\n )\n `;\n }\n }),\n { discard: true },\n );\n }),\n );\n\n /**\n * Look up `recordIds` for objects that are already stored in the ObjectMetaIndex.\n * Mutates the objects in place.\n */\n lookupRecordIds = Effect.fn('ObjectMetaIndex.lookupRecordIds')(\n (objects: IndexerObject[]): Effect.Effect<void, SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n for (const object of objects) {\n const { spaceId, queueId, documentId, data } = object;\n const objectId = data.id;\n\n let result: readonly { recordId: number }[];\n if (documentId) {\n result = yield* sql<{\n recordId: number;\n }>`SELECT recordId FROM objectMeta WHERE spaceId = ${spaceId} AND documentId = ${documentId} AND objectId = ${objectId} LIMIT 1`;\n } else if (queueId) {\n result = yield* sql<{\n recordId: number;\n }>`SELECT recordId FROM objectMeta WHERE spaceId = ${spaceId} AND queueId = ${queueId} AND objectId = ${objectId} LIMIT 1`;\n } else {\n result = [];\n }\n\n if (result.length === 0) {\n // TODO(mykola): Handle this case gracefully.\n return yield* Effect.die(\n new Error(`Object not found in ObjectMetaIndex: ${spaceId}/${documentId ?? queueId}/${objectId}`),\n );\n }\n object.recordId = result[0].recordId;\n }\n }),\n );\n\n /**\n * Look up object metadata by recordIds.\n */\n lookupByRecordIds = Effect.fn('ObjectMetaIndex.lookupByRecordIds')(\n (recordIds: number[]): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n if (recordIds.length === 0) {\n return [];\n }\n\n const sql = yield* SqlClient.SqlClient;\n const rows = yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sql.in('recordId', recordIds)}`;\n\n return rows.map((row) => ({\n ...row,\n deleted: !!row.deleted,\n }));\n }),\n );\n\n /**\n * Look up object metadata by objectId, spaceId, and queueId.\n */\n lookupByObjectId = Effect.fn('ObjectMetaIndex.lookupByObjectId')(\n (query: {\n objectId: string;\n spaceId: string;\n queueId: string;\n }): Effect.Effect<ObjectMeta | null, SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n const rows =\n yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE spaceId = ${query.spaceId} AND queueId = ${query.queueId} AND objectId = ${query.objectId} LIMIT 1`;\n\n if (rows.length === 0) {\n return null;\n }\n\n return {\n ...rows[0],\n deleted: !!rows[0].deleted,\n };\n }),\n );\n\n /**\n * Query objects by timestamp range.\n */\n queryByTimeRange = Effect.fn('ObjectMetaIndex.queryByTimeRange')(\n (query: {\n spaceIds: readonly string[];\n updatedAfter?: number;\n updatedBefore?: number;\n createdAfter?: number;\n createdBefore?: number;\n includeAllQueues?: boolean;\n queueIds?: readonly string[] | null;\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n if (query.spaceIds.length === 0 && (!query.queueIds || query.queueIds.length === 0)) {\n return [];\n }\n\n const sql = yield* SqlClient.SqlClient;\n const sourceCondition = buildSourceCondition(\n sql,\n query.spaceIds,\n query.includeAllQueues ?? false,\n query.queueIds ?? null,\n );\n\n const timeConditions: Statement.Fragment[] = [];\n if (query.updatedAfter != null) {\n timeConditions.push(sql`updatedAt >= ${query.updatedAfter}`);\n }\n if (query.updatedBefore != null) {\n timeConditions.push(sql`updatedAt <= ${query.updatedBefore}`);\n }\n if (query.createdAfter != null) {\n timeConditions.push(sql`createdAt >= ${query.createdAfter}`);\n }\n if (query.createdBefore != null) {\n timeConditions.push(sql`createdAt <= ${query.createdBefore}`);\n }\n\n const rows =\n timeConditions.length > 0\n ? yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition} AND ${sql.and(timeConditions)}`\n : yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition}`;\n\n return rows.map((row) => ({\n ...row,\n deleted: !!row.deleted,\n }));\n }),\n );\n\n /**\n * Query children by parent object ids.\n * Matches both:\n * - Objects whose `parent` field references one of the given parent ids (standard parent/child hierarchy).\n * - Queue items whose `queueId` equals one of the parent ids (e.g. items inside a Feed, since a feed's queue\n * DXN uses the feed's object id as its queue id — see `Feed.getQueueDxn`).\n */\n queryChildren = Effect.fn('ObjectMetaIndex.queryChildren')(\n (query: {\n spaceId: SpaceId[];\n parentIds: ObjectId[];\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n if (query.parentIds.length === 0) {\n return [];\n }\n\n const sql = yield* SqlClient.SqlClient;\n const parentDxns = query.parentIds.map((id) => DXN.fromLocalObjectId(id).toString());\n const rows =\n yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sql.in('spaceId', query.spaceId)} AND (${sql.in('parent', parentDxns)} OR ${sql.in('queueId', query.parentIds)})`;\n\n return rows.map((row) => ({\n ...row,\n deleted: !!row.deleted,\n }));\n }),\n );\n}\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as SqlClient from '@effect/sql/SqlClient';\nimport type * as SqlError from '@effect/sql/SqlError';\nimport * as Effect from 'effect/Effect';\nimport * as Schema from 'effect/Schema';\n\nimport { EncodedReference, isEncodedReference } from '@dxos/echo-protocol';\n\nimport { EscapedPropPath } from '../utils';\nimport type { Index, IndexerObject } from './interface';\n\n/**\n * Extracts all outgoing references from an object's data.\n */\nconst extractReferences = (data: Record<string, unknown>): { path: string[]; targetDxn: string }[] => {\n const refs: { path: string[]; targetDxn: string }[] = [];\n const visit = (path: string[], value: unknown) => {\n if (isEncodedReference(value)) {\n const dxn = EncodedReference.toDXN(value);\n const echoId = dxn.asEchoDXN()?.echoId;\n if (!echoId) {\n return; // Skip non-echo references.\n }\n refs.push({ path, targetDxn: dxn.toString() });\n } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n for (const [key, v] of Object.entries(value)) {\n visit([...path, key], v);\n }\n } else if (Array.isArray(value)) {\n for (let i = 0; i < value.length; i++) {\n visit([...path, String(i)], value[i]);\n }\n }\n };\n visit([], data);\n return refs;\n};\n\nexport const ReverseRef = Schema.Struct({\n recordId: Schema.Number,\n targetDxn: Schema.String,\n /**\n * Escaped property path within an object.\n *\n * Escaping rules:\n *\n * - '.' -> '\\.'\n * - '\\' -> '\\\\'\n * - contact with .\n */\n propPath: Schema.String,\n});\nexport interface ReverseRef extends Schema.Schema.Type<typeof ReverseRef> {}\n\nexport interface ReverseRefQuery {\n targetDxn: string;\n // TODO: Add prop filter\n}\n\n/**\n * Indexes reverse references - tracks which objects reference which targets.\n * Only indexes references, not relations.\n */\nexport class ReverseRefIndex implements Index {\n migrate = Effect.fn('ReverseRefIndex.migrate')(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n yield* sql`CREATE TABLE IF NOT EXISTS reverseRef (\n recordId INTEGER NOT NULL,\n targetDxn TEXT NOT NULL,\n propPath TEXT NOT NULL,\n PRIMARY KEY (recordId, targetDxn, propPath)\n )`;\n\n yield* sql`CREATE INDEX IF NOT EXISTS idx_reverse_ref_target ON reverseRef(targetDxn)`;\n });\n\n /**\n * Query all references pointing to a target DXN.\n */\n query = Effect.fn('ReverseRefIndex.query')(\n ({ targetDxn }: ReverseRefQuery): Effect.Effect<readonly ReverseRef[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n // TODO(mykola): Join objectMeta table here.\n const rows = yield* sql`SELECT * FROM reverseRef WHERE targetDxn = ${targetDxn}`;\n return rows as ReverseRef[];\n }),\n );\n\n update = Effect.fn('ReverseRefIndex.update')(\n (objects: IndexerObject[]): Effect.Effect<void, SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n yield* Effect.forEach(\n objects,\n (object) =>\n Effect.gen(function* () {\n const { recordId, data } = object;\n if (recordId === null) {\n yield* Effect.die(new Error('ReverseRefIndex.update requires recordId to be set'));\n }\n\n // Delete existing references for this record.\n yield* sql`DELETE FROM reverseRef WHERE recordId = ${recordId}`;\n\n // Extract references from data.\n const refs = extractReferences(data as unknown as Record<string, unknown>);\n\n // Insert new references.\n yield* Effect.forEach(\n refs,\n (ref) =>\n sql`INSERT INTO reverseRef (recordId, targetDxn, propPath) VALUES (${recordId}, ${ref.targetDxn}, ${EscapedPropPath.escape(ref.path)})`,\n { discard: true },\n );\n }),\n { discard: true },\n );\n }),\n );\n}\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Schema from 'effect/Schema';\n\nimport { invariant } from '@dxos/invariant';\n\nexport type ObjectPropPath = string[];\n\n/**\n * Escaped property path within an object.\n *\n * Escaping rules:\n *\n * - '.' -> '\\.'\n * - '\\' -> '\\\\'\n * - contact with .\n */\nexport const EscapedPropPath: Schema.SchemaClass<string, string> & {\n escape: (path: ObjectPropPath) => EscapedPropPath;\n unescape: (path: EscapedPropPath) => ObjectPropPath;\n} = class extends Schema.String.annotations({ title: 'EscapedPropPath' }) {\n static escape(path: ObjectPropPath): EscapedPropPath {\n return path.map((p) => p.toString().replaceAll('\\\\', '\\\\\\\\').replaceAll('.', '\\\\.')).join('.');\n }\n\n static unescape(path: EscapedPropPath): ObjectPropPath {\n const parts: string[] = [];\n let current = '';\n\n for (let i = 0; i < path.length; i++) {\n if (path[i] === '\\\\') {\n invariant(i + 1 < path.length && (path[i + 1] === '.' || path[i + 1] === '\\\\'), 'Malformed escaping.');\n current = current + path[i + 1];\n i++;\n } else if (path[i] === '.') {\n parts.push(current);\n current = '';\n } else {\n current += path[i];\n }\n }\n parts.push(current);\n\n return parts;\n }\n};\nexport type EscapedPropPath = Schema.Schema.Type<typeof EscapedPropPath>;\n"],
5
+ "mappings": "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",
6
+ "names": ["Effect", "ATTR_TYPE", "SqlTransaction", "SqlClient", "Effect", "Schema", "SpaceId", "IndexCursor", "Struct", "indexName", "String", "spaceId", "NullOr", "sourceName", "resourceId", "cursor", "Union", "Number", "DEPRECATED_INDEX_NAMES", "IndexTracker", "migrate", "fn", "sql", "forEach", "queryCursors", "query", "gen", "spaceIdParam", "undefined", "sourceNameParam", "resourceIdParam", "rows", "map", "row", "decodeSync", "updateCursors", "cursors", "discard", "SqlClient", "Effect", "SQL_CHUNK_SIZE", "escapeFts5Query", "text", "split", "filter", "Boolean", "map", "term", "replace", "join", "FtsIndex", "migrate", "fn", "sql", "query", "spaceId", "includeAllQueues", "queueIds", "gen", "trimmed", "trim", "length", "terms", "minTermLength", "Math", "min", "t", "useBm25", "conditions", "sourceConditions", "push", "in", "or", "rows", "and", "row", "rank", "querySnapshotsJSON", "recordIds", "chunks", "i", "slice", "allResults", "chunk", "r", "recordId", "rowid", "snapshot", "JSON", "parse", "update", "objects", "forEach", "object", "data", "die", "Error", "stringify", "existing", "discard", "SqlClient", "Effect", "Schema", "ATTR_DELETED", "ATTR_PARENT", "ATTR_RELATION_SOURCE", "ATTR_RELATION_TARGET", "ATTR_TYPE", "DXN", "_escapeLikePrefix", "prefix", "escaped", "replaceAll", "ObjectMeta", "Struct", "recordId", "Number", "objectId", "String", "queueId", "spaceId", "documentId", "entityKind", "typeDxn", "deleted", "Boolean", "source", "NullOr", "target", "parent", "version", "createdAt", "updatedAt", "buildSourceCondition", "sql", "spaceIds", "includeAllQueues", "queueIds", "conditions", "length", "push", "in", "or", "ObjectMetaIndex", "migrate", "fn", "catchAll", "void", "query", "gen", "parsedType", "tryParse", "asTypeDXN", "rows", "undefined", "map", "row", "queryAll", "sourceCondition", "queryTypes", "typeDxns", "inverted", "typeWhere", "queryRelations", "endpoint", "anchorDxns", "column", "update", "objects", "forEach", "object", "data", "castData", "id", "existing", "result", "v", "sourceTimestamp", "discard", "lookupRecordIds", "die", "Error", "lookupByRecordIds", "recordIds", "lookupByObjectId", "queryByTimeRange", "timeConditions", "updatedAfter", "updatedBefore", "createdAfter", "createdBefore", "and", "queryChildren", "parentIds", "parentDxns", "fromLocalObjectId", "toString", "SqlClient", "Effect", "Schema", "EncodedReference", "isEncodedReference", "Schema", "invariant", "escape", "unescape", "path", "current", "i", "length", "parts", "extractReferences", "data", "refs", "visit", "path", "value", "isEncodedReference", "dxn", "EncodedReference", "toDXN", "echoId", "asEchoDXN", "push", "targetDxn", "toString", "Array", "isArray", "key", "v", "Object", "entries", "i", "length", "String", "ReverseRef", "Struct", "recordId", "Number", "propPath", "ReverseRefIndex", "migrate", "fn", "sql", "SqlClient", "query", "gen", "rows", "update", "objects", "forEach", "object", "die", "Error", "ref", "EscapedPropPath", "escape", "discard", "makeEmptyIndexingResult", "updated", "done", "spaces", "Set", "queues", "documents", "types", "objects", "accumulateIndexingResult", "acc", "obj", "add", "spaceId", "queueId", "documentId", "t", "data", "ATTR_TYPE", "String", "id", "IndexEngine", "params", "tracker", "IndexTracker", "objectMetaIndex", "ObjectMetaIndex", "ftsIndex", "FtsIndex", "reverseRefIndex", "ReverseRefIndex", "migrate", "gen", "queryText", "query", "queryReverseRef", "queryAll", "querySnapshotsJSON", "recordIds", "queryType", "queryChildren", "queryTypes", "queryByTimeRange", "queryRelations", "lookupByRecordIds", "lookupByObjectId", "update", "ctx", "dataSource", "opts", "result", "updatedFtsIndex", "doneFtsIndex", "ftsObjects", "indexName", "limit", "updatedReverseRefIndex", "doneReverseRefIndex", "reverseRefObjects", "pipe", "withSpan", "index", "source", "sqlTransaction", "SqlTransaction", "withTransaction", "cursors", "queryCursors", "sourceName", "undefined", "updatedCursors", "getChangedObjects", "length", "lookupRecordIds", "updateCursors", "map", "_", "resourceId", "cursor"]
7
+ }