@dxos/index-core 0.8.4-main.c85a9c8dae → 0.8.4-main.cb12b3f963

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.
@@ -1,5 +1,6 @@
1
1
  // src/index-engine.ts
2
2
  import * as Effect5 from "effect/Effect";
3
+ import { ATTR_TYPE as ATTR_TYPE2 } from "@dxos/echo/internal";
3
4
  import * as SqlTransaction from "@dxos/sql-sqlite/SqlTransaction";
4
5
 
5
6
  // src/index-tracker.ts
@@ -31,6 +32,9 @@ var IndexCursor = Schema.Struct({
31
32
  */
32
33
  cursor: Schema.Union(Schema.Number, Schema.String)
33
34
  });
35
+ var DEPRECATED_INDEX_NAMES = [
36
+ "fts"
37
+ ];
34
38
  var IndexTracker = class {
35
39
  migrate = Effect.fn("IndexTracker.migrate")(function* () {
36
40
  const sql = yield* SqlClient.SqlClient;
@@ -42,6 +46,9 @@ var IndexTracker = class {
42
46
  cursor,
43
47
  PRIMARY KEY (indexName, spaceId, sourceName, resourceId)
44
48
  )`;
49
+ yield* Effect.forEach(DEPRECATED_INDEX_NAMES, (indexName) => {
50
+ return sql`DELETE FROM indexCursor WHERE indexName = ${indexName}`;
51
+ });
45
52
  });
46
53
  queryCursors = Effect.fn("IndexTracker.queryCursors")((query) => Effect.gen(function* () {
47
54
  const sql = yield* SqlClient.SqlClient;
@@ -82,6 +89,7 @@ var IndexTracker = class {
82
89
  // src/indexes/fts-index.ts
83
90
  import * as SqlClient3 from "@effect/sql/SqlClient";
84
91
  import * as Effect2 from "effect/Effect";
92
+ var SQL_CHUNK_SIZE = 500;
85
93
  var escapeFts5Query = (text) => {
86
94
  return text.split(/\s+/).filter(Boolean).map((term) => `"${term.replace(/"/g, '""')}"`).join(" ");
87
95
  };
