@dxos/index-core 0.0.0 → 0.8.4-main.1068cf700f
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.
- package/dist/lib/neutral/index.mjs +658 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/types/src/index-engine.d.ts +83 -0
- package/dist/types/src/index-engine.d.ts.map +1 -0
- package/dist/types/src/index-engine.test.d.ts +2 -0
- package/dist/types/src/index-engine.test.d.ts.map +1 -0
- package/dist/types/src/index-tracker.d.ts +44 -0
- package/dist/types/src/index-tracker.d.ts.map +1 -0
- package/dist/types/src/index-tracker.test.d.ts +2 -0
- package/dist/types/src/index-tracker.test.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +8 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/indexes/fts-index.d.ts +63 -0
- package/dist/types/src/indexes/fts-index.d.ts.map +1 -0
- package/dist/types/src/indexes/fts-index.test.d.ts +2 -0
- package/dist/types/src/indexes/fts-index.test.d.ts.map +1 -0
- package/dist/types/src/indexes/fts5.test.d.ts +2 -0
- package/dist/types/src/indexes/fts5.test.d.ts.map +1 -0
- package/dist/types/src/indexes/index.d.ts +5 -0
- package/dist/types/src/indexes/index.d.ts.map +1 -0
- package/dist/types/src/indexes/interface.d.ts +47 -0
- package/dist/types/src/indexes/interface.d.ts.map +1 -0
- package/dist/types/src/indexes/object-meta-index.d.ts +60 -0
- package/dist/types/src/indexes/object-meta-index.d.ts.map +1 -0
- package/dist/types/src/indexes/object-meta-index.test.d.ts +2 -0
- package/dist/types/src/indexes/object-meta-index.test.d.ts.map +1 -0
- package/dist/types/src/indexes/reverse-ref-index.d.ts +37 -0
- package/dist/types/src/indexes/reverse-ref-index.d.ts.map +1 -0
- package/dist/types/src/indexes/reverse-ref-index.test.d.ts +2 -0
- package/dist/types/src/indexes/reverse-ref-index.test.d.ts.map +1 -0
- package/dist/types/src/utils.d.ts +17 -0
- package/dist/types/src/utils.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +22 -18
- package/src/index-engine.test.ts +8 -5
- package/src/index-engine.ts +53 -9
- package/src/index.ts +3 -2
- package/src/indexes/fts-index.ts +41 -2
- package/src/indexes/object-meta-index.test.ts +179 -0
- package/src/indexes/object-meta-index.ts +148 -13
- package/src/utils.ts +1 -1
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/index-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.4-main.1068cf700f",
|
|
4
4
|
"description": "Indexing core.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/dxos/dxos"
|
|
10
|
+
},
|
|
7
11
|
"license": "MIT",
|
|
8
12
|
"author": "info@dxos.org",
|
|
9
13
|
"sideEffects": false,
|
|
@@ -12,8 +16,7 @@
|
|
|
12
16
|
".": {
|
|
13
17
|
"source": "./src/index.ts",
|
|
14
18
|
"types": "./dist/types/src/index.d.ts",
|
|
15
|
-
"
|
|
16
|
-
"node": "./dist/lib/node-esm/index.mjs"
|
|
19
|
+
"default": "./dist/lib/neutral/index.mjs"
|
|
17
20
|
}
|
|
18
21
|
},
|
|
19
22
|
"files": [
|
|
@@ -21,23 +24,24 @@
|
|
|
21
24
|
"src"
|
|
22
25
|
],
|
|
23
26
|
"dependencies": {
|
|
24
|
-
"@
|
|
25
|
-
"@
|
|
26
|
-
"@
|
|
27
|
-
"@
|
|
28
|
-
"
|
|
29
|
-
"@dxos/
|
|
30
|
-
"@dxos/
|
|
31
|
-
"@dxos/
|
|
32
|
-
"@dxos/
|
|
33
|
-
"@effect
|
|
34
|
-
"@
|
|
35
|
-
"@
|
|
36
|
-
"@
|
|
37
|
-
"
|
|
27
|
+
"@effect/cli": "0.73.2",
|
|
28
|
+
"@effect/experimental": "0.58.0",
|
|
29
|
+
"@effect/platform": "0.94.4",
|
|
30
|
+
"@effect/sql": "0.49.0",
|
|
31
|
+
"effect": "3.19.16",
|
|
32
|
+
"@dxos/context": "0.8.4-main.1068cf700f",
|
|
33
|
+
"@dxos/async": "0.8.4-main.1068cf700f",
|
|
34
|
+
"@dxos/echo": "0.8.4-main.1068cf700f",
|
|
35
|
+
"@dxos/debug": "0.8.4-main.1068cf700f",
|
|
36
|
+
"@dxos/effect": "0.8.4-main.1068cf700f",
|
|
37
|
+
"@dxos/echo-protocol": "0.8.4-main.1068cf700f",
|
|
38
|
+
"@dxos/invariant": "0.8.4-main.1068cf700f",
|
|
39
|
+
"@dxos/log": "0.8.4-main.1068cf700f",
|
|
40
|
+
"@dxos/sql-sqlite": "0.8.4-main.1068cf700f",
|
|
41
|
+
"@dxos/keys": "0.8.4-main.1068cf700f"
|
|
38
42
|
},
|
|
39
43
|
"devDependencies": {
|
|
40
|
-
"@effect/sql-sqlite-node": "0.
|
|
44
|
+
"@effect/sql-sqlite-node": "0.50.1"
|
|
41
45
|
},
|
|
42
46
|
"publishConfig": {
|
|
43
47
|
"access": "public"
|
package/src/index-engine.test.ts
CHANGED
|
@@ -11,6 +11,7 @@ import * as Layer from 'effect/Layer';
|
|
|
11
11
|
import { ATTR_TYPE } from '@dxos/echo/internal';
|
|
12
12
|
import { invariant } from '@dxos/invariant';
|
|
13
13
|
import { DXN, ObjectId, SpaceId } from '@dxos/keys';
|
|
14
|
+
import * as SqlTransaction from '@dxos/sql-sqlite/SqlTransaction';
|
|
14
15
|
|
|
15
16
|
import { type DataSourceCursor, type IndexDataSource, IndexEngine } from './index-engine';
|
|
16
17
|
import { type IndexCursor, IndexTracker } from './index-tracker';
|
|
@@ -20,11 +21,13 @@ const TYPE_DEFAULT = DXN.parse('dxn:type:test.com/type/Type:0.1.0').toString();
|
|
|
20
21
|
const TYPE_A = DXN.parse('dxn:type:test.com/type/TypeA:0.1.0').toString();
|
|
21
22
|
const TYPE_B = DXN.parse('dxn:type:test.com/type/TypeB:0.1.0').toString();
|
|
22
23
|
|
|
23
|
-
const TestLayer =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
const TestLayer = SqlTransaction.layer.pipe(
|
|
25
|
+
Layer.provideMerge(
|
|
26
|
+
SqliteClient.layer({
|
|
27
|
+
filename: ':memory:',
|
|
28
|
+
}),
|
|
29
|
+
),
|
|
30
|
+
Layer.provideMerge(Reactivity.layer),
|
|
28
31
|
);
|
|
29
32
|
|
|
30
33
|
class MockIndexDataSource implements IndexDataSource {
|
package/src/index-engine.ts
CHANGED
|
@@ -2,16 +2,18 @@
|
|
|
2
2
|
// Copyright 2026 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import * as SqlClient from '@effect/sql/SqlClient';
|
|
5
|
+
import type * as SqlClient from '@effect/sql/SqlClient';
|
|
6
6
|
import type * as SqlError from '@effect/sql/SqlError';
|
|
7
7
|
import * as Effect from 'effect/Effect';
|
|
8
8
|
|
|
9
|
-
import type { SpaceId } from '@dxos/keys';
|
|
9
|
+
import type { ObjectId, SpaceId } from '@dxos/keys';
|
|
10
|
+
import * as SqlTransaction from '@dxos/sql-sqlite/SqlTransaction';
|
|
10
11
|
|
|
11
12
|
import { type IndexCursor, IndexTracker } from './index-tracker';
|
|
12
13
|
import {
|
|
13
14
|
FtsIndex,
|
|
14
15
|
type FtsQuery,
|
|
16
|
+
type FtsQueryResult,
|
|
15
17
|
type Index,
|
|
16
18
|
type IndexerObject,
|
|
17
19
|
type ObjectMeta,
|
|
@@ -76,9 +78,9 @@ export class IndexEngine {
|
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
/**
|
|
79
|
-
* Query text index and return full object metadata.
|
|
81
|
+
* Query text index and return full object metadata with rank.
|
|
80
82
|
*/
|
|
81
|
-
queryText(query: FtsQuery): Effect.Effect<readonly
|
|
83
|
+
queryText(query: FtsQuery): Effect.Effect<readonly FtsQueryResult[], SqlError.SqlError, SqlClient.SqlClient> {
|
|
82
84
|
return Effect.gen(this, function* () {
|
|
83
85
|
return yield* this.#ftsIndex.query(query);
|
|
84
86
|
});
|
|
@@ -89,6 +91,12 @@ export class IndexEngine {
|
|
|
89
91
|
return this.#reverseRefIndex.query(query);
|
|
90
92
|
}
|
|
91
93
|
|
|
94
|
+
queryAll(query: {
|
|
95
|
+
spaceIds: readonly SpaceId[];
|
|
96
|
+
}): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {
|
|
97
|
+
return this.#objectMetaIndex.queryAll(query);
|
|
98
|
+
}
|
|
99
|
+
|
|
92
100
|
/**
|
|
93
101
|
* Query snapshots by recordIds.
|
|
94
102
|
* Used to load queue objects from indexed snapshots.
|
|
@@ -103,10 +111,41 @@ export class IndexEngine {
|
|
|
103
111
|
return this.#objectMetaIndex.query(query);
|
|
104
112
|
}
|
|
105
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Query children by parent object ids.
|
|
116
|
+
*/
|
|
117
|
+
queryChildren(query: {
|
|
118
|
+
spaceId: SpaceId[];
|
|
119
|
+
parentIds: ObjectId[];
|
|
120
|
+
}): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {
|
|
121
|
+
return this.#objectMetaIndex.queryChildren(query);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
queryTypes(query: {
|
|
125
|
+
spaceIds: readonly SpaceId[];
|
|
126
|
+
typeDxns: readonly ObjectMeta['typeDxn'][];
|
|
127
|
+
inverted?: boolean;
|
|
128
|
+
}): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {
|
|
129
|
+
return this.#objectMetaIndex.queryTypes(query);
|
|
130
|
+
}
|
|
131
|
+
queryRelations(query: {
|
|
132
|
+
endpoint: 'source' | 'target';
|
|
133
|
+
anchorDxns: readonly string[];
|
|
134
|
+
}): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {
|
|
135
|
+
return this.#objectMetaIndex.queryRelations(query);
|
|
136
|
+
}
|
|
137
|
+
lookupByRecordIds(recordIds: number[]): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {
|
|
138
|
+
return this.#objectMetaIndex.lookupByRecordIds(recordIds);
|
|
139
|
+
}
|
|
140
|
+
|
|
106
141
|
update(
|
|
107
142
|
dataSource: IndexDataSource,
|
|
108
143
|
opts: { spaceId: SpaceId | null; limit?: number },
|
|
109
|
-
): Effect.Effect<
|
|
144
|
+
): Effect.Effect<
|
|
145
|
+
{ updated: number; done: boolean },
|
|
146
|
+
SqlError.SqlError,
|
|
147
|
+
SqlTransaction.SqlTransaction | SqlClient.SqlClient
|
|
148
|
+
> {
|
|
110
149
|
return Effect.gen(this, function* () {
|
|
111
150
|
let updated = 0;
|
|
112
151
|
|
|
@@ -145,10 +184,15 @@ export class IndexEngine {
|
|
|
145
184
|
index: Index,
|
|
146
185
|
source: IndexDataSource,
|
|
147
186
|
opts: { indexName: string; spaceId: SpaceId | null; limit?: number },
|
|
148
|
-
): Effect.Effect<
|
|
187
|
+
): Effect.Effect<
|
|
188
|
+
{ updated: number; done: boolean },
|
|
189
|
+
SqlError.SqlError,
|
|
190
|
+
SqlTransaction.SqlTransaction | SqlClient.SqlClient
|
|
191
|
+
> {
|
|
149
192
|
return Effect.gen(this, function* () {
|
|
150
|
-
const
|
|
151
|
-
|
|
193
|
+
const sqlTransaction = yield* SqlTransaction.SqlTransaction;
|
|
194
|
+
|
|
195
|
+
return yield* sqlTransaction.withTransaction(
|
|
152
196
|
Effect.gen(this, function* () {
|
|
153
197
|
const cursors = yield* this.#tracker.queryCursors({
|
|
154
198
|
indexName: opts.indexName,
|
|
@@ -182,6 +226,6 @@ export class IndexEngine {
|
|
|
182
226
|
return { updated: objects.length, done: false };
|
|
183
227
|
}),
|
|
184
228
|
);
|
|
185
|
-
}).pipe(Effect.withSpan('IndexEngine.#
|
|
229
|
+
}).pipe(Effect.withSpan('IndexEngine.#update'));
|
|
186
230
|
}
|
|
187
231
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
export { IndexEngine, type IndexDataSource, type DataSourceCursor, type IndexEngineParams } from './index-engine';
|
|
6
6
|
export { IndexTracker, type IndexCursor } from './index-tracker';
|
|
7
7
|
export { type IndexerObject, type Index } from './indexes/interface';
|
|
8
|
-
export { FtsIndex } from './indexes/fts-index';
|
|
8
|
+
export { FtsIndex, type FtsQuery } from './indexes/fts-index';
|
|
9
9
|
export { ObjectMetaIndex, type ObjectMeta } from './indexes/object-meta-index';
|
|
10
|
-
export { ReverseRefIndex, type ReverseRef } from './indexes/reverse-ref-index';
|
|
10
|
+
export { ReverseRefIndex, type ReverseRef, type ReverseRefQuery } from './indexes/reverse-ref-index';
|
|
11
|
+
export { EscapedPropPath, type ObjectPropPath } from './utils';
|
package/src/indexes/fts-index.ts
CHANGED
|
@@ -49,6 +49,18 @@ export interface FtsResult extends ObjectMeta {
|
|
|
49
49
|
snapshot: string;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Result of FTS query with rank.
|
|
54
|
+
*/
|
|
55
|
+
export interface FtsQueryResult extends ObjectMeta {
|
|
56
|
+
/**
|
|
57
|
+
* Relevance rank from FTS5.
|
|
58
|
+
* Higher values indicate better matches.
|
|
59
|
+
* Uses BM25 algorithm when available, falls back to 1 for non-BM25 queries.
|
|
60
|
+
*/
|
|
61
|
+
rank: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
52
64
|
/**
|
|
53
65
|
* Escapes user input for safe FTS5 queries.
|
|
54
66
|
*
|
|
@@ -92,7 +104,7 @@ export class FtsIndex implements Index {
|
|
|
92
104
|
spaceId,
|
|
93
105
|
includeAllQueues,
|
|
94
106
|
queueIds,
|
|
95
|
-
}: FtsQuery): Effect.Effect<readonly
|
|
107
|
+
}: FtsQuery): Effect.Effect<readonly FtsQueryResult[], SqlError.SqlError, SqlClient.SqlClient> {
|
|
96
108
|
return Effect.gen(function* () {
|
|
97
109
|
const trimmed = query.trim();
|
|
98
110
|
if (trimmed.length === 0) {
|
|
@@ -106,6 +118,11 @@ export class FtsIndex implements Index {
|
|
|
106
118
|
const terms = trimmed.split(/\s+/).filter(Boolean);
|
|
107
119
|
const minTermLength = Math.min(...terms.map((t) => t.length));
|
|
108
120
|
|
|
121
|
+
// Use BM25 ranking for FTS5 MATCH queries, fall back to rank 1 for LIKE queries.
|
|
122
|
+
// BM25 returns negative values where lower (more negative) means better match,
|
|
123
|
+
// so we negate it to get higher = better.
|
|
124
|
+
const useBm25 = minTermLength >= 3;
|
|
125
|
+
|
|
109
126
|
const conditions =
|
|
110
127
|
minTermLength < 3
|
|
111
128
|
? // LIKE fallback - scan the entire table, AND all terms.
|
|
@@ -135,7 +152,29 @@ export class FtsIndex implements Index {
|
|
|
135
152
|
conditions.push(sql`(${sql.or(sourceConditions)})`);
|
|
136
153
|
}
|
|
137
154
|
|
|
138
|
-
|
|
155
|
+
if (useBm25) {
|
|
156
|
+
// Use BM25 ranking for FTS5 MATCH queries.
|
|
157
|
+
// BM25 returns negative values, negate to get higher = better match.
|
|
158
|
+
// Order by rank descending so best matches come first.
|
|
159
|
+
// Note: bm25() requires the actual table name, not an alias.
|
|
160
|
+
const rows = yield* sql<ObjectMeta & { rank: number }>`
|
|
161
|
+
SELECT m.*, -bm25(ftsIndex) AS rank
|
|
162
|
+
FROM ftsIndex AS f
|
|
163
|
+
JOIN objectMeta AS m ON f.rowid = m.recordId
|
|
164
|
+
WHERE ${sql.and(conditions)}
|
|
165
|
+
ORDER BY rank DESC
|
|
166
|
+
`;
|
|
167
|
+
return rows;
|
|
168
|
+
} else {
|
|
169
|
+
// LIKE fallback - no ranking available, default to 1.
|
|
170
|
+
const rows = yield* sql<ObjectMeta>`
|
|
171
|
+
SELECT m.*
|
|
172
|
+
FROM ftsIndex AS f
|
|
173
|
+
JOIN objectMeta AS m ON f.rowid = m.recordId
|
|
174
|
+
WHERE ${sql.and(conditions)}
|
|
175
|
+
`;
|
|
176
|
+
return rows.map((row) => ({ ...row, rank: 1 }));
|
|
177
|
+
}
|
|
139
178
|
});
|
|
140
179
|
}
|
|
141
180
|
|
|
@@ -15,8 +15,12 @@ import type { IndexerObject } from './interface';
|
|
|
15
15
|
import { ObjectMetaIndex } from './object-meta-index';
|
|
16
16
|
|
|
17
17
|
const TYPE_PERSON = DXN.parse('dxn:type:example.com/type/Person:0.1.0').toString();
|
|
18
|
+
const TYPE_PERSON_VERSIONLESS = DXN.parse('dxn:type:example.com/type/Person').toString();
|
|
18
19
|
const TYPE_RELATION = DXN.parse('dxn:type:example.com/type/Relation:0.1.0').toString();
|
|
19
20
|
const TYPE_RELATION_UPDATED = DXN.parse('dxn:type:example.com/type/RelationUpdated:0.1.0').toString();
|
|
21
|
+
const TYPE_WITH_UNDERSCORE = DXN.parse('dxn:type:example.com/type/Person_Extra:0.1.0').toString();
|
|
22
|
+
const TYPE_WITH_UNDERSCORE_VERSIONLESS = DXN.parse('dxn:type:example.com/type/Person_Extra').toString();
|
|
23
|
+
const TYPE_UNDERSCORE_FALSE_POSITIVE = DXN.parse('dxn:type:example.com/type/PersonAExtra:0.1.0').toString();
|
|
20
24
|
|
|
21
25
|
const TestLayer = Layer.merge(
|
|
22
26
|
SqliteClient.layer({
|
|
@@ -26,6 +30,86 @@ const TestLayer = Layer.merge(
|
|
|
26
30
|
);
|
|
27
31
|
|
|
28
32
|
describe('ObjectMetaIndex', () => {
|
|
33
|
+
it.effect('should match versioned types when queried by versionless type', () =>
|
|
34
|
+
Effect.gen(function* () {
|
|
35
|
+
const index = new ObjectMetaIndex();
|
|
36
|
+
yield* index.migrate();
|
|
37
|
+
|
|
38
|
+
const spaceId = SpaceId.random();
|
|
39
|
+
const objectId = ObjectId.random();
|
|
40
|
+
|
|
41
|
+
const item: IndexerObject = {
|
|
42
|
+
spaceId,
|
|
43
|
+
queueId: ObjectId.random(),
|
|
44
|
+
documentId: null,
|
|
45
|
+
recordId: null,
|
|
46
|
+
data: {
|
|
47
|
+
id: objectId,
|
|
48
|
+
[ATTR_TYPE]: TYPE_PERSON,
|
|
49
|
+
[ATTR_DELETED]: false,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
yield* index.update([item]);
|
|
54
|
+
|
|
55
|
+
const results = yield* index.query({ spaceId, typeDxn: TYPE_PERSON_VERSIONLESS });
|
|
56
|
+
expect(results.map((_) => _.objectId)).toEqual([objectId]);
|
|
57
|
+
|
|
58
|
+
const otherTypeResults = yield* index.query({
|
|
59
|
+
spaceId,
|
|
60
|
+
typeDxn: DXN.parse('dxn:type:example.com/type/Other').toString(),
|
|
61
|
+
});
|
|
62
|
+
expect(otherTypeResults).toEqual([]);
|
|
63
|
+
}).pipe(Effect.provide(TestLayer)),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
it.effect('should not treat LIKE wildcards in versionless type queries', () =>
|
|
67
|
+
Effect.gen(function* () {
|
|
68
|
+
const index = new ObjectMetaIndex();
|
|
69
|
+
yield* index.migrate();
|
|
70
|
+
|
|
71
|
+
const spaceId = SpaceId.random();
|
|
72
|
+
const objectIdMatch = ObjectId.random();
|
|
73
|
+
const objectIdFalsePositive = ObjectId.random();
|
|
74
|
+
|
|
75
|
+
const match: IndexerObject = {
|
|
76
|
+
spaceId,
|
|
77
|
+
queueId: ObjectId.random(),
|
|
78
|
+
documentId: null,
|
|
79
|
+
recordId: null,
|
|
80
|
+
data: {
|
|
81
|
+
id: objectIdMatch,
|
|
82
|
+
[ATTR_TYPE]: TYPE_WITH_UNDERSCORE,
|
|
83
|
+
[ATTR_DELETED]: false,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Would match prior implementation because '_' is a LIKE wildcard.
|
|
88
|
+
const falsePositive: IndexerObject = {
|
|
89
|
+
spaceId,
|
|
90
|
+
queueId: ObjectId.random(),
|
|
91
|
+
documentId: null,
|
|
92
|
+
recordId: null,
|
|
93
|
+
data: {
|
|
94
|
+
id: objectIdFalsePositive,
|
|
95
|
+
[ATTR_TYPE]: TYPE_UNDERSCORE_FALSE_POSITIVE,
|
|
96
|
+
[ATTR_DELETED]: false,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
yield* index.update([match, falsePositive]);
|
|
101
|
+
|
|
102
|
+
const queryResults = yield* index.query({ spaceId, typeDxn: TYPE_WITH_UNDERSCORE_VERSIONLESS });
|
|
103
|
+
expect(queryResults.map((_) => _.objectId)).toEqual([objectIdMatch]);
|
|
104
|
+
|
|
105
|
+
const queryTypesResults = yield* index.queryTypes({
|
|
106
|
+
spaceIds: [spaceId],
|
|
107
|
+
typeDxns: [TYPE_WITH_UNDERSCORE_VERSIONLESS],
|
|
108
|
+
});
|
|
109
|
+
expect(queryTypesResults.map((_) => _.objectId)).toEqual([objectIdMatch]);
|
|
110
|
+
}).pipe(Effect.provide(TestLayer)),
|
|
111
|
+
);
|
|
112
|
+
|
|
29
113
|
it.effect('should store and update object metadata', () =>
|
|
30
114
|
Effect.gen(function* () {
|
|
31
115
|
const index = new ObjectMetaIndex();
|
|
@@ -116,4 +200,99 @@ describe('ObjectMetaIndex', () => {
|
|
|
116
200
|
expect(oldTypeResults.length).toBe(0);
|
|
117
201
|
}).pipe(Effect.provide(TestLayer)),
|
|
118
202
|
);
|
|
203
|
+
|
|
204
|
+
it.effect('should support queryAll/queryTypes/queryRelations', () =>
|
|
205
|
+
Effect.gen(function* () {
|
|
206
|
+
const index = new ObjectMetaIndex();
|
|
207
|
+
yield* index.migrate();
|
|
208
|
+
|
|
209
|
+
const spaceId = SpaceId.random();
|
|
210
|
+
const objectId1 = ObjectId.random();
|
|
211
|
+
const objectId2 = ObjectId.random();
|
|
212
|
+
const relationId = ObjectId.random();
|
|
213
|
+
|
|
214
|
+
const item1: IndexerObject = {
|
|
215
|
+
spaceId,
|
|
216
|
+
queueId: ObjectId.random(),
|
|
217
|
+
documentId: null,
|
|
218
|
+
recordId: null,
|
|
219
|
+
data: {
|
|
220
|
+
id: objectId1,
|
|
221
|
+
[ATTR_TYPE]: TYPE_PERSON,
|
|
222
|
+
[ATTR_DELETED]: false,
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const item2: IndexerObject = {
|
|
227
|
+
spaceId,
|
|
228
|
+
queueId: ObjectId.random(),
|
|
229
|
+
documentId: null,
|
|
230
|
+
recordId: null,
|
|
231
|
+
data: {
|
|
232
|
+
id: objectId2,
|
|
233
|
+
[ATTR_TYPE]: TYPE_RELATION,
|
|
234
|
+
[ATTR_DELETED]: false,
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const relation: IndexerObject = {
|
|
239
|
+
spaceId,
|
|
240
|
+
queueId: ObjectId.random(),
|
|
241
|
+
documentId: null,
|
|
242
|
+
recordId: null,
|
|
243
|
+
data: {
|
|
244
|
+
id: relationId,
|
|
245
|
+
[ATTR_TYPE]: TYPE_RELATION,
|
|
246
|
+
[ATTR_RELATION_SOURCE]: DXN.fromLocalObjectId(objectId1).toString(),
|
|
247
|
+
[ATTR_RELATION_TARGET]: DXN.fromLocalObjectId(objectId2).toString(),
|
|
248
|
+
[ATTR_DELETED]: false,
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
yield* index.update([item1, item2, relation]);
|
|
253
|
+
|
|
254
|
+
const all = yield* index.queryAll({ spaceIds: [spaceId] });
|
|
255
|
+
expect(all.map((_) => _.objectId).sort()).toEqual([objectId1, objectId2, relationId].sort());
|
|
256
|
+
|
|
257
|
+
const types = yield* index.queryTypes({ spaceIds: [spaceId], typeDxns: [TYPE_PERSON, TYPE_RELATION] });
|
|
258
|
+
expect(types.map((_) => _.objectId).sort()).toEqual([objectId1, objectId2, relationId].sort());
|
|
259
|
+
|
|
260
|
+
const onlyPerson = yield* index.queryTypes({ spaceIds: [spaceId], typeDxns: [TYPE_PERSON] });
|
|
261
|
+
expect(onlyPerson.map((_) => _.objectId)).toEqual([objectId1]);
|
|
262
|
+
|
|
263
|
+
const onlyPersonVersionless = yield* index.queryTypes({
|
|
264
|
+
spaceIds: [spaceId],
|
|
265
|
+
typeDxns: [TYPE_PERSON_VERSIONLESS],
|
|
266
|
+
});
|
|
267
|
+
expect(onlyPersonVersionless.map((_) => _.objectId)).toEqual([objectId1]);
|
|
268
|
+
|
|
269
|
+
const notPerson = yield* index.queryTypes({ spaceIds: [spaceId], typeDxns: [TYPE_PERSON], inverted: true });
|
|
270
|
+
expect(notPerson.map((_) => _.objectId).sort()).toEqual([objectId2, relationId].sort());
|
|
271
|
+
|
|
272
|
+
const notPersonVersionless = yield* index.queryTypes({
|
|
273
|
+
spaceIds: [spaceId],
|
|
274
|
+
typeDxns: [TYPE_PERSON_VERSIONLESS],
|
|
275
|
+
inverted: true,
|
|
276
|
+
});
|
|
277
|
+
expect(notPersonVersionless.map((_) => _.objectId).sort()).toEqual([objectId2, relationId].sort());
|
|
278
|
+
|
|
279
|
+
const emptyTypes = yield* index.queryTypes({ spaceIds: [spaceId], typeDxns: [] });
|
|
280
|
+
expect(emptyTypes).toEqual([]);
|
|
281
|
+
|
|
282
|
+
const notEmptyTypes = yield* index.queryTypes({ spaceIds: [spaceId], typeDxns: [], inverted: true });
|
|
283
|
+
expect(notEmptyTypes.map((_) => _.objectId).sort()).toEqual([objectId1, objectId2, relationId].sort());
|
|
284
|
+
|
|
285
|
+
const bySource = yield* index.queryRelations({
|
|
286
|
+
endpoint: 'source',
|
|
287
|
+
anchorDxns: [DXN.fromLocalObjectId(objectId1).toString()],
|
|
288
|
+
});
|
|
289
|
+
expect(bySource.map((_) => _.objectId)).toEqual([relationId]);
|
|
290
|
+
|
|
291
|
+
const byTarget = yield* index.queryRelations({
|
|
292
|
+
endpoint: 'target',
|
|
293
|
+
anchorDxns: [DXN.fromLocalObjectId(objectId2).toString()],
|
|
294
|
+
});
|
|
295
|
+
expect(byTarget.map((_) => _.objectId)).toEqual([relationId]);
|
|
296
|
+
}).pipe(Effect.provide(TestLayer)),
|
|
297
|
+
);
|
|
119
298
|
});
|