@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.
- package/LICENSE +102 -5
- package/README.md +1 -1
- package/dist/lib/neutral/index.mjs +190 -101
- package/dist/lib/neutral/index.mjs.map +4 -4
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/types/src/index-engine.d.ts +31 -17
- package/dist/types/src/index-engine.d.ts.map +1 -1
- package/dist/types/src/index-tracker.d.ts +3 -3
- package/dist/types/src/index.d.ts +3 -3
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/indexes/entity-meta-index.d.ts +113 -0
- package/dist/types/src/indexes/entity-meta-index.d.ts.map +1 -0
- package/dist/types/src/indexes/entity-meta-index.test.d.ts +2 -0
- package/dist/types/src/indexes/entity-meta-index.test.d.ts.map +1 -0
- package/dist/types/src/indexes/fts-index.d.ts +7 -6
- package/dist/types/src/indexes/fts-index.d.ts.map +1 -1
- package/dist/types/src/indexes/index.d.ts +1 -1
- package/dist/types/src/indexes/interface.d.ts +15 -4
- package/dist/types/src/indexes/interface.d.ts.map +1 -1
- package/dist/types/src/indexes/reverse-ref-index.d.ts +3 -2
- package/dist/types/src/indexes/reverse-ref-index.d.ts.map +1 -1
- package/dist/types/src/utils.d.ts +3 -3
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +13 -18
- package/src/index-engine.test.ts +138 -16
- package/src/index-engine.ts +123 -58
- package/src/index.ts +9 -3
- package/src/indexes/{object-meta-index.test.ts → entity-meta-index.test.ts} +114 -53
- package/src/indexes/{object-meta-index.ts → entity-meta-index.ts} +140 -71
- package/src/indexes/fts-index.test.ts +188 -34
- package/src/indexes/fts-index.ts +32 -15
- package/src/indexes/index.ts +1 -1
- package/src/indexes/interface.ts +16 -4
- package/src/indexes/reverse-ref-index.test.ts +57 -43
- package/src/indexes/reverse-ref-index.ts +22 -14
- package/src/utils.ts +5 -5
- package/dist/types/src/indexes/object-meta-index.d.ts +0 -88
- package/dist/types/src/indexes/object-meta-index.d.ts.map +0 -1
- package/dist/types/src/indexes/object-meta-index.test.d.ts +0 -2
- package/dist/types/src/indexes/object-meta-index.test.d.ts.map +0 -1
|
@@ -9,11 +9,28 @@ import * as Effect from 'effect/Effect';
|
|
|
9
9
|
import * as Schema from 'effect/Schema';
|
|
10
10
|
|
|
11
11
|
import { ATTR_DELETED, ATTR_PARENT, ATTR_RELATION_SOURCE, ATTR_RELATION_TARGET, ATTR_TYPE } from '@dxos/echo/internal';
|
|
12
|
-
import { DXN,
|
|
12
|
+
import { DXN, EID, EntityId, SpaceId, URI } from '@dxos/keys';
|
|
13
13
|
|
|
14
14
|
import type { IndexerObject } from './interface';
|
|
15
15
|
import type { Index } from './interface';
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Normalizes an echo: EID to the local (unqualified) form so SQL comparisons are consistent.
|
|
19
|
+
* DB rows always store `echo:/<entityId>`; a space-qualified `echo://<space>/<entityId>` would
|
|
20
|
+
* otherwise miss every row for that type.
|
|
21
|
+
*/
|
|
22
|
+
const _normalizeTypeUri = (typeDXN: string): string => {
|
|
23
|
+
if (!typeDXN.startsWith('echo:')) {
|
|
24
|
+
return typeDXN;
|
|
25
|
+
}
|
|
26
|
+
const eid = EID.tryParse(typeDXN);
|
|
27
|
+
if (!eid) {
|
|
28
|
+
return typeDXN;
|
|
29
|
+
}
|
|
30
|
+
const entityId = EID.getEntityId(eid);
|
|
31
|
+
return entityId ? EID.make({ entityId }) : typeDXN;
|
|
32
|
+
};
|
|
33
|
+
|
|
17
34
|
const _escapeLikePrefix = (prefix: string) => {
|
|
18
35
|
// Escape LIKE metacharacters in the *literal* prefix (we still append a wildcard for the version suffix).
|
|
19
36
|
// Backslash is used as the ESCAPE character.
|
|
@@ -22,20 +39,27 @@ const _escapeLikePrefix = (prefix: string) => {
|
|
|
22
39
|
return `${escaped}:%`;
|
|
23
40
|
};
|
|
24
41
|
|
|
25
|
-
export const
|
|
42
|
+
export const EntityMeta = Schema.Struct({
|
|
26
43
|
recordId: Schema.Number,
|
|
27
|
-
objectId:
|
|
44
|
+
objectId: EntityId,
|
|
45
|
+
/** Empty string for non-queue objects. */
|
|
28
46
|
queueId: Schema.String,
|
|
29
|
-
|
|
47
|
+
/** Queue subspace namespace (e.g. 'data', 'trace'). Empty string for non-queue objects. */
|
|
48
|
+
queueNamespace: Schema.String,
|
|
49
|
+
spaceId: SpaceId,
|
|
30
50
|
documentId: Schema.String,
|
|
31
51
|
entityKind: Schema.String,
|
|
32
|
-
/**
|
|
33
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Type identifier URI for the object — typename DXN for non-stored schemas,
|
|
54
|
+
* schema-as-object EID for stored (dynamic) schemas. Mirrors the value
|
|
55
|
+
* written into the object's `system.type`.
|
|
56
|
+
*/
|
|
57
|
+
typeDXN: URI.Schema,
|
|
34
58
|
deleted: Schema.Boolean,
|
|
35
|
-
source: Schema.NullOr(Schema
|
|
36
|
-
target: Schema.NullOr(Schema
|
|
59
|
+
source: Schema.NullOr(EID.Schema),
|
|
60
|
+
target: Schema.NullOr(EID.Schema),
|
|
37
61
|
/** Parent object id (nullable). */
|
|
38
|
-
parent: Schema.NullOr(Schema
|
|
62
|
+
parent: Schema.NullOr(EID.Schema),
|
|
39
63
|
/** Monotonically increasing sequence number assigned on insert/update for tracking indexing order. */
|
|
40
64
|
version: Schema.Number,
|
|
41
65
|
/** Unix ms timestamp when the object was first indexed. */
|
|
@@ -43,7 +67,7 @@ export const ObjectMeta = Schema.Struct({
|
|
|
43
67
|
/** Unix ms timestamp when the object was last re-indexed. */
|
|
44
68
|
updatedAt: Schema.NullOr(Schema.Number),
|
|
45
69
|
});
|
|
46
|
-
export interface
|
|
70
|
+
export interface EntityMeta extends Schema.Schema.Type<typeof EntityMeta> {}
|
|
47
71
|
|
|
48
72
|
/**
|
|
49
73
|
* Builds a SQL condition for filtering by space and queue source.
|
|
@@ -76,18 +100,19 @@ const buildSourceCondition = (
|
|
|
76
100
|
return sql.or(conditions);
|
|
77
101
|
};
|
|
78
102
|
|
|
79
|
-
export class
|
|
80
|
-
migrate = Effect.fn('
|
|
103
|
+
export class EntityMetaIndex implements Index {
|
|
104
|
+
migrate = Effect.fn('EntityMetaIndex.runMigrations')(function* () {
|
|
81
105
|
const sql = yield* SqlClient.SqlClient;
|
|
82
106
|
|
|
83
107
|
yield* sql`CREATE TABLE IF NOT EXISTS objectMeta (
|
|
84
108
|
recordId INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
85
109
|
objectId TEXT NOT NULL,
|
|
86
110
|
queueId TEXT NOT NULL DEFAULT '',
|
|
111
|
+
queueNamespace TEXT NOT NULL DEFAULT '',
|
|
87
112
|
spaceId TEXT NOT NULL,
|
|
88
113
|
documentId TEXT NOT NULL DEFAULT '',
|
|
89
114
|
entityKind TEXT NOT NULL,
|
|
90
|
-
|
|
115
|
+
typeDXN TEXT NOT NULL,
|
|
91
116
|
deleted INTEGER NOT NULL,
|
|
92
117
|
source TEXT,
|
|
93
118
|
target TEXT,
|
|
@@ -102,30 +127,36 @@ export class ObjectMetaIndex implements Index {
|
|
|
102
127
|
// Add timestamp columns for tables created before they were introduced.
|
|
103
128
|
yield* Effect.catchAll(sql`ALTER TABLE objectMeta ADD COLUMN createdAt INTEGER`, () => Effect.void);
|
|
104
129
|
yield* Effect.catchAll(sql`ALTER TABLE objectMeta ADD COLUMN updatedAt INTEGER`, () => Effect.void);
|
|
130
|
+
// Add queueNamespace column for tables created before it was introduced.
|
|
131
|
+
yield* Effect.catchAll(
|
|
132
|
+
sql`ALTER TABLE objectMeta ADD COLUMN queueNamespace TEXT NOT NULL DEFAULT ''`,
|
|
133
|
+
() => Effect.void,
|
|
134
|
+
);
|
|
105
135
|
|
|
106
136
|
yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_objectId ON objectMeta(spaceId, objectId)`;
|
|
107
|
-
yield* sql`CREATE INDEX IF NOT EXISTS
|
|
137
|
+
yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_typeDXN ON objectMeta(spaceId, typeDXN)`;
|
|
108
138
|
yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_version ON objectMeta(version)`;
|
|
109
139
|
yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_parent ON objectMeta(spaceId, parent)`;
|
|
110
140
|
yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_updatedAt ON objectMeta(updatedAt)`;
|
|
111
141
|
yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_createdAt ON objectMeta(createdAt)`;
|
|
112
142
|
});
|
|
113
143
|
|
|
114
|
-
query = Effect.fn('
|
|
144
|
+
query = Effect.fn('EntityMetaIndex.query')(
|
|
115
145
|
(
|
|
116
|
-
query: Pick<
|
|
117
|
-
): Effect.Effect<readonly
|
|
146
|
+
query: Pick<EntityMeta, 'spaceId' | 'typeDXN'>,
|
|
147
|
+
): Effect.Effect<readonly EntityMeta[], SqlError.SqlError, SqlClient.SqlClient> =>
|
|
118
148
|
Effect.gen(function* () {
|
|
119
149
|
const sql = yield* SqlClient.SqlClient;
|
|
120
|
-
const
|
|
150
|
+
const normalizedTypeDXN = _normalizeTypeUri(query.typeDXN);
|
|
151
|
+
const parsedDxn = DXN.isDXN(normalizedTypeDXN) ? normalizedTypeDXN : undefined;
|
|
152
|
+
const hasNoVersion = parsedDxn !== undefined && DXN.getVersion(parsedDxn) === undefined;
|
|
121
153
|
|
|
122
154
|
// SQLite stores booleans as integers, so we need to specify the raw row type.
|
|
123
|
-
const rows =
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
: yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE spaceId = ${query.spaceId} AND typeDxn = ${query.typeDxn}`;
|
|
155
|
+
const rows = hasNoVersion
|
|
156
|
+
? yield* sql<EntityMeta>`SELECT * FROM objectMeta WHERE spaceId = ${query.spaceId} AND (typeDXN = ${
|
|
157
|
+
normalizedTypeDXN
|
|
158
|
+
} OR typeDXN LIKE ${_escapeLikePrefix(normalizedTypeDXN)} ESCAPE '\\')`
|
|
159
|
+
: yield* sql<EntityMeta>`SELECT * FROM objectMeta WHERE spaceId = ${query.spaceId} AND typeDXN = ${normalizedTypeDXN}`;
|
|
129
160
|
return rows.map((row) => ({
|
|
130
161
|
...row,
|
|
131
162
|
deleted: !!row.deleted,
|
|
@@ -133,12 +164,12 @@ export class ObjectMetaIndex implements Index {
|
|
|
133
164
|
}),
|
|
134
165
|
);
|
|
135
166
|
|
|
136
|
-
queryAll = Effect.fn('
|
|
167
|
+
queryAll = Effect.fn('EntityMetaIndex.queryAll')(
|
|
137
168
|
(query: {
|
|
138
|
-
spaceIds: readonly
|
|
169
|
+
spaceIds: readonly EntityMeta['spaceId'][];
|
|
139
170
|
includeAllQueues?: boolean;
|
|
140
171
|
queueIds?: readonly string[] | null;
|
|
141
|
-
}): Effect.Effect<readonly
|
|
172
|
+
}): Effect.Effect<readonly EntityMeta[], SqlError.SqlError, SqlClient.SqlClient> =>
|
|
142
173
|
Effect.gen(function* () {
|
|
143
174
|
if (query.spaceIds.length === 0 && (!query.queueIds || query.queueIds.length === 0)) {
|
|
144
175
|
return [];
|
|
@@ -151,7 +182,7 @@ export class ObjectMetaIndex implements Index {
|
|
|
151
182
|
query.includeAllQueues ?? false,
|
|
152
183
|
query.queueIds ?? null,
|
|
153
184
|
);
|
|
154
|
-
const rows = yield* sql<
|
|
185
|
+
const rows = yield* sql<EntityMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition}`;
|
|
155
186
|
return rows.map((row) => ({
|
|
156
187
|
...row,
|
|
157
188
|
deleted: !!row.deleted,
|
|
@@ -159,7 +190,7 @@ export class ObjectMetaIndex implements Index {
|
|
|
159
190
|
}),
|
|
160
191
|
);
|
|
161
192
|
|
|
162
|
-
queryTypes = Effect.fn('
|
|
193
|
+
queryTypes = Effect.fn('EntityMetaIndex.queryTypes')(
|
|
163
194
|
({
|
|
164
195
|
spaceIds,
|
|
165
196
|
typeDxns,
|
|
@@ -167,12 +198,12 @@ export class ObjectMetaIndex implements Index {
|
|
|
167
198
|
includeAllQueues = false,
|
|
168
199
|
queueIds = null,
|
|
169
200
|
}: {
|
|
170
|
-
spaceIds: readonly
|
|
171
|
-
typeDxns: readonly
|
|
201
|
+
spaceIds: readonly EntityMeta['spaceId'][];
|
|
202
|
+
typeDxns: readonly EntityMeta['typeDXN'][];
|
|
172
203
|
inverted?: boolean;
|
|
173
204
|
includeAllQueues?: boolean;
|
|
174
205
|
queueIds?: readonly string[] | null;
|
|
175
|
-
}): Effect.Effect<readonly
|
|
206
|
+
}): Effect.Effect<readonly EntityMeta[], SqlError.SqlError, SqlClient.SqlClient> =>
|
|
176
207
|
Effect.gen(function* () {
|
|
177
208
|
if (spaceIds.length === 0 && (!queueIds || queueIds.length === 0)) {
|
|
178
209
|
return [];
|
|
@@ -185,7 +216,7 @@ export class ObjectMetaIndex implements Index {
|
|
|
185
216
|
|
|
186
217
|
const sql = yield* SqlClient.SqlClient;
|
|
187
218
|
const sourceCondition = buildSourceCondition(sql, spaceIds, includeAllQueues, queueIds);
|
|
188
|
-
const rows = yield* sql<
|
|
219
|
+
const rows = yield* sql<EntityMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition}`;
|
|
189
220
|
return rows.map((row) => ({
|
|
190
221
|
...row,
|
|
191
222
|
deleted: !!row.deleted,
|
|
@@ -194,16 +225,19 @@ export class ObjectMetaIndex implements Index {
|
|
|
194
225
|
const sql = yield* SqlClient.SqlClient;
|
|
195
226
|
const sourceCondition = buildSourceCondition(sql, spaceIds, includeAllQueues, queueIds);
|
|
196
227
|
const typeWhere = sql.or(
|
|
197
|
-
typeDxns.map((
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
228
|
+
typeDxns.map((typeDXN) => {
|
|
229
|
+
const normalized = _normalizeTypeUri(typeDXN);
|
|
230
|
+
const parsedDxn = DXN.isDXN(normalized) ? normalized : undefined;
|
|
231
|
+
const hasNoVersion = parsedDxn !== undefined && DXN.getVersion(parsedDxn) === undefined;
|
|
232
|
+
const exactMatch = sql`typeDXN = ${normalized}`;
|
|
233
|
+
return hasNoVersion
|
|
234
|
+
? sql.or([exactMatch, sql`typeDXN LIKE ${_escapeLikePrefix(normalized)} ESCAPE '\\'`])
|
|
235
|
+
: exactMatch;
|
|
202
236
|
}),
|
|
203
237
|
);
|
|
204
238
|
const rows = inverted
|
|
205
|
-
? yield* sql<
|
|
206
|
-
: yield* sql<
|
|
239
|
+
? yield* sql<EntityMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition} AND NOT ${typeWhere}`
|
|
240
|
+
: yield* sql<EntityMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition} AND ${typeWhere}`;
|
|
207
241
|
return rows.map((row) => ({
|
|
208
242
|
...row,
|
|
209
243
|
deleted: !!row.deleted,
|
|
@@ -211,21 +245,21 @@ export class ObjectMetaIndex implements Index {
|
|
|
211
245
|
}),
|
|
212
246
|
);
|
|
213
247
|
|
|
214
|
-
queryRelations = Effect.fn('
|
|
248
|
+
queryRelations = Effect.fn('EntityMetaIndex.queryRelations')(
|
|
215
249
|
({
|
|
216
250
|
endpoint,
|
|
217
251
|
anchorDxns,
|
|
218
252
|
}: {
|
|
219
253
|
endpoint: 'source' | 'target';
|
|
220
254
|
anchorDxns: readonly string[];
|
|
221
|
-
}): Effect.Effect<readonly
|
|
255
|
+
}): Effect.Effect<readonly EntityMeta[], SqlError.SqlError, SqlClient.SqlClient> =>
|
|
222
256
|
Effect.gen(function* () {
|
|
223
257
|
if (anchorDxns.length === 0) {
|
|
224
258
|
return [];
|
|
225
259
|
}
|
|
226
260
|
const sql = yield* SqlClient.SqlClient;
|
|
227
261
|
const column = endpoint === 'source' ? 'source' : 'target';
|
|
228
|
-
const rows = yield* sql<
|
|
262
|
+
const rows = yield* sql<EntityMeta>`SELECT * FROM objectMeta WHERE entityKind = 'relation' AND ${sql.in(
|
|
229
263
|
column,
|
|
230
264
|
anchorDxns,
|
|
231
265
|
)}`;
|
|
@@ -237,7 +271,7 @@ export class ObjectMetaIndex implements Index {
|
|
|
237
271
|
);
|
|
238
272
|
|
|
239
273
|
// TODO(dmaretskyi): Update recordId on objects so that we don't need to look it up separately.
|
|
240
|
-
update = Effect.fn('
|
|
274
|
+
update = Effect.fn('EntityMetaIndex.update')(
|
|
241
275
|
(objects: IndexerObject[]): Effect.Effect<void, SqlError.SqlError, SqlClient.SqlClient> =>
|
|
242
276
|
Effect.gen(function* () {
|
|
243
277
|
const sql = yield* SqlClient.SqlClient;
|
|
@@ -246,7 +280,7 @@ export class ObjectMetaIndex implements Index {
|
|
|
246
280
|
objects,
|
|
247
281
|
(object) =>
|
|
248
282
|
Effect.gen(function* () {
|
|
249
|
-
const { spaceId, queueId, documentId, data } = object;
|
|
283
|
+
const { spaceId, queueId, queueNamespace, documentId, data } = object;
|
|
250
284
|
|
|
251
285
|
// Extract metadata (Logic emulating Echo APIs as strict imports are unavailable).
|
|
252
286
|
const castData = data;
|
|
@@ -274,7 +308,9 @@ export class ObjectMetaIndex implements Index {
|
|
|
274
308
|
|
|
275
309
|
// Extract metadata.
|
|
276
310
|
const entityKind = castData[ATTR_RELATION_SOURCE] ? 'relation' : 'object';
|
|
277
|
-
|
|
311
|
+
// Type identifier as stored on `system.type`: a typename DXN for static schemas,
|
|
312
|
+
// an `echo:` EID for stored (dynamic) schemas.
|
|
313
|
+
const typeDXN = URI.make(castData[ATTR_TYPE] ? String(castData[ATTR_TYPE]) : 'type');
|
|
278
314
|
const deleted = castData[ATTR_DELETED] ? 1 : 0;
|
|
279
315
|
// Relations.
|
|
280
316
|
const source = entityKind === 'relation' ? (castData[ATTR_RELATION_SOURCE] ?? null) : null;
|
|
@@ -282,32 +318,36 @@ export class ObjectMetaIndex implements Index {
|
|
|
282
318
|
// Parent (nullable).
|
|
283
319
|
const parent = castData[ATTR_PARENT] ?? null;
|
|
284
320
|
|
|
285
|
-
const
|
|
321
|
+
const updatedAtTimestamp = object.updatedAt;
|
|
322
|
+
// Prefer the creation timestamp stored in the document (survives compaction/migrations).
|
|
323
|
+
// Fall back to the automerge-derived updatedAt for legacy objects that predate this field.
|
|
324
|
+
const createdAtTimestamp = object.createdAt ?? updatedAtTimestamp;
|
|
286
325
|
|
|
287
326
|
if (existing.length > 0) {
|
|
288
327
|
yield* sql`
|
|
289
328
|
UPDATE objectMeta SET
|
|
290
329
|
version = ${version},
|
|
330
|
+
queueNamespace = ${queueNamespace ?? ''},
|
|
291
331
|
entityKind = ${entityKind},
|
|
292
|
-
|
|
332
|
+
typeDXN = ${typeDXN},
|
|
293
333
|
deleted = ${deleted},
|
|
294
334
|
source = ${source},
|
|
295
335
|
target = ${target},
|
|
296
336
|
parent = ${parent},
|
|
297
|
-
updatedAt = ${
|
|
337
|
+
updatedAt = ${updatedAtTimestamp}
|
|
298
338
|
WHERE recordId = ${existing[0].recordId}
|
|
299
339
|
`;
|
|
300
340
|
} else {
|
|
301
341
|
yield* sql`
|
|
302
342
|
INSERT INTO objectMeta (
|
|
303
|
-
objectId, queueId, spaceId, documentId,
|
|
304
|
-
entityKind,
|
|
343
|
+
objectId, queueId, queueNamespace, spaceId, documentId,
|
|
344
|
+
entityKind, typeDXN, deleted, source, target, parent, version,
|
|
305
345
|
createdAt, updatedAt
|
|
306
346
|
) VALUES (
|
|
307
|
-
${objectId}, ${queueId ?? ''}, ${spaceId}, ${documentId ?? ''},
|
|
308
|
-
${entityKind}, ${
|
|
347
|
+
${objectId}, ${queueId ?? ''}, ${queueNamespace ?? ''}, ${spaceId}, ${documentId ?? ''},
|
|
348
|
+
${entityKind}, ${typeDXN}, ${deleted},
|
|
309
349
|
${source}, ${target}, ${parent}, ${version},
|
|
310
|
-
${
|
|
350
|
+
${createdAtTimestamp}, ${updatedAtTimestamp}
|
|
311
351
|
)
|
|
312
352
|
`;
|
|
313
353
|
}
|
|
@@ -318,10 +358,10 @@ export class ObjectMetaIndex implements Index {
|
|
|
318
358
|
);
|
|
319
359
|
|
|
320
360
|
/**
|
|
321
|
-
* Look up `recordIds` for objects that are already stored in the
|
|
361
|
+
* Look up `recordIds` for objects that are already stored in the EntityMetaIndex.
|
|
322
362
|
* Mutates the objects in place.
|
|
323
363
|
*/
|
|
324
|
-
lookupRecordIds = Effect.fn('
|
|
364
|
+
lookupRecordIds = Effect.fn('EntityMetaIndex.lookupRecordIds')(
|
|
325
365
|
(objects: IndexerObject[]): Effect.Effect<void, SqlError.SqlError, SqlClient.SqlClient> =>
|
|
326
366
|
Effect.gen(function* () {
|
|
327
367
|
const sql = yield* SqlClient.SqlClient;
|
|
@@ -346,7 +386,7 @@ export class ObjectMetaIndex implements Index {
|
|
|
346
386
|
if (result.length === 0) {
|
|
347
387
|
// TODO(mykola): Handle this case gracefully.
|
|
348
388
|
return yield* Effect.die(
|
|
349
|
-
new Error(`Object not found in
|
|
389
|
+
new Error(`Object not found in EntityMetaIndex: ${spaceId}/${documentId ?? queueId}/${objectId}`),
|
|
350
390
|
);
|
|
351
391
|
}
|
|
352
392
|
object.recordId = result[0].recordId;
|
|
@@ -357,15 +397,15 @@ export class ObjectMetaIndex implements Index {
|
|
|
357
397
|
/**
|
|
358
398
|
* Look up object metadata by recordIds.
|
|
359
399
|
*/
|
|
360
|
-
lookupByRecordIds = Effect.fn('
|
|
361
|
-
(recordIds: number[]): Effect.Effect<readonly
|
|
400
|
+
lookupByRecordIds = Effect.fn('EntityMetaIndex.lookupByRecordIds')(
|
|
401
|
+
(recordIds: number[]): Effect.Effect<readonly EntityMeta[], SqlError.SqlError, SqlClient.SqlClient> =>
|
|
362
402
|
Effect.gen(function* () {
|
|
363
403
|
if (recordIds.length === 0) {
|
|
364
404
|
return [];
|
|
365
405
|
}
|
|
366
406
|
|
|
367
407
|
const sql = yield* SqlClient.SqlClient;
|
|
368
|
-
const rows = yield* sql<
|
|
408
|
+
const rows = yield* sql<EntityMeta>`SELECT * FROM objectMeta WHERE ${sql.in('recordId', recordIds)}`;
|
|
369
409
|
|
|
370
410
|
return rows.map((row) => ({
|
|
371
411
|
...row,
|
|
@@ -374,19 +414,42 @@ export class ObjectMetaIndex implements Index {
|
|
|
374
414
|
}),
|
|
375
415
|
);
|
|
376
416
|
|
|
417
|
+
/**
|
|
418
|
+
* Look up object metadata by object id across one or more spaces (space db and queue items).
|
|
419
|
+
*/
|
|
420
|
+
queryObjectIds = Effect.fn('EntityMetaIndex.queryObjectIds')(
|
|
421
|
+
(query: {
|
|
422
|
+
spaceIds: readonly EntityMeta['spaceId'][];
|
|
423
|
+
objectIds: readonly EntityMeta['objectId'][];
|
|
424
|
+
}): Effect.Effect<readonly EntityMeta[], SqlError.SqlError, SqlClient.SqlClient> =>
|
|
425
|
+
Effect.gen(function* () {
|
|
426
|
+
if (query.spaceIds.length === 0 || query.objectIds.length === 0) {
|
|
427
|
+
return [];
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const sql = yield* SqlClient.SqlClient;
|
|
431
|
+
const rows =
|
|
432
|
+
yield* sql<EntityMeta>`SELECT * FROM objectMeta WHERE ${sql.in('spaceId', query.spaceIds)} AND ${sql.in('objectId', query.objectIds)}`;
|
|
433
|
+
return rows.map((row) => ({
|
|
434
|
+
...row,
|
|
435
|
+
deleted: !!row.deleted,
|
|
436
|
+
}));
|
|
437
|
+
}),
|
|
438
|
+
);
|
|
439
|
+
|
|
377
440
|
/**
|
|
378
441
|
* Look up object metadata by objectId, spaceId, and queueId.
|
|
379
442
|
*/
|
|
380
|
-
lookupByObjectId = Effect.fn('
|
|
443
|
+
lookupByObjectId = Effect.fn('EntityMetaIndex.lookupByObjectId')(
|
|
381
444
|
(query: {
|
|
382
445
|
objectId: string;
|
|
383
446
|
spaceId: string;
|
|
384
447
|
queueId: string;
|
|
385
|
-
}): Effect.Effect<
|
|
448
|
+
}): Effect.Effect<EntityMeta | null, SqlError.SqlError, SqlClient.SqlClient> =>
|
|
386
449
|
Effect.gen(function* () {
|
|
387
450
|
const sql = yield* SqlClient.SqlClient;
|
|
388
451
|
const rows =
|
|
389
|
-
yield* sql<
|
|
452
|
+
yield* sql<EntityMeta>`SELECT * FROM objectMeta WHERE spaceId = ${query.spaceId} AND queueId = ${query.queueId} AND objectId = ${query.objectId} LIMIT 1`;
|
|
390
453
|
|
|
391
454
|
if (rows.length === 0) {
|
|
392
455
|
return null;
|
|
@@ -402,7 +465,7 @@ export class ObjectMetaIndex implements Index {
|
|
|
402
465
|
/**
|
|
403
466
|
* Query objects by timestamp range.
|
|
404
467
|
*/
|
|
405
|
-
queryByTimeRange = Effect.fn('
|
|
468
|
+
queryByTimeRange = Effect.fn('EntityMetaIndex.queryByTimeRange')(
|
|
406
469
|
(query: {
|
|
407
470
|
spaceIds: readonly string[];
|
|
408
471
|
updatedAfter?: number;
|
|
@@ -411,7 +474,7 @@ export class ObjectMetaIndex implements Index {
|
|
|
411
474
|
createdBefore?: number;
|
|
412
475
|
includeAllQueues?: boolean;
|
|
413
476
|
queueIds?: readonly string[] | null;
|
|
414
|
-
}): Effect.Effect<readonly
|
|
477
|
+
}): Effect.Effect<readonly EntityMeta[], SqlError.SqlError, SqlClient.SqlClient> =>
|
|
415
478
|
Effect.gen(function* () {
|
|
416
479
|
if (query.spaceIds.length === 0 && (!query.queueIds || query.queueIds.length === 0)) {
|
|
417
480
|
return [];
|
|
@@ -441,8 +504,8 @@ export class ObjectMetaIndex implements Index {
|
|
|
441
504
|
|
|
442
505
|
const rows =
|
|
443
506
|
timeConditions.length > 0
|
|
444
|
-
? yield* sql<
|
|
445
|
-
: yield* sql<
|
|
507
|
+
? yield* sql<EntityMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition} AND ${sql.and(timeConditions)}`
|
|
508
|
+
: yield* sql<EntityMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition}`;
|
|
446
509
|
|
|
447
510
|
return rows.map((row) => ({
|
|
448
511
|
...row,
|
|
@@ -453,20 +516,26 @@ export class ObjectMetaIndex implements Index {
|
|
|
453
516
|
|
|
454
517
|
/**
|
|
455
518
|
* Query children by parent object ids.
|
|
519
|
+
* Matches both:
|
|
520
|
+
* - Objects whose `parent` field references one of the given parent ids (standard parent/child hierarchy).
|
|
521
|
+
* - Queue items whose `queueId` equals one of the parent ids (e.g. items inside a Feed, since a feed's queue
|
|
522
|
+
* DXN uses the feed's object id as its queue id — see `Feed.getQueueUri`).
|
|
456
523
|
*/
|
|
457
|
-
queryChildren = Effect.fn('
|
|
524
|
+
queryChildren = Effect.fn('EntityMetaIndex.queryChildren')(
|
|
458
525
|
(query: {
|
|
459
526
|
spaceId: SpaceId[];
|
|
460
|
-
parentIds:
|
|
461
|
-
}): Effect.Effect<readonly
|
|
527
|
+
parentIds: EntityId[];
|
|
528
|
+
}): Effect.Effect<readonly EntityMeta[], SqlError.SqlError, SqlClient.SqlClient> =>
|
|
462
529
|
Effect.gen(function* () {
|
|
463
530
|
if (query.parentIds.length === 0) {
|
|
464
531
|
return [];
|
|
465
532
|
}
|
|
466
533
|
|
|
467
534
|
const sql = yield* SqlClient.SqlClient;
|
|
535
|
+
const parentDzns = query.parentIds.map((id) => EID.make({ entityId: id }));
|
|
536
|
+
const parentDxns = parentDzns;
|
|
468
537
|
const rows =
|
|
469
|
-
yield* sql<
|
|
538
|
+
yield* sql<EntityMeta>`SELECT * FROM objectMeta WHERE ${sql.in('spaceId', query.spaceId)} AND (${sql.in('parent', parentDxns)} OR ${sql.in('queueId', query.parentIds)})`;
|
|
470
539
|
|
|
471
540
|
return rows.map((row) => ({
|
|
472
541
|
...row,
|