@dxos/index-core 0.0.0 → 0.8.4-main.03d5cd7b56
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
|
@@ -4,30 +4,80 @@
|
|
|
4
4
|
|
|
5
5
|
import * as SqlClient from '@effect/sql/SqlClient';
|
|
6
6
|
import type * as SqlError from '@effect/sql/SqlError';
|
|
7
|
+
import type * as Statement from '@effect/sql/Statement';
|
|
7
8
|
import * as Effect from 'effect/Effect';
|
|
8
9
|
import * as Schema from 'effect/Schema';
|
|
9
10
|
|
|
10
|
-
import { ATTR_DELETED, ATTR_RELATION_SOURCE, ATTR_RELATION_TARGET, ATTR_TYPE } from '@dxos/echo/internal';
|
|
11
|
+
import { ATTR_DELETED, ATTR_PARENT, ATTR_RELATION_SOURCE, ATTR_RELATION_TARGET, ATTR_TYPE } from '@dxos/echo/internal';
|
|
12
|
+
import { DXN, type ObjectId, type SpaceId } from '@dxos/keys';
|
|
11
13
|
|
|
12
14
|
import type { IndexerObject } from './interface';
|
|
13
15
|
import type { Index } from './interface';
|
|
14
16
|
|
|
17
|
+
const _escapeLikePrefix = (prefix: string) => {
|
|
18
|
+
// Escape LIKE metacharacters in the *literal* prefix (we still append a wildcard for the version suffix).
|
|
19
|
+
// Backslash is used as the ESCAPE character.
|
|
20
|
+
// See: https://www.sqlite.org/lang_expr.html#like
|
|
21
|
+
const escaped = prefix.replaceAll('\\', '\\\\').replaceAll('%', '\\%').replaceAll('_', '\\_');
|
|
22
|
+
return `${escaped}:%`;
|
|
23
|
+
};
|
|
24
|
+
|
|
15
25
|
export const ObjectMeta = Schema.Struct({
|
|
16
26
|
recordId: Schema.Number,
|
|
17
27
|
objectId: Schema.String,
|
|
18
28
|
queueId: Schema.String,
|
|
29
|
+
/** Queue subspace namespace (e.g. 'data', 'trace'). Empty string for non-queue objects. */
|
|
30
|
+
queueNamespace: Schema.String,
|
|
19
31
|
spaceId: Schema.String,
|
|
20
32
|
documentId: Schema.String,
|
|
21
33
|
entityKind: Schema.String,
|
|
34
|
+
/** The versioned DXN of the type of the object. */
|
|
22
35
|
typeDxn: Schema.String,
|
|
23
36
|
deleted: Schema.Boolean,
|
|
24
37
|
source: Schema.NullOr(Schema.String),
|
|
25
38
|
target: Schema.NullOr(Schema.String),
|
|
39
|
+
/** Parent object id (nullable). */
|
|
40
|
+
parent: Schema.NullOr(Schema.String),
|
|
26
41
|
/** Monotonically increasing sequence number assigned on insert/update for tracking indexing order. */
|
|
27
42
|
version: Schema.Number,
|
|
43
|
+
/** Unix ms timestamp when the object was first indexed. */
|
|
44
|
+
createdAt: Schema.NullOr(Schema.Number),
|
|
45
|
+
/** Unix ms timestamp when the object was last re-indexed. */
|
|
46
|
+
updatedAt: Schema.NullOr(Schema.Number),
|
|
28
47
|
});
|
|
29
48
|
export interface ObjectMeta extends Schema.Schema.Type<typeof ObjectMeta> {}
|
|
30
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Builds a SQL condition for filtering by space and queue source.
|
|
52
|
+
* When `includeAllQueues` is false and no `queueIds`, only non-queue objects are returned.
|
|
53
|
+
*/
|
|
54
|
+
const buildSourceCondition = (
|
|
55
|
+
sql: SqlClient.SqlClient,
|
|
56
|
+
spaceIds: readonly string[],
|
|
57
|
+
includeAllQueues: boolean,
|
|
58
|
+
queueIds: readonly string[] | null,
|
|
59
|
+
): Statement.Fragment => {
|
|
60
|
+
const conditions: Statement.Fragment[] = [];
|
|
61
|
+
|
|
62
|
+
if (spaceIds.length > 0) {
|
|
63
|
+
if (includeAllQueues) {
|
|
64
|
+
conditions.push(sql`${sql.in('spaceId', spaceIds)}`);
|
|
65
|
+
} else {
|
|
66
|
+
conditions.push(sql`(${sql.in('spaceId', spaceIds)} AND queueId = '')`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (queueIds && queueIds.length > 0) {
|
|
71
|
+
conditions.push(sql`${sql.in('queueId', queueIds)}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (conditions.length === 0) {
|
|
75
|
+
return sql`1 = 0`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return sql.or(conditions);
|
|
79
|
+
};
|
|
80
|
+
|
|
31
81
|
export class ObjectMetaIndex implements Index {
|
|
32
82
|
migrate = Effect.fn('ObjectMetaIndex.runMigrations')(function* () {
|
|
33
83
|
const sql = yield* SqlClient.SqlClient;
|
|
@@ -36,6 +86,7 @@ export class ObjectMetaIndex implements Index {
|
|
|
36
86
|
recordId INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
37
87
|
objectId TEXT NOT NULL,
|
|
38
88
|
queueId TEXT NOT NULL DEFAULT '',
|
|
89
|
+
queueNamespace TEXT NOT NULL DEFAULT '',
|
|
39
90
|
spaceId TEXT NOT NULL,
|
|
40
91
|
documentId TEXT NOT NULL DEFAULT '',
|
|
41
92
|
entityKind TEXT NOT NULL,
|
|
@@ -43,23 +94,149 @@ export class ObjectMetaIndex implements Index {
|
|
|
43
94
|
deleted INTEGER NOT NULL,
|
|
44
95
|
source TEXT,
|
|
45
96
|
target TEXT,
|
|
46
|
-
|
|
97
|
+
parent TEXT,
|
|
98
|
+
version INTEGER NOT NULL,
|
|
99
|
+
createdAt INTEGER,
|
|
100
|
+
updatedAt INTEGER
|
|
47
101
|
)`;
|
|
48
102
|
|
|
103
|
+
// Add `parent` column for tables created before it was introduced.
|
|
104
|
+
yield* Effect.catchAll(sql`ALTER TABLE objectMeta ADD COLUMN parent TEXT`, () => Effect.void);
|
|
105
|
+
// Add timestamp columns for tables created before they were introduced.
|
|
106
|
+
yield* Effect.catchAll(sql`ALTER TABLE objectMeta ADD COLUMN createdAt INTEGER`, () => Effect.void);
|
|
107
|
+
yield* Effect.catchAll(sql`ALTER TABLE objectMeta ADD COLUMN updatedAt INTEGER`, () => Effect.void);
|
|
108
|
+
// Add queueNamespace column for tables created before it was introduced.
|
|
109
|
+
yield* Effect.catchAll(
|
|
110
|
+
sql`ALTER TABLE objectMeta ADD COLUMN queueNamespace TEXT NOT NULL DEFAULT ''`,
|
|
111
|
+
() => Effect.void,
|
|
112
|
+
);
|
|
113
|
+
|
|
49
114
|
yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_objectId ON objectMeta(spaceId, objectId)`;
|
|
50
115
|
yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_typeDxn ON objectMeta(spaceId, typeDxn)`;
|
|
51
116
|
yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_version ON objectMeta(version)`;
|
|
117
|
+
yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_parent ON objectMeta(spaceId, parent)`;
|
|
118
|
+
yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_updatedAt ON objectMeta(updatedAt)`;
|
|
119
|
+
yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_createdAt ON objectMeta(createdAt)`;
|
|
52
120
|
});
|
|
53
121
|
|
|
54
|
-
query = Effect.fn('ObjectMetaIndex.
|
|
122
|
+
query = Effect.fn('ObjectMetaIndex.query')(
|
|
55
123
|
(
|
|
56
124
|
query: Pick<ObjectMeta, 'spaceId' | 'typeDxn'>,
|
|
57
125
|
): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>
|
|
58
126
|
Effect.gen(function* () {
|
|
59
127
|
const sql = yield* SqlClient.SqlClient;
|
|
128
|
+
const parsedType = DXN.tryParse(query.typeDxn)?.asTypeDXN();
|
|
129
|
+
|
|
60
130
|
// SQLite stores booleans as integers, so we need to specify the raw row type.
|
|
61
131
|
const rows =
|
|
62
|
-
|
|
132
|
+
parsedType && parsedType.version === undefined
|
|
133
|
+
? yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE spaceId = ${query.spaceId} AND (typeDxn = ${
|
|
134
|
+
query.typeDxn
|
|
135
|
+
} OR typeDxn LIKE ${_escapeLikePrefix(query.typeDxn)} ESCAPE '\\')`
|
|
136
|
+
: yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE spaceId = ${query.spaceId} AND typeDxn = ${query.typeDxn}`;
|
|
137
|
+
return rows.map((row) => ({
|
|
138
|
+
...row,
|
|
139
|
+
deleted: !!row.deleted,
|
|
140
|
+
}));
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
queryAll = Effect.fn('ObjectMetaIndex.queryAll')(
|
|
145
|
+
(query: {
|
|
146
|
+
spaceIds: readonly ObjectMeta['spaceId'][];
|
|
147
|
+
includeAllQueues?: boolean;
|
|
148
|
+
queueIds?: readonly string[] | null;
|
|
149
|
+
}): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>
|
|
150
|
+
Effect.gen(function* () {
|
|
151
|
+
if (query.spaceIds.length === 0 && (!query.queueIds || query.queueIds.length === 0)) {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const sql = yield* SqlClient.SqlClient;
|
|
156
|
+
const sourceCondition = buildSourceCondition(
|
|
157
|
+
sql,
|
|
158
|
+
query.spaceIds,
|
|
159
|
+
query.includeAllQueues ?? false,
|
|
160
|
+
query.queueIds ?? null,
|
|
161
|
+
);
|
|
162
|
+
const rows = yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition}`;
|
|
163
|
+
return rows.map((row) => ({
|
|
164
|
+
...row,
|
|
165
|
+
deleted: !!row.deleted,
|
|
166
|
+
}));
|
|
167
|
+
}),
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
queryTypes = Effect.fn('ObjectMetaIndex.queryTypes')(
|
|
171
|
+
({
|
|
172
|
+
spaceIds,
|
|
173
|
+
typeDxns,
|
|
174
|
+
inverted = false,
|
|
175
|
+
includeAllQueues = false,
|
|
176
|
+
queueIds = null,
|
|
177
|
+
}: {
|
|
178
|
+
spaceIds: readonly ObjectMeta['spaceId'][];
|
|
179
|
+
typeDxns: readonly ObjectMeta['typeDxn'][];
|
|
180
|
+
inverted?: boolean;
|
|
181
|
+
includeAllQueues?: boolean;
|
|
182
|
+
queueIds?: readonly string[] | null;
|
|
183
|
+
}): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>
|
|
184
|
+
Effect.gen(function* () {
|
|
185
|
+
if (spaceIds.length === 0 && (!queueIds || queueIds.length === 0)) {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (typeDxns.length === 0) {
|
|
190
|
+
if (!inverted) {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const sql = yield* SqlClient.SqlClient;
|
|
195
|
+
const sourceCondition = buildSourceCondition(sql, spaceIds, includeAllQueues, queueIds);
|
|
196
|
+
const rows = yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition}`;
|
|
197
|
+
return rows.map((row) => ({
|
|
198
|
+
...row,
|
|
199
|
+
deleted: !!row.deleted,
|
|
200
|
+
}));
|
|
201
|
+
}
|
|
202
|
+
const sql = yield* SqlClient.SqlClient;
|
|
203
|
+
const sourceCondition = buildSourceCondition(sql, spaceIds, includeAllQueues, queueIds);
|
|
204
|
+
const typeWhere = sql.or(
|
|
205
|
+
typeDxns.map((typeDxn) => {
|
|
206
|
+
const parsedType = DXN.tryParse(typeDxn)?.asTypeDXN();
|
|
207
|
+
return parsedType && parsedType.version === undefined
|
|
208
|
+
? sql.or([sql`typeDxn = ${typeDxn}`, sql`typeDxn LIKE ${_escapeLikePrefix(typeDxn)} ESCAPE '\\'`])
|
|
209
|
+
: sql`typeDxn = ${typeDxn}`;
|
|
210
|
+
}),
|
|
211
|
+
);
|
|
212
|
+
const rows = inverted
|
|
213
|
+
? yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition} AND NOT ${typeWhere}`
|
|
214
|
+
: yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition} AND ${typeWhere}`;
|
|
215
|
+
return rows.map((row) => ({
|
|
216
|
+
...row,
|
|
217
|
+
deleted: !!row.deleted,
|
|
218
|
+
}));
|
|
219
|
+
}),
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
queryRelations = Effect.fn('ObjectMetaIndex.queryRelations')(
|
|
223
|
+
({
|
|
224
|
+
endpoint,
|
|
225
|
+
anchorDxns,
|
|
226
|
+
}: {
|
|
227
|
+
endpoint: 'source' | 'target';
|
|
228
|
+
anchorDxns: readonly string[];
|
|
229
|
+
}): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>
|
|
230
|
+
Effect.gen(function* () {
|
|
231
|
+
if (anchorDxns.length === 0) {
|
|
232
|
+
return [];
|
|
233
|
+
}
|
|
234
|
+
const sql = yield* SqlClient.SqlClient;
|
|
235
|
+
const column = endpoint === 'source' ? 'source' : 'target';
|
|
236
|
+
const rows = yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE entityKind = 'relation' AND ${sql.in(
|
|
237
|
+
column,
|
|
238
|
+
anchorDxns,
|
|
239
|
+
)}`;
|
|
63
240
|
return rows.map((row) => ({
|
|
64
241
|
...row,
|
|
65
242
|
deleted: !!row.deleted,
|
|
@@ -77,10 +254,9 @@ export class ObjectMetaIndex implements Index {
|
|
|
77
254
|
objects,
|
|
78
255
|
(object) =>
|
|
79
256
|
Effect.gen(function* () {
|
|
80
|
-
const { spaceId, queueId, documentId, data } = object;
|
|
257
|
+
const { spaceId, queueId, queueNamespace, documentId, data } = object;
|
|
81
258
|
|
|
82
259
|
// Extract metadata (Logic emulating Echo APIs as strict imports are unavailable).
|
|
83
|
-
// TODO(agent): Verify property access matches Obj.JSON structure.
|
|
84
260
|
const castData = data;
|
|
85
261
|
const objectId = castData.id;
|
|
86
262
|
|
|
@@ -111,27 +287,36 @@ export class ObjectMetaIndex implements Index {
|
|
|
111
287
|
// Relations.
|
|
112
288
|
const source = entityKind === 'relation' ? (castData[ATTR_RELATION_SOURCE] ?? null) : null;
|
|
113
289
|
const target = entityKind === 'relation' ? (castData[ATTR_RELATION_TARGET] ?? null) : null;
|
|
290
|
+
// Parent (nullable).
|
|
291
|
+
const parent = castData[ATTR_PARENT] ?? null;
|
|
292
|
+
|
|
293
|
+
const sourceTimestamp = object.updatedAt;
|
|
114
294
|
|
|
115
295
|
if (existing.length > 0) {
|
|
116
296
|
yield* sql`
|
|
117
297
|
UPDATE objectMeta SET
|
|
118
298
|
version = ${version},
|
|
299
|
+
queueNamespace = ${queueNamespace ?? ''},
|
|
119
300
|
entityKind = ${entityKind},
|
|
120
301
|
typeDxn = ${typeDxn},
|
|
121
302
|
deleted = ${deleted},
|
|
122
303
|
source = ${source},
|
|
123
|
-
target = ${target}
|
|
304
|
+
target = ${target},
|
|
305
|
+
parent = ${parent},
|
|
306
|
+
updatedAt = ${sourceTimestamp}
|
|
124
307
|
WHERE recordId = ${existing[0].recordId}
|
|
125
308
|
`;
|
|
126
309
|
} else {
|
|
127
310
|
yield* sql`
|
|
128
311
|
INSERT INTO objectMeta (
|
|
129
|
-
objectId, queueId, spaceId, documentId,
|
|
130
|
-
entityKind, typeDxn, deleted, source, target, version
|
|
312
|
+
objectId, queueId, queueNamespace, spaceId, documentId,
|
|
313
|
+
entityKind, typeDxn, deleted, source, target, parent, version,
|
|
314
|
+
createdAt, updatedAt
|
|
131
315
|
) VALUES (
|
|
132
|
-
${objectId}, ${queueId ?? ''}, ${spaceId}, ${documentId ?? ''},
|
|
133
|
-
${entityKind}, ${typeDxn}, ${deleted},
|
|
134
|
-
${source}, ${target}, ${version}
|
|
316
|
+
${objectId}, ${queueId ?? ''}, ${queueNamespace ?? ''}, ${spaceId}, ${documentId ?? ''},
|
|
317
|
+
${entityKind}, ${typeDxn}, ${deleted},
|
|
318
|
+
${source}, ${target}, ${parent}, ${version},
|
|
319
|
+
${sourceTimestamp}, ${sourceTimestamp}
|
|
135
320
|
)
|
|
136
321
|
`;
|
|
137
322
|
}
|
|
@@ -169,7 +354,7 @@ export class ObjectMetaIndex implements Index {
|
|
|
169
354
|
|
|
170
355
|
if (result.length === 0) {
|
|
171
356
|
// TODO(mykola): Handle this case gracefully.
|
|
172
|
-
yield* Effect.die(
|
|
357
|
+
return yield* Effect.die(
|
|
173
358
|
new Error(`Object not found in ObjectMetaIndex: ${spaceId}/${documentId ?? queueId}/${objectId}`),
|
|
174
359
|
);
|
|
175
360
|
}
|
|
@@ -189,12 +374,114 @@ export class ObjectMetaIndex implements Index {
|
|
|
189
374
|
}
|
|
190
375
|
|
|
191
376
|
const sql = yield* SqlClient.SqlClient;
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
377
|
+
const rows = yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sql.in('recordId', recordIds)}`;
|
|
378
|
+
|
|
379
|
+
return rows.map((row) => ({
|
|
380
|
+
...row,
|
|
381
|
+
deleted: !!row.deleted,
|
|
382
|
+
}));
|
|
383
|
+
}),
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Look up object metadata by objectId, spaceId, and queueId.
|
|
388
|
+
*/
|
|
389
|
+
lookupByObjectId = Effect.fn('ObjectMetaIndex.lookupByObjectId')(
|
|
390
|
+
(query: {
|
|
391
|
+
objectId: string;
|
|
392
|
+
spaceId: string;
|
|
393
|
+
queueId: string;
|
|
394
|
+
}): Effect.Effect<ObjectMeta | null, SqlError.SqlError, SqlClient.SqlClient> =>
|
|
395
|
+
Effect.gen(function* () {
|
|
396
|
+
const sql = yield* SqlClient.SqlClient;
|
|
397
|
+
const rows =
|
|
398
|
+
yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE spaceId = ${query.spaceId} AND queueId = ${query.queueId} AND objectId = ${query.objectId} LIMIT 1`;
|
|
399
|
+
|
|
400
|
+
if (rows.length === 0) {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
...rows[0],
|
|
406
|
+
deleted: !!rows[0].deleted,
|
|
407
|
+
};
|
|
408
|
+
}),
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Query objects by timestamp range.
|
|
413
|
+
*/
|
|
414
|
+
queryByTimeRange = Effect.fn('ObjectMetaIndex.queryByTimeRange')(
|
|
415
|
+
(query: {
|
|
416
|
+
spaceIds: readonly string[];
|
|
417
|
+
updatedAfter?: number;
|
|
418
|
+
updatedBefore?: number;
|
|
419
|
+
createdAfter?: number;
|
|
420
|
+
createdBefore?: number;
|
|
421
|
+
includeAllQueues?: boolean;
|
|
422
|
+
queueIds?: readonly string[] | null;
|
|
423
|
+
}): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>
|
|
424
|
+
Effect.gen(function* () {
|
|
425
|
+
if (query.spaceIds.length === 0 && (!query.queueIds || query.queueIds.length === 0)) {
|
|
426
|
+
return [];
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const sql = yield* SqlClient.SqlClient;
|
|
430
|
+
const sourceCondition = buildSourceCondition(
|
|
431
|
+
sql,
|
|
432
|
+
query.spaceIds,
|
|
433
|
+
query.includeAllQueues ?? false,
|
|
434
|
+
query.queueIds ?? null,
|
|
196
435
|
);
|
|
197
436
|
|
|
437
|
+
const timeConditions: Statement.Fragment[] = [];
|
|
438
|
+
if (query.updatedAfter != null) {
|
|
439
|
+
timeConditions.push(sql`updatedAt >= ${query.updatedAfter}`);
|
|
440
|
+
}
|
|
441
|
+
if (query.updatedBefore != null) {
|
|
442
|
+
timeConditions.push(sql`updatedAt <= ${query.updatedBefore}`);
|
|
443
|
+
}
|
|
444
|
+
if (query.createdAfter != null) {
|
|
445
|
+
timeConditions.push(sql`createdAt >= ${query.createdAfter}`);
|
|
446
|
+
}
|
|
447
|
+
if (query.createdBefore != null) {
|
|
448
|
+
timeConditions.push(sql`createdAt <= ${query.createdBefore}`);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const rows =
|
|
452
|
+
timeConditions.length > 0
|
|
453
|
+
? yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition} AND ${sql.and(timeConditions)}`
|
|
454
|
+
: yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition}`;
|
|
455
|
+
|
|
456
|
+
return rows.map((row) => ({
|
|
457
|
+
...row,
|
|
458
|
+
deleted: !!row.deleted,
|
|
459
|
+
}));
|
|
460
|
+
}),
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Query children by parent object ids.
|
|
465
|
+
* Matches both:
|
|
466
|
+
* - Objects whose `parent` field references one of the given parent ids (standard parent/child hierarchy).
|
|
467
|
+
* - Queue items whose `queueId` equals one of the parent ids (e.g. items inside a Feed, since a feed's queue
|
|
468
|
+
* DXN uses the feed's object id as its queue id — see `Feed.getQueueDxn`).
|
|
469
|
+
*/
|
|
470
|
+
queryChildren = Effect.fn('ObjectMetaIndex.queryChildren')(
|
|
471
|
+
(query: {
|
|
472
|
+
spaceId: SpaceId[];
|
|
473
|
+
parentIds: ObjectId[];
|
|
474
|
+
}): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>
|
|
475
|
+
Effect.gen(function* () {
|
|
476
|
+
if (query.parentIds.length === 0) {
|
|
477
|
+
return [];
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const sql = yield* SqlClient.SqlClient;
|
|
481
|
+
const parentDxns = query.parentIds.map((id) => DXN.fromLocalObjectId(id).toString());
|
|
482
|
+
const rows =
|
|
483
|
+
yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sql.in('spaceId', query.spaceId)} AND (${sql.in('parent', parentDxns)} OR ${sql.in('queueId', query.parentIds)})`;
|
|
484
|
+
|
|
198
485
|
return rows.map((row) => ({
|
|
199
486
|
...row,
|
|
200
487
|
deleted: !!row.deleted,
|
|
@@ -14,8 +14,8 @@ import { DXN, ObjectId, SpaceId } from '@dxos/keys';
|
|
|
14
14
|
import type { IndexerObject } from './interface';
|
|
15
15
|
import { ReverseRefIndex } from './reverse-ref-index';
|
|
16
16
|
|
|
17
|
-
const TYPE_PERSON = DXN.parse('dxn:type:example.
|
|
18
|
-
const TYPE_EXAMPLE = DXN.parse('dxn:type:example.
|
|
17
|
+
const TYPE_PERSON = DXN.parse('dxn:type:com.example.type.person:0.1.0').toString();
|
|
18
|
+
const TYPE_EXAMPLE = DXN.parse('dxn:type:com.example.type.example:0.1.0').toString();
|
|
19
19
|
|
|
20
20
|
const TestLayer = Layer.merge(
|
|
21
21
|
SqliteClient.layer({
|
|
@@ -38,8 +38,10 @@ describe('ReverseRefIndex', () => {
|
|
|
38
38
|
const sourceObject: IndexerObject = {
|
|
39
39
|
spaceId,
|
|
40
40
|
queueId: ObjectId.random(),
|
|
41
|
+
queueNamespace: 'data',
|
|
41
42
|
documentId: null,
|
|
42
43
|
recordId: 1,
|
|
44
|
+
updatedAt: Date.now(),
|
|
43
45
|
data: {
|
|
44
46
|
id: sourceObjectId,
|
|
45
47
|
[ATTR_TYPE]: TYPE_PERSON,
|
|
@@ -71,8 +73,10 @@ describe('ReverseRefIndex', () => {
|
|
|
71
73
|
const sourceObject: IndexerObject = {
|
|
72
74
|
spaceId,
|
|
73
75
|
queueId: ObjectId.random(),
|
|
76
|
+
queueNamespace: 'data',
|
|
74
77
|
documentId: null,
|
|
75
78
|
recordId: 1,
|
|
79
|
+
updatedAt: Date.now(),
|
|
76
80
|
data: {
|
|
77
81
|
id: sourceObjectId,
|
|
78
82
|
[ATTR_TYPE]: TYPE_EXAMPLE,
|
|
@@ -112,8 +116,10 @@ describe('ReverseRefIndex', () => {
|
|
|
112
116
|
const sourceObject: IndexerObject = {
|
|
113
117
|
spaceId,
|
|
114
118
|
queueId: ObjectId.random(),
|
|
119
|
+
queueNamespace: 'data',
|
|
115
120
|
documentId: null,
|
|
116
121
|
recordId: 1,
|
|
122
|
+
updatedAt: Date.now(),
|
|
117
123
|
data: {
|
|
118
124
|
id: sourceObjectId,
|
|
119
125
|
[ATTR_TYPE]: TYPE_EXAMPLE,
|
|
@@ -151,8 +157,10 @@ describe('ReverseRefIndex', () => {
|
|
|
151
157
|
const sourceObject: IndexerObject = {
|
|
152
158
|
spaceId,
|
|
153
159
|
queueId,
|
|
160
|
+
queueNamespace: 'data',
|
|
154
161
|
documentId: null,
|
|
155
162
|
recordId,
|
|
163
|
+
updatedAt: Date.now(),
|
|
156
164
|
data: {
|
|
157
165
|
id: sourceObjectId,
|
|
158
166
|
[ATTR_TYPE]: TYPE_EXAMPLE,
|
|
@@ -169,8 +177,10 @@ describe('ReverseRefIndex', () => {
|
|
|
169
177
|
const updatedObject: IndexerObject = {
|
|
170
178
|
spaceId,
|
|
171
179
|
queueId,
|
|
180
|
+
queueNamespace: 'data',
|
|
172
181
|
documentId: null,
|
|
173
182
|
recordId,
|
|
183
|
+
updatedAt: Date.now(),
|
|
174
184
|
data: {
|
|
175
185
|
id: sourceObjectId,
|
|
176
186
|
[ATTR_TYPE]: TYPE_EXAMPLE,
|
|
@@ -201,8 +211,10 @@ describe('ReverseRefIndex', () => {
|
|
|
201
211
|
const sourceObject: IndexerObject = {
|
|
202
212
|
spaceId,
|
|
203
213
|
queueId: ObjectId.random(),
|
|
214
|
+
queueNamespace: 'data',
|
|
204
215
|
documentId: null,
|
|
205
216
|
recordId: 1,
|
|
217
|
+
updatedAt: Date.now(),
|
|
206
218
|
data: {
|
|
207
219
|
id: sourceObjectId,
|
|
208
220
|
[ATTR_TYPE]: TYPE_EXAMPLE,
|
|
@@ -232,8 +244,10 @@ describe('ReverseRefIndex', () => {
|
|
|
232
244
|
const sourceObject: IndexerObject = {
|
|
233
245
|
spaceId,
|
|
234
246
|
queueId: null,
|
|
247
|
+
queueNamespace: null,
|
|
235
248
|
documentId: 'doc-123',
|
|
236
249
|
recordId: 1,
|
|
250
|
+
updatedAt: Date.now(),
|
|
237
251
|
data: {
|
|
238
252
|
id: sourceObjectId,
|
|
239
253
|
[ATTR_TYPE]: TYPE_EXAMPLE,
|
package/src/utils.ts
CHANGED