@@ -143,6 +151,7 @@ var FtsIndex = class {
143
151
  /**
144
152
  * Query snapshots by recordIds.
145
153
  * Returns the parsed JSON snapshots for queue objects.
154
+ * RecordIds not present in the FTS index are silently omitted from the result.
146
155
  */
147
156
  querySnapshotsJSON(recordIds) {
148
157
  return Effect2.gen(function* () {
@@ -150,11 +159,21 @@ var FtsIndex = class {
150
159
  return [];
151
160
  }
152
161
  const sql = yield* SqlClient3.SqlClient;
153
- const results = yield* sql`SELECT rowid, snapshot FROM ftsIndex WHERE rowid IN ${sql.in(recordIds)}`;
154
- return results.map((r) => ({
155
- recordId: r.rowid,
156
- snapshot: JSON.parse(r.snapshot)
157
- }));
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;
158
177
  });
159
178
  }
160
179
  update = Effect2.fn("FtsIndex.update")((objects) => Effect2.gen(function* () {
@@ -201,7 +220,11 @@ var ObjectMeta = Schema2.Struct({
201
220
  /** Parent object id (nullable). */
202
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
  });
206
229
  var buildSourceCondition = (sql, spaceIds, includeAllQueues, queueIds) => {
207
230
  const conditions = [];
@@ -235,13 +258,19 @@ var ObjectMetaIndex = class {
235
258
  source TEXT,
236
259
  target TEXT,
237
260
  parent TEXT,
238
- version INTEGER NOT NULL
261
+ version INTEGER NOT NULL,
262
+ createdAt INTEGER,
263
+ updatedAt INTEGER
239
264
  )`;
240
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);
241
268
  yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_objectId ON objectMeta(spaceId, objectId)`;
242
269
  yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_typeDxn ON objectMeta(spaceId, typeDxn)`;
243
270
  yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_version ON objectMeta(version)`;
244
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)`;
245
274
  });
246
275
  query = Effect3.fn("ObjectMetaIndex.query")((query) => Effect3.gen(function* () {
247
276
  const sql = yield* SqlClient5.SqlClient;
@@ -331,6 +360,7 @@ var ObjectMetaIndex = class {
331
360
  const source = entityKind === "relation" ? castData[ATTR_RELATION_SOURCE] ?? null : null;
332
361
  const target = entityKind === "relation" ? castData[ATTR_RELATION_TARGET] ?? null : null;
333
362
  const parent = castData[ATTR_PARENT] ?? null;
363
+ const sourceTimestamp = object.updatedAt;
334
364
  if (existing.length > 0) {
335
365
  yield* sql`
336
366
  UPDATE objectMeta SET
@@ -340,18 +370,21 @@ var ObjectMetaIndex = class {
340
370
  deleted = ${deleted},
341
371
  source = ${source},
342
372
  target = ${target},
343
- parent = ${parent}
373
+ parent = ${parent},
374
+ updatedAt = ${sourceTimestamp}
344
375
  WHERE recordId = ${existing[0].recordId}
345
376
  `;
346
377
  } else {
347
378
  yield* sql`
348
379
  INSERT INTO objectMeta (
349
380
  objectId, queueId, spaceId, documentId,
350
- entityKind, typeDxn, deleted, source, target, parent, version
381
+ entityKind, typeDxn, deleted, source, target, parent, version,
382
+ createdAt, updatedAt
351
383
  ) VALUES (
352
384
  ${objectId}, ${queueId ?? ""}, ${spaceId}, ${documentId ?? ""},
353
385
  ${entityKind}, ${typeDxn}, ${deleted},
354
- ${source}, ${target}, ${parent}, ${version}
386
+ ${source}, ${target}, ${parent}, ${version},
387
+ ${sourceTimestamp}, ${sourceTimestamp}
355
388
  )
356
389
  `;
357
390
  }
@@ -411,14 +444,47 @@ var ObjectMetaIndex = class {
411
444
  };
412
445
  }));
413
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
+ /**
414
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`).
415
480
  */
416
481
  queryChildren = Effect3.fn("ObjectMetaIndex.queryChildren")((query) => Effect3.gen(function* () {
417
482
  if (query.parentIds.length === 0) {
418
483
  return [];
419
484
  }
420
485
  const sql = yield* SqlClient5.SqlClient;
421
- const rows = yield* sql`SELECT * FROM objectMeta WHERE spaceId IN ${sql.in(query.spaceId)} AND parent IN ${sql.in(query.parentIds.map((id) => DXN.fromLocalObjectId(id).toString()))}`;
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)})`;
422
488
  return rows.map((row) => ({
423
489
  ...row,
424
490
  deleted: !!row.deleted
@@ -447,15 +513,7 @@ var EscapedPropPath = class extends Schema3.String.annotations({
447
513
  let current = "";
448
514
  for (let i = 0; i < path.length; i++) {
449
515
  if (path[i] === "\\") {
450
- invariant(i + 1 < path.length && (path[i + 1] === "." || path[i + 1] === "\\"), "Malformed escaping.", {
451
- F: __dxlog_file,
452
- L: 34,
453
- S: this,
454
- A: [
455
- "i + 1 < path.length && (path[i + 1] === '.' || path[i + 1] === '\\\\')",
456
- "'Malformed escaping.'"
457
- ]
458
- });
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.'"] });
459
517
  current = current + path[i + 1];
460
518
  i++;
461
519
  } else if (path[i] === ".") {
@@ -555,6 +613,33 @@ var ReverseRefIndex = class {
555
613
  };
556
614
 
557
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
+ };
558
643
  var IndexEngine = class {
559
644
  #tracker;
560
645
  #objectMetaIndex;
@@ -607,6 +692,9 @@ var IndexEngine = class {
607
692
  queryTypes(query) {
608
693
  return this.#objectMetaIndex.queryTypes(query);
609
694
  }
695
+ queryByTimeRange(query) {
696
+ return this.#objectMetaIndex.queryByTimeRange(query);
697
+ }
610
698
  queryRelations(query) {
611
699
  return this.#objectMetaIndex.queryRelations(query);
612
700
  }
@@ -616,25 +704,26 @@ var IndexEngine = class {
616
704
  lookupByObjectId(query) {
617
705
  return this.#objectMetaIndex.lookupByObjectId(query);
618
706
  }
619
- update(dataSource, opts) {
707
+ update(ctx, dataSource, opts) {
620
708
  return Effect5.gen(this, function* () {
621
- let updated = 0;
622
- const { updated: updatedFtsIndex, done: doneFtsIndex } = yield* this.#update(this.#ftsIndex, dataSource, {
623
- indexName: "fts",
709
+ const result = makeEmptyIndexingResult();
710
+ const { updated: updatedFtsIndex, done: doneFtsIndex, objects: ftsObjects } = yield* this.#update(ctx, this.#ftsIndex, dataSource, {
711
+ indexName: "fts5",
624
712
  spaceId: opts.spaceId,
625
713
  limit: opts.limit
626
714
  });
627
- updated += updatedFtsIndex;
628
- 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, {
629
719
  indexName: "reverseRef",
630
720
  spaceId: opts.spaceId,
631
721
  limit: opts.limit
632
722
  });
633
- updated += updatedReverseRefIndex;
634
- return {
635
- updated,
636
- done: doneFtsIndex && doneReverseRefIndex
637
- };
723
+ result.updated += updatedReverseRefIndex;
724
+ result.done = result.done && doneReverseRefIndex;
725
+ accumulateIndexingResult(result, reverseRefObjects);
726
+ return result;
638
727
  }).pipe(Effect5.withSpan("IndexEngine.update"));
639
728
  }
640
729
  /**
@@ -646,7 +735,7 @@ var IndexEngine = class {
646
735
  * 4. Enriches objects with recordIds.
647
736
  * 5. Updates the dependent index.
648
737
  */
649
- #update(index, source, opts) {
738
+ #update(ctx, index, source, opts) {
650
739
  return Effect5.gen(this, function* () {
651
740
  const sqlTransaction = yield* SqlTransaction.SqlTransaction;
652
741
  return yield* sqlTransaction.withTransaction(Effect5.gen(this, function* () {
@@ -656,13 +745,14 @@ var IndexEngine = class {
656
745
  // Pass undefined to get all cursors when spaceId is null.
657
746
  spaceId: opts.spaceId ?? void 0
658
747
  });
659
- const { objects, cursors: updatedCursors } = yield* source.getChangedObjects(cursors, {
748
+ const { objects, cursors: updatedCursors } = yield* source.getChangedObjects(ctx, cursors, {
660
749
  limit: opts.limit
661
750
  });
662
751
  if (objects.length === 0) {
663
752
  return {
664
753
  updated: 0,
665
- done: true
754
+ done: true,
755
+ objects: []
666
756
  };
667
757
  }
668
758
  yield* this.#objectMetaIndex.update(objects);
@@ -677,7 +767,8 @@ var IndexEngine = class {
677
767
  })));
678
768
  return {
679
769
  updated: objects.length,
680
- done: false
770
+ done: false,
771
+ objects
681
772
  };
682
773
  }));
683
774
  }).pipe(Effect5.withSpan("IndexEngine.#update"));