@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
|
@@ -7,11 +7,20 @@ import type * as SqlError from '@effect/sql/SqlError';
|
|
|
7
7
|
import * as Effect from 'effect/Effect';
|
|
8
8
|
import * as Schema from 'effect/Schema';
|
|
9
9
|
|
|
10
|
-
import { ATTR_DELETED, ATTR_RELATION_SOURCE, ATTR_RELATION_TARGET, ATTR_TYPE } from '@dxos/echo/internal';
|
|
10
|
+
import { ATTR_DELETED, ATTR_PARENT, ATTR_RELATION_SOURCE, ATTR_RELATION_TARGET, ATTR_TYPE } from '@dxos/echo/internal';
|
|
11
|
+
import { DXN, type ObjectId, type SpaceId } from '@dxos/keys';
|
|
11
12
|
|
|
12
13
|
import type { IndexerObject } from './interface';
|
|
13
14
|
import type { Index } from './interface';
|
|
14
15
|
|
|
16
|
+
const _escapeLikePrefix = (prefix: string) => {
|
|
17
|
+
// Escape LIKE metacharacters in the *literal* prefix (we still append a wildcard for the version suffix).
|
|
18
|
+
// Backslash is used as the ESCAPE character.
|
|
19
|
+
// See: https://www.sqlite.org/lang_expr.html#like
|
|
20
|
+
const escaped = prefix.replaceAll('\\', '\\\\').replaceAll('%', '\\%').replaceAll('_', '\\_');
|
|
21
|
+
return `${escaped}:%`;
|
|
22
|
+
};
|
|
23
|
+
|
|
15
24
|
export const ObjectMeta = Schema.Struct({
|
|
16
25
|
recordId: Schema.Number,
|
|
17
26
|
objectId: Schema.String,
|
|
@@ -19,10 +28,13 @@ export const ObjectMeta = Schema.Struct({
|
|
|
19
28
|
spaceId: Schema.String,
|
|
20
29
|
documentId: Schema.String,
|
|
21
30
|
entityKind: Schema.String,
|
|
31
|
+
/** The versioned DXN of the type of the object. */
|
|
22
32
|
typeDxn: Schema.String,
|
|
23
33
|
deleted: Schema.Boolean,
|
|
24
34
|
source: Schema.NullOr(Schema.String),
|
|
25
35
|
target: Schema.NullOr(Schema.String),
|
|
36
|
+
/** Parent object id (nullable). */
|
|
37
|
+
parent: Schema.NullOr(Schema.String),
|
|
26
38
|
/** Monotonically increasing sequence number assigned on insert/update for tracking indexing order. */
|
|
27
39
|
version: Schema.Number,
|
|
28
40
|
});
|
|
@@ -43,23 +55,124 @@ export class ObjectMetaIndex implements Index {
|
|
|
43
55
|
deleted INTEGER NOT NULL,
|
|
44
56
|
source TEXT,
|
|
45
57
|
target TEXT,
|
|
58
|
+
parent TEXT,
|
|
46
59
|
version INTEGER NOT NULL
|
|
47
60
|
)`;
|
|
48
61
|
|
|
62
|
+
// Add `parent` column for tables created before it was introduced.
|
|
63
|
+
yield* Effect.catchAll(sql`ALTER TABLE objectMeta ADD COLUMN parent TEXT`, () => Effect.void);
|
|
64
|
+
|
|
49
65
|
yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_objectId ON objectMeta(spaceId, objectId)`;
|
|
50
66
|
yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_typeDxn ON objectMeta(spaceId, typeDxn)`;
|
|
51
67
|
yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_version ON objectMeta(version)`;
|
|
68
|
+
yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_parent ON objectMeta(spaceId, parent)`;
|
|
52
69
|
});
|
|
53
70
|
|
|
54
|
-
query = Effect.fn('ObjectMetaIndex.
|
|
71
|
+
query = Effect.fn('ObjectMetaIndex.query')(
|
|
55
72
|
(
|
|
56
73
|
query: Pick<ObjectMeta, 'spaceId' | 'typeDxn'>,
|
|
57
74
|
): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>
|
|
58
75
|
Effect.gen(function* () {
|
|
59
76
|
const sql = yield* SqlClient.SqlClient;
|
|
77
|
+
const parsedType = DXN.tryParse(query.typeDxn)?.asTypeDXN();
|
|
78
|
+
|
|
60
79
|
// SQLite stores booleans as integers, so we need to specify the raw row type.
|
|
61
80
|
const rows =
|
|
62
|
-
|
|
81
|
+
parsedType && parsedType.version === undefined
|
|
82
|
+
? yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE spaceId = ${query.spaceId} AND (typeDxn = ${
|
|
83
|
+
query.typeDxn
|
|
84
|
+
} OR typeDxn LIKE ${_escapeLikePrefix(query.typeDxn)} ESCAPE '\\')`
|
|
85
|
+
: yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE spaceId = ${query.spaceId} AND typeDxn = ${query.typeDxn}`;
|
|
86
|
+
return rows.map((row) => ({
|
|
87
|
+
...row,
|
|
88
|
+
deleted: !!row.deleted,
|
|
89
|
+
}));
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
queryAll = Effect.fn('ObjectMetaIndex.queryAll')(
|
|
94
|
+
(query: {
|
|
95
|
+
spaceIds: readonly ObjectMeta['spaceId'][];
|
|
96
|
+
}): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>
|
|
97
|
+
Effect.gen(function* () {
|
|
98
|
+
if (query.spaceIds.length === 0) {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const sql = yield* SqlClient.SqlClient;
|
|
103
|
+
const rows = yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sql.in('spaceId', query.spaceIds)}`;
|
|
104
|
+
return rows.map((row) => ({
|
|
105
|
+
...row,
|
|
106
|
+
deleted: !!row.deleted,
|
|
107
|
+
}));
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
queryTypes = Effect.fn('ObjectMetaIndex.queryTypes')(
|
|
112
|
+
({
|
|
113
|
+
spaceIds,
|
|
114
|
+
typeDxns,
|
|
115
|
+
inverted = false,
|
|
116
|
+
}: {
|
|
117
|
+
spaceIds: readonly ObjectMeta['spaceId'][];
|
|
118
|
+
typeDxns: readonly ObjectMeta['typeDxn'][];
|
|
119
|
+
inverted?: boolean;
|
|
120
|
+
}): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>
|
|
121
|
+
Effect.gen(function* () {
|
|
122
|
+
if (spaceIds.length === 0) {
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (typeDxns.length === 0) {
|
|
127
|
+
if (!inverted) {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const sql = yield* SqlClient.SqlClient;
|
|
132
|
+
const rows = yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sql.in('spaceId', spaceIds)}`;
|
|
133
|
+
return rows.map((row) => ({
|
|
134
|
+
...row,
|
|
135
|
+
deleted: !!row.deleted,
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
const sql = yield* SqlClient.SqlClient;
|
|
139
|
+
const spaceWhere = sql.in('spaceId', spaceIds);
|
|
140
|
+
const typeWhere = sql.or(
|
|
141
|
+
typeDxns.map((typeDxn) => {
|
|
142
|
+
const parsedType = DXN.tryParse(typeDxn)?.asTypeDXN();
|
|
143
|
+
return parsedType && parsedType.version === undefined
|
|
144
|
+
? sql.or([sql`typeDxn = ${typeDxn}`, sql`typeDxn LIKE ${_escapeLikePrefix(typeDxn)} ESCAPE '\\'`])
|
|
145
|
+
: sql`typeDxn = ${typeDxn}`;
|
|
146
|
+
}),
|
|
147
|
+
);
|
|
148
|
+
const rows = inverted
|
|
149
|
+
? yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${spaceWhere} AND NOT ${typeWhere}`
|
|
150
|
+
: yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${spaceWhere} AND ${typeWhere}`;
|
|
151
|
+
return rows.map((row) => ({
|
|
152
|
+
...row,
|
|
153
|
+
deleted: !!row.deleted,
|
|
154
|
+
}));
|
|
155
|
+
}),
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
queryRelations = Effect.fn('ObjectMetaIndex.queryRelations')(
|
|
159
|
+
({
|
|
160
|
+
endpoint,
|
|
161
|
+
anchorDxns,
|
|
162
|
+
}: {
|
|
163
|
+
endpoint: 'source' | 'target';
|
|
164
|
+
anchorDxns: readonly string[];
|
|
165
|
+
}): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>
|
|
166
|
+
Effect.gen(function* () {
|
|
167
|
+
if (anchorDxns.length === 0) {
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
const sql = yield* SqlClient.SqlClient;
|
|
171
|
+
const column = endpoint === 'source' ? 'source' : 'target';
|
|
172
|
+
const rows = yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE entityKind = 'relation' AND ${sql.in(
|
|
173
|
+
column,
|
|
174
|
+
anchorDxns,
|
|
175
|
+
)}`;
|
|
63
176
|
return rows.map((row) => ({
|
|
64
177
|
...row,
|
|
65
178
|
deleted: !!row.deleted,
|
|
@@ -80,7 +193,6 @@ export class ObjectMetaIndex implements Index {
|
|
|
80
193
|
const { spaceId, queueId, documentId, data } = object;
|
|
81
194
|
|
|
82
195
|
// Extract metadata (Logic emulating Echo APIs as strict imports are unavailable).
|
|
83
|
-
// TODO(agent): Verify property access matches Obj.JSON structure.
|
|
84
196
|
const castData = data;
|
|
85
197
|
const objectId = castData.id;
|
|
86
198
|
|
|
@@ -111,6 +223,8 @@ export class ObjectMetaIndex implements Index {
|
|
|
111
223
|
// Relations.
|
|
112
224
|
const source = entityKind === 'relation' ? (castData[ATTR_RELATION_SOURCE] ?? null) : null;
|
|
113
225
|
const target = entityKind === 'relation' ? (castData[ATTR_RELATION_TARGET] ?? null) : null;
|
|
226
|
+
// Parent (nullable).
|
|
227
|
+
const parent = castData[ATTR_PARENT] ?? null;
|
|
114
228
|
|
|
115
229
|
if (existing.length > 0) {
|
|
116
230
|
yield* sql`
|
|
@@ -120,18 +234,19 @@ export class ObjectMetaIndex implements Index {
|
|
|
120
234
|
typeDxn = ${typeDxn},
|
|
121
235
|
deleted = ${deleted},
|
|
122
236
|
source = ${source},
|
|
123
|
-
target = ${target}
|
|
237
|
+
target = ${target},
|
|
238
|
+
parent = ${parent}
|
|
124
239
|
WHERE recordId = ${existing[0].recordId}
|
|
125
240
|
`;
|
|
126
241
|
} else {
|
|
127
242
|
yield* sql`
|
|
128
243
|
INSERT INTO objectMeta (
|
|
129
244
|
objectId, queueId, spaceId, documentId,
|
|
130
|
-
entityKind, typeDxn, deleted, source, target, version
|
|
245
|
+
entityKind, typeDxn, deleted, source, target, parent, version
|
|
131
246
|
) VALUES (
|
|
132
247
|
${objectId}, ${queueId ?? ''}, ${spaceId}, ${documentId ?? ''},
|
|
133
248
|
${entityKind}, ${typeDxn}, ${deleted},
|
|
134
|
-
${source}, ${target}, ${version}
|
|
249
|
+
${source}, ${target}, ${parent}, ${version}
|
|
135
250
|
)
|
|
136
251
|
`;
|
|
137
252
|
}
|
|
@@ -169,7 +284,7 @@ export class ObjectMetaIndex implements Index {
|
|
|
169
284
|
|
|
170
285
|
if (result.length === 0) {
|
|
171
286
|
// TODO(mykola): Handle this case gracefully.
|
|
172
|
-
yield* Effect.die(
|
|
287
|
+
return yield* Effect.die(
|
|
173
288
|
new Error(`Object not found in ObjectMetaIndex: ${spaceId}/${documentId ?? queueId}/${objectId}`),
|
|
174
289
|
);
|
|
175
290
|
}
|
|
@@ -189,11 +304,31 @@ export class ObjectMetaIndex implements Index {
|
|
|
189
304
|
}
|
|
190
305
|
|
|
191
306
|
const sql = yield* SqlClient.SqlClient;
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
307
|
+
const rows = yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sql.in('recordId', recordIds)}`;
|
|
308
|
+
|
|
309
|
+
return rows.map((row) => ({
|
|
310
|
+
...row,
|
|
311
|
+
deleted: !!row.deleted,
|
|
312
|
+
}));
|
|
313
|
+
}),
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Query children by parent object ids.
|
|
318
|
+
*/
|
|
319
|
+
queryChildren = Effect.fn('ObjectMetaIndex.queryChildren')(
|
|
320
|
+
(query: {
|
|
321
|
+
spaceId: SpaceId[];
|
|
322
|
+
parentIds: ObjectId[];
|
|
323
|
+
}): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>
|
|
324
|
+
Effect.gen(function* () {
|
|
325
|
+
if (query.parentIds.length === 0) {
|
|
326
|
+
return [];
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const sql = yield* SqlClient.SqlClient;
|
|
330
|
+
const rows =
|
|
331
|
+
yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE spaceId IN ${sql.in(query.spaceId)} AND parent IN ${sql.in(query.parentIds.map((id) => DXN.fromLocalObjectId(id).toString()))}`;
|
|
197
332
|
|
|
198
333
|
return rows.map((row) => ({
|
|
199
334
|
...row,
|
package/src/utils.ts
CHANGED