@dxos/index-core 0.8.4-main.fcfe5033a5 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/LICENSE +102 -5
  2. package/README.md +1 -1
  3. package/dist/lib/neutral/index.mjs +190 -101
  4. package/dist/lib/neutral/index.mjs.map +4 -4
  5. package/dist/lib/neutral/meta.json +1 -1
  6. package/dist/types/src/index-engine.d.ts +31 -17
  7. package/dist/types/src/index-engine.d.ts.map +1 -1
  8. package/dist/types/src/index-tracker.d.ts +3 -3
  9. package/dist/types/src/index.d.ts +3 -3
  10. package/dist/types/src/index.d.ts.map +1 -1
  11. package/dist/types/src/indexes/entity-meta-index.d.ts +113 -0
  12. package/dist/types/src/indexes/entity-meta-index.d.ts.map +1 -0
  13. package/dist/types/src/indexes/entity-meta-index.test.d.ts +2 -0
  14. package/dist/types/src/indexes/entity-meta-index.test.d.ts.map +1 -0
  15. package/dist/types/src/indexes/fts-index.d.ts +7 -6
  16. package/dist/types/src/indexes/fts-index.d.ts.map +1 -1
  17. package/dist/types/src/indexes/index.d.ts +1 -1
  18. package/dist/types/src/indexes/interface.d.ts +15 -4
  19. package/dist/types/src/indexes/interface.d.ts.map +1 -1
  20. package/dist/types/src/indexes/reverse-ref-index.d.ts +3 -2
  21. package/dist/types/src/indexes/reverse-ref-index.d.ts.map +1 -1
  22. package/dist/types/src/utils.d.ts +3 -3
  23. package/dist/types/tsconfig.tsbuildinfo +1 -1
  24. package/package.json +13 -18
  25. package/src/index-engine.test.ts +138 -16
  26. package/src/index-engine.ts +123 -58
  27. package/src/index.ts +9 -3
  28. package/src/indexes/{object-meta-index.test.ts → entity-meta-index.test.ts} +114 -53
  29. package/src/indexes/{object-meta-index.ts → entity-meta-index.ts} +140 -71
  30. package/src/indexes/fts-index.test.ts +188 -34
  31. package/src/indexes/fts-index.ts +32 -15
  32. package/src/indexes/index.ts +1 -1
  33. package/src/indexes/interface.ts +16 -4
  34. package/src/indexes/reverse-ref-index.test.ts +57 -43
  35. package/src/indexes/reverse-ref-index.ts +22 -14
  36. package/src/utils.ts +5 -5
  37. package/dist/types/src/indexes/object-meta-index.d.ts +0 -88
  38. package/dist/types/src/indexes/object-meta-index.d.ts.map +0 -1
  39. package/dist/types/src/indexes/object-meta-index.test.d.ts +0 -2
  40. package/dist/types/src/indexes/object-meta-index.test.d.ts.map +0 -1
@@ -9,18 +9,18 @@ import * as Effect from 'effect/Effect';
9
9
  import * as Layer from 'effect/Layer';
10
10
 
11
11
  import { ATTR_DELETED, ATTR_RELATION_SOURCE, ATTR_RELATION_TARGET, ATTR_TYPE } from '@dxos/echo/internal';
12
- import { DXN, ObjectId, SpaceId } from '@dxos/keys';
12
+ import { DXN, EID, EntityId, SpaceId } from '@dxos/keys';
13
13
 
14
+ import { EntityMetaIndex } from './entity-meta-index';
14
15
  import type { IndexerObject } from './interface';
15
- import { ObjectMetaIndex } from './object-meta-index';
16
16
 
