@dxos/index-core 0.0.0 → 0.8.4-main.1c7ec43d41
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 +790 -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 +112 -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 +64 -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 +56 -0
- package/dist/types/src/indexes/interface.d.ts.map +1 -0
- package/dist/types/src/indexes/object-meta-index.d.ts +94 -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 +172 -9
- package/src/index-engine.ts +161 -29
- package/src/index-tracker.ts +9 -0
- package/src/index.ts +10 -3
- package/src/indexes/fts-index.test.ts +153 -3
- package/src/indexes/fts-index.ts +66 -10
- package/src/indexes/interface.ts +10 -0
- package/src/indexes/object-meta-index.test.ts +361 -3
- package/src/indexes/object-meta-index.ts +304 -17
- package/src/indexes/reverse-ref-index.test.ts +16 -2
- package/src/indexes/reverse-ref-index.ts +0 -1
- package/src/utils.ts +1 -1
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
// src/index-engine.ts
|
|
2
|
+
import * as Effect5 from "effect/Effect";
|
|
3
|
+
import { ATTR_TYPE as ATTR_TYPE2 } from "@dxos/echo/internal";
|
|
4
|
+
import * as SqlTransaction from "@dxos/sql-sqlite/SqlTransaction";
|
|
5
|
+
|
|
6
|
+
// src/index-tracker.ts
|
|
7
|
+
import * as SqlClient from "@effect/sql/SqlClient";
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
9
|
+
import * as Schema from "effect/Schema";
|
|
10
|
+
import { SpaceId } from "@dxos/keys";
|
|
11
|
+
var IndexCursor = Schema.Struct({
|
|
12
|
+
/**
|
|
13
|
+
* Name of the index owning this cursor.
|
|
14
|
+
*/
|
|
15
|
+
indexName: Schema.String,
|
|
16
|
+
/**
|
|
17
|
+
* Space id.
|
|
18
|
+
*/
|
|
19
|
+
spaceId: Schema.NullOr(SpaceId),
|
|
20
|
+
/**
|
|
21
|
+
* Source name.
|
|
22
|
+
* 'automerge' / 'queue' / 'index' (for secondary indexes)
|
|
23
|
+
*/
|
|
24
|
+
sourceName: Schema.String,
|
|
25
|
+
/**
|
|
26
|
+
* Document id or queue id.
|
|
27
|
+
* doc_id, queue_id, '' <empty string> (if indexing entire namespace)
|
|
28
|
+
*/
|
|
29
|
+
resourceId: Schema.NullOr(Schema.String),
|
|
30
|
+
/**
|
|
31
|
+
* Heads, queue position, version.
|
|
32
|
+
*/
|
|
33
|
+
cursor: Schema.Union(Schema.Number, Schema.String)
|
|
34
|
+
});
|
|
35
|
+
var DEPRECATED_INDEX_NAMES = [
|
|
36
|
+
"fts"
|
|
37
|
+
];
|
|
38
|
+
var IndexTracker = class {
|
|
39
|
+
migrate = Effect.fn("IndexTracker.migrate")(function* () {
|
|
40
|
+
const sql = yield* SqlClient.SqlClient;
|
|
41
|
+
yield* sql`CREATE TABLE IF NOT EXISTS indexCursor (
|
|
42
|
+
indexName TEXT NOT NULL,
|
|
43
|
+
spaceId TEXT NOT NULL DEFAULT '',
|
|
44
|
+
sourceName TEXT NOT NULL,
|
|
45
|
+
resourceId TEXT NOT NULL DEFAULT '',
|
|
46
|
+
cursor,
|
|
47
|
+
PRIMARY KEY (indexName, spaceId, sourceName, resourceId)
|
|
48
|
+
)`;
|
|
49
|
+
yield* Effect.forEach(DEPRECATED_INDEX_NAMES, (indexName) => {
|
|
50
|
+
return sql`DELETE FROM indexCursor WHERE indexName = ${indexName}`;
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
queryCursors = Effect.fn("IndexTracker.queryCursors")((query) => Effect.gen(function* () {
|
|
54
|
+
const sql = yield* SqlClient.SqlClient;
|
|
55
|
+
const spaceIdParam = query.spaceId === void 0 ? null : query.spaceId ?? "";
|
|
56
|
+
const sourceNameParam = query.sourceName === void 0 ? null : query.sourceName;
|
|
57
|
+
const resourceIdParam = query.resourceId === void 0 ? null : query.resourceId ?? "";
|
|
58
|
+
const rows = yield* sql`
|
|
59
|
+
SELECT * FROM indexCursor
|
|
60
|
+
WHERE indexName = ${query.indexName}
|
|
61
|
+
AND (${spaceIdParam} IS NULL OR spaceId = ${spaceIdParam})
|
|
62
|
+
AND (${sourceNameParam} IS NULL OR sourceName = ${sourceNameParam})
|
|
63
|
+
AND (${resourceIdParam} IS NULL OR resourceId = ${resourceIdParam})
|
|
64
|
+
`;
|
|
65
|
+
return rows.map((row) => ({
|
|
66
|
+
indexName: row.indexName,
|
|
67
|
+
spaceId: row.spaceId === "" ? null : Schema.decodeSync(SpaceId)(row.spaceId),
|
|
68
|
+
sourceName: row.sourceName,
|
|
69
|
+
resourceId: row.resourceId === "" ? null : row.resourceId,
|
|
70
|
+
cursor: row.cursor
|
|
71
|
+
}));
|
|
72
|
+
}));
|
|
73
|
+
updateCursors = Effect.fn("IndexTracker.updateCursors")((cursors) => Effect.gen(function* () {
|
|
74
|
+
const sql = yield* SqlClient.SqlClient;
|
|
75
|
+
yield* Effect.forEach(cursors, (cursor) => {
|
|
76
|
+
const spaceId = cursor.spaceId ?? "";
|
|
77
|
+
const resourceId = cursor.resourceId ?? "";
|
|
78
|
+
return sql`
|
|
79
|
+
INSERT INTO indexCursor (indexName, spaceId, sourceName, resourceId, cursor)
|
|
80
|
+
VALUES (${cursor.indexName}, ${spaceId}, ${cursor.sourceName}, ${resourceId}, ${cursor.cursor})
|
|
81
|
+
ON CONFLICT(indexName, spaceId, sourceName, resourceId) DO UPDATE SET cursor = excluded.cursor
|
|
82
|
+
`;
|
|
83
|
+
}, {
|
|
84
|
+
discard: true
|
|
85
|
+
});
|
|
86
|
+
}));
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// src/indexes/fts-index.ts
|
|
90
|
+
import * as SqlClient3 from "@effect/sql/SqlClient";
|
|
91
|
+
import * as Effect2 from "effect/Effect";
|
|
92
|
+
var SQL_CHUNK_SIZE = 500;
|
|
93
|
+
var escapeFts5Query = (text) => {
|
|
94
|
+
return text.split(/\s+/).filter(Boolean).map((term) => `"${term.replace(/"/g, '""')}"`).join(" ");
|
|
95
|
+
};
|
|
96
|
+
var FtsIndex = class {
|
|
97
|
+
migrate = Effect2.fn("FtsIndex.migrate")(function* () {
|
|
98
|
+
const sql = yield* SqlClient3.SqlClient;
|
|
99
|
+
yield* sql`CREATE VIRTUAL TABLE IF NOT EXISTS ftsIndex USING fts5(snapshot, tokenize='trigram')`;
|
|
100
|
+
});
|
|
101
|
+
query({ query, spaceId, includeAllQueues, queueIds }) {
|
|
102
|
+
return Effect2.gen(function* () {
|
|
103
|
+
const trimmed = query.trim();
|
|
104
|
+
if (trimmed.length === 0) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
const sql = yield* SqlClient3.SqlClient;
|
|
108
|
+
const terms = trimmed.split(/\s+/).filter(Boolean);
|
|
109
|
+
const minTermLength = Math.min(...terms.map((t) => t.length));
|
|
110
|
+
const useBm25 = minTermLength >= 3;
|
|
111
|
+
const conditions = minTermLength < 3 ? terms.map((term) => sql`f.snapshot LIKE ${"%" + term + "%"}`) : [
|
|
112
|
+
sql`f.snapshot MATCH ${escapeFts5Query(trimmed)}`
|
|
113
|
+
];
|
|
114
|
+
const sourceConditions = [];
|
|
115
|
+
if (spaceId && spaceId.length > 0) {
|
|
116
|
+
if (includeAllQueues) {
|
|
117
|
+
sourceConditions.push(sql`m.spaceId IN ${sql.in(spaceId)}`);
|
|
118
|
+
} else {
|
|
119
|
+
sourceConditions.push(sql`(m.spaceId IN ${sql.in(spaceId)} AND m.queueId = '')`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (queueIds && queueIds.length > 0) {
|
|
123
|
+
sourceConditions.push(sql`m.queueId IN ${sql.in(queueIds)}`);
|
|
124
|
+
}
|
|
125
|
+
if (sourceConditions.length > 0) {
|
|
126
|
+
conditions.push(sql`(${sql.or(sourceConditions)})`);
|
|
127
|
+
}
|
|
128
|
+
if (useBm25) {
|
|
129
|
+
const rows = yield* sql`
|
|
130
|
+
SELECT m.*, -bm25(ftsIndex) AS rank
|
|
131
|
+
FROM ftsIndex AS f
|
|
132
|
+
JOIN objectMeta AS m ON f.rowid = m.recordId
|
|
133
|
+
WHERE ${sql.and(conditions)}
|
|
134
|
+
ORDER BY rank DESC
|
|
135
|
+
`;
|
|
136
|
+
return rows;
|
|
137
|
+
} else {
|
|
138
|
+
const rows = yield* sql`
|
|
139
|
+
SELECT m.*
|
|
140
|
+
FROM ftsIndex AS f
|
|
141
|
+
JOIN objectMeta AS m ON f.rowid = m.recordId
|
|
142
|
+
WHERE ${sql.and(conditions)}
|
|
143
|
+
`;
|
|
144
|
+
return rows.map((row) => ({
|
|
145
|
+
...row,
|
|
146
|
+
rank: 1
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Query snapshots by recordIds.
|
|
153
|
+
* Returns the parsed JSON snapshots for queue objects.
|
|
154
|
+
* RecordIds not present in the FTS index are silently omitted from the result.
|
|
155
|
+
*/
|
|
156
|
+
querySnapshotsJSON(recordIds) {
|
|
157
|
+
return Effect2.gen(function* () {
|
|
158
|
+
if (recordIds.length === 0) {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
const sql = yield* SqlClient3.SqlClient;
|
|
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;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
update = Effect2.fn("FtsIndex.update")((objects) => Effect2.gen(function* () {
|
|
180
|
+
const sql = yield* SqlClient3.SqlClient;
|
|
181
|
+
yield* Effect2.forEach(objects, (object) => Effect2.gen(function* () {
|
|
182
|
+
const { recordId, data } = object;
|
|
183
|
+
if (recordId === null) {
|
|
184
|
+
return yield* Effect2.die(new Error("FtsIndex.update requires recordId to be set"));
|
|
185
|
+
}
|
|
186
|
+
const snapshot = JSON.stringify(data);
|
|
187
|
+
const existing = yield* sql`SELECT rowid FROM ftsIndex WHERE rowid = ${recordId}`;
|
|
188
|
+
if (existing.length > 0) {
|
|
189
|
+
yield* sql`DELETE FROM ftsIndex WHERE rowid = ${recordId}`;
|
|
190
|
+
}
|
|
191
|
+
yield* sql`INSERT INTO ftsIndex (rowid, snapshot) VALUES (${recordId}, ${snapshot})`;
|
|
192
|
+
}), {
|
|
193
|
+
discard: true
|
|
194
|
+
});
|
|
195
|
+
}));
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// src/indexes/object-meta-index.ts
|
|
199
|
+
import * as SqlClient5 from "@effect/sql/SqlClient";
|
|
200
|
+
import * as Effect3 from "effect/Effect";
|
|
201
|
+
import * as Schema2 from "effect/Schema";
|
|
202
|
+
import { ATTR_DELETED, ATTR_PARENT, ATTR_RELATION_SOURCE, ATTR_RELATION_TARGET, ATTR_TYPE } from "@dxos/echo/internal";
|
|
203
|
+
import { DXN } from "@dxos/keys";
|
|
204
|
+
var _escapeLikePrefix = (prefix) => {
|
|
205
|
+
const escaped = prefix.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
|
|
206
|
+
return `${escaped}:%`;
|
|
207
|
+
};
|
|
208
|
+
var ObjectMeta = Schema2.Struct({
|
|
209
|
+
recordId: Schema2.Number,
|
|
210
|
+
objectId: Schema2.String,
|
|
211
|
+
queueId: Schema2.String,
|
|
212
|
+
/** Queue subspace namespace (e.g. 'data', 'trace'). Empty string for non-queue objects. */
|
|
213
|
+
queueNamespace: Schema2.String,
|
|
214
|
+
spaceId: Schema2.String,
|
|
215
|
+
documentId: Schema2.String,
|
|
216
|
+
entityKind: Schema2.String,
|
|
217
|
+
/** The versioned DXN of the type of the object. */
|
|
218
|
+
typeDxn: Schema2.String,
|
|
219
|
+
deleted: Schema2.Boolean,
|
|
220
|
+
source: Schema2.NullOr(Schema2.String),
|
|
221
|
+
target: Schema2.NullOr(Schema2.String),
|
|
222
|
+
/** Parent object id (nullable). */
|
|
223
|
+
parent: Schema2.NullOr(Schema2.String),
|
|
224
|
+
/** Monotonically increasing sequence number assigned on insert/update for tracking indexing order. */
|
|
225
|
+
version: Schema2.Number,
|
|
226
|
+
/** Unix ms timestamp when the object was first indexed. */
|
|
227
|
+
createdAt: Schema2.NullOr(Schema2.Number),
|
|
228
|
+
/** Unix ms timestamp when the object was last re-indexed. */
|
|
229
|
+
updatedAt: Schema2.NullOr(Schema2.Number)
|
|
230
|
+
});
|
|
231
|
+
var buildSourceCondition = (sql, spaceIds, includeAllQueues, queueIds) => {
|
|
232
|
+
const conditions = [];
|
|
233
|
+
if (spaceIds.length > 0) {
|
|
234
|
+
if (includeAllQueues) {
|
|
235
|
+
conditions.push(sql`${sql.in("spaceId", spaceIds)}`);
|
|
236
|
+
} else {
|
|
237
|
+
conditions.push(sql`(${sql.in("spaceId", spaceIds)} AND queueId = '')`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (queueIds && queueIds.length > 0) {
|
|
241
|
+
conditions.push(sql`${sql.in("queueId", queueIds)}`);
|
|
242
|
+
}
|
|
243
|
+
if (conditions.length === 0) {
|
|
244
|
+
return sql`1 = 0`;
|
|
245
|
+
}
|
|
246
|
+
return sql.or(conditions);
|
|
247
|
+
};
|
|
248
|
+
var ObjectMetaIndex = class {
|
|
249
|
+
migrate = Effect3.fn("ObjectMetaIndex.runMigrations")(function* () {
|
|
250
|
+
const sql = yield* SqlClient5.SqlClient;
|
|
251
|
+
yield* sql`CREATE TABLE IF NOT EXISTS objectMeta (
|
|
252
|
+
recordId INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
253
|
+
objectId TEXT NOT NULL,
|
|
254
|
+
queueId TEXT NOT NULL DEFAULT '',
|
|
255
|
+
queueNamespace TEXT NOT NULL DEFAULT '',
|
|
256
|
+
spaceId TEXT NOT NULL,
|
|
257
|
+
documentId TEXT NOT NULL DEFAULT '',
|
|
258
|
+
entityKind TEXT NOT NULL,
|
|
259
|
+
typeDxn TEXT NOT NULL,
|
|
260
|
+
deleted INTEGER NOT NULL,
|
|
261
|
+
source TEXT,
|
|
262
|
+
target TEXT,
|
|
263
|
+
parent TEXT,
|
|
264
|
+
version INTEGER NOT NULL,
|
|
265
|
+
createdAt INTEGER,
|
|
266
|
+
updatedAt INTEGER
|
|
267
|
+
)`;
|
|
268
|
+
yield* Effect3.catchAll(sql`ALTER TABLE objectMeta ADD COLUMN parent TEXT`, () => Effect3.void);
|
|
269
|
+
yield* Effect3.catchAll(sql`ALTER TABLE objectMeta ADD COLUMN createdAt INTEGER`, () => Effect3.void);
|
|
270
|
+
yield* Effect3.catchAll(sql`ALTER TABLE objectMeta ADD COLUMN updatedAt INTEGER`, () => Effect3.void);
|
|
271
|
+
yield* Effect3.catchAll(sql`ALTER TABLE objectMeta ADD COLUMN queueNamespace TEXT NOT NULL DEFAULT ''`, () => Effect3.void);
|
|
272
|
+
yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_objectId ON objectMeta(spaceId, objectId)`;
|
|
273
|
+
yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_typeDxn ON objectMeta(spaceId, typeDxn)`;
|
|
274
|
+
yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_version ON objectMeta(version)`;
|
|
275
|
+
yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_parent ON objectMeta(spaceId, parent)`;
|
|
276
|
+
yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_updatedAt ON objectMeta(updatedAt)`;
|
|
277
|
+
yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_createdAt ON objectMeta(createdAt)`;
|
|
278
|
+
});
|
|
279
|
+
query = Effect3.fn("ObjectMetaIndex.query")((query) => Effect3.gen(function* () {
|
|
280
|
+
const sql = yield* SqlClient5.SqlClient;
|
|
281
|
+
const parsedType = DXN.tryParse(query.typeDxn)?.asTypeDXN();
|
|
282
|
+
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}`;
|
|
283
|
+
return rows.map((row) => ({
|
|
284
|
+
...row,
|
|
285
|
+
deleted: !!row.deleted
|
|
286
|
+
}));
|
|
287
|
+
}));
|
|
288
|
+
queryAll = Effect3.fn("ObjectMetaIndex.queryAll")((query) => Effect3.gen(function* () {
|
|
289
|
+
if (query.spaceIds.length === 0 && (!query.queueIds || query.queueIds.length === 0)) {
|
|
290
|
+
return [];
|
|
291
|
+
}
|
|
292
|
+
const sql = yield* SqlClient5.SqlClient;
|
|
293
|
+
const sourceCondition = buildSourceCondition(sql, query.spaceIds, query.includeAllQueues ?? false, query.queueIds ?? null);
|
|
294
|
+
const rows = yield* sql`SELECT * FROM objectMeta WHERE ${sourceCondition}`;
|
|
295
|
+
return rows.map((row) => ({
|
|
296
|
+
...row,
|
|
297
|
+
deleted: !!row.deleted
|
|
298
|
+
}));
|
|
299
|
+
}));
|
|
300
|
+
queryTypes = Effect3.fn("ObjectMetaIndex.queryTypes")(({ spaceIds, typeDxns, inverted = false, includeAllQueues = false, queueIds = null }) => Effect3.gen(function* () {
|
|
301
|
+
if (spaceIds.length === 0 && (!queueIds || queueIds.length === 0)) {
|
|
302
|
+
return [];
|
|
303
|
+
}
|
|
304
|
+
if (typeDxns.length === 0) {
|
|
305
|
+
if (!inverted) {
|
|
306
|
+
return [];
|
|
307
|
+
}
|
|
308
|
+
const sql2 = yield* SqlClient5.SqlClient;
|
|
309
|
+
const sourceCondition2 = buildSourceCondition(sql2, spaceIds, includeAllQueues, queueIds);
|
|
310
|
+
const rows2 = yield* sql2`SELECT * FROM objectMeta WHERE ${sourceCondition2}`;
|
|
311
|
+
return rows2.map((row) => ({
|
|
312
|
+
...row,
|
|
313
|
+
deleted: !!row.deleted
|
|
314
|
+
}));
|
|
315
|
+
}
|
|
316
|
+
const sql = yield* SqlClient5.SqlClient;
|
|
317
|
+
const sourceCondition = buildSourceCondition(sql, spaceIds, includeAllQueues, queueIds);
|
|
318
|
+
const typeWhere = sql.or(typeDxns.map((typeDxn) => {
|
|
319
|
+
const parsedType = DXN.tryParse(typeDxn)?.asTypeDXN();
|
|
320
|
+
return parsedType && parsedType.version === void 0 ? sql.or([
|
|
321
|
+
sql`typeDxn = ${typeDxn}`,
|
|
322
|
+
sql`typeDxn LIKE ${_escapeLikePrefix(typeDxn)} ESCAPE '\\'`
|
|
323
|
+
]) : sql`typeDxn = ${typeDxn}`;
|
|
324
|
+
}));
|
|
325
|
+
const rows = inverted ? yield* sql`SELECT * FROM objectMeta WHERE ${sourceCondition} AND NOT ${typeWhere}` : yield* sql`SELECT * FROM objectMeta WHERE ${sourceCondition} AND ${typeWhere}`;
|
|
326
|
+
return rows.map((row) => ({
|
|
327
|
+
...row,
|
|
328
|
+
deleted: !!row.deleted
|
|
329
|
+
}));
|
|
330
|
+
}));
|
|
331
|
+
queryRelations = Effect3.fn("ObjectMetaIndex.queryRelations")(({ endpoint, anchorDxns }) => Effect3.gen(function* () {
|
|
332
|
+
if (anchorDxns.length === 0) {
|
|
333
|
+
return [];
|
|
334
|
+
}
|
|
335
|
+
const sql = yield* SqlClient5.SqlClient;
|
|
336
|
+
const column = endpoint === "source" ? "source" : "target";
|
|
337
|
+
const rows = yield* sql`SELECT * FROM objectMeta WHERE entityKind = 'relation' AND ${sql.in(column, anchorDxns)}`;
|
|
338
|
+
return rows.map((row) => ({
|
|
339
|
+
...row,
|
|
340
|
+
deleted: !!row.deleted
|
|
341
|
+
}));
|
|
342
|
+
}));
|
|
343
|
+
// TODO(dmaretskyi): Update recordId on objects so that we don't need to look it up separately.
|
|
344
|
+
update = Effect3.fn("ObjectMetaIndex.update")((objects) => Effect3.gen(function* () {
|
|
345
|
+
const sql = yield* SqlClient5.SqlClient;
|
|
346
|
+
yield* Effect3.forEach(objects, (object) => Effect3.gen(function* () {
|
|
347
|
+
const { spaceId, queueId, queueNamespace, documentId, data } = object;
|
|
348
|
+
const castData = data;
|
|
349
|
+
const objectId = castData.id;
|
|
350
|
+
let existing;
|
|
351
|
+
if (documentId) {
|
|
352
|
+
existing = yield* sql`SELECT recordId FROM objectMeta WHERE spaceId = ${spaceId} AND documentId = ${documentId} AND objectId = ${objectId} LIMIT 1`;
|
|
353
|
+
} else if (queueId) {
|
|
354
|
+
existing = yield* sql`SELECT recordId FROM objectMeta WHERE spaceId = ${spaceId} AND queueId = ${queueId} AND objectId = ${objectId} LIMIT 1`;
|
|
355
|
+
} else {
|
|
356
|
+
existing = [];
|
|
357
|
+
}
|
|
358
|
+
const result = yield* sql`SELECT MAX(version) as v FROM objectMeta`;
|
|
359
|
+
const [{ v }] = result;
|
|
360
|
+
const version = (v ?? 0) + 1;
|
|
361
|
+
const entityKind = castData[ATTR_RELATION_SOURCE] ? "relation" : "object";
|
|
362
|
+
const typeDxn = castData[ATTR_TYPE] ? String(castData[ATTR_TYPE]) : "type";
|
|
363
|
+
const deleted = castData[ATTR_DELETED] ? 1 : 0;
|
|
364
|
+
const source = entityKind === "relation" ? castData[ATTR_RELATION_SOURCE] ?? null : null;
|
|
365
|
+
const target = entityKind === "relation" ? castData[ATTR_RELATION_TARGET] ?? null : null;
|
|
366
|
+
const parent = castData[ATTR_PARENT] ?? null;
|
|
367
|
+
const sourceTimestamp = object.updatedAt;
|
|
368
|
+
if (existing.length > 0) {
|
|
369
|
+
yield* sql`
|
|
370
|
+
UPDATE objectMeta SET
|
|
371
|
+
version = ${version},
|
|
372
|
+
queueNamespace = ${queueNamespace ?? ""},
|
|
373
|
+
entityKind = ${entityKind},
|
|
374
|
+
typeDxn = ${typeDxn},
|
|
375
|
+
deleted = ${deleted},
|
|
376
|
+
source = ${source},
|
|
377
|
+
target = ${target},
|
|
378
|
+
parent = ${parent},
|
|
379
|
+
updatedAt = ${sourceTimestamp}
|
|
380
|
+
WHERE recordId = ${existing[0].recordId}
|
|
381
|
+
`;
|
|
382
|
+
} else {
|
|
383
|
+
yield* sql`
|
|
384
|
+
INSERT INTO objectMeta (
|
|
385
|
+
objectId, queueId, queueNamespace, spaceId, documentId,
|
|
386
|
+
entityKind, typeDxn, deleted, source, target, parent, version,
|
|
387
|
+
createdAt, updatedAt
|
|
388
|
+
) VALUES (
|
|
389
|
+
${objectId}, ${queueId ?? ""}, ${queueNamespace ?? ""}, ${spaceId}, ${documentId ?? ""},
|
|
390
|
+
${entityKind}, ${typeDxn}, ${deleted},
|
|
391
|
+
${source}, ${target}, ${parent}, ${version},
|
|
392
|
+
${sourceTimestamp}, ${sourceTimestamp}
|
|
393
|
+
)
|
|
394
|
+
`;
|
|
395
|
+
}
|
|
396
|
+
}), {
|
|
397
|
+
discard: true
|
|
398
|
+
});
|
|
399
|
+
}));
|
|
400
|
+
/**
|
|
401
|
+
* Look up `recordIds` for objects that are already stored in the ObjectMetaIndex.
|
|
402
|
+
* Mutates the objects in place.
|
|
403
|
+
*/
|
|
404
|
+
lookupRecordIds = Effect3.fn("ObjectMetaIndex.lookupRecordIds")((objects) => Effect3.gen(function* () {
|
|
405
|
+
const sql = yield* SqlClient5.SqlClient;
|
|
406
|
+
for (const object of objects) {
|
|
407
|
+
const { spaceId, queueId, documentId, data } = object;
|
|
408
|
+
const objectId = data.id;
|
|
409
|
+
let result;
|
|
410
|
+
if (documentId) {
|
|
411
|
+
result = yield* sql`SELECT recordId FROM objectMeta WHERE spaceId = ${spaceId} AND documentId = ${documentId} AND objectId = ${objectId} LIMIT 1`;
|
|
412
|
+
} else if (queueId) {
|
|
413
|
+
result = yield* sql`SELECT recordId FROM objectMeta WHERE spaceId = ${spaceId} AND queueId = ${queueId} AND objectId = ${objectId} LIMIT 1`;
|
|
414
|
+
} else {
|
|
415
|
+
result = [];
|
|
416
|
+
}
|
|
417
|
+
if (result.length === 0) {
|
|
418
|
+
return yield* Effect3.die(new Error(`Object not found in ObjectMetaIndex: ${spaceId}/${documentId ?? queueId}/${objectId}`));
|
|
419
|
+
}
|
|
420
|
+
object.recordId = result[0].recordId;
|
|
421
|
+
}
|
|
422
|
+
}));
|
|
423
|
+
/**
|
|
424
|
+
* Look up object metadata by recordIds.
|
|
425
|
+
*/
|
|
426
|
+
lookupByRecordIds = Effect3.fn("ObjectMetaIndex.lookupByRecordIds")((recordIds) => Effect3.gen(function* () {
|
|
427
|
+
if (recordIds.length === 0) {
|
|
428
|
+
return [];
|
|
429
|
+
}
|
|
430
|
+
const sql = yield* SqlClient5.SqlClient;
|
|
431
|
+
const rows = yield* sql`SELECT * FROM objectMeta WHERE ${sql.in("recordId", recordIds)}`;
|
|
432
|
+
return rows.map((row) => ({
|
|
433
|
+
...row,
|
|
434
|
+
deleted: !!row.deleted
|
|
435
|
+
}));
|
|
436
|
+
}));
|
|
437
|
+
/**
|
|
438
|
+
* Look up object metadata by objectId, spaceId, and queueId.
|
|
439
|
+
*/
|
|
440
|
+
lookupByObjectId = Effect3.fn("ObjectMetaIndex.lookupByObjectId")((query) => Effect3.gen(function* () {
|
|
441
|
+
const sql = yield* SqlClient5.SqlClient;
|
|
442
|
+
const rows = yield* sql`SELECT * FROM objectMeta WHERE spaceId = ${query.spaceId} AND queueId = ${query.queueId} AND objectId = ${query.objectId} LIMIT 1`;
|
|
443
|
+
if (rows.length === 0) {
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
...rows[0],
|
|
448
|
+
deleted: !!rows[0].deleted
|
|
449
|
+
};
|
|
450
|
+
}));
|
|
451
|
+
/**
|
|
452
|
+
* Query objects by timestamp range.
|
|
453
|
+
*/
|
|
454
|
+
queryByTimeRange = Effect3.fn("ObjectMetaIndex.queryByTimeRange")((query) => Effect3.gen(function* () {
|
|
455
|
+
if (query.spaceIds.length === 0 && (!query.queueIds || query.queueIds.length === 0)) {
|
|
456
|
+
return [];
|
|
457
|
+
}
|
|
458
|
+
const sql = yield* SqlClient5.SqlClient;
|
|
459
|
+
const sourceCondition = buildSourceCondition(sql, query.spaceIds, query.includeAllQueues ?? false, query.queueIds ?? null);
|
|
460
|
+
const timeConditions = [];
|
|
461
|
+
if (query.updatedAfter != null) {
|
|
462
|
+
timeConditions.push(sql`updatedAt >= ${query.updatedAfter}`);
|
|
463
|
+
}
|
|
464
|
+
if (query.updatedBefore != null) {
|
|
465
|
+
timeConditions.push(sql`updatedAt <= ${query.updatedBefore}`);
|
|
466
|
+
}
|
|
467
|
+
if (query.createdAfter != null) {
|
|
468
|
+
timeConditions.push(sql`createdAt >= ${query.createdAfter}`);
|
|
469
|
+
}
|
|
470
|
+
if (query.createdBefore != null) {
|
|
471
|
+
timeConditions.push(sql`createdAt <= ${query.createdBefore}`);
|
|
472
|
+
}
|
|
473
|
+
const rows = timeConditions.length > 0 ? yield* sql`SELECT * FROM objectMeta WHERE ${sourceCondition} AND ${sql.and(timeConditions)}` : yield* sql`SELECT * FROM objectMeta WHERE ${sourceCondition}`;
|
|
474
|
+
return rows.map((row) => ({
|
|
475
|
+
...row,
|
|
476
|
+
deleted: !!row.deleted
|
|
477
|
+
}));
|
|
478
|
+
}));
|
|
479
|
+
/**
|
|
480
|
+
* Query children by parent object ids.
|
|
481
|
+
* Matches both:
|
|
482
|
+
* - Objects whose `parent` field references one of the given parent ids (standard parent/child hierarchy).
|
|
483
|
+
* - Queue items whose `queueId` equals one of the parent ids (e.g. items inside a Feed, since a feed's queue
|
|
484
|
+
* DXN uses the feed's object id as its queue id — see `Feed.getQueueDxn`).
|
|
485
|
+
*/
|
|
486
|
+
queryChildren = Effect3.fn("ObjectMetaIndex.queryChildren")((query) => Effect3.gen(function* () {
|
|
487
|
+
if (query.parentIds.length === 0) {
|
|
488
|
+
return [];
|
|
489
|
+
}
|
|
490
|
+
const sql = yield* SqlClient5.SqlClient;
|
|
491
|
+
const parentDxns = query.parentIds.map((id) => DXN.fromLocalObjectId(id).toString());
|
|
492
|
+
const rows = yield* sql`SELECT * FROM objectMeta WHERE ${sql.in("spaceId", query.spaceId)} AND (${sql.in("parent", parentDxns)} OR ${sql.in("queueId", query.parentIds)})`;
|
|
493
|
+
return rows.map((row) => ({
|
|
494
|
+
...row,
|
|
495
|
+
deleted: !!row.deleted
|
|
496
|
+
}));
|
|
497
|
+
}));
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
// src/indexes/reverse-ref-index.ts
|
|
501
|
+
import * as SqlClient7 from "@effect/sql/SqlClient";
|
|
502
|
+
import * as Effect4 from "effect/Effect";
|
|
503
|
+
import * as Schema4 from "effect/Schema";
|
|
504
|
+
import { EncodedReference, isEncodedReference } from "@dxos/echo-protocol";
|
|
505
|
+
|
|
506
|
+
// src/utils.ts
|
|
507
|
+
import * as Schema3 from "effect/Schema";
|
|
508
|
+
import { invariant } from "@dxos/invariant";
|
|
509
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/core/echo/index-core/src/utils.ts";
|
|
510
|
+
var EscapedPropPath = class extends Schema3.String.annotations({
|
|
511
|
+
title: "EscapedPropPath"
|
|
512
|
+
}) {
|
|
513
|
+
static escape(path) {
|
|
514
|
+
return path.map((p) => p.toString().replaceAll("\\", "\\\\").replaceAll(".", "\\.")).join(".");
|
|
515
|
+
}
|
|
516
|
+
static unescape(path) {
|
|
517
|
+
const parts = [];
|
|
518
|
+
let current = "";
|
|
519
|
+
for (let i = 0; i < path.length; i++) {
|
|
520
|
+
if (path[i] === "\\") {
|
|
521
|
+
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.'"] });
|
|
522
|
+
current = current + path[i + 1];
|
|
523
|
+
i++;
|
|
524
|
+
} else if (path[i] === ".") {
|
|
525
|
+
parts.push(current);
|
|
526
|
+
current = "";
|
|
527
|
+
} else {
|
|
528
|
+
current += path[i];
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
parts.push(current);
|
|
532
|
+
return parts;
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
// src/indexes/reverse-ref-index.ts
|
|
537
|
+
var extractReferences = (data) => {
|
|
538
|
+
const refs = [];
|
|
539
|
+
const visit = (path, value) => {
|
|
540
|
+
if (isEncodedReference(value)) {
|
|
541
|
+
const dxn = EncodedReference.toDXN(value);
|
|
542
|
+
const echoId = dxn.asEchoDXN()?.echoId;
|
|
543
|
+
if (!echoId) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
refs.push({
|
|
547
|
+
path,
|
|
548
|
+
targetDxn: dxn.toString()
|
|
549
|
+
});
|
|
550
|
+
} else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
551
|
+
for (const [key, v] of Object.entries(value)) {
|
|
552
|
+
visit([
|
|
553
|
+
...path,
|
|
554
|
+
key
|
|
555
|
+
], v);
|
|
556
|
+
}
|
|
557
|
+
} else if (Array.isArray(value)) {
|
|
558
|
+
for (let i = 0; i < value.length; i++) {
|
|
559
|
+
visit([
|
|
560
|
+
...path,
|
|
561
|
+
String(i)
|
|
562
|
+
], value[i]);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
visit([], data);
|
|
567
|
+
return refs;
|
|
568
|
+
};
|
|
569
|
+
var ReverseRef = Schema4.Struct({
|
|
570
|
+
recordId: Schema4.Number,
|
|
571
|
+
targetDxn: Schema4.String,
|
|
572
|
+
/**
|
|
573
|
+
* Escaped property path within an object.
|
|
574
|
+
*
|
|
575
|
+
* Escaping rules:
|
|
576
|
+
*
|
|
577
|
+
* - '.' -> '\.'
|
|
578
|
+
* - '\' -> '\\'
|
|
579
|
+
* - contact with .
|
|
580
|
+
*/
|
|
581
|
+
propPath: Schema4.String
|
|
582
|
+
});
|
|
583
|
+
var ReverseRefIndex = class {
|
|
584
|
+
migrate = Effect4.fn("ReverseRefIndex.migrate")(function* () {
|
|
585
|
+
const sql = yield* SqlClient7.SqlClient;
|
|
586
|
+
yield* sql`CREATE TABLE IF NOT EXISTS reverseRef (
|
|
587
|
+
recordId INTEGER NOT NULL,
|
|
588
|
+
targetDxn TEXT NOT NULL,
|
|
589
|
+
propPath TEXT NOT NULL,
|
|
590
|
+
PRIMARY KEY (recordId, targetDxn, propPath)
|
|
591
|
+
)`;
|
|
592
|
+
yield* sql`CREATE INDEX IF NOT EXISTS idx_reverse_ref_target ON reverseRef(targetDxn)`;
|
|
593
|
+
});
|
|
594
|
+
/**
|
|
595
|
+
* Query all references pointing to a target DXN.
|
|
596
|
+
*/
|
|
597
|
+
query = Effect4.fn("ReverseRefIndex.query")(({ targetDxn }) => Effect4.gen(function* () {
|
|
598
|
+
const sql = yield* SqlClient7.SqlClient;
|
|
599
|
+
const rows = yield* sql`SELECT * FROM reverseRef WHERE targetDxn = ${targetDxn}`;
|
|
600
|
+
return rows;
|
|
601
|
+
}));
|
|
602
|
+
update = Effect4.fn("ReverseRefIndex.update")((objects) => Effect4.gen(function* () {
|
|
603
|
+
const sql = yield* SqlClient7.SqlClient;
|
|
604
|
+
yield* Effect4.forEach(objects, (object) => Effect4.gen(function* () {
|
|
605
|
+
const { recordId, data } = object;
|
|
606
|
+
if (recordId === null) {
|
|
607
|
+
yield* Effect4.die(new Error("ReverseRefIndex.update requires recordId to be set"));
|
|
608
|
+
}
|
|
609
|
+
yield* sql`DELETE FROM reverseRef WHERE recordId = ${recordId}`;
|
|
610
|
+
const refs = extractReferences(data);
|
|
611
|
+
yield* Effect4.forEach(refs, (ref) => sql`INSERT INTO reverseRef (recordId, targetDxn, propPath) VALUES (${recordId}, ${ref.targetDxn}, ${EscapedPropPath.escape(ref.path)})`, {
|
|
612
|
+
discard: true
|
|
613
|
+
});
|
|
614
|
+
}), {
|
|
615
|
+
discard: true
|
|
616
|
+
});
|
|
617
|
+
}));
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
// src/index-engine.ts
|
|
621
|
+
var makeEmptyIndexingResult = () => ({
|
|
622
|
+
updated: 0,
|
|
623
|
+
done: true,
|
|
624
|
+
spaces: /* @__PURE__ */ new Set(),
|
|
625
|
+
queues: /* @__PURE__ */ new Set(),
|
|
626
|
+
documents: /* @__PURE__ */ new Set(),
|
|
627
|
+
types: /* @__PURE__ */ new Set(),
|
|
628
|
+
objects: /* @__PURE__ */ new Set()
|
|
629
|
+
});
|
|
630
|
+
var accumulateIndexingResult = (acc, objects) => {
|
|
631
|
+
for (const obj of objects) {
|
|
632
|
+
acc.spaces.add(obj.spaceId);
|
|
633
|
+
if (obj.queueId) {
|
|
634
|
+
acc.queues.add(obj.queueId);
|
|
635
|
+
}
|
|
636
|
+
if (obj.documentId) {
|
|
637
|
+
acc.documents.add(obj.documentId);
|
|
638
|
+
}
|
|
639
|
+
const t = obj.data[ATTR_TYPE2];
|
|
640
|
+
if (t) {
|
|
641
|
+
acc.types.add(String(t));
|
|
642
|
+
}
|
|
643
|
+
if (obj.data.id) {
|
|
644
|
+
acc.objects.add(obj.data.id);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
var IndexEngine = class {
|
|
649
|
+
#tracker;
|
|
650
|
+
#objectMetaIndex;
|
|
651
|
+
#ftsIndex;
|
|
652
|
+
#reverseRefIndex;
|
|
653
|
+
constructor(params) {
|
|
654
|
+
this.#tracker = params?.tracker ?? new IndexTracker();
|
|
655
|
+
this.#objectMetaIndex = params?.objectMetaIndex ?? new ObjectMetaIndex();
|
|
656
|
+
this.#ftsIndex = params?.ftsIndex ?? new FtsIndex();
|
|
657
|
+
this.#reverseRefIndex = params?.reverseRefIndex ?? new ReverseRefIndex();
|
|
658
|
+
}
|
|
659
|
+
migrate() {
|
|
660
|
+
return Effect5.gen(this, function* () {
|
|
661
|
+
yield* this.#tracker.migrate();
|
|
662
|
+
yield* this.#objectMetaIndex.migrate();
|
|
663
|
+
yield* this.#ftsIndex.migrate();
|
|
664
|
+
yield* this.#reverseRefIndex.migrate();
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Query text index and return full object metadata with rank.
|
|
669
|
+
*/
|
|
670
|
+
queryText(query) {
|
|
671
|
+
return Effect5.gen(this, function* () {
|
|
672
|
+
return yield* this.#ftsIndex.query(query);
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
queryReverseRef(query) {
|
|
676
|
+
return this.#reverseRefIndex.query(query);
|
|
677
|
+
}
|
|
678
|
+
queryAll(query) {
|
|
679
|
+
return this.#objectMetaIndex.queryAll(query);
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Query snapshots by recordIds.
|
|
683
|
+
* Used to load queue objects from indexed snapshots.
|
|
684
|
+
*/
|
|
685
|
+
querySnapshotsJSON(recordIds) {
|
|
686
|
+
return this.#ftsIndex.querySnapshotsJSON(recordIds);
|
|
687
|
+
}
|
|
688
|
+
queryType(query) {
|
|
689
|
+
return this.#objectMetaIndex.query(query);
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Query children by parent object ids.
|
|
693
|
+
*/
|
|
694
|
+
queryChildren(query) {
|
|
695
|
+
return this.#objectMetaIndex.queryChildren(query);
|
|
696
|
+
}
|
|
697
|
+
queryTypes(query) {
|
|
698
|
+
return this.#objectMetaIndex.queryTypes(query);
|
|
699
|
+
}
|
|
700
|
+
queryByTimeRange(query) {
|
|
701
|
+
return this.#objectMetaIndex.queryByTimeRange(query);
|
|
702
|
+
}
|
|
703
|
+
queryRelations(query) {
|
|
704
|
+
return this.#objectMetaIndex.queryRelations(query);
|
|
705
|
+
}
|
|
706
|
+
lookupByRecordIds(recordIds) {
|
|
707
|
+
return this.#objectMetaIndex.lookupByRecordIds(recordIds);
|
|
708
|
+
}
|
|
709
|
+
lookupByObjectId(query) {
|
|
710
|
+
return this.#objectMetaIndex.lookupByObjectId(query);
|
|
711
|
+
}
|
|
712
|
+
update(ctx, dataSource, opts) {
|
|
713
|
+
return Effect5.gen(this, function* () {
|
|
714
|
+
const result = makeEmptyIndexingResult();
|
|
715
|
+
const { updated: updatedFtsIndex, done: doneFtsIndex, objects: ftsObjects } = yield* this.#update(ctx, this.#ftsIndex, dataSource, {
|
|
716
|
+
indexName: "fts5",
|
|
717
|
+
spaceId: opts.spaceId,
|
|
718
|
+
limit: opts.limit
|
|
719
|
+
});
|
|
720
|
+
result.updated += updatedFtsIndex;
|
|
721
|
+
result.done = result.done && doneFtsIndex;
|
|
722
|
+
accumulateIndexingResult(result, ftsObjects);
|
|
723
|
+
const { updated: updatedReverseRefIndex, done: doneReverseRefIndex, objects: reverseRefObjects } = yield* this.#update(ctx, this.#reverseRefIndex, dataSource, {
|
|
724
|
+
indexName: "reverseRef",
|
|
725
|
+
spaceId: opts.spaceId,
|
|
726
|
+
limit: opts.limit
|
|
727
|
+
});
|
|
728
|
+
result.updated += updatedReverseRefIndex;
|
|
729
|
+
result.done = result.done && doneReverseRefIndex;
|
|
730
|
+
accumulateIndexingResult(result, reverseRefObjects);
|
|
731
|
+
return result;
|
|
732
|
+
}).pipe(Effect5.withSpan("IndexEngine.update"));
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Update a dependent index that requires recordId enrichment.
|
|
736
|
+
* This method:
|
|
737
|
+
* 1. Gets changed objects from the source.
|
|
738
|
+
* 2. Ensures those objects exist in ObjectMetaIndex.
|
|
739
|
+
* 3. Looks up recordIds for those objects.
|
|
740
|
+
* 4. Enriches objects with recordIds.
|
|
741
|
+
* 5. Updates the dependent index.
|
|
742
|
+
*/
|
|
743
|
+
#update(ctx, index, source, opts) {
|
|
744
|
+
return Effect5.gen(this, function* () {
|
|
745
|
+
const sqlTransaction = yield* SqlTransaction.SqlTransaction;
|
|
746
|
+
return yield* sqlTransaction.withTransaction(Effect5.gen(this, function* () {
|
|
747
|
+
const cursors = yield* this.#tracker.queryCursors({
|
|
748
|
+
indexName: opts.indexName,
|
|
749
|
+
sourceName: source.sourceName,
|
|
750
|
+
// Pass undefined to get all cursors when spaceId is null.
|
|
751
|
+
spaceId: opts.spaceId ?? void 0
|
|
752
|
+
});
|
|
753
|
+
const { objects, cursors: updatedCursors } = yield* source.getChangedObjects(ctx, cursors, {
|
|
754
|
+
limit: opts.limit
|
|
755
|
+
});
|
|
756
|
+
if (objects.length === 0) {
|
|
757
|
+
return {
|
|
758
|
+
updated: 0,
|
|
759
|
+
done: true,
|
|
760
|
+
objects: []
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
yield* this.#objectMetaIndex.update(objects);
|
|
764
|
+
yield* this.#objectMetaIndex.lookupRecordIds(objects);
|
|
765
|
+
yield* index.update(objects);
|
|
766
|
+
yield* this.#tracker.updateCursors(updatedCursors.map((_) => ({
|
|
767
|
+
indexName: opts.indexName,
|
|
768
|
+
spaceId: _.spaceId,
|
|
769
|
+
sourceName: source.sourceName,
|
|
770
|
+
resourceId: _.resourceId,
|
|
771
|
+
cursor: _.cursor
|
|
772
|
+
})));
|
|
773
|
+
return {
|
|
774
|
+
updated: objects.length,
|
|
775
|
+
done: false,
|
|
776
|
+
objects
|
|
777
|
+
};
|
|
778
|
+
}));
|
|
779
|
+
}).pipe(Effect5.withSpan("IndexEngine.#update"));
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
export {
|
|
783
|
+
EscapedPropPath,
|
|
784
|
+
FtsIndex,
|
|
785
|
+
IndexEngine,
|
|
786
|
+
IndexTracker,
|
|
787
|
+
ObjectMetaIndex,
|
|
788
|
+
ReverseRefIndex
|
|
789
|
+
};
|
|
790
|
+
//# sourceMappingURL=index.mjs.map
|