@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.
Files changed (42) hide show
  1. package/dist/lib/neutral/index.mjs +658 -0
  2. package/dist/lib/neutral/index.mjs.map +7 -0
  3. package/dist/lib/neutral/meta.json +1 -0
  4. package/dist/types/src/index-engine.d.ts +83 -0
  5. package/dist/types/src/index-engine.d.ts.map +1 -0
  6. package/dist/types/src/index-engine.test.d.ts +2 -0
  7. package/dist/types/src/index-engine.test.d.ts.map +1 -0
  8. package/dist/types/src/index-tracker.d.ts +44 -0
  9. package/dist/types/src/index-tracker.d.ts.map +1 -0
  10. package/dist/types/src/index-tracker.test.d.ts +2 -0
  11. package/dist/types/src/index-tracker.test.d.ts.map +1 -0
  12. package/dist/types/src/index.d.ts +8 -0
  13. package/dist/types/src/index.d.ts.map +1 -0
  14. package/dist/types/src/indexes/fts-index.d.ts +63 -0
  15. package/dist/types/src/indexes/fts-index.d.ts.map +1 -0
  16. package/dist/types/src/indexes/fts-index.test.d.ts +2 -0
  17. package/dist/types/src/indexes/fts-index.test.d.ts.map +1 -0
  18. package/dist/types/src/indexes/fts5.test.d.ts +2 -0
  19. package/dist/types/src/indexes/fts5.test.d.ts.map +1 -0
  20. package/dist/types/src/indexes/index.d.ts +5 -0
  21. package/dist/types/src/indexes/index.d.ts.map +1 -0
  22. package/dist/types/src/indexes/interface.d.ts +47 -0
  23. package/dist/types/src/indexes/interface.d.ts.map +1 -0
  24. package/dist/types/src/indexes/object-meta-index.d.ts +60 -0
  25. package/dist/types/src/indexes/object-meta-index.d.ts.map +1 -0
  26. package/dist/types/src/indexes/object-meta-index.test.d.ts +2 -0
  27. package/dist/types/src/indexes/object-meta-index.test.d.ts.map +1 -0
  28. package/dist/types/src/indexes/reverse-ref-index.d.ts +37 -0
  29. package/dist/types/src/indexes/reverse-ref-index.d.ts.map +1 -0
  30. package/dist/types/src/indexes/reverse-ref-index.test.d.ts +2 -0
  31. package/dist/types/src/indexes/reverse-ref-index.test.d.ts.map +1 -0
  32. package/dist/types/src/utils.d.ts +17 -0
  33. package/dist/types/src/utils.d.ts.map +1 -0
  34. package/dist/types/tsconfig.tsbuildinfo +1 -0
  35. package/package.json +22 -18
  36. package/src/index-engine.test.ts +8 -5
  37. package/src/index-engine.ts +53 -9
  38. package/src/index.ts +3 -2
  39. package/src/indexes/fts-index.ts +41 -2
  40. package/src/indexes/object-meta-index.test.ts +179 -0
  41. package/src/indexes/object-meta-index.ts +148 -13
  42. 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.queryType')(
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
- yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE spaceId = ${query.spaceId} AND typeDxn = ${query.typeDxn}`;
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 placeholders = recordIds.map(() => '?').join(', ');
193
- const rows = yield* sql.unsafe<ObjectMeta>(
194
- `SELECT * FROM objectMeta WHERE recordId IN (${placeholders})`,
195
- recordIds,
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
@@ -6,7 +6,7 @@ import * as Schema from 'effect/Schema';
6
6
 
7
7
  import { invariant } from '@dxos/invariant';
8
8
 
9
- export type ObjectPropPath = (string | number)[];
9
+ export type ObjectPropPath = string[];
10
10
 
11
11
  /**
12
12
  * Escaped property path within an object.