17
- const TYPE_PERSON = DXN.parse('dxn:type:com.example.type.person:0.1.0').toString();
18
- const TYPE_PERSON_VERSIONLESS = DXN.parse('dxn:type:com.example.type.person').toString();
19
- const TYPE_RELATION = DXN.parse('dxn:type:com.example.type.relation:0.1.0').toString();
20
- const TYPE_RELATION_UPDATED = DXN.parse('dxn:type:com.example.type.relation-updated:0.1.0').toString();
21
- const TYPE_WITH_UNDERSCORE = DXN.parse('dxn:type:com.example.type.person-extra:0.1.0').toString();
22
- const TYPE_WITH_UNDERSCORE_VERSIONLESS = DXN.parse('dxn:type:com.example.type.person-extra').toString();
23
- const TYPE_UNDERSCORE_FALSE_POSITIVE = DXN.parse('dxn:type:com.example.type.person-a-extra:0.1.0').toString();
17
+ const TYPE_PERSON = DXN.make('com.example.type.person', '0.1.0');
18
+ const TYPE_PERSON_VERSIONLESS = DXN.make('com.example.type.person');
19
+ const TYPE_RELATION = DXN.make('com.example.type.relation', '0.1.0');
20
+ const TYPE_RELATION_UPDATED = DXN.make('com.example.type.relationUpdated', '0.1.0');
21
+ const TYPE_WITH_UNDERSCORE = DXN.make('com.example.type.personextra', '0.1.0');
22
+ const TYPE_WITH_UNDERSCORE_VERSIONLESS = DXN.make('com.example.type.personextra');
23
+ const TYPE_UNDERSCORE_FALSE_POSITIVE = DXN.make('com.example.type.personaextra', '0.1.0');
24
24
 
25
25
  const TestLayer = Layer.merge(
26
26
  SqliteClient.layer({
@@ -29,20 +29,22 @@ const TestLayer = Layer.merge(
29
29
  Reactivity.layer,
30
30
  );
31
31
 
32
- describe('ObjectMetaIndex', () => {
32
+ describe('EntityMetaIndex', () => {
33
33
  it.effect('should match versioned types when queried by versionless type', () =>
34
34
  Effect.gen(function* () {
35
- const index = new ObjectMetaIndex();
35
+ const index = new EntityMetaIndex();
36
36
  yield* index.migrate();
37
37
 
38
38
  const spaceId = SpaceId.random();
39
- const objectId = ObjectId.random();
39
+ const objectId = EntityId.random();
40
40
 
41
41
  const item: IndexerObject = {
42
42
  spaceId,
43
- queueId: ObjectId.random(),
43
+ queueId: EntityId.random(),
44
+ queueNamespace: 'data',
44
45
  documentId: null,
45
46
  recordId: null,
47
+ createdAt: null,
46
48
  updatedAt: Date.now(),
47
49
  data: {
48
50
  id: objectId,
@@ -53,12 +55,12 @@ describe('ObjectMetaIndex', () => {
53
55
 
54
56
  yield* index.update([item]);
55
57
 
56
- const results = yield* index.query({ spaceId, typeDxn: TYPE_PERSON_VERSIONLESS });
58
+ const results = yield* index.query({ spaceId, typeDXN: TYPE_PERSON_VERSIONLESS });
57
59
  expect(results.map((_) => _.objectId)).toEqual([objectId]);
58
60
 
59
61
  const otherTypeResults = yield* index.query({
60
62
  spaceId,
61
- typeDxn: DXN.parse('dxn:type:com.example.type.other').toString(),
63
+ typeDXN: DXN.make('com.example.type.other'),
62
64
  });
63
65
  expect(otherTypeResults).toEqual([]);
64
66
  }).pipe(Effect.provide(TestLayer)),
@@ -66,18 +68,20 @@ describe('ObjectMetaIndex', () => {
66
68
 
67
69
  it.effect('should not treat LIKE wildcards in versionless type queries', () =>
68
70
  Effect.gen(function* () {
69
- const index = new ObjectMetaIndex();
71
+ const index = new EntityMetaIndex();
70
72
  yield* index.migrate();
71
73
 
72
74
  const spaceId = SpaceId.random();
73
- const objectIdMatch = ObjectId.random();
74
- const objectIdFalsePositive = ObjectId.random();
75
+ const objectIdMatch = EntityId.random();
76
+ const objectIdFalsePositive = EntityId.random();
75
77
 
76
78
  const match: IndexerObject = {
77
79
  spaceId,
78
- queueId: ObjectId.random(),
80
+ queueId: EntityId.random(),
81
+ queueNamespace: 'data',
79
82
  documentId: null,
80
83
  recordId: null,
84
+ createdAt: null,
81
85
  updatedAt: Date.now(),
82
86
  data: {
83
87
  id: objectIdMatch,
@@ -89,9 +93,11 @@ describe('ObjectMetaIndex', () => {
89
93
  // Would match prior implementation because '_' is a LIKE wildcard.
90
94
  const falsePositive: IndexerObject = {
91
95
  spaceId,
92
- queueId: ObjectId.random(),
96
+ queueId: EntityId.random(),
97
+ queueNamespace: 'data',
93
98
  documentId: null,
94
99
  recordId: null,
100
+ createdAt: null,
95
101
  updatedAt: Date.now(),
96
102
  data: {
97
103
  id: objectIdFalsePositive,
@@ -102,7 +108,7 @@ describe('ObjectMetaIndex', () => {
102
108
 
103
109
  yield* index.update([match, falsePositive]);
104
110
 
105
- const queryResults = yield* index.query({ spaceId, typeDxn: TYPE_WITH_UNDERSCORE_VERSIONLESS });
111
+ const queryResults = yield* index.query({ spaceId, typeDXN: TYPE_WITH_UNDERSCORE_VERSIONLESS });
106
112
  expect(queryResults.map((_) => _.objectId)).toEqual([objectIdMatch]);
107
113
 
108
114
  const queryTypesResults = yield* index.queryTypes({
@@ -116,18 +122,20 @@ describe('ObjectMetaIndex', () => {
116
122
 
117
123
  it.effect('should store and update object metadata', () =>
118
124
  Effect.gen(function* () {
119
- const index = new ObjectMetaIndex();
125
+ const index = new EntityMetaIndex();
120
126
  yield* index.migrate();
121
127
 
122
128
  const spaceId = SpaceId.random();
123
- const objectId1 = ObjectId.random();
124
- const objectId2 = ObjectId.random();
129
+ const objectId1 = EntityId.random();
130
+ const objectId2 = EntityId.random();
125
131
 
126
132
  const item1: IndexerObject = {
127
133
  spaceId,
128
- queueId: ObjectId.random(),
134
+ queueId: EntityId.random(),
135
+ queueNamespace: 'data',
129
136
  documentId: null,
130
137
  recordId: null,
138
+ createdAt: null,
131
139
  updatedAt: Date.now(),
132
140
  data: {
133
141
  id: objectId1,
@@ -139,14 +147,16 @@ describe('ObjectMetaIndex', () => {
139
147
  const item2: IndexerObject = {
140
148
  spaceId,
141
149
  queueId: null,
150
+ queueNamespace: null,
142
151
  documentId: 'doc-123',
143
152
  recordId: null,
153
+ createdAt: null,
144
154
  updatedAt: Date.now(),
145
155
  data: {
146
156
  id: objectId2,
147
157
  [ATTR_TYPE]: TYPE_RELATION,
148
- [ATTR_RELATION_SOURCE]: DXN.parse(`dxn:echo:${spaceId}:${ObjectId.random()}}`).toString(),
149
- [ATTR_RELATION_TARGET]: DXN.parse(`dxn:echo:${spaceId}:${ObjectId.random()}}`).toString(),
158
+ [ATTR_RELATION_SOURCE]: EID.make({ spaceId: spaceId, entityId: EntityId.random() }),
159
+ [ATTR_RELATION_TARGET]: EID.make({ spaceId: spaceId, entityId: EntityId.random() }),
150
160
  [ATTR_DELETED]: false,
151
161
  },
152
162
  };
@@ -155,12 +165,12 @@ describe('ObjectMetaIndex', () => {
155
165
  yield* index.update([item1, item2]);
156
166
 
157
167
  // Verify Query.
158
- const results = yield* index.query({ spaceId, typeDxn: TYPE_PERSON });
168
+ const results = yield* index.query({ spaceId, typeDXN: TYPE_PERSON });
159
169
  expect(results.length).toBe(1);
160
170
  expect(results[0].objectId).toBe(objectId1);
161
171
  expect(results[0].version).toBe(1);
162
172
 
163
- const relationResults = yield* index.query({ spaceId, typeDxn: TYPE_RELATION });
173
+ const relationResults = yield* index.query({ spaceId, typeDXN: TYPE_RELATION });
164
174
  expect(relationResults.length).toBe(1);
165
175
  expect(relationResults[0].objectId).toBe(objectId2);
166
176
  expect(relationResults[0].entityKind).toBe('relation');
@@ -179,7 +189,7 @@ describe('ObjectMetaIndex', () => {
179
189
 
180
190
  yield* index.update([item1Update]);
181
191
 
182
- const updatedResults = yield* index.query({ spaceId, typeDxn: TYPE_PERSON });
192
+ const updatedResults = yield* index.query({ spaceId, typeDXN: TYPE_PERSON });
183
193
  // Depending on implementation, query might filter deleted or not.
184
194
  // Current implementation is SELECT * without deleted filter for queryType
185
195
  expect(updatedResults.length).toBe(1);
@@ -197,31 +207,33 @@ describe('ObjectMetaIndex', () => {
197
207
 
198
208
  yield* index.update([item2Update]);
199
209
 
200
- const newTypeResults = yield* index.query({ spaceId, typeDxn: TYPE_RELATION_UPDATED });
210
+ const newTypeResults = yield* index.query({ spaceId, typeDXN: TYPE_RELATION_UPDATED });
201
211
  expect(newTypeResults.length).toBe(1);
202
212
  expect(newTypeResults[0].version).toBe(4);
203
213
  expect(newTypeResults[0].objectId).toBe(objectId2);
204
214
 
205
- const oldTypeResults = yield* index.query({ spaceId, typeDxn: TYPE_RELATION });
215
+ const oldTypeResults = yield* index.query({ spaceId, typeDXN: TYPE_RELATION });
206
216
  expect(oldTypeResults.length).toBe(0);
207
217
  }).pipe(Effect.provide(TestLayer)),
208
218
  );
209
219
 
210
220
  it.effect('should support queryAll/queryTypes/queryRelations', () =>
211
221
  Effect.gen(function* () {
212
- const index = new ObjectMetaIndex();
222
+ const index = new EntityMetaIndex();
213
223
  yield* index.migrate();
214
224
 
215
225
  const spaceId = SpaceId.random();
216
- const objectId1 = ObjectId.random();
217
- const objectId2 = ObjectId.random();
218
- const relationId = ObjectId.random();
226
+ const objectId1 = EntityId.random();
227
+ const objectId2 = EntityId.random();
228
+ const relationId = EntityId.random();
219
229
 
220
230
  const item1: IndexerObject = {
221
231
  spaceId,
222
- queueId: ObjectId.random(),
232
+ queueId: EntityId.random(),
233
+ queueNamespace: 'data',
223
234
  documentId: null,
224
235
  recordId: null,
236
+ createdAt: null,
225
237
  updatedAt: Date.now(),
226
238
  data: {
227
239
  id: objectId1,
@@ -232,9 +244,11 @@ describe('ObjectMetaIndex', () => {
232
244
 
233
245
  const item2: IndexerObject = {
234
246
  spaceId,
235
- queueId: ObjectId.random(),
247
+ queueId: EntityId.random(),
248
+ queueNamespace: 'data',
236
249
  documentId: null,
237
250
  recordId: null,
251
+ createdAt: null,
238
252
  updatedAt: Date.now(),
239
253
  data: {
240
254
  id: objectId2,
@@ -245,15 +259,17 @@ describe('ObjectMetaIndex', () => {
245
259
 
246
260
  const relation: IndexerObject = {
247
261
  spaceId,
248
- queueId: ObjectId.random(),
262
+ queueId: EntityId.random(),
263
+ queueNamespace: 'data',
249
264
  documentId: null,
250
265
  recordId: null,
266
+ createdAt: null,
251
267
  updatedAt: Date.now(),
252
268
  data: {
253
269
  id: relationId,
254
270
  [ATTR_TYPE]: TYPE_RELATION,
255
- [ATTR_RELATION_SOURCE]: DXN.fromLocalObjectId(objectId1).toString(),
256
- [ATTR_RELATION_TARGET]: DXN.fromLocalObjectId(objectId2).toString(),
271
+ [ATTR_RELATION_SOURCE]: EID.make({ entityId: objectId1 }),
272
+ [ATTR_RELATION_TARGET]: EID.make({ entityId: objectId2 }),
257
273
  [ATTR_DELETED]: false,
258
274
  },
259
275
  };
@@ -316,13 +332,13 @@ describe('ObjectMetaIndex', () => {
316
332
 
317
333
  const bySource = yield* index.queryRelations({
318
334
  endpoint: 'source',
319
- anchorDxns: [DXN.fromLocalObjectId(objectId1).toString()],
335
+ anchorDxns: [EID.make({ entityId: objectId1 })],
320
336
  });
321
337
  expect(bySource.map((_) => _.objectId)).toEqual([relationId]);
322
338
 
323
339
  const byTarget = yield* index.queryRelations({
324
340
  endpoint: 'target',
325
- anchorDxns: [DXN.fromLocalObjectId(objectId2).toString()],
341
+ anchorDxns: [EID.make({ entityId: objectId2 })],
326
342
  });
327
343
  expect(byTarget.map((_) => _.objectId)).toEqual([relationId]);
328
344
  }).pipe(Effect.provide(TestLayer)),
@@ -330,19 +346,21 @@ describe('ObjectMetaIndex', () => {
330
346
 
331
347
  it.effect('should set createdAt and updatedAt from source timestamp on insert and updatedAt on update', () =>
332
348
  Effect.gen(function* () {
333
- const index = new ObjectMetaIndex();
349
+ const index = new EntityMetaIndex();
334
350
  yield* index.migrate();
335
351
 
336
352
  const spaceId = SpaceId.random();
337
- const objectId = ObjectId.random();
338
- const queueId = ObjectId.random();
353
+ const objectId = EntityId.random();
354
+ const queueId = EntityId.random();
339
355
 
340
356
  const insertTimestamp = 1700000000000;
341
357
  const item: IndexerObject = {
342
358
  spaceId,
343
359
  queueId,
360
+ queueNamespace: 'data',
344
361
  documentId: null,
345
362
  recordId: null,
363
+ createdAt: null,
346
364
  updatedAt: insertTimestamp,
347
365
  data: {
348
366
  id: objectId,
@@ -353,7 +371,7 @@ describe('ObjectMetaIndex', () => {
353
371
 
354
372
  yield* index.update([item]);
355
373
 
356
- const results = yield* index.query({ spaceId, typeDxn: TYPE_PERSON });
374
+ const results = yield* index.query({ spaceId, typeDXN: TYPE_PERSON });
357
375
  expect(results.length).toBe(1);
358
376
  expect(results[0].createdAt).toBe(insertTimestamp);
359
377
  expect(results[0].updatedAt).toBe(insertTimestamp);
@@ -361,7 +379,7 @@ describe('ObjectMetaIndex', () => {
361
379
  const updateTimestamp = 1700001000000;
362
380
  yield* index.update([{ ...item, updatedAt: updateTimestamp, data: { ...item.data, [ATTR_DELETED]: true } }]);
363
381
 
364
- const updated = yield* index.query({ spaceId, typeDxn: TYPE_PERSON });
382
+ const updated = yield* index.query({ spaceId, typeDXN: TYPE_PERSON });
365
383
  expect(updated[0].createdAt).toBe(insertTimestamp);
366
384
  expect(updated[0].updatedAt).toBe(updateTimestamp);
367
385
  }).pipe(Effect.provide(TestLayer)),
@@ -369,14 +387,14 @@ describe('ObjectMetaIndex', () => {
369
387
 
370
388
  it.effect('should query by time range', () =>
371
389
  Effect.gen(function* () {
372
- const index = new ObjectMetaIndex();
390
+ const index = new EntityMetaIndex();
373
391
  yield* index.migrate();
374
392
 
375
393
  const spaceId = SpaceId.random();
376
- const queueId1 = ObjectId.random();
377
- const queueId2 = ObjectId.random();
378
- const objectId1 = ObjectId.random();
379
- const objectId2 = ObjectId.random();
394
+ const queueId1 = EntityId.random();
395
+ const queueId2 = EntityId.random();
396
+ const objectId1 = EntityId.random();
397
+ const objectId2 = EntityId.random();
380
398
 
381
399
  const earlyTimestamp = 1700000000000;
382
400
  const midpoint = 1700000500000;
@@ -386,8 +404,10 @@ describe('ObjectMetaIndex', () => {
386
404
  {
387
405
  spaceId,
388
406
  queueId: queueId1,
407
+ queueNamespace: 'data',
389
408
  documentId: null,
390
409
  recordId: null,
410
+ createdAt: null,
391
411
  updatedAt: earlyTimestamp,
392
412
  data: { id: objectId1, [ATTR_TYPE]: TYPE_PERSON, [ATTR_DELETED]: false },
393
413
  },
@@ -397,8 +417,10 @@ describe('ObjectMetaIndex', () => {
397
417
  {
398
418
  spaceId,
399
419
  queueId: queueId2,
420
+ queueNamespace: 'data',
400
421
  documentId: null,
401
422
  recordId: null,
423
+ createdAt: null,
402
424
  updatedAt: lateTimestamp,
403
425
  data: { id: objectId2, [ATTR_TYPE]: TYPE_PERSON, [ATTR_DELETED]: false },
404
426
  },
@@ -425,4 +447,43 @@ describe('ObjectMetaIndex', () => {
425
447
  expect(beforeMid.map((_) => _.objectId)).toEqual([objectId1]);
426
448
  }).pipe(Effect.provide(TestLayer)),
427
449
  );
450
+
451
+ it.effect('should round-trip queueNamespace and persist it through updates', () =>
452
+ Effect.gen(function* () {
453
+ const index = new EntityMetaIndex();
454
+ yield* index.migrate();
455
+
456
+ const spaceId = SpaceId.random();
457
+ const traceQueueId = EntityId.random();
458
+ const traceObjectId = EntityId.random();
459
+
460
+ // Initial insert with 'trace' namespace.
461
+ const traceItem: IndexerObject = {
462
+ spaceId,
463
+ queueId: traceQueueId,
464
+ queueNamespace: 'trace',
465
+ documentId: null,
466
+ recordId: null,
467
+ createdAt: null,
468
+ updatedAt: Date.now(),
469
+ data: {
470
+ id: traceObjectId,
471
+ [ATTR_TYPE]: TYPE_PERSON,
472
+ [ATTR_DELETED]: false,
473
+ },
474
+ };
475
+ yield* index.update([traceItem]);
476
+
477
+ const initial = yield* index.queryAll({ spaceIds: [spaceId], includeAllQueues: true });
478
+ expect(initial).toHaveLength(1);
479
+ expect(initial[0].queueNamespace).toBe('trace');
480
+
481
+ // Re-index the same object: the UPDATE branch must preserve the namespace.
482
+ yield* index.update([{ ...traceItem, updatedAt: Date.now() + 1 }]);
483
+
484
+ const afterUpdate = yield* index.queryAll({ spaceIds: [spaceId], includeAllQueues: true });
485
+ expect(afterUpdate).toHaveLength(1);
486
+ expect(afterUpdate[0].queueNamespace).toBe('trace');
487
+ }).pipe(Effect.provide(TestLayer)),
488
+ );
428
489
  });