@dxos/index-core 0.8.4-main.03d5cd7b56 → 0.8.4-main.05e74ebcff

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 CHANGED
@@ -1,8 +1,105 @@
1
- MIT License
2
- Copyright (c) 2022 DXOS
1
+ # Functional Source License, Version 1.1, ALv2 Future License
3
2
 
4
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
3
+ ## Abbreviation
5
4
 
6
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
5
+ FSL-1.1-Apache-2.0
7
6
 
8
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
7
+ ## Notice
8
+
9
+ Copyright 2026 DXOS
10
+
11
+ ## Terms and Conditions
12
+
13
+ ### Licensor ("We")
14
+
15
+ The party offering the Software under these Terms and Conditions.
16
+
17
+ ### The Software
18
+
19
+ The "Software" is each version of the software that we make available under
20
+ these Terms and Conditions, as indicated by our inclusion of these Terms and
21
+ Conditions with the Software.
22
+
23
+ ### License Grant
24
+
25
+ Subject to your compliance with this License Grant and the Patents,
26
+ Redistribution and Trademark clauses below, we hereby grant you the right to
27
+ use, copy, modify, create derivative works, publicly perform, publicly display
28
+ and redistribute the Software for any Permitted Purpose identified below.
29
+
30
+ ### Permitted Purpose
31
+
32
+ A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
33
+ means making the Software available to others in a commercial product or
34
+ service that:
35
+
36
+ 1. substitutes for the Software;
37
+
38
+ 2. substitutes for any other product or service we offer using the Software
39
+ that exists as of the date we make the Software available; or
40
+
41
+ 3. offers the same or substantially similar functionality as the Software.
42
+
43
+ Permitted Purposes specifically include using the Software:
44
+
45
+ 1. for your internal use and access;
46
+
47
+ 2. for non-commercial education;
48
+
49
+ 3. for non-commercial research; and
50
+
51
+ 4. in connection with professional services that you provide to a licensee
52
+ using the Software in accordance with these Terms and Conditions.
53
+
54
+ ### Patents
55
+
56
+ To the extent your use for a Permitted Purpose would necessarily infringe our
57
+ patents, the license grant above includes a license under our patents. If you
58
+ make a claim against any party that the Software infringes or contributes to
59
+ the infringement of any patent, then your patent license to the Software ends
60
+ immediately.
61
+
62
+ ### Redistribution
63
+
64
+ The Terms and Conditions apply to all copies, modifications and derivatives of
65
+ the Software.
66
+
67
+ If you redistribute any copies, modifications or derivatives of the Software,
68
+ you must include a copy of or a link to these Terms and Conditions and not
69
+ remove any copyright notices provided in or with the Software.
70
+
71
+ ### Disclaimer
72
+
73
+ THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
74
+ IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
75
+ PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
76
+
77
+ IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
78
+ SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
79
+ EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
80
+
81
+ ### Trademarks
82
+
83
+ Except for displaying the License Details and identifying us as the origin of
84
+ the Software, you have no right under these Terms and Conditions to use our
85
+ trademarks, trade names, service marks or product names.
86
+
87
+ ## Grant of Future License
88
+
89
+ We hereby irrevocably grant you an additional license to use the Software under
90
+ the Apache License, Version 2.0 that is effective on the second anniversary of
91
+ the date we make the Software available. On or after that date, you may use the
92
+ Software under the Apache License, Version 2.0, in which case the following
93
+ will apply:
94
+
95
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
96
+ this file except in compliance with the License.
97
+
98
+ You may obtain a copy of the License at
99
+
100
+ http://www.apache.org/licenses/LICENSE-2.0
101
+
102
+ Unless required by applicable law or agreed to in writing, software distributed
103
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
104
+ CONDITIONS OF ANY KIND, either express or implied. See the License for the
105
+ specific language governing permissions and limitations under the License.
package/README.md CHANGED
@@ -18,4 +18,4 @@ pnpm i @dxos/index-core
18
18
 
19
19
  Your ideas, issues, and code are most welcome. Please take a look at our [community code of conduct](https://github.com/dxos/dxos/blob/main/CODE_OF_CONDUCT.md), the [issue guide](https://github.com/dxos/dxos/blob/main/CONTRIBUTING.md#submitting-issues), and the [PR contribution guide](https://github.com/dxos/dxos/blob/main/CONTRIBUTING.md#submitting-prs).
20
20
 
21
- License: [MIT](./LICENSE) Copyright 2022 © DXOS
21
+ License: [FSL-1.1-Apache-2.0](./LICENSE) Copyright 2022 © DXOS
@@ -215,7 +215,7 @@ var ObjectMeta = Schema2.Struct({
215
215
  documentId: Schema2.String,
216
216
  entityKind: Schema2.String,
217
217
  /** The versioned DXN of the type of the object. */
218
- typeDxn: Schema2.String,
218
+ typeDXN: Schema2.String,
219
219
  deleted: Schema2.Boolean,
220
220
  source: Schema2.NullOr(Schema2.String),
221
221
  target: Schema2.NullOr(Schema2.String),
@@ -256,7 +256,7 @@ var ObjectMetaIndex = class {
256
256
  spaceId TEXT NOT NULL,
257
257
  documentId TEXT NOT NULL DEFAULT '',
258
258
  entityKind TEXT NOT NULL,
259
- typeDxn TEXT NOT NULL,
259
+ typeDXN TEXT NOT NULL,
260
260
  deleted INTEGER NOT NULL,
261
261
  source TEXT,
262
262
  target TEXT,
@@ -270,7 +270,7 @@ var ObjectMetaIndex = class {
270
270
  yield* Effect3.catchAll(sql`ALTER TABLE objectMeta ADD COLUMN updatedAt INTEGER`, () => Effect3.void);
271
271
  yield* Effect3.catchAll(sql`ALTER TABLE objectMeta ADD COLUMN queueNamespace TEXT NOT NULL DEFAULT ''`, () => Effect3.void);
272
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)`;
273
+ yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_typeDXN ON objectMeta(spaceId, typeDXN)`;
274
274
  yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_version ON objectMeta(version)`;
275
275
  yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_parent ON objectMeta(spaceId, parent)`;
276
276
  yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_updatedAt ON objectMeta(updatedAt)`;
@@ -278,8 +278,8 @@ var ObjectMetaIndex = class {
278
278
  });
279
279
  query = Effect3.fn("ObjectMetaIndex.query")((query) => Effect3.gen(function* () {
280
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}`;
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
283
  return rows.map((row) => ({
284
284
  ...row,
285
285
  deleted: !!row.deleted
@@ -315,12 +315,12 @@ var ObjectMetaIndex = class {
315
315
  }
316
316
  const sql = yield* SqlClient5.SqlClient;
317
317
  const sourceCondition = buildSourceCondition(sql, spaceIds, includeAllQueues, queueIds);
318
- const typeWhere = sql.or(typeDxns.map((typeDxn) => {
319
- const parsedType = DXN.tryParse(typeDxn)?.asTypeDXN();
318
+ const typeWhere = sql.or(typeDxns.map((typeDXN) => {
319
+ const parsedType = DXN.tryParse(typeDXN)?.asTypeDXN();
320
320
  return parsedType && parsedType.version === void 0 ? sql.or([
321
- sql`typeDxn = ${typeDxn}`,
322
- sql`typeDxn LIKE ${_escapeLikePrefix(typeDxn)} ESCAPE '\\'`
323
- ]) : sql`typeDxn = ${typeDxn}`;
321
+ sql`typeDXN = ${typeDXN}`,
322
+ sql`typeDXN LIKE ${_escapeLikePrefix(typeDXN)} ESCAPE '\\'`
323
+ ]) : sql`typeDXN = ${typeDXN}`;
324
324
  }));
325
325
  const rows = inverted ? yield* sql`SELECT * FROM objectMeta WHERE ${sourceCondition} AND NOT ${typeWhere}` : yield* sql`SELECT * FROM objectMeta WHERE ${sourceCondition} AND ${typeWhere}`;
326
326
  return rows.map((row) => ({
@@ -359,7 +359,7 @@ var ObjectMetaIndex = class {
359
359
  const [{ v }] = result;
360
360
  const version = (v ?? 0) + 1;
361
361
  const entityKind = castData[ATTR_RELATION_SOURCE] ? "relation" : "object";
362
- const typeDxn = castData[ATTR_TYPE] ? String(castData[ATTR_TYPE]) : "type";
362
+ const typeDXN = castData[ATTR_TYPE] ? String(castData[ATTR_TYPE]) : "type";
363
363
  const deleted = castData[ATTR_DELETED] ? 1 : 0;
364
364
  const source = entityKind === "relation" ? castData[ATTR_RELATION_SOURCE] ?? null : null;
365
365
  const target = entityKind === "relation" ? castData[ATTR_RELATION_TARGET] ?? null : null;
@@ -371,7 +371,7 @@ var ObjectMetaIndex = class {
371
371
  version = ${version},
372
372
  queueNamespace = ${queueNamespace ?? ""},
373
373
  entityKind = ${entityKind},
374
- typeDxn = ${typeDxn},
374
+ typeDXN = ${typeDXN},
375
375
  deleted = ${deleted},
376
376
  source = ${source},
377
377
  target = ${target},
@@ -383,11 +383,11 @@ var ObjectMetaIndex = class {
383
383
  yield* sql`
384
384
  INSERT INTO objectMeta (
385
385
  objectId, queueId, queueNamespace, spaceId, documentId,
386
- entityKind, typeDxn, deleted, source, target, parent, version,
386
+ entityKind, typeDXN, deleted, source, target, parent, version,
387
387
  createdAt, updatedAt
388
388
  ) VALUES (
389
389
  ${objectId}, ${queueId ?? ""}, ${queueNamespace ?? ""}, ${spaceId}, ${documentId ?? ""},
390
- ${entityKind}, ${typeDxn}, ${deleted},
390
+ ${entityKind}, ${typeDXN}, ${deleted},
391
391
  ${source}, ${target}, ${parent}, ${version},
392
392
  ${sourceTimestamp}, ${sourceTimestamp}
393
393
  )
@@ -545,7 +545,7 @@ var extractReferences = (data) => {
545
545
  }
546
546
  refs.push({
547
547
  path,
548
- targetDxn: dxn.toString()
548
+ targetDXN: dxn.toString()
549
549
  });
550
550
  } else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
551
551
  for (const [key, v] of Object.entries(value)) {
@@ -568,7 +568,7 @@ var extractReferences = (data) => {
568
568
  };
569
569
  var ReverseRef = Schema4.Struct({
570
570
  recordId: Schema4.Number,
571
- targetDxn: Schema4.String,
571
+ targetDXN: Schema4.String,
572
572
  /**
573
573
  * Escaped property path within an object.
574
574
  *
@@ -585,18 +585,18 @@ var ReverseRefIndex = class {
585
585
  const sql = yield* SqlClient7.SqlClient;
586
586
  yield* sql`CREATE TABLE IF NOT EXISTS reverseRef (
587
587
  recordId INTEGER NOT NULL,
588
- targetDxn TEXT NOT NULL,
588
+ targetDXN TEXT NOT NULL,
589
589
  propPath TEXT NOT NULL,
590
- PRIMARY KEY (recordId, targetDxn, propPath)
590
+ PRIMARY KEY (recordId, targetDXN, propPath)
591
591
  )`;
592
- yield* sql`CREATE INDEX IF NOT EXISTS idx_reverse_ref_target ON reverseRef(targetDxn)`;
592
+ yield* sql`CREATE INDEX IF NOT EXISTS idx_reverse_ref_target ON reverseRef(targetDXN)`;
593
593
  });
594
594
  /**
595
595
  * Query all references pointing to a target DXN.
596
596
  */
597
- query = Effect4.fn("ReverseRefIndex.query")(({ targetDxn }) => Effect4.gen(function* () {
597
+ query = Effect4.fn("ReverseRefIndex.query")(({ targetDXN }) => Effect4.gen(function* () {
598
598
  const sql = yield* SqlClient7.SqlClient;
599
- const rows = yield* sql`SELECT * FROM reverseRef WHERE targetDxn = ${targetDxn}`;
599
+ const rows = yield* sql`SELECT * FROM reverseRef WHERE targetDXN = ${targetDXN}`;
600
600
  return rows;
601
601
  }));
602
602
  update = Effect4.fn("ReverseRefIndex.update")((objects) => Effect4.gen(function* () {
@@ -608,7 +608,7 @@ var ReverseRefIndex = class {
608
608
  }
609
609
  yield* sql`DELETE FROM reverseRef WHERE recordId = ${recordId}`;
610
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)})`, {
611
+ yield* Effect4.forEach(refs, (ref) => sql`INSERT INTO reverseRef (recordId, targetDXN, propPath) VALUES (${recordId}, ${ref.targetDXN}, ${EscapedPropPath.escape(ref.path)})`, {
612
612
  discard: true
613
613
  });
614
614
  }), {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/index-engine.ts", "../../../src/index-tracker.ts", "../../../src/indexes/fts-index.ts", "../../../src/indexes/object-meta-index.ts", "../../../src/indexes/reverse-ref-index.ts", "../../../src/utils.ts"],
4
- "sourcesContent": ["//\n// Copyright 2026 DXOS.org\n//\n\nimport type * as SqlClient from '@effect/sql/SqlClient';\nimport type * as SqlError from '@effect/sql/SqlError';\nimport * as Effect from 'effect/Effect';\n\nimport { type Context } from '@dxos/context';\nimport { ATTR_TYPE } from '@dxos/echo/internal';\nimport type { ObjectId, SpaceId } from '@dxos/keys';\nimport * as SqlTransaction from '@dxos/sql-sqlite/SqlTransaction';\n\nimport { type IndexCursor, IndexTracker } from './index-tracker';\nimport {\n FtsIndex,\n type FtsQuery,\n type FtsQueryResult,\n type Index,\n type IndexerObject,\n type ObjectMeta,\n ObjectMetaIndex,\n ReverseRefIndex,\n type ReverseRefQuery,\n} from './indexes';\n\n/**\n * Result of a single indexing pass over a data source.\n * Carries enough metadata for callers to build targeted invalidation hints.\n */\nexport type IndexingResult = {\n updated: number;\n done: boolean;\n spaces: ReadonlySet<SpaceId>;\n queues: ReadonlySet<ObjectId>;\n documents: ReadonlySet<string>;\n types: ReadonlySet<string>;\n objects: ReadonlySet<ObjectId>;\n};\n\ntype MutableIndexingResult = {\n updated: number;\n done: boolean;\n spaces: Set<SpaceId>;\n queues: Set<ObjectId>;\n documents: Set<string>;\n types: Set<string>;\n objects: Set<ObjectId>;\n};\n\nconst makeEmptyIndexingResult = (): MutableIndexingResult => ({\n updated: 0,\n done: true,\n spaces: new Set(),\n queues: new Set(),\n documents: new Set(),\n types: new Set(),\n objects: new Set(),\n});\n\nconst accumulateIndexingResult = (acc: MutableIndexingResult, objects: readonly IndexerObject[]) => {\n for (const obj of objects) {\n acc.spaces.add(obj.spaceId);\n if (obj.queueId) {\n acc.queues.add(obj.queueId);\n }\n if (obj.documentId) {\n acc.documents.add(obj.documentId);\n }\n const t = (obj.data as Record<string, unknown>)[ATTR_TYPE];\n if (t) {\n acc.types.add(String(t));\n }\n if (obj.data.id) {\n acc.objects.add(obj.data.id as ObjectId);\n }\n }\n};\n\n/**\n * Cursor into indexable data-source.\n */\nexport interface DataSourceCursor {\n spaceId: SpaceId | null;\n\n /**\n * documentId or queueNamespace.\n */\n resourceId: string | null;\n\n /**\n * heads or queue position.\n */\n cursor: number | string;\n}\n\nexport interface IndexDataSource {\n readonly sourceName: string; // e.g. queue, automerge, etc.\n\n getChangedObjects(\n ctx: Context,\n cursors: DataSourceCursor[],\n opts?: { limit?: number },\n ): Effect.Effect<{ objects: IndexerObject[]; cursors: DataSourceCursor[] }>;\n}\n\nexport interface IndexEngineParams {\n tracker: IndexTracker;\n objectMetaIndex: ObjectMetaIndex;\n ftsIndex: FtsIndex;\n reverseRefIndex: ReverseRefIndex;\n}\n\nexport class IndexEngine {\n readonly #tracker: IndexTracker;\n readonly #objectMetaIndex: ObjectMetaIndex;\n readonly #ftsIndex: FtsIndex;\n readonly #reverseRefIndex: ReverseRefIndex;\n\n constructor(params?: IndexEngineParams) {\n this.#tracker = params?.tracker ?? new IndexTracker();\n this.#objectMetaIndex = params?.objectMetaIndex ?? new ObjectMetaIndex();\n this.#ftsIndex = params?.ftsIndex ?? new FtsIndex();\n this.#reverseRefIndex = params?.reverseRefIndex ?? new ReverseRefIndex();\n }\n\n migrate() {\n return Effect.gen(this, function* () {\n yield* this.#tracker.migrate();\n yield* this.#objectMetaIndex.migrate();\n yield* this.#ftsIndex.migrate();\n yield* this.#reverseRefIndex.migrate();\n });\n }\n\n /**\n * Query text index and return full object metadata with rank.\n */\n queryText(query: FtsQuery): Effect.Effect<readonly FtsQueryResult[], SqlError.SqlError, SqlClient.SqlClient> {\n return Effect.gen(this, function* () {\n return yield* this.#ftsIndex.query(query);\n });\n }\n\n queryReverseRef(query: ReverseRefQuery) {\n // TODO(mykola): Join with metadata table here.\n return this.#reverseRefIndex.query(query);\n }\n\n queryAll(query: {\n spaceIds: readonly SpaceId[];\n includeAllQueues?: boolean;\n queueIds?: readonly string[] | null;\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {\n return this.#objectMetaIndex.queryAll(query);\n }\n\n /**\n * Query snapshots by recordIds.\n * Used to load queue objects from indexed snapshots.\n */\n querySnapshotsJSON(recordIds: number[]) {\n return this.#ftsIndex.querySnapshotsJSON(recordIds);\n }\n\n queryType(\n query: Pick<ObjectMeta, 'spaceId' | 'typeDxn'>,\n ): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {\n return this.#objectMetaIndex.query(query);\n }\n\n /**\n * Query children by parent object ids.\n */\n queryChildren(query: {\n spaceId: SpaceId[];\n parentIds: ObjectId[];\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {\n return this.#objectMetaIndex.queryChildren(query);\n }\n\n queryTypes(query: {\n spaceIds: readonly SpaceId[];\n typeDxns: readonly ObjectMeta['typeDxn'][];\n inverted?: boolean;\n includeAllQueues?: boolean;\n queueIds?: readonly string[] | null;\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {\n return this.#objectMetaIndex.queryTypes(query);\n }\n queryByTimeRange(query: {\n spaceIds: readonly string[];\n updatedAfter?: number;\n updatedBefore?: number;\n createdAfter?: number;\n createdBefore?: number;\n includeAllQueues?: boolean;\n queueIds?: readonly string[] | null;\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {\n return this.#objectMetaIndex.queryByTimeRange(query);\n }\n\n queryRelations(query: {\n endpoint: 'source' | 'target';\n anchorDxns: readonly string[];\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {\n return this.#objectMetaIndex.queryRelations(query);\n }\n lookupByRecordIds(recordIds: number[]): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {\n return this.#objectMetaIndex.lookupByRecordIds(recordIds);\n }\n\n lookupByObjectId(query: {\n objectId: string;\n spaceId: string;\n queueId: string;\n }): Effect.Effect<ObjectMeta | null, SqlError.SqlError, SqlClient.SqlClient> {\n return this.#objectMetaIndex.lookupByObjectId(query);\n }\n\n update(\n ctx: Context,\n dataSource: IndexDataSource,\n opts: { spaceId: SpaceId | null; limit?: number },\n ): Effect.Effect<IndexingResult, SqlError.SqlError, SqlTransaction.SqlTransaction | SqlClient.SqlClient> {\n return Effect.gen(this, function* () {\n const result = makeEmptyIndexingResult();\n\n const {\n updated: updatedFtsIndex,\n done: doneFtsIndex,\n objects: ftsObjects,\n } = yield* this.#update(ctx, this.#ftsIndex, dataSource, {\n indexName: 'fts5',\n spaceId: opts.spaceId,\n limit: opts.limit,\n });\n result.updated += updatedFtsIndex;\n result.done = result.done && doneFtsIndex;\n accumulateIndexingResult(result, ftsObjects);\n\n const {\n updated: updatedReverseRefIndex,\n done: doneReverseRefIndex,\n objects: reverseRefObjects,\n } = yield* this.#update(ctx, this.#reverseRefIndex, dataSource, {\n indexName: 'reverseRef',\n spaceId: opts.spaceId,\n limit: opts.limit,\n });\n result.updated += updatedReverseRefIndex;\n result.done = result.done && doneReverseRefIndex;\n accumulateIndexingResult(result, reverseRefObjects);\n\n return result as IndexingResult;\n }).pipe(Effect.withSpan('IndexEngine.update'));\n }\n\n /**\n * Update a dependent index that requires recordId enrichment.\n * This method:\n * 1. Gets changed objects from the source.\n * 2. Ensures those objects exist in ObjectMetaIndex.\n * 3. Looks up recordIds for those objects.\n * 4. Enriches objects with recordIds.\n * 5. Updates the dependent index.\n */\n #update(\n ctx: Context,\n index: Index,\n source: IndexDataSource,\n opts: { indexName: string; spaceId: SpaceId | null; limit?: number },\n ): Effect.Effect<\n { updated: number; done: boolean; objects: readonly IndexerObject[] },\n SqlError.SqlError,\n SqlTransaction.SqlTransaction | SqlClient.SqlClient\n > {\n return Effect.gen(this, function* () {\n const sqlTransaction = yield* SqlTransaction.SqlTransaction;\n\n return yield* sqlTransaction.withTransaction(\n Effect.gen(this, function* () {\n const cursors = yield* this.#tracker.queryCursors({\n indexName: opts.indexName,\n sourceName: source.sourceName,\n // Pass undefined to get all cursors when spaceId is null.\n spaceId: opts.spaceId ?? undefined,\n });\n const { objects, cursors: updatedCursors } = yield* source.getChangedObjects(ctx, cursors, {\n limit: opts.limit,\n });\n if (objects.length === 0) {\n return { updated: 0, done: true, objects: [] as readonly IndexerObject[] };\n }\n\n // Ensure objects exist in ObjectMetaIndex.\n yield* this.#objectMetaIndex.update(objects);\n\n // Look up recordIds for the objects.\n yield* this.#objectMetaIndex.lookupRecordIds(objects);\n\n yield* index.update(objects);\n yield* this.#tracker.updateCursors(\n updatedCursors.map(\n (_): IndexCursor => ({\n indexName: opts.indexName,\n spaceId: _.spaceId,\n sourceName: source.sourceName,\n resourceId: _.resourceId,\n cursor: _.cursor,\n }),\n ),\n );\n return { updated: objects.length, done: false, objects };\n }),\n );\n }).pipe(Effect.withSpan('IndexEngine.#update'));\n }\n}\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as SqlClient from '@effect/sql/SqlClient';\nimport type * as SqlError from '@effect/sql/SqlError';\nimport * as Effect from 'effect/Effect';\nimport * as Schema from 'effect/Schema';\n\nimport { SpaceId } from '@dxos/keys';\n\nexport const IndexCursor = Schema.Struct({\n /**\n * Name of the index owning this cursor.\n */\n indexName: Schema.String,\n /**\n * Space id.\n */\n spaceId: Schema.NullOr(SpaceId),\n /**\n * Source name.\n * 'automerge' / 'queue' / 'index' (for secondary indexes)\n */\n sourceName: Schema.String,\n /**\n * Document id or queue id.\n * doc_id, queue_id, '' <empty string> (if indexing entire namespace)\n */\n resourceId: Schema.NullOr(Schema.String),\n /**\n * Heads, queue position, version.\n */\n cursor: Schema.Union(Schema.Number, Schema.String),\n});\nexport interface IndexCursor extends Schema.Schema.Type<typeof IndexCursor> {}\n\n/**\n * Deprecated index names that are no longer used. Will be cleaned up on migration.\n */\nconst DEPRECATED_INDEX_NAMES = ['fts'];\n\nexport class IndexTracker {\n migrate = Effect.fn('IndexTracker.migrate')(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n // For automerge: last-indexed heads of the document\n // For queue: the position of the item that was indexed last\n yield* sql`CREATE TABLE IF NOT EXISTS indexCursor (\n indexName TEXT NOT NULL,\n spaceId TEXT NOT NULL DEFAULT '',\n sourceName TEXT NOT NULL,\n resourceId TEXT NOT NULL DEFAULT '',\n cursor,\n PRIMARY KEY (indexName, spaceId, sourceName, resourceId)\n )`;\n\n yield* Effect.forEach(DEPRECATED_INDEX_NAMES, (indexName) => {\n return sql`DELETE FROM indexCursor WHERE indexName = ${indexName}`;\n });\n });\n\n queryCursors = Effect.fn('IndexTracker.queryCursors')(\n (\n query: Pick<IndexCursor, 'indexName'> & Partial<Pick<IndexCursor, 'sourceName' | 'resourceId' | 'spaceId'>>,\n ): Effect.Effect<IndexCursor[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n const spaceIdParam = query.spaceId === undefined ? null : (query.spaceId ?? '');\n const sourceNameParam = query.sourceName === undefined ? null : query.sourceName;\n const resourceIdParam = query.resourceId === undefined ? null : (query.resourceId ?? '');\n\n const rows = yield* sql<IndexCursor>`\n SELECT * FROM indexCursor \n WHERE indexName = ${query.indexName}\n AND (${spaceIdParam} IS NULL OR spaceId = ${spaceIdParam})\n AND (${sourceNameParam} IS NULL OR sourceName = ${sourceNameParam})\n AND (${resourceIdParam} IS NULL OR resourceId = ${resourceIdParam})\n `;\n\n return rows.map(\n (row): IndexCursor => ({\n indexName: row.indexName,\n spaceId: row.spaceId === '' ? null : Schema.decodeSync(SpaceId)(row.spaceId!),\n sourceName: row.sourceName,\n resourceId: row.resourceId === '' ? null : row.resourceId,\n cursor: row.cursor,\n }),\n );\n }),\n );\n\n updateCursors = Effect.fn('IndexTracker.updateCursors')(\n (cursors: IndexCursor[]): Effect.Effect<void, SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n yield* Effect.forEach(\n cursors,\n (cursor) => {\n const spaceId = cursor.spaceId ?? '';\n const resourceId = cursor.resourceId ?? '';\n return sql`\n INSERT INTO indexCursor (indexName, spaceId, sourceName, resourceId, cursor)\n VALUES (${cursor.indexName}, ${spaceId}, ${cursor.sourceName}, ${resourceId}, ${cursor.cursor})\n ON CONFLICT(indexName, spaceId, sourceName, resourceId) DO UPDATE SET cursor = excluded.cursor\n `;\n },\n { discard: true },\n );\n }),\n );\n}\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as SqlClient from '@effect/sql/SqlClient';\nimport type * as SqlError from '@effect/sql/SqlError';\nimport type * as Statement from '@effect/sql/Statement';\nimport * as Effect from 'effect/Effect';\n\nimport type { Obj } from '@dxos/echo';\nimport type { ObjectId, SpaceId } from '@dxos/keys';\n\nimport type { Index, IndexerObject } from './interface';\nimport type { ObjectMeta } from './object-meta-index';\n\n// SQLite bound-variable limit (SQLITE_LIMIT_VARIABLE_NUMBER) is 999 in most builds.\n// Use 500 as a safe chunk size for IN (...) clauses.\nconst SQL_CHUNK_SIZE = 500;\n\n/**\n * The space and queue constrains are combined together using a logical OR.\n */\nexport interface FtsQuery {\n /**\n * Text to search.\n */\n query: string;\n\n /**\n * Space ID to search within.\n */\n spaceId: readonly SpaceId[] | null;\n\n /**\n * If true, include all queues in the spaces specified by `spaceId`.\n */\n includeAllQueues: boolean;\n\n /**\n * Queue IDs to search within.\n */\n queueIds: readonly ObjectId[] | null;\n}\n\n/**\n * Result of FTS query including the indexed snapshot data.\n */\nexport interface FtsResult extends ObjectMeta {\n /**\n * The indexed snapshot data (JSON string).\n * Used to load queue objects without going through document loading.\n */\n snapshot: string;\n}\n\n/**\n * Result of FTS query with rank.\n */\nexport interface FtsQueryResult extends ObjectMeta {\n /**\n * Relevance rank from FTS5.\n * Higher values indicate better matches.\n * Uses BM25 algorithm when available, falls back to 1 for non-BM25 queries.\n */\n rank: number;\n}\n\n/**\n * Escapes user input for safe FTS5 queries.\n *\n * FTS5 has special syntax characters that can cause errors or unexpected behavior:\n * - `*` suffix for prefix matching (e.g., `prog*` matches \"program\", \"programming\")\n * - `\"...\"` for phrase queries\n * - `.` for column specification\n * - `AND`, `OR`, `NOT` boolean operators\n * - `+`, `-` for required/excluded terms\n *\n * This function wraps each whitespace-separated term in double quotes, treating all\n * characters as literals. Double quotes within terms are escaped by doubling (`\"\"`).\n *\n * Example: `prog* AND test.` becomes `\"prog*\" \"AND\" \"test.\"`.\n */\nconst escapeFts5Query = (text: string): string => {\n return text\n .split(/\\s+/)\n .filter(Boolean)\n .map((term) => `\"${term.replace(/\"/g, '\"\"')}\"`)\n .join(' ');\n};\n\nexport class FtsIndex implements Index {\n migrate = Effect.fn('FtsIndex.migrate')(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n // https://sqlite.org/fts5.html#the_trigram_tokenizer\n // FTS5 tables are created as virtual tables; they implicitly have a `rowid`.\n // Trigram tokenizer enables substring matching (e.g., \"rog\" matches \"programming\").\n //\n // Data structure: inverted index mapping trigrams to document IDs.\n // \"hello\" → trigrams [\"hel\", \"ell\", \"llo\"] → B-tree entries: \"hel\"→[1], \"ell\"→[1], \"llo\"→[1].\n // Query \"ell\" → O(log n) B-tree lookup → returns [1].\n // Posting lists are compressed, so index size scales well with document count.\n yield* sql`CREATE VIRTUAL TABLE IF NOT EXISTS ftsIndex USING fts5(snapshot, tokenize='trigram')`;\n });\n\n query({\n query,\n spaceId,\n includeAllQueues,\n queueIds,\n }: FtsQuery): Effect.Effect<readonly FtsQueryResult[], SqlError.SqlError, SqlClient.SqlClient> {\n return Effect.gen(function* () {\n const trimmed = query.trim();\n if (trimmed.length === 0) {\n return [];\n }\n\n const sql = yield* SqlClient.SqlClient;\n\n // Trigram tokenizer requires at least 3 characters per term.\n // Check if ALL terms are at least 3 chars; otherwise use LIKE fallback.\n const terms = trimmed.split(/\\s+/).filter(Boolean);\n const minTermLength = Math.min(...terms.map((t) => t.length));\n\n // Use BM25 ranking for FTS5 MATCH queries, fall back to rank 1 for LIKE queries.\n // BM25 returns negative values where lower (more negative) means better match,\n // so we negate it to get higher = better.\n const useBm25 = minTermLength >= 3;\n\n const conditions =\n minTermLength < 3\n ? // LIKE fallback - scan the entire table, AND all terms.\n terms.map((term) => sql`f.snapshot LIKE ${'%' + term + '%'}`)\n : // MATCH - fast index lookup.\n [sql`f.snapshot MATCH ${escapeFts5Query(trimmed)}`];\n\n // Space and queue constraints are combined with OR.\n const sourceConditions: Statement.Statement<{}>[] = [];\n\n if (spaceId && spaceId.length > 0) {\n if (includeAllQueues) {\n // All items from these spaces (both space objects and queue objects).\n sourceConditions.push(sql`m.spaceId IN ${sql.in(spaceId)}`);\n } else {\n // Only space objects (not queue objects) from these spaces.\n sourceConditions.push(sql`(m.spaceId IN ${sql.in(spaceId)} AND m.queueId = '')`);\n }\n }\n\n if (queueIds && queueIds.length > 0) {\n // Items from specific queues.\n sourceConditions.push(sql`m.queueId IN ${sql.in(queueIds)}`);\n }\n\n if (sourceConditions.length > 0) {\n conditions.push(sql`(${sql.or(sourceConditions)})`);\n }\n\n if (useBm25) {\n // Use BM25 ranking for FTS5 MATCH queries.\n // BM25 returns negative values, negate to get higher = better match.\n // Order by rank descending so best matches come first.\n // Note: bm25() requires the actual table name, not an alias.\n const rows = yield* sql<ObjectMeta & { rank: number }>`\n SELECT m.*, -bm25(ftsIndex) AS rank \n FROM ftsIndex AS f \n JOIN objectMeta AS m ON f.rowid = m.recordId \n WHERE ${sql.and(conditions)}\n ORDER BY rank DESC\n `;\n return rows;\n } else {\n // LIKE fallback - no ranking available, default to 1.\n const rows = yield* sql<ObjectMeta>`\n SELECT m.* \n FROM ftsIndex AS f \n JOIN objectMeta AS m ON f.rowid = m.recordId \n WHERE ${sql.and(conditions)}\n `;\n return rows.map((row) => ({ ...row, rank: 1 }));\n }\n });\n }\n\n /**\n * Query snapshots by recordIds.\n * Returns the parsed JSON snapshots for queue objects.\n * RecordIds not present in the FTS index are silently omitted from the result.\n */\n querySnapshotsJSON(\n recordIds: number[],\n ): Effect.Effect<readonly { recordId: number; snapshot: Obj.JSON }[], SqlError.SqlError, SqlClient.SqlClient> {\n return Effect.gen(function* () {\n if (recordIds.length === 0) {\n return [];\n }\n const sql = yield* SqlClient.SqlClient;\n\n // Chunk to avoid SQLite bound-variable limit (SQLITE_LIMIT_VARIABLE_NUMBER,\n // typically 999 in wasm builds). 500 gives a safe margin.\n const chunks: number[][] = [];\n for (let i = 0; i < recordIds.length; i += SQL_CHUNK_SIZE) {\n chunks.push(recordIds.slice(i, i + SQL_CHUNK_SIZE));\n }\n\n const allResults: { recordId: number; snapshot: Obj.JSON }[] = [];\n for (const chunk of chunks) {\n const rows = yield* sql<{\n rowid: number;\n snapshot: string;\n }>`SELECT rowid, snapshot FROM ftsIndex WHERE rowid IN ${sql.in(chunk)}`;\n for (const r of rows) {\n allResults.push({ recordId: r.rowid, snapshot: JSON.parse(r.snapshot) });\n }\n }\n\n return allResults;\n });\n }\n\n update = Effect.fn('FtsIndex.update')(\n (objects: IndexerObject[]): Effect.Effect<void, SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n yield* Effect.forEach(\n objects,\n (object) =>\n Effect.gen(function* () {\n const { recordId, data } = object;\n if (recordId === null) {\n return yield* Effect.die(new Error('FtsIndex.update requires recordId to be set'));\n }\n\n const snapshot = JSON.stringify(data);\n\n // FTS5 doesn't support UPDATE, need DELETE + INSERT for upsert.\n const existing = yield* sql<{ rowid: number }>`SELECT rowid FROM ftsIndex WHERE rowid = ${recordId}`;\n if (existing.length > 0) {\n yield* sql`DELETE FROM ftsIndex WHERE rowid = ${recordId}`;\n }\n\n yield* sql`INSERT INTO ftsIndex (rowid, snapshot) VALUES (${recordId}, ${snapshot})`;\n }),\n { discard: true },\n );\n }),\n );\n}\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as SqlClient from '@effect/sql/SqlClient';\nimport type * as SqlError from '@effect/sql/SqlError';\nimport type * as Statement from '@effect/sql/Statement';\nimport * as Effect from 'effect/Effect';\nimport * as Schema from 'effect/Schema';\n\nimport { ATTR_DELETED, ATTR_PARENT, ATTR_RELATION_SOURCE, ATTR_RELATION_TARGET, ATTR_TYPE } from '@dxos/echo/internal';\nimport { DXN, type ObjectId, type SpaceId } from '@dxos/keys';\n\nimport type { IndexerObject } from './interface';\nimport type { Index } from './interface';\n\nconst _escapeLikePrefix = (prefix: string) => {\n // Escape LIKE metacharacters in the *literal* prefix (we still append a wildcard for the version suffix).\n // Backslash is used as the ESCAPE character.\n // See: https://www.sqlite.org/lang_expr.html#like\n const escaped = prefix.replaceAll('\\\\', '\\\\\\\\').replaceAll('%', '\\\\%').replaceAll('_', '\\\\_');\n return `${escaped}:%`;\n};\n\nexport const ObjectMeta = Schema.Struct({\n recordId: Schema.Number,\n objectId: Schema.String,\n queueId: Schema.String,\n /** Queue subspace namespace (e.g. 'data', 'trace'). Empty string for non-queue objects. */\n queueNamespace: Schema.String,\n spaceId: Schema.String,\n documentId: Schema.String,\n entityKind: Schema.String,\n /** The versioned DXN of the type of the object. */\n typeDxn: Schema.String,\n deleted: Schema.Boolean,\n source: Schema.NullOr(Schema.String),\n target: Schema.NullOr(Schema.String),\n /** Parent object id (nullable). */\n parent: Schema.NullOr(Schema.String),\n /** Monotonically increasing sequence number assigned on insert/update for tracking indexing order. */\n version: Schema.Number,\n /** Unix ms timestamp when the object was first indexed. */\n createdAt: Schema.NullOr(Schema.Number),\n /** Unix ms timestamp when the object was last re-indexed. */\n updatedAt: Schema.NullOr(Schema.Number),\n});\nexport interface ObjectMeta extends Schema.Schema.Type<typeof ObjectMeta> {}\n\n/**\n * Builds a SQL condition for filtering by space and queue source.\n * When `includeAllQueues` is false and no `queueIds`, only non-queue objects are returned.\n */\nconst buildSourceCondition = (\n sql: SqlClient.SqlClient,\n spaceIds: readonly string[],\n includeAllQueues: boolean,\n queueIds: readonly string[] | null,\n): Statement.Fragment => {\n const conditions: Statement.Fragment[] = [];\n\n if (spaceIds.length > 0) {\n if (includeAllQueues) {\n conditions.push(sql`${sql.in('spaceId', spaceIds)}`);\n } else {\n conditions.push(sql`(${sql.in('spaceId', spaceIds)} AND queueId = '')`);\n }\n }\n\n if (queueIds && queueIds.length > 0) {\n conditions.push(sql`${sql.in('queueId', queueIds)}`);\n }\n\n if (conditions.length === 0) {\n return sql`1 = 0`;\n }\n\n return sql.or(conditions);\n};\n\nexport class ObjectMetaIndex implements Index {\n migrate = Effect.fn('ObjectMetaIndex.runMigrations')(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n yield* sql`CREATE TABLE IF NOT EXISTS objectMeta (\n recordId INTEGER PRIMARY KEY AUTOINCREMENT,\n objectId TEXT NOT NULL,\n queueId TEXT NOT NULL DEFAULT '',\n queueNamespace TEXT NOT NULL DEFAULT '',\n spaceId TEXT NOT NULL,\n documentId TEXT NOT NULL DEFAULT '',\n entityKind TEXT NOT NULL,\n typeDxn TEXT NOT NULL,\n deleted INTEGER NOT NULL,\n source TEXT,\n target TEXT,\n parent TEXT,\n version INTEGER NOT NULL,\n createdAt INTEGER,\n updatedAt INTEGER\n )`;\n\n // Add `parent` column for tables created before it was introduced.\n yield* Effect.catchAll(sql`ALTER TABLE objectMeta ADD COLUMN parent TEXT`, () => Effect.void);\n // Add timestamp columns for tables created before they were introduced.\n yield* Effect.catchAll(sql`ALTER TABLE objectMeta ADD COLUMN createdAt INTEGER`, () => Effect.void);\n yield* Effect.catchAll(sql`ALTER TABLE objectMeta ADD COLUMN updatedAt INTEGER`, () => Effect.void);\n // Add queueNamespace column for tables created before it was introduced.\n yield* Effect.catchAll(\n sql`ALTER TABLE objectMeta ADD COLUMN queueNamespace TEXT NOT NULL DEFAULT ''`,\n () => Effect.void,\n );\n\n yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_objectId ON objectMeta(spaceId, objectId)`;\n yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_typeDxn ON objectMeta(spaceId, typeDxn)`;\n yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_version ON objectMeta(version)`;\n yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_parent ON objectMeta(spaceId, parent)`;\n yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_updatedAt ON objectMeta(updatedAt)`;\n yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_createdAt ON objectMeta(createdAt)`;\n });\n\n query = Effect.fn('ObjectMetaIndex.query')(\n (\n query: Pick<ObjectMeta, 'spaceId' | 'typeDxn'>,\n ): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n const parsedType = DXN.tryParse(query.typeDxn)?.asTypeDXN();\n\n // SQLite stores booleans as integers, so we need to specify the raw row type.\n const rows =\n parsedType && parsedType.version === undefined\n ? yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE spaceId = ${query.spaceId} AND (typeDxn = ${\n query.typeDxn\n } OR typeDxn LIKE ${_escapeLikePrefix(query.typeDxn)} ESCAPE '\\\\')`\n : yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE spaceId = ${query.spaceId} AND typeDxn = ${query.typeDxn}`;\n return rows.map((row) => ({\n ...row,\n deleted: !!row.deleted,\n }));\n }),\n );\n\n queryAll = Effect.fn('ObjectMetaIndex.queryAll')(\n (query: {\n spaceIds: readonly ObjectMeta['spaceId'][];\n includeAllQueues?: boolean;\n queueIds?: readonly string[] | null;\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n if (query.spaceIds.length === 0 && (!query.queueIds || query.queueIds.length === 0)) {\n return [];\n }\n\n const sql = yield* SqlClient.SqlClient;\n const sourceCondition = buildSourceCondition(\n sql,\n query.spaceIds,\n query.includeAllQueues ?? false,\n query.queueIds ?? null,\n );\n const rows = yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition}`;\n return rows.map((row) => ({\n ...row,\n deleted: !!row.deleted,\n }));\n }),\n );\n\n queryTypes = Effect.fn('ObjectMetaIndex.queryTypes')(\n ({\n spaceIds,\n typeDxns,\n inverted = false,\n includeAllQueues = false,\n queueIds = null,\n }: {\n spaceIds: readonly ObjectMeta['spaceId'][];\n typeDxns: readonly ObjectMeta['typeDxn'][];\n inverted?: boolean;\n includeAllQueues?: boolean;\n queueIds?: readonly string[] | null;\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n if (spaceIds.length === 0 && (!queueIds || queueIds.length === 0)) {\n return [];\n }\n\n if (typeDxns.length === 0) {\n if (!inverted) {\n return [];\n }\n\n const sql = yield* SqlClient.SqlClient;\n const sourceCondition = buildSourceCondition(sql, spaceIds, includeAllQueues, queueIds);\n const rows = yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition}`;\n return rows.map((row) => ({\n ...row,\n deleted: !!row.deleted,\n }));\n }\n const sql = yield* SqlClient.SqlClient;\n const sourceCondition = buildSourceCondition(sql, spaceIds, includeAllQueues, queueIds);\n const typeWhere = sql.or(\n typeDxns.map((typeDxn) => {\n const parsedType = DXN.tryParse(typeDxn)?.asTypeDXN();\n return parsedType && parsedType.version === undefined\n ? sql.or([sql`typeDxn = ${typeDxn}`, sql`typeDxn LIKE ${_escapeLikePrefix(typeDxn)} ESCAPE '\\\\'`])\n : sql`typeDxn = ${typeDxn}`;\n }),\n );\n const rows = inverted\n ? yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition} AND NOT ${typeWhere}`\n : yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition} AND ${typeWhere}`;\n return rows.map((row) => ({\n ...row,\n deleted: !!row.deleted,\n }));\n }),\n );\n\n queryRelations = Effect.fn('ObjectMetaIndex.queryRelations')(\n ({\n endpoint,\n anchorDxns,\n }: {\n endpoint: 'source' | 'target';\n anchorDxns: readonly string[];\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n if (anchorDxns.length === 0) {\n return [];\n }\n const sql = yield* SqlClient.SqlClient;\n const column = endpoint === 'source' ? 'source' : 'target';\n const rows = yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE entityKind = 'relation' AND ${sql.in(\n column,\n anchorDxns,\n )}`;\n return rows.map((row) => ({\n ...row,\n deleted: !!row.deleted,\n }));\n }),\n );\n\n // TODO(dmaretskyi): Update recordId on objects so that we don't need to look it up separately.\n update = Effect.fn('ObjectMetaIndex.update')(\n (objects: IndexerObject[]): Effect.Effect<void, SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n yield* Effect.forEach(\n objects,\n (object) =>\n Effect.gen(function* () {\n const { spaceId, queueId, queueNamespace, documentId, data } = object;\n\n // Extract metadata (Logic emulating Echo APIs as strict imports are unavailable).\n const castData = data;\n const objectId = castData.id;\n\n // Check for existing record by (spaceId, queueId) or (spaceId, documentId).\n let existing: readonly { recordId: number }[];\n if (documentId) {\n existing = yield* sql<{\n recordId: number;\n }>`SELECT recordId FROM objectMeta WHERE spaceId = ${spaceId} AND documentId = ${documentId} AND objectId = ${objectId} LIMIT 1`;\n } else if (queueId) {\n existing = yield* sql<{\n recordId: number;\n }>`SELECT recordId FROM objectMeta WHERE spaceId = ${spaceId} AND queueId = ${queueId} AND objectId = ${objectId} LIMIT 1`;\n } else {\n // Should not happen based on IndexerObject definition (one must be present ideally), but handle gracefully.\n existing = [];\n }\n\n // Get max version + 1.\n const result = yield* sql<{ v: number | null }>`SELECT MAX(version) as v FROM objectMeta`;\n const [{ v }] = result;\n const version = (v ?? 0) + 1;\n\n // Extract metadata.\n const entityKind = castData[ATTR_RELATION_SOURCE] ? 'relation' : 'object';\n const typeDxn = castData[ATTR_TYPE] ? String(castData[ATTR_TYPE]) : 'type';\n const deleted = castData[ATTR_DELETED] ? 1 : 0;\n // Relations.\n const source = entityKind === 'relation' ? (castData[ATTR_RELATION_SOURCE] ?? null) : null;\n const target = entityKind === 'relation' ? (castData[ATTR_RELATION_TARGET] ?? null) : null;\n // Parent (nullable).\n const parent = castData[ATTR_PARENT] ?? null;\n\n const sourceTimestamp = object.updatedAt;\n\n if (existing.length > 0) {\n yield* sql`\n UPDATE objectMeta SET\n version = ${version},\n queueNamespace = ${queueNamespace ?? ''},\n entityKind = ${entityKind},\n typeDxn = ${typeDxn},\n deleted = ${deleted},\n source = ${source},\n target = ${target},\n parent = ${parent},\n updatedAt = ${sourceTimestamp}\n WHERE recordId = ${existing[0].recordId}\n `;\n } else {\n yield* sql`\n INSERT INTO objectMeta (\n objectId, queueId, queueNamespace, spaceId, documentId,\n entityKind, typeDxn, deleted, source, target, parent, version,\n createdAt, updatedAt\n ) VALUES (\n ${objectId}, ${queueId ?? ''}, ${queueNamespace ?? ''}, ${spaceId}, ${documentId ?? ''},\n ${entityKind}, ${typeDxn}, ${deleted},\n ${source}, ${target}, ${parent}, ${version},\n ${sourceTimestamp}, ${sourceTimestamp}\n )\n `;\n }\n }),\n { discard: true },\n );\n }),\n );\n\n /**\n * Look up `recordIds` for objects that are already stored in the ObjectMetaIndex.\n * Mutates the objects in place.\n */\n lookupRecordIds = Effect.fn('ObjectMetaIndex.lookupRecordIds')(\n (objects: IndexerObject[]): Effect.Effect<void, SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n for (const object of objects) {\n const { spaceId, queueId, documentId, data } = object;\n const objectId = data.id;\n\n let result: readonly { recordId: number }[];\n if (documentId) {\n result = yield* sql<{\n recordId: number;\n }>`SELECT recordId FROM objectMeta WHERE spaceId = ${spaceId} AND documentId = ${documentId} AND objectId = ${objectId} LIMIT 1`;\n } else if (queueId) {\n result = yield* sql<{\n recordId: number;\n }>`SELECT recordId FROM objectMeta WHERE spaceId = ${spaceId} AND queueId = ${queueId} AND objectId = ${objectId} LIMIT 1`;\n } else {\n result = [];\n }\n\n if (result.length === 0) {\n // TODO(mykola): Handle this case gracefully.\n return yield* Effect.die(\n new Error(`Object not found in ObjectMetaIndex: ${spaceId}/${documentId ?? queueId}/${objectId}`),\n );\n }\n object.recordId = result[0].recordId;\n }\n }),\n );\n\n /**\n * Look up object metadata by recordIds.\n */\n lookupByRecordIds = Effect.fn('ObjectMetaIndex.lookupByRecordIds')(\n (recordIds: number[]): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n if (recordIds.length === 0) {\n return [];\n }\n\n const sql = yield* SqlClient.SqlClient;\n const rows = yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sql.in('recordId', recordIds)}`;\n\n return rows.map((row) => ({\n ...row,\n deleted: !!row.deleted,\n }));\n }),\n );\n\n /**\n * Look up object metadata by objectId, spaceId, and queueId.\n */\n lookupByObjectId = Effect.fn('ObjectMetaIndex.lookupByObjectId')(\n (query: {\n objectId: string;\n spaceId: string;\n queueId: string;\n }): Effect.Effect<ObjectMeta | null, SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n const rows =\n yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE spaceId = ${query.spaceId} AND queueId = ${query.queueId} AND objectId = ${query.objectId} LIMIT 1`;\n\n if (rows.length === 0) {\n return null;\n }\n\n return {\n ...rows[0],\n deleted: !!rows[0].deleted,\n };\n }),\n );\n\n /**\n * Query objects by timestamp range.\n */\n queryByTimeRange = Effect.fn('ObjectMetaIndex.queryByTimeRange')(\n (query: {\n spaceIds: readonly string[];\n updatedAfter?: number;\n updatedBefore?: number;\n createdAfter?: number;\n createdBefore?: number;\n includeAllQueues?: boolean;\n queueIds?: readonly string[] | null;\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n if (query.spaceIds.length === 0 && (!query.queueIds || query.queueIds.length === 0)) {\n return [];\n }\n\n const sql = yield* SqlClient.SqlClient;\n const sourceCondition = buildSourceCondition(\n sql,\n query.spaceIds,\n query.includeAllQueues ?? false,\n query.queueIds ?? null,\n );\n\n const timeConditions: Statement.Fragment[] = [];\n if (query.updatedAfter != null) {\n timeConditions.push(sql`updatedAt >= ${query.updatedAfter}`);\n }\n if (query.updatedBefore != null) {\n timeConditions.push(sql`updatedAt <= ${query.updatedBefore}`);\n }\n if (query.createdAfter != null) {\n timeConditions.push(sql`createdAt >= ${query.createdAfter}`);\n }\n if (query.createdBefore != null) {\n timeConditions.push(sql`createdAt <= ${query.createdBefore}`);\n }\n\n const rows =\n timeConditions.length > 0\n ? yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition} AND ${sql.and(timeConditions)}`\n : yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition}`;\n\n return rows.map((row) => ({\n ...row,\n deleted: !!row.deleted,\n }));\n }),\n );\n\n /**\n * Query children by parent object ids.\n * Matches both:\n * - Objects whose `parent` field references one of the given parent ids (standard parent/child hierarchy).\n * - Queue items whose `queueId` equals one of the parent ids (e.g. items inside a Feed, since a feed's queue\n * DXN uses the feed's object id as its queue id — see `Feed.getQueueDxn`).\n */\n queryChildren = Effect.fn('ObjectMetaIndex.queryChildren')(\n (query: {\n spaceId: SpaceId[];\n parentIds: ObjectId[];\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n if (query.parentIds.length === 0) {\n return [];\n }\n\n const sql = yield* SqlClient.SqlClient;\n const parentDxns = query.parentIds.map((id) => DXN.fromLocalObjectId(id).toString());\n const rows =\n yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sql.in('spaceId', query.spaceId)} AND (${sql.in('parent', parentDxns)} OR ${sql.in('queueId', query.parentIds)})`;\n\n return rows.map((row) => ({\n ...row,\n deleted: !!row.deleted,\n }));\n }),\n );\n}\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as SqlClient from '@effect/sql/SqlClient';\nimport type * as SqlError from '@effect/sql/SqlError';\nimport * as Effect from 'effect/Effect';\nimport * as Schema from 'effect/Schema';\n\nimport { EncodedReference, isEncodedReference } from '@dxos/echo-protocol';\n\nimport { EscapedPropPath } from '../utils';\nimport type { Index, IndexerObject } from './interface';\n\n/**\n * Extracts all outgoing references from an object's data.\n */\nconst extractReferences = (data: Record<string, unknown>): { path: string[]; targetDxn: string }[] => {\n const refs: { path: string[]; targetDxn: string }[] = [];\n const visit = (path: string[], value: unknown) => {\n if (isEncodedReference(value)) {\n const dxn = EncodedReference.toDXN(value);\n const echoId = dxn.asEchoDXN()?.echoId;\n if (!echoId) {\n return; // Skip non-echo references.\n }\n refs.push({ path, targetDxn: dxn.toString() });\n } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n for (const [key, v] of Object.entries(value)) {\n visit([...path, key], v);\n }\n } else if (Array.isArray(value)) {\n for (let i = 0; i < value.length; i++) {\n visit([...path, String(i)], value[i]);\n }\n }\n };\n visit([], data);\n return refs;\n};\n\nexport const ReverseRef = Schema.Struct({\n recordId: Schema.Number,\n targetDxn: Schema.String,\n /**\n * Escaped property path within an object.\n *\n * Escaping rules:\n *\n * - '.' -> '\\.'\n * - '\\' -> '\\\\'\n * - contact with .\n */\n propPath: Schema.String,\n});\nexport interface ReverseRef extends Schema.Schema.Type<typeof ReverseRef> {}\n\nexport interface ReverseRefQuery {\n targetDxn: string;\n // TODO: Add prop filter\n}\n\n/**\n * Indexes reverse references - tracks which objects reference which targets.\n * Only indexes references, not relations.\n */\nexport class ReverseRefIndex implements Index {\n migrate = Effect.fn('ReverseRefIndex.migrate')(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n yield* sql`CREATE TABLE IF NOT EXISTS reverseRef (\n recordId INTEGER NOT NULL,\n targetDxn TEXT NOT NULL,\n propPath TEXT NOT NULL,\n PRIMARY KEY (recordId, targetDxn, propPath)\n )`;\n\n yield* sql`CREATE INDEX IF NOT EXISTS idx_reverse_ref_target ON reverseRef(targetDxn)`;\n });\n\n /**\n * Query all references pointing to a target DXN.\n */\n query = Effect.fn('ReverseRefIndex.query')(\n ({ targetDxn }: ReverseRefQuery): Effect.Effect<readonly ReverseRef[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n // TODO(mykola): Join objectMeta table here.\n const rows = yield* sql`SELECT * FROM reverseRef WHERE targetDxn = ${targetDxn}`;\n return rows as ReverseRef[];\n }),\n );\n\n update = Effect.fn('ReverseRefIndex.update')(\n (objects: IndexerObject[]): Effect.Effect<void, SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n yield* Effect.forEach(\n objects,\n (object) =>\n Effect.gen(function* () {\n const { recordId, data } = object;\n if (recordId === null) {\n yield* Effect.die(new Error('ReverseRefIndex.update requires recordId to be set'));\n }\n\n // Delete existing references for this record.\n yield* sql`DELETE FROM reverseRef WHERE recordId = ${recordId}`;\n\n // Extract references from data.\n const refs = extractReferences(data as unknown as Record<string, unknown>);\n\n // Insert new references.\n yield* Effect.forEach(\n refs,\n (ref) =>\n sql`INSERT INTO reverseRef (recordId, targetDxn, propPath) VALUES (${recordId}, ${ref.targetDxn}, ${EscapedPropPath.escape(ref.path)})`,\n { discard: true },\n );\n }),\n { discard: true },\n );\n }),\n );\n}\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Schema from 'effect/Schema';\n\nimport { invariant } from '@dxos/invariant';\n\nexport type ObjectPropPath = string[];\n\n/**\n * Escaped property path within an object.\n *\n * Escaping rules:\n *\n * - '.' -> '\\.'\n * - '\\' -> '\\\\'\n * - contact with .\n */\nexport const EscapedPropPath: Schema.SchemaClass<string, string> & {\n escape: (path: ObjectPropPath) => EscapedPropPath;\n unescape: (path: EscapedPropPath) => ObjectPropPath;\n} = class extends Schema.String.annotations({ title: 'EscapedPropPath' }) {\n static escape(path: ObjectPropPath): EscapedPropPath {\n return path.map((p) => p.toString().replaceAll('\\\\', '\\\\\\\\').replaceAll('.', '\\\\.')).join('.');\n }\n\n static unescape(path: EscapedPropPath): ObjectPropPath {\n const parts: string[] = [];\n let current = '';\n\n for (let i = 0; i < path.length; i++) {\n if (path[i] === '\\\\') {\n invariant(i + 1 < path.length && (path[i + 1] === '.' || path[i + 1] === '\\\\'), 'Malformed escaping.');\n current = current + path[i + 1];\n i++;\n } else if (path[i] === '.') {\n parts.push(current);\n current = '';\n } else {\n current += path[i];\n }\n }\n parts.push(current);\n\n return parts;\n }\n};\nexport type EscapedPropPath = Schema.Schema.Type<typeof EscapedPropPath>;\n"],
4
+ "sourcesContent": ["//\n// Copyright 2026 DXOS.org\n//\n\nimport type * as SqlClient from '@effect/sql/SqlClient';\nimport type * as SqlError from '@effect/sql/SqlError';\nimport * as Effect from 'effect/Effect';\n\nimport { type Context } from '@dxos/context';\nimport { ATTR_TYPE } from '@dxos/echo/internal';\nimport type { ObjectId, SpaceId } from '@dxos/keys';\nimport * as SqlTransaction from '@dxos/sql-sqlite/SqlTransaction';\n\nimport { type IndexCursor, IndexTracker } from './index-tracker';\nimport {\n FtsIndex,\n type FtsQuery,\n type FtsQueryResult,\n type Index,\n type IndexerObject,\n type ObjectMeta,\n ObjectMetaIndex,\n ReverseRefIndex,\n type ReverseRefQuery,\n} from './indexes';\n\n/**\n * Result of a single indexing pass over a data source.\n * Carries enough metadata for callers to build targeted invalidation hints.\n */\nexport type IndexingResult = {\n updated: number;\n done: boolean;\n spaces: ReadonlySet<SpaceId>;\n queues: ReadonlySet<ObjectId>;\n documents: ReadonlySet<string>;\n types: ReadonlySet<string>;\n objects: ReadonlySet<ObjectId>;\n};\n\ntype MutableIndexingResult = {\n updated: number;\n done: boolean;\n spaces: Set<SpaceId>;\n queues: Set<ObjectId>;\n documents: Set<string>;\n types: Set<string>;\n objects: Set<ObjectId>;\n};\n\nconst makeEmptyIndexingResult = (): MutableIndexingResult => ({\n updated: 0,\n done: true,\n spaces: new Set(),\n queues: new Set(),\n documents: new Set(),\n types: new Set(),\n objects: new Set(),\n});\n\nconst accumulateIndexingResult = (acc: MutableIndexingResult, objects: readonly IndexerObject[]) => {\n for (const obj of objects) {\n acc.spaces.add(obj.spaceId);\n if (obj.queueId) {\n acc.queues.add(obj.queueId);\n }\n if (obj.documentId) {\n acc.documents.add(obj.documentId);\n }\n const t = (obj.data as Record<string, unknown>)[ATTR_TYPE];\n if (t) {\n acc.types.add(String(t));\n }\n if (obj.data.id) {\n acc.objects.add(obj.data.id as ObjectId);\n }\n }\n};\n\n/**\n * Cursor into indexable data-source.\n */\nexport interface DataSourceCursor {\n spaceId: SpaceId | null;\n\n /**\n * documentId or queueNamespace.\n */\n resourceId: string | null;\n\n /**\n * heads or queue position.\n */\n cursor: number | string;\n}\n\nexport interface IndexDataSource {\n readonly sourceName: string; // e.g. queue, automerge, etc.\n\n getChangedObjects(\n ctx: Context,\n cursors: DataSourceCursor[],\n opts?: { limit?: number },\n ): Effect.Effect<{ objects: IndexerObject[]; cursors: DataSourceCursor[] }>;\n}\n\nexport interface IndexEngineParams {\n tracker: IndexTracker;\n objectMetaIndex: ObjectMetaIndex;\n ftsIndex: FtsIndex;\n reverseRefIndex: ReverseRefIndex;\n}\n\nexport class IndexEngine {\n readonly #tracker: IndexTracker;\n readonly #objectMetaIndex: ObjectMetaIndex;\n readonly #ftsIndex: FtsIndex;\n readonly #reverseRefIndex: ReverseRefIndex;\n\n constructor(params?: IndexEngineParams) {\n this.#tracker = params?.tracker ?? new IndexTracker();\n this.#objectMetaIndex = params?.objectMetaIndex ?? new ObjectMetaIndex();\n this.#ftsIndex = params?.ftsIndex ?? new FtsIndex();\n this.#reverseRefIndex = params?.reverseRefIndex ?? new ReverseRefIndex();\n }\n\n migrate() {\n return Effect.gen(this, function* () {\n yield* this.#tracker.migrate();\n yield* this.#objectMetaIndex.migrate();\n yield* this.#ftsIndex.migrate();\n yield* this.#reverseRefIndex.migrate();\n });\n }\n\n /**\n * Query text index and return full object metadata with rank.\n */\n queryText(query: FtsQuery): Effect.Effect<readonly FtsQueryResult[], SqlError.SqlError, SqlClient.SqlClient> {\n return Effect.gen(this, function* () {\n return yield* this.#ftsIndex.query(query);\n });\n }\n\n queryReverseRef(query: ReverseRefQuery) {\n // TODO(mykola): Join with metadata table here.\n return this.#reverseRefIndex.query(query);\n }\n\n queryAll(query: {\n spaceIds: readonly SpaceId[];\n includeAllQueues?: boolean;\n queueIds?: readonly string[] | null;\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {\n return this.#objectMetaIndex.queryAll(query);\n }\n\n /**\n * Query snapshots by recordIds.\n * Used to load queue objects from indexed snapshots.\n */\n querySnapshotsJSON(recordIds: number[]) {\n return this.#ftsIndex.querySnapshotsJSON(recordIds);\n }\n\n queryType(\n query: Pick<ObjectMeta, 'spaceId' | 'typeDXN'>,\n ): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {\n return this.#objectMetaIndex.query(query);\n }\n\n /**\n * Query children by parent object ids.\n */\n queryChildren(query: {\n spaceId: SpaceId[];\n parentIds: ObjectId[];\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {\n return this.#objectMetaIndex.queryChildren(query);\n }\n\n queryTypes(query: {\n spaceIds: readonly SpaceId[];\n typeDxns: readonly ObjectMeta['typeDXN'][];\n inverted?: boolean;\n includeAllQueues?: boolean;\n queueIds?: readonly string[] | null;\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {\n return this.#objectMetaIndex.queryTypes(query);\n }\n queryByTimeRange(query: {\n spaceIds: readonly string[];\n updatedAfter?: number;\n updatedBefore?: number;\n createdAfter?: number;\n createdBefore?: number;\n includeAllQueues?: boolean;\n queueIds?: readonly string[] | null;\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {\n return this.#objectMetaIndex.queryByTimeRange(query);\n }\n\n queryRelations(query: {\n endpoint: 'source' | 'target';\n anchorDxns: readonly string[];\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {\n return this.#objectMetaIndex.queryRelations(query);\n }\n lookupByRecordIds(recordIds: number[]): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> {\n return this.#objectMetaIndex.lookupByRecordIds(recordIds);\n }\n\n lookupByObjectId(query: {\n objectId: string;\n spaceId: string;\n queueId: string;\n }): Effect.Effect<ObjectMeta | null, SqlError.SqlError, SqlClient.SqlClient> {\n return this.#objectMetaIndex.lookupByObjectId(query);\n }\n\n update(\n ctx: Context,\n dataSource: IndexDataSource,\n opts: { spaceId: SpaceId | null; limit?: number },\n ): Effect.Effect<IndexingResult, SqlError.SqlError, SqlTransaction.SqlTransaction | SqlClient.SqlClient> {\n return Effect.gen(this, function* () {\n const result = makeEmptyIndexingResult();\n\n const {\n updated: updatedFtsIndex,\n done: doneFtsIndex,\n objects: ftsObjects,\n } = yield* this.#update(ctx, this.#ftsIndex, dataSource, {\n indexName: 'fts5',\n spaceId: opts.spaceId,\n limit: opts.limit,\n });\n result.updated += updatedFtsIndex;\n result.done = result.done && doneFtsIndex;\n accumulateIndexingResult(result, ftsObjects);\n\n const {\n updated: updatedReverseRefIndex,\n done: doneReverseRefIndex,\n objects: reverseRefObjects,\n } = yield* this.#update(ctx, this.#reverseRefIndex, dataSource, {\n indexName: 'reverseRef',\n spaceId: opts.spaceId,\n limit: opts.limit,\n });\n result.updated += updatedReverseRefIndex;\n result.done = result.done && doneReverseRefIndex;\n accumulateIndexingResult(result, reverseRefObjects);\n\n return result as IndexingResult;\n }).pipe(Effect.withSpan('IndexEngine.update'));\n }\n\n /**\n * Update a dependent index that requires recordId enrichment.\n * This method:\n * 1. Gets changed objects from the source.\n * 2. Ensures those objects exist in ObjectMetaIndex.\n * 3. Looks up recordIds for those objects.\n * 4. Enriches objects with recordIds.\n * 5. Updates the dependent index.\n */\n #update(\n ctx: Context,\n index: Index,\n source: IndexDataSource,\n opts: { indexName: string; spaceId: SpaceId | null; limit?: number },\n ): Effect.Effect<\n { updated: number; done: boolean; objects: readonly IndexerObject[] },\n SqlError.SqlError,\n SqlTransaction.SqlTransaction | SqlClient.SqlClient\n > {\n return Effect.gen(this, function* () {\n const sqlTransaction = yield* SqlTransaction.SqlTransaction;\n\n return yield* sqlTransaction.withTransaction(\n Effect.gen(this, function* () {\n const cursors = yield* this.#tracker.queryCursors({\n indexName: opts.indexName,\n sourceName: source.sourceName,\n // Pass undefined to get all cursors when spaceId is null.\n spaceId: opts.spaceId ?? undefined,\n });\n const { objects, cursors: updatedCursors } = yield* source.getChangedObjects(ctx, cursors, {\n limit: opts.limit,\n });\n if (objects.length === 0) {\n return { updated: 0, done: true, objects: [] as readonly IndexerObject[] };\n }\n\n // Ensure objects exist in ObjectMetaIndex.\n yield* this.#objectMetaIndex.update(objects);\n\n // Look up recordIds for the objects.\n yield* this.#objectMetaIndex.lookupRecordIds(objects);\n\n yield* index.update(objects);\n yield* this.#tracker.updateCursors(\n updatedCursors.map(\n (_): IndexCursor => ({\n indexName: opts.indexName,\n spaceId: _.spaceId,\n sourceName: source.sourceName,\n resourceId: _.resourceId,\n cursor: _.cursor,\n }),\n ),\n );\n return { updated: objects.length, done: false, objects };\n }),\n );\n }).pipe(Effect.withSpan('IndexEngine.#update'));\n }\n}\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as SqlClient from '@effect/sql/SqlClient';\nimport type * as SqlError from '@effect/sql/SqlError';\nimport * as Effect from 'effect/Effect';\nimport * as Schema from 'effect/Schema';\n\nimport { SpaceId } from '@dxos/keys';\n\nexport const IndexCursor = Schema.Struct({\n /**\n * Name of the index owning this cursor.\n */\n indexName: Schema.String,\n /**\n * Space id.\n */\n spaceId: Schema.NullOr(SpaceId),\n /**\n * Source name.\n * 'automerge' / 'queue' / 'index' (for secondary indexes)\n */\n sourceName: Schema.String,\n /**\n * Document id or queue id.\n * doc_id, queue_id, '' <empty string> (if indexing entire namespace)\n */\n resourceId: Schema.NullOr(Schema.String),\n /**\n * Heads, queue position, version.\n */\n cursor: Schema.Union(Schema.Number, Schema.String),\n});\nexport interface IndexCursor extends Schema.Schema.Type<typeof IndexCursor> {}\n\n/**\n * Deprecated index names that are no longer used. Will be cleaned up on migration.\n */\nconst DEPRECATED_INDEX_NAMES = ['fts'];\n\nexport class IndexTracker {\n migrate = Effect.fn('IndexTracker.migrate')(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n // For automerge: last-indexed heads of the document\n // For queue: the position of the item that was indexed last\n yield* sql`CREATE TABLE IF NOT EXISTS indexCursor (\n indexName TEXT NOT NULL,\n spaceId TEXT NOT NULL DEFAULT '',\n sourceName TEXT NOT NULL,\n resourceId TEXT NOT NULL DEFAULT '',\n cursor,\n PRIMARY KEY (indexName, spaceId, sourceName, resourceId)\n )`;\n\n yield* Effect.forEach(DEPRECATED_INDEX_NAMES, (indexName) => {\n return sql`DELETE FROM indexCursor WHERE indexName = ${indexName}`;\n });\n });\n\n queryCursors = Effect.fn('IndexTracker.queryCursors')(\n (\n query: Pick<IndexCursor, 'indexName'> & Partial<Pick<IndexCursor, 'sourceName' | 'resourceId' | 'spaceId'>>,\n ): Effect.Effect<IndexCursor[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n const spaceIdParam = query.spaceId === undefined ? null : (query.spaceId ?? '');\n const sourceNameParam = query.sourceName === undefined ? null : query.sourceName;\n const resourceIdParam = query.resourceId === undefined ? null : (query.resourceId ?? '');\n\n const rows = yield* sql<IndexCursor>`\n SELECT * FROM indexCursor \n WHERE indexName = ${query.indexName}\n AND (${spaceIdParam} IS NULL OR spaceId = ${spaceIdParam})\n AND (${sourceNameParam} IS NULL OR sourceName = ${sourceNameParam})\n AND (${resourceIdParam} IS NULL OR resourceId = ${resourceIdParam})\n `;\n\n return rows.map(\n (row): IndexCursor => ({\n indexName: row.indexName,\n spaceId: row.spaceId === '' ? null : Schema.decodeSync(SpaceId)(row.spaceId!),\n sourceName: row.sourceName,\n resourceId: row.resourceId === '' ? null : row.resourceId,\n cursor: row.cursor,\n }),\n );\n }),\n );\n\n updateCursors = Effect.fn('IndexTracker.updateCursors')(\n (cursors: IndexCursor[]): Effect.Effect<void, SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n yield* Effect.forEach(\n cursors,\n (cursor) => {\n const spaceId = cursor.spaceId ?? '';\n const resourceId = cursor.resourceId ?? '';\n return sql`\n INSERT INTO indexCursor (indexName, spaceId, sourceName, resourceId, cursor)\n VALUES (${cursor.indexName}, ${spaceId}, ${cursor.sourceName}, ${resourceId}, ${cursor.cursor})\n ON CONFLICT(indexName, spaceId, sourceName, resourceId) DO UPDATE SET cursor = excluded.cursor\n `;\n },\n { discard: true },\n );\n }),\n );\n}\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as SqlClient from '@effect/sql/SqlClient';\nimport type * as SqlError from '@effect/sql/SqlError';\nimport type * as Statement from '@effect/sql/Statement';\nimport * as Effect from 'effect/Effect';\n\nimport type { Obj } from '@dxos/echo';\nimport type { ObjectId, SpaceId } from '@dxos/keys';\n\nimport type { Index, IndexerObject } from './interface';\nimport type { ObjectMeta } from './object-meta-index';\n\n// SQLite bound-variable limit (SQLITE_LIMIT_VARIABLE_NUMBER) is 999 in most builds.\n// Use 500 as a safe chunk size for IN (...) clauses.\nconst SQL_CHUNK_SIZE = 500;\n\n/**\n * The space and queue constrains are combined together using a logical OR.\n */\nexport interface FtsQuery {\n /**\n * Text to search.\n */\n query: string;\n\n /**\n * Space ID to search within.\n */\n spaceId: readonly SpaceId[] | null;\n\n /**\n * If true, include all queues in the spaces specified by `spaceId`.\n */\n includeAllQueues: boolean;\n\n /**\n * Queue IDs to search within.\n */\n queueIds: readonly ObjectId[] | null;\n}\n\n/**\n * Result of FTS query including the indexed snapshot data.\n */\nexport interface FtsResult extends ObjectMeta {\n /**\n * The indexed snapshot data (JSON string).\n * Used to load queue objects without going through document loading.\n */\n snapshot: string;\n}\n\n/**\n * Result of FTS query with rank.\n */\nexport interface FtsQueryResult extends ObjectMeta {\n /**\n * Relevance rank from FTS5.\n * Higher values indicate better matches.\n * Uses BM25 algorithm when available, falls back to 1 for non-BM25 queries.\n */\n rank: number;\n}\n\n/**\n * Escapes user input for safe FTS5 queries.\n *\n * FTS5 has special syntax characters that can cause errors or unexpected behavior:\n * - `*` suffix for prefix matching (e.g., `prog*` matches \"program\", \"programming\")\n * - `\"...\"` for phrase queries\n * - `.` for column specification\n * - `AND`, `OR`, `NOT` boolean operators\n * - `+`, `-` for required/excluded terms\n *\n * This function wraps each whitespace-separated term in double quotes, treating all\n * characters as literals. Double quotes within terms are escaped by doubling (`\"\"`).\n *\n * Example: `prog* AND test.` becomes `\"prog*\" \"AND\" \"test.\"`.\n */\nconst escapeFts5Query = (text: string): string => {\n return text\n .split(/\\s+/)\n .filter(Boolean)\n .map((term) => `\"${term.replace(/\"/g, '\"\"')}\"`)\n .join(' ');\n};\n\nexport class FtsIndex implements Index {\n migrate = Effect.fn('FtsIndex.migrate')(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n // https://sqlite.org/fts5.html#the_trigram_tokenizer\n // FTS5 tables are created as virtual tables; they implicitly have a `rowid`.\n // Trigram tokenizer enables substring matching (e.g., \"rog\" matches \"programming\").\n //\n // Data structure: inverted index mapping trigrams to document IDs.\n // \"hello\" → trigrams [\"hel\", \"ell\", \"llo\"] → B-tree entries: \"hel\"→[1], \"ell\"→[1], \"llo\"→[1].\n // Query \"ell\" → O(log n) B-tree lookup → returns [1].\n // Posting lists are compressed, so index size scales well with document count.\n yield* sql`CREATE VIRTUAL TABLE IF NOT EXISTS ftsIndex USING fts5(snapshot, tokenize='trigram')`;\n });\n\n query({\n query,\n spaceId,\n includeAllQueues,\n queueIds,\n }: FtsQuery): Effect.Effect<readonly FtsQueryResult[], SqlError.SqlError, SqlClient.SqlClient> {\n return Effect.gen(function* () {\n const trimmed = query.trim();\n if (trimmed.length === 0) {\n return [];\n }\n\n const sql = yield* SqlClient.SqlClient;\n\n // Trigram tokenizer requires at least 3 characters per term.\n // Check if ALL terms are at least 3 chars; otherwise use LIKE fallback.\n const terms = trimmed.split(/\\s+/).filter(Boolean);\n const minTermLength = Math.min(...terms.map((t) => t.length));\n\n // Use BM25 ranking for FTS5 MATCH queries, fall back to rank 1 for LIKE queries.\n // BM25 returns negative values where lower (more negative) means better match,\n // so we negate it to get higher = better.\n const useBm25 = minTermLength >= 3;\n\n const conditions =\n minTermLength < 3\n ? // LIKE fallback - scan the entire table, AND all terms.\n terms.map((term) => sql`f.snapshot LIKE ${'%' + term + '%'}`)\n : // MATCH - fast index lookup.\n [sql`f.snapshot MATCH ${escapeFts5Query(trimmed)}`];\n\n // Space and queue constraints are combined with OR.\n const sourceConditions: Statement.Statement<{}>[] = [];\n\n if (spaceId && spaceId.length > 0) {\n if (includeAllQueues) {\n // All items from these spaces (both space objects and queue objects).\n sourceConditions.push(sql`m.spaceId IN ${sql.in(spaceId)}`);\n } else {\n // Only space objects (not queue objects) from these spaces.\n sourceConditions.push(sql`(m.spaceId IN ${sql.in(spaceId)} AND m.queueId = '')`);\n }\n }\n\n if (queueIds && queueIds.length > 0) {\n // Items from specific queues.\n sourceConditions.push(sql`m.queueId IN ${sql.in(queueIds)}`);\n }\n\n if (sourceConditions.length > 0) {\n conditions.push(sql`(${sql.or(sourceConditions)})`);\n }\n\n if (useBm25) {\n // Use BM25 ranking for FTS5 MATCH queries.\n // BM25 returns negative values, negate to get higher = better match.\n // Order by rank descending so best matches come first.\n // Note: bm25() requires the actual table name, not an alias.\n const rows = yield* sql<ObjectMeta & { rank: number }>`\n SELECT m.*, -bm25(ftsIndex) AS rank \n FROM ftsIndex AS f \n JOIN objectMeta AS m ON f.rowid = m.recordId \n WHERE ${sql.and(conditions)}\n ORDER BY rank DESC\n `;\n return rows;\n } else {\n // LIKE fallback - no ranking available, default to 1.\n const rows = yield* sql<ObjectMeta>`\n SELECT m.* \n FROM ftsIndex AS f \n JOIN objectMeta AS m ON f.rowid = m.recordId \n WHERE ${sql.and(conditions)}\n `;\n return rows.map((row) => ({ ...row, rank: 1 }));\n }\n });\n }\n\n /**\n * Query snapshots by recordIds.\n * Returns the parsed JSON snapshots for queue objects.\n * RecordIds not present in the FTS index are silently omitted from the result.\n */\n querySnapshotsJSON(\n recordIds: number[],\n ): Effect.Effect<readonly { recordId: number; snapshot: Obj.JSON }[], SqlError.SqlError, SqlClient.SqlClient> {\n return Effect.gen(function* () {\n if (recordIds.length === 0) {\n return [];\n }\n const sql = yield* SqlClient.SqlClient;\n\n // Chunk to avoid SQLite bound-variable limit (SQLITE_LIMIT_VARIABLE_NUMBER,\n // typically 999 in wasm builds). 500 gives a safe margin.\n const chunks: number[][] = [];\n for (let i = 0; i < recordIds.length; i += SQL_CHUNK_SIZE) {\n chunks.push(recordIds.slice(i, i + SQL_CHUNK_SIZE));\n }\n\n const allResults: { recordId: number; snapshot: Obj.JSON }[] = [];\n for (const chunk of chunks) {\n const rows = yield* sql<{\n rowid: number;\n snapshot: string;\n }>`SELECT rowid, snapshot FROM ftsIndex WHERE rowid IN ${sql.in(chunk)}`;\n for (const r of rows) {\n allResults.push({ recordId: r.rowid, snapshot: JSON.parse(r.snapshot) });\n }\n }\n\n return allResults;\n });\n }\n\n update = Effect.fn('FtsIndex.update')(\n (objects: IndexerObject[]): Effect.Effect<void, SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n yield* Effect.forEach(\n objects,\n (object) =>\n Effect.gen(function* () {\n const { recordId, data } = object;\n if (recordId === null) {\n return yield* Effect.die(new Error('FtsIndex.update requires recordId to be set'));\n }\n\n const snapshot = JSON.stringify(data);\n\n // FTS5 doesn't support UPDATE, need DELETE + INSERT for upsert.\n const existing = yield* sql<{ rowid: number }>`SELECT rowid FROM ftsIndex WHERE rowid = ${recordId}`;\n if (existing.length > 0) {\n yield* sql`DELETE FROM ftsIndex WHERE rowid = ${recordId}`;\n }\n\n yield* sql`INSERT INTO ftsIndex (rowid, snapshot) VALUES (${recordId}, ${snapshot})`;\n }),\n { discard: true },\n );\n }),\n );\n}\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as SqlClient from '@effect/sql/SqlClient';\nimport type * as SqlError from '@effect/sql/SqlError';\nimport type * as Statement from '@effect/sql/Statement';\nimport * as Effect from 'effect/Effect';\nimport * as Schema from 'effect/Schema';\n\nimport { ATTR_DELETED, ATTR_PARENT, ATTR_RELATION_SOURCE, ATTR_RELATION_TARGET, ATTR_TYPE } from '@dxos/echo/internal';\nimport { DXN, type ObjectId, type SpaceId } from '@dxos/keys';\n\nimport type { IndexerObject } from './interface';\nimport type { Index } from './interface';\n\nconst _escapeLikePrefix = (prefix: string) => {\n // Escape LIKE metacharacters in the *literal* prefix (we still append a wildcard for the version suffix).\n // Backslash is used as the ESCAPE character.\n // See: https://www.sqlite.org/lang_expr.html#like\n const escaped = prefix.replaceAll('\\\\', '\\\\\\\\').replaceAll('%', '\\\\%').replaceAll('_', '\\\\_');\n return `${escaped}:%`;\n};\n\nexport const ObjectMeta = Schema.Struct({\n recordId: Schema.Number,\n objectId: Schema.String,\n queueId: Schema.String,\n /** Queue subspace namespace (e.g. 'data', 'trace'). Empty string for non-queue objects. */\n queueNamespace: Schema.String,\n spaceId: Schema.String,\n documentId: Schema.String,\n entityKind: Schema.String,\n /** The versioned DXN of the type of the object. */\n typeDXN: Schema.String,\n deleted: Schema.Boolean,\n source: Schema.NullOr(Schema.String),\n target: Schema.NullOr(Schema.String),\n /** Parent object id (nullable). */\n parent: Schema.NullOr(Schema.String),\n /** Monotonically increasing sequence number assigned on insert/update for tracking indexing order. */\n version: Schema.Number,\n /** Unix ms timestamp when the object was first indexed. */\n createdAt: Schema.NullOr(Schema.Number),\n /** Unix ms timestamp when the object was last re-indexed. */\n updatedAt: Schema.NullOr(Schema.Number),\n});\nexport interface ObjectMeta extends Schema.Schema.Type<typeof ObjectMeta> {}\n\n/**\n * Builds a SQL condition for filtering by space and queue source.\n * When `includeAllQueues` is false and no `queueIds`, only non-queue objects are returned.\n */\nconst buildSourceCondition = (\n sql: SqlClient.SqlClient,\n spaceIds: readonly string[],\n includeAllQueues: boolean,\n queueIds: readonly string[] | null,\n): Statement.Fragment => {\n const conditions: Statement.Fragment[] = [];\n\n if (spaceIds.length > 0) {\n if (includeAllQueues) {\n conditions.push(sql`${sql.in('spaceId', spaceIds)}`);\n } else {\n conditions.push(sql`(${sql.in('spaceId', spaceIds)} AND queueId = '')`);\n }\n }\n\n if (queueIds && queueIds.length > 0) {\n conditions.push(sql`${sql.in('queueId', queueIds)}`);\n }\n\n if (conditions.length === 0) {\n return sql`1 = 0`;\n }\n\n return sql.or(conditions);\n};\n\nexport class ObjectMetaIndex implements Index {\n migrate = Effect.fn('ObjectMetaIndex.runMigrations')(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n yield* sql`CREATE TABLE IF NOT EXISTS objectMeta (\n recordId INTEGER PRIMARY KEY AUTOINCREMENT,\n objectId TEXT NOT NULL,\n queueId TEXT NOT NULL DEFAULT '',\n queueNamespace TEXT NOT NULL DEFAULT '',\n spaceId TEXT NOT NULL,\n documentId TEXT NOT NULL DEFAULT '',\n entityKind TEXT NOT NULL,\n typeDXN TEXT NOT NULL,\n deleted INTEGER NOT NULL,\n source TEXT,\n target TEXT,\n parent TEXT,\n version INTEGER NOT NULL,\n createdAt INTEGER,\n updatedAt INTEGER\n )`;\n\n // Add `parent` column for tables created before it was introduced.\n yield* Effect.catchAll(sql`ALTER TABLE objectMeta ADD COLUMN parent TEXT`, () => Effect.void);\n // Add timestamp columns for tables created before they were introduced.\n yield* Effect.catchAll(sql`ALTER TABLE objectMeta ADD COLUMN createdAt INTEGER`, () => Effect.void);\n yield* Effect.catchAll(sql`ALTER TABLE objectMeta ADD COLUMN updatedAt INTEGER`, () => Effect.void);\n // Add queueNamespace column for tables created before it was introduced.\n yield* Effect.catchAll(\n sql`ALTER TABLE objectMeta ADD COLUMN queueNamespace TEXT NOT NULL DEFAULT ''`,\n () => Effect.void,\n );\n\n yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_objectId ON objectMeta(spaceId, objectId)`;\n yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_typeDXN ON objectMeta(spaceId, typeDXN)`;\n yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_version ON objectMeta(version)`;\n yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_parent ON objectMeta(spaceId, parent)`;\n yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_updatedAt ON objectMeta(updatedAt)`;\n yield* sql`CREATE INDEX IF NOT EXISTS idx_object_index_createdAt ON objectMeta(createdAt)`;\n });\n\n query = Effect.fn('ObjectMetaIndex.query')(\n (\n query: Pick<ObjectMeta, 'spaceId' | 'typeDXN'>,\n ): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n const parsedType = DXN.tryParse(query.typeDXN)?.asTypeDXN();\n\n // SQLite stores booleans as integers, so we need to specify the raw row type.\n const rows =\n parsedType && parsedType.version === undefined\n ? yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE spaceId = ${query.spaceId} AND (typeDXN = ${\n query.typeDXN\n } OR typeDXN LIKE ${_escapeLikePrefix(query.typeDXN)} ESCAPE '\\\\')`\n : yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE spaceId = ${query.spaceId} AND typeDXN = ${query.typeDXN}`;\n return rows.map((row) => ({\n ...row,\n deleted: !!row.deleted,\n }));\n }),\n );\n\n queryAll = Effect.fn('ObjectMetaIndex.queryAll')(\n (query: {\n spaceIds: readonly ObjectMeta['spaceId'][];\n includeAllQueues?: boolean;\n queueIds?: readonly string[] | null;\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n if (query.spaceIds.length === 0 && (!query.queueIds || query.queueIds.length === 0)) {\n return [];\n }\n\n const sql = yield* SqlClient.SqlClient;\n const sourceCondition = buildSourceCondition(\n sql,\n query.spaceIds,\n query.includeAllQueues ?? false,\n query.queueIds ?? null,\n );\n const rows = yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition}`;\n return rows.map((row) => ({\n ...row,\n deleted: !!row.deleted,\n }));\n }),\n );\n\n queryTypes = Effect.fn('ObjectMetaIndex.queryTypes')(\n ({\n spaceIds,\n typeDxns,\n inverted = false,\n includeAllQueues = false,\n queueIds = null,\n }: {\n spaceIds: readonly ObjectMeta['spaceId'][];\n typeDxns: readonly ObjectMeta['typeDXN'][];\n inverted?: boolean;\n includeAllQueues?: boolean;\n queueIds?: readonly string[] | null;\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n if (spaceIds.length === 0 && (!queueIds || queueIds.length === 0)) {\n return [];\n }\n\n if (typeDxns.length === 0) {\n if (!inverted) {\n return [];\n }\n\n const sql = yield* SqlClient.SqlClient;\n const sourceCondition = buildSourceCondition(sql, spaceIds, includeAllQueues, queueIds);\n const rows = yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition}`;\n return rows.map((row) => ({\n ...row,\n deleted: !!row.deleted,\n }));\n }\n const sql = yield* SqlClient.SqlClient;\n const sourceCondition = buildSourceCondition(sql, spaceIds, includeAllQueues, queueIds);\n const typeWhere = sql.or(\n typeDxns.map((typeDXN) => {\n const parsedType = DXN.tryParse(typeDXN)?.asTypeDXN();\n return parsedType && parsedType.version === undefined\n ? sql.or([sql`typeDXN = ${typeDXN}`, sql`typeDXN LIKE ${_escapeLikePrefix(typeDXN)} ESCAPE '\\\\'`])\n : sql`typeDXN = ${typeDXN}`;\n }),\n );\n const rows = inverted\n ? yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition} AND NOT ${typeWhere}`\n : yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition} AND ${typeWhere}`;\n return rows.map((row) => ({\n ...row,\n deleted: !!row.deleted,\n }));\n }),\n );\n\n queryRelations = Effect.fn('ObjectMetaIndex.queryRelations')(\n ({\n endpoint,\n anchorDxns,\n }: {\n endpoint: 'source' | 'target';\n anchorDxns: readonly string[];\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n if (anchorDxns.length === 0) {\n return [];\n }\n const sql = yield* SqlClient.SqlClient;\n const column = endpoint === 'source' ? 'source' : 'target';\n const rows = yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE entityKind = 'relation' AND ${sql.in(\n column,\n anchorDxns,\n )}`;\n return rows.map((row) => ({\n ...row,\n deleted: !!row.deleted,\n }));\n }),\n );\n\n // TODO(dmaretskyi): Update recordId on objects so that we don't need to look it up separately.\n update = Effect.fn('ObjectMetaIndex.update')(\n (objects: IndexerObject[]): Effect.Effect<void, SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n yield* Effect.forEach(\n objects,\n (object) =>\n Effect.gen(function* () {\n const { spaceId, queueId, queueNamespace, documentId, data } = object;\n\n // Extract metadata (Logic emulating Echo APIs as strict imports are unavailable).\n const castData = data;\n const objectId = castData.id;\n\n // Check for existing record by (spaceId, queueId) or (spaceId, documentId).\n let existing: readonly { recordId: number }[];\n if (documentId) {\n existing = yield* sql<{\n recordId: number;\n }>`SELECT recordId FROM objectMeta WHERE spaceId = ${spaceId} AND documentId = ${documentId} AND objectId = ${objectId} LIMIT 1`;\n } else if (queueId) {\n existing = yield* sql<{\n recordId: number;\n }>`SELECT recordId FROM objectMeta WHERE spaceId = ${spaceId} AND queueId = ${queueId} AND objectId = ${objectId} LIMIT 1`;\n } else {\n // Should not happen based on IndexerObject definition (one must be present ideally), but handle gracefully.\n existing = [];\n }\n\n // Get max version + 1.\n const result = yield* sql<{ v: number | null }>`SELECT MAX(version) as v FROM objectMeta`;\n const [{ v }] = result;\n const version = (v ?? 0) + 1;\n\n // Extract metadata.\n const entityKind = castData[ATTR_RELATION_SOURCE] ? 'relation' : 'object';\n const typeDXN = castData[ATTR_TYPE] ? String(castData[ATTR_TYPE]) : 'type';\n const deleted = castData[ATTR_DELETED] ? 1 : 0;\n // Relations.\n const source = entityKind === 'relation' ? (castData[ATTR_RELATION_SOURCE] ?? null) : null;\n const target = entityKind === 'relation' ? (castData[ATTR_RELATION_TARGET] ?? null) : null;\n // Parent (nullable).\n const parent = castData[ATTR_PARENT] ?? null;\n\n const sourceTimestamp = object.updatedAt;\n\n if (existing.length > 0) {\n yield* sql`\n UPDATE objectMeta SET\n version = ${version},\n queueNamespace = ${queueNamespace ?? ''},\n entityKind = ${entityKind},\n typeDXN = ${typeDXN},\n deleted = ${deleted},\n source = ${source},\n target = ${target},\n parent = ${parent},\n updatedAt = ${sourceTimestamp}\n WHERE recordId = ${existing[0].recordId}\n `;\n } else {\n yield* sql`\n INSERT INTO objectMeta (\n objectId, queueId, queueNamespace, spaceId, documentId,\n entityKind, typeDXN, deleted, source, target, parent, version,\n createdAt, updatedAt\n ) VALUES (\n ${objectId}, ${queueId ?? ''}, ${queueNamespace ?? ''}, ${spaceId}, ${documentId ?? ''},\n ${entityKind}, ${typeDXN}, ${deleted},\n ${source}, ${target}, ${parent}, ${version},\n ${sourceTimestamp}, ${sourceTimestamp}\n )\n `;\n }\n }),\n { discard: true },\n );\n }),\n );\n\n /**\n * Look up `recordIds` for objects that are already stored in the ObjectMetaIndex.\n * Mutates the objects in place.\n */\n lookupRecordIds = Effect.fn('ObjectMetaIndex.lookupRecordIds')(\n (objects: IndexerObject[]): Effect.Effect<void, SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n for (const object of objects) {\n const { spaceId, queueId, documentId, data } = object;\n const objectId = data.id;\n\n let result: readonly { recordId: number }[];\n if (documentId) {\n result = yield* sql<{\n recordId: number;\n }>`SELECT recordId FROM objectMeta WHERE spaceId = ${spaceId} AND documentId = ${documentId} AND objectId = ${objectId} LIMIT 1`;\n } else if (queueId) {\n result = yield* sql<{\n recordId: number;\n }>`SELECT recordId FROM objectMeta WHERE spaceId = ${spaceId} AND queueId = ${queueId} AND objectId = ${objectId} LIMIT 1`;\n } else {\n result = [];\n }\n\n if (result.length === 0) {\n // TODO(mykola): Handle this case gracefully.\n return yield* Effect.die(\n new Error(`Object not found in ObjectMetaIndex: ${spaceId}/${documentId ?? queueId}/${objectId}`),\n );\n }\n object.recordId = result[0].recordId;\n }\n }),\n );\n\n /**\n * Look up object metadata by recordIds.\n */\n lookupByRecordIds = Effect.fn('ObjectMetaIndex.lookupByRecordIds')(\n (recordIds: number[]): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n if (recordIds.length === 0) {\n return [];\n }\n\n const sql = yield* SqlClient.SqlClient;\n const rows = yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sql.in('recordId', recordIds)}`;\n\n return rows.map((row) => ({\n ...row,\n deleted: !!row.deleted,\n }));\n }),\n );\n\n /**\n * Look up object metadata by objectId, spaceId, and queueId.\n */\n lookupByObjectId = Effect.fn('ObjectMetaIndex.lookupByObjectId')(\n (query: {\n objectId: string;\n spaceId: string;\n queueId: string;\n }): Effect.Effect<ObjectMeta | null, SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n const rows =\n yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE spaceId = ${query.spaceId} AND queueId = ${query.queueId} AND objectId = ${query.objectId} LIMIT 1`;\n\n if (rows.length === 0) {\n return null;\n }\n\n return {\n ...rows[0],\n deleted: !!rows[0].deleted,\n };\n }),\n );\n\n /**\n * Query objects by timestamp range.\n */\n queryByTimeRange = Effect.fn('ObjectMetaIndex.queryByTimeRange')(\n (query: {\n spaceIds: readonly string[];\n updatedAfter?: number;\n updatedBefore?: number;\n createdAfter?: number;\n createdBefore?: number;\n includeAllQueues?: boolean;\n queueIds?: readonly string[] | null;\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n if (query.spaceIds.length === 0 && (!query.queueIds || query.queueIds.length === 0)) {\n return [];\n }\n\n const sql = yield* SqlClient.SqlClient;\n const sourceCondition = buildSourceCondition(\n sql,\n query.spaceIds,\n query.includeAllQueues ?? false,\n query.queueIds ?? null,\n );\n\n const timeConditions: Statement.Fragment[] = [];\n if (query.updatedAfter != null) {\n timeConditions.push(sql`updatedAt >= ${query.updatedAfter}`);\n }\n if (query.updatedBefore != null) {\n timeConditions.push(sql`updatedAt <= ${query.updatedBefore}`);\n }\n if (query.createdAfter != null) {\n timeConditions.push(sql`createdAt >= ${query.createdAfter}`);\n }\n if (query.createdBefore != null) {\n timeConditions.push(sql`createdAt <= ${query.createdBefore}`);\n }\n\n const rows =\n timeConditions.length > 0\n ? yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition} AND ${sql.and(timeConditions)}`\n : yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sourceCondition}`;\n\n return rows.map((row) => ({\n ...row,\n deleted: !!row.deleted,\n }));\n }),\n );\n\n /**\n * Query children by parent object ids.\n * Matches both:\n * - Objects whose `parent` field references one of the given parent ids (standard parent/child hierarchy).\n * - Queue items whose `queueId` equals one of the parent ids (e.g. items inside a Feed, since a feed's queue\n * DXN uses the feed's object id as its queue id — see `Feed.getQueueDxn`).\n */\n queryChildren = Effect.fn('ObjectMetaIndex.queryChildren')(\n (query: {\n spaceId: SpaceId[];\n parentIds: ObjectId[];\n }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n if (query.parentIds.length === 0) {\n return [];\n }\n\n const sql = yield* SqlClient.SqlClient;\n const parentDxns = query.parentIds.map((id) => DXN.fromLocalObjectId(id).toString());\n const rows =\n yield* sql<ObjectMeta>`SELECT * FROM objectMeta WHERE ${sql.in('spaceId', query.spaceId)} AND (${sql.in('parent', parentDxns)} OR ${sql.in('queueId', query.parentIds)})`;\n\n return rows.map((row) => ({\n ...row,\n deleted: !!row.deleted,\n }));\n }),\n );\n}\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as SqlClient from '@effect/sql/SqlClient';\nimport type * as SqlError from '@effect/sql/SqlError';\nimport * as Effect from 'effect/Effect';\nimport * as Schema from 'effect/Schema';\n\nimport { EncodedReference, isEncodedReference } from '@dxos/echo-protocol';\n\nimport { EscapedPropPath } from '../utils';\nimport type { Index, IndexerObject } from './interface';\n\n/**\n * Extracts all outgoing references from an object's data.\n */\nconst extractReferences = (data: Record<string, unknown>): { path: string[]; targetDXN: string }[] => {\n const refs: { path: string[]; targetDXN: string }[] = [];\n const visit = (path: string[], value: unknown) => {\n if (isEncodedReference(value)) {\n const dxn = EncodedReference.toDXN(value);\n const echoId = dxn.asEchoDXN()?.echoId;\n if (!echoId) {\n return; // Skip non-echo references.\n }\n refs.push({ path, targetDXN: dxn.toString() });\n } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n for (const [key, v] of Object.entries(value)) {\n visit([...path, key], v);\n }\n } else if (Array.isArray(value)) {\n for (let i = 0; i < value.length; i++) {\n visit([...path, String(i)], value[i]);\n }\n }\n };\n visit([], data);\n return refs;\n};\n\nexport const ReverseRef = Schema.Struct({\n recordId: Schema.Number,\n targetDXN: Schema.String,\n /**\n * Escaped property path within an object.\n *\n * Escaping rules:\n *\n * - '.' -> '\\.'\n * - '\\' -> '\\\\'\n * - contact with .\n */\n propPath: Schema.String,\n});\nexport interface ReverseRef extends Schema.Schema.Type<typeof ReverseRef> {}\n\nexport interface ReverseRefQuery {\n targetDXN: string;\n // TODO: Add prop filter\n}\n\n/**\n * Indexes reverse references - tracks which objects reference which targets.\n * Only indexes references, not relations.\n */\nexport class ReverseRefIndex implements Index {\n migrate = Effect.fn('ReverseRefIndex.migrate')(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n yield* sql`CREATE TABLE IF NOT EXISTS reverseRef (\n recordId INTEGER NOT NULL,\n targetDXN TEXT NOT NULL,\n propPath TEXT NOT NULL,\n PRIMARY KEY (recordId, targetDXN, propPath)\n )`;\n\n yield* sql`CREATE INDEX IF NOT EXISTS idx_reverse_ref_target ON reverseRef(targetDXN)`;\n });\n\n /**\n * Query all references pointing to a target DXN.\n */\n query = Effect.fn('ReverseRefIndex.query')(\n ({ targetDXN }: ReverseRefQuery): Effect.Effect<readonly ReverseRef[], SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n // TODO(mykola): Join objectMeta table here.\n const rows = yield* sql`SELECT * FROM reverseRef WHERE targetDXN = ${targetDXN}`;\n return rows as ReverseRef[];\n }),\n );\n\n update = Effect.fn('ReverseRefIndex.update')(\n (objects: IndexerObject[]): Effect.Effect<void, SqlError.SqlError, SqlClient.SqlClient> =>\n Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient;\n\n yield* Effect.forEach(\n objects,\n (object) =>\n Effect.gen(function* () {\n const { recordId, data } = object;\n if (recordId === null) {\n yield* Effect.die(new Error('ReverseRefIndex.update requires recordId to be set'));\n }\n\n // Delete existing references for this record.\n yield* sql`DELETE FROM reverseRef WHERE recordId = ${recordId}`;\n\n // Extract references from data.\n const refs = extractReferences(data as unknown as Record<string, unknown>);\n\n // Insert new references.\n yield* Effect.forEach(\n refs,\n (ref) =>\n sql`INSERT INTO reverseRef (recordId, targetDXN, propPath) VALUES (${recordId}, ${ref.targetDXN}, ${EscapedPropPath.escape(ref.path)})`,\n { discard: true },\n );\n }),\n { discard: true },\n );\n }),\n );\n}\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Schema from 'effect/Schema';\n\nimport { invariant } from '@dxos/invariant';\n\nexport type ObjectPropPath = string[];\n\n/**\n * Escaped property path within an object.\n *\n * Escaping rules:\n *\n * - '.' -> '\\.'\n * - '\\' -> '\\\\'\n * - contact with .\n */\nexport const EscapedPropPath: Schema.SchemaClass<string, string> & {\n escape: (path: ObjectPropPath) => EscapedPropPath;\n unescape: (path: EscapedPropPath) => ObjectPropPath;\n} = class extends Schema.String.annotations({ title: 'EscapedPropPath' }) {\n static escape(path: ObjectPropPath): EscapedPropPath {\n return path.map((p) => p.toString().replaceAll('\\\\', '\\\\\\\\').replaceAll('.', '\\\\.')).join('.');\n }\n\n static unescape(path: EscapedPropPath): ObjectPropPath {\n const parts: string[] = [];\n let current = '';\n\n for (let i = 0; i < path.length; i++) {\n if (path[i] === '\\\\') {\n invariant(i + 1 < path.length && (path[i + 1] === '.' || path[i + 1] === '\\\\'), 'Malformed escaping.');\n current = current + path[i + 1];\n i++;\n } else if (path[i] === '.') {\n parts.push(current);\n current = '';\n } else {\n current += path[i];\n }\n }\n parts.push(current);\n\n return parts;\n }\n};\nexport type EscapedPropPath = Schema.Schema.Type<typeof EscapedPropPath>;\n"],
5
5
  "mappings": "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",
6
- "names": ["Effect", "ATTR_TYPE", "SqlTransaction", "SqlClient", "Effect", "Schema", "SpaceId", "IndexCursor", "Struct", "indexName", "String", "spaceId", "NullOr", "sourceName", "resourceId", "cursor", "Union", "Number", "DEPRECATED_INDEX_NAMES", "IndexTracker", "migrate", "fn", "sql", "forEach", "queryCursors", "query", "gen", "spaceIdParam", "undefined", "sourceNameParam", "resourceIdParam", "rows", "map", "row", "decodeSync", "updateCursors", "cursors", "discard", "SqlClient", "Effect", "SQL_CHUNK_SIZE", "escapeFts5Query", "text", "split", "filter", "Boolean", "map", "term", "replace", "join", "FtsIndex", "migrate", "fn", "sql", "query", "spaceId", "includeAllQueues", "queueIds", "gen", "trimmed", "trim", "length", "terms", "minTermLength", "Math", "min", "t", "useBm25", "conditions", "sourceConditions", "push", "in", "or", "rows", "and", "row", "rank", "querySnapshotsJSON", "recordIds", "chunks", "i", "slice", "allResults", "chunk", "r", "recordId", "rowid", "snapshot", "JSON", "parse", "update", "objects", "forEach", "object", "data", "die", "Error", "stringify", "existing", "discard", "SqlClient", "Effect", "Schema", "ATTR_DELETED", "ATTR_PARENT", "ATTR_RELATION_SOURCE", "ATTR_RELATION_TARGET", "ATTR_TYPE", "DXN", "_escapeLikePrefix", "prefix", "escaped", "replaceAll", "ObjectMeta", "Struct", "recordId", "Number", "objectId", "String", "queueId", "queueNamespace", "spaceId", "documentId", "entityKind", "typeDxn", "deleted", "Boolean", "source", "NullOr", "target", "parent", "version", "createdAt", "updatedAt", "buildSourceCondition", "sql", "spaceIds", "includeAllQueues", "queueIds", "conditions", "length", "push", "in", "or", "ObjectMetaIndex", "migrate", "fn", "catchAll", "void", "query", "gen", "parsedType", "tryParse", "asTypeDXN", "rows", "undefined", "map", "row", "queryAll", "sourceCondition", "queryTypes", "typeDxns", "inverted", "typeWhere", "queryRelations", "endpoint", "anchorDxns", "column", "update", "objects", "forEach", "object", "data", "castData", "id", "existing", "result", "v", "sourceTimestamp", "discard", "lookupRecordIds", "die", "Error", "lookupByRecordIds", "recordIds", "lookupByObjectId", "queryByTimeRange", "timeConditions", "updatedAfter", "updatedBefore", "createdAfter", "createdBefore", "and", "queryChildren", "parentIds", "parentDxns", "fromLocalObjectId", "toString", "SqlClient", "Effect", "Schema", "EncodedReference", "isEncodedReference", "Schema", "invariant", "escape", "unescape", "path", "current", "i", "length", "parts", "extractReferences", "data", "refs", "visit", "path", "value", "isEncodedReference", "dxn", "EncodedReference", "toDXN", "echoId", "asEchoDXN", "push", "targetDxn", "toString", "Array", "isArray", "key", "v", "Object", "entries", "i", "length", "String", "ReverseRef", "Struct", "recordId", "Number", "propPath", "ReverseRefIndex", "migrate", "fn", "sql", "SqlClient", "query", "gen", "rows", "update", "objects", "forEach", "object", "die", "Error", "ref", "EscapedPropPath", "escape", "discard", "makeEmptyIndexingResult", "updated", "done", "spaces", "Set", "queues", "documents", "types", "objects", "accumulateIndexingResult", "acc", "obj", "add", "spaceId", "queueId", "documentId", "t", "data", "ATTR_TYPE", "String", "id", "IndexEngine", "params", "tracker", "IndexTracker", "objectMetaIndex", "ObjectMetaIndex", "ftsIndex", "FtsIndex", "reverseRefIndex", "ReverseRefIndex", "migrate", "gen", "queryText", "query", "queryReverseRef", "queryAll", "querySnapshotsJSON", "recordIds", "queryType", "queryChildren", "queryTypes", "queryByTimeRange", "queryRelations", "lookupByRecordIds", "lookupByObjectId", "update", "ctx", "dataSource", "opts", "result", "updatedFtsIndex", "doneFtsIndex", "ftsObjects", "indexName", "limit", "updatedReverseRefIndex", "doneReverseRefIndex", "reverseRefObjects", "pipe", "withSpan", "index", "source", "sqlTransaction", "SqlTransaction", "withTransaction", "cursors", "queryCursors", "sourceName", "undefined", "updatedCursors", "getChangedObjects", "length", "lookupRecordIds", "updateCursors", "map", "_", "resourceId", "cursor"]
6
+ "names": ["Effect", "ATTR_TYPE", "SqlTransaction", "SqlClient", "Effect", "Schema", "SpaceId", "IndexCursor", "Struct", "indexName", "String", "spaceId", "NullOr", "sourceName", "resourceId", "cursor", "Union", "Number", "DEPRECATED_INDEX_NAMES", "IndexTracker", "migrate", "fn", "sql", "forEach", "queryCursors", "query", "gen", "spaceIdParam", "undefined", "sourceNameParam", "resourceIdParam", "rows", "map", "row", "decodeSync", "updateCursors", "cursors", "discard", "SqlClient", "Effect", "SQL_CHUNK_SIZE", "escapeFts5Query", "text", "split", "filter", "Boolean", "map", "term", "replace", "join", "FtsIndex", "migrate", "fn", "sql", "query", "spaceId", "includeAllQueues", "queueIds", "gen", "trimmed", "trim", "length", "terms", "minTermLength", "Math", "min", "t", "useBm25", "conditions", "sourceConditions", "push", "in", "or", "rows", "and", "row", "rank", "querySnapshotsJSON", "recordIds", "chunks", "i", "slice", "allResults", "chunk", "r", "recordId", "rowid", "snapshot", "JSON", "parse", "update", "objects", "forEach", "object", "data", "die", "Error", "stringify", "existing", "discard", "SqlClient", "Effect", "Schema", "ATTR_DELETED", "ATTR_PARENT", "ATTR_RELATION_SOURCE", "ATTR_RELATION_TARGET", "ATTR_TYPE", "DXN", "_escapeLikePrefix", "prefix", "escaped", "replaceAll", "ObjectMeta", "Struct", "recordId", "Number", "objectId", "String", "queueId", "queueNamespace", "spaceId", "documentId", "entityKind", "typeDXN", "deleted", "Boolean", "source", "NullOr", "target", "parent", "version", "createdAt", "updatedAt", "buildSourceCondition", "sql", "spaceIds", "includeAllQueues", "queueIds", "conditions", "length", "push", "in", "or", "ObjectMetaIndex", "migrate", "fn", "catchAll", "void", "query", "gen", "parsedType", "tryParse", "asTypeDXN", "rows", "undefined", "map", "row", "queryAll", "sourceCondition", "queryTypes", "typeDxns", "inverted", "typeWhere", "queryRelations", "endpoint", "anchorDxns", "column", "update", "objects", "forEach", "object", "data", "castData", "id", "existing", "result", "v", "sourceTimestamp", "discard", "lookupRecordIds", "die", "Error", "lookupByRecordIds", "recordIds", "lookupByObjectId", "queryByTimeRange", "timeConditions", "updatedAfter", "updatedBefore", "createdAfter", "createdBefore", "and", "queryChildren", "parentIds", "parentDxns", "fromLocalObjectId", "toString", "SqlClient", "Effect", "Schema", "EncodedReference", "isEncodedReference", "Schema", "invariant", "escape", "unescape", "path", "current", "i", "length", "parts", "extractReferences", "data", "refs", "visit", "path", "value", "isEncodedReference", "dxn", "EncodedReference", "toDXN", "echoId", "asEchoDXN", "push", "targetDXN", "toString", "Array", "isArray", "key", "v", "Object", "entries", "i", "length", "String", "ReverseRef", "Struct", "recordId", "Number", "propPath", "ReverseRefIndex", "migrate", "fn", "sql", "SqlClient", "query", "gen", "rows", "update", "objects", "forEach", "object", "die", "Error", "ref", "EscapedPropPath", "escape", "discard", "makeEmptyIndexingResult", "updated", "done", "spaces", "Set", "queues", "documents", "types", "objects", "accumulateIndexingResult", "acc", "obj", "add", "spaceId", "queueId", "documentId", "t", "data", "ATTR_TYPE", "String", "id", "IndexEngine", "params", "tracker", "IndexTracker", "objectMetaIndex", "ObjectMetaIndex", "ftsIndex", "FtsIndex", "reverseRefIndex", "ReverseRefIndex", "migrate", "gen", "queryText", "query", "queryReverseRef", "queryAll", "querySnapshotsJSON", "recordIds", "queryType", "queryChildren", "queryTypes", "queryByTimeRange", "queryRelations", "lookupByRecordIds", "lookupByObjectId", "update", "ctx", "dataSource", "opts", "result", "updatedFtsIndex", "doneFtsIndex", "ftsObjects", "indexName", "limit", "updatedReverseRefIndex", "doneReverseRefIndex", "reverseRefObjects", "pipe", "withSpan", "index", "source", "sqlTransaction", "SqlTransaction", "withTransaction", "cursors", "queryCursors", "sourceName", "undefined", "updatedCursors", "getChangedObjects", "length", "lookupRecordIds", "updateCursors", "map", "_", "resourceId", "cursor"]
7
7
  }
@@ -70,7 +70,7 @@ export declare class IndexEngine {
70
70
  recordId: number;
71
71
  snapshot: import("@dxos/echo/Obj").JSON;
72
72
  }[], SqlError.SqlError, SqlClient.SqlClient>;
73
- queryType(query: Pick<ObjectMeta, 'spaceId' | 'typeDxn'>): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient>;
73
+ queryType(query: Pick<ObjectMeta, 'spaceId' | 'typeDXN'>): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient>;
74
74
  /**
75
75
  * Query children by parent object ids.
76
76
  */
@@ -80,7 +80,7 @@ export declare class IndexEngine {
80
80
  }): Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient>;
81
81
  queryTypes(query: {
82
82
  spaceIds: readonly SpaceId[];
83
- typeDxns: readonly ObjectMeta['typeDxn'][];
83
+ typeDxns: readonly ObjectMeta['typeDXN'][];
84
84
  inverted?: boolean;
85
85
  includeAllQueues?: boolean;
86
86
  queueIds?: readonly string[] | null;
@@ -15,7 +15,7 @@ export declare const ObjectMeta: Schema.Struct<{
15
15
  documentId: typeof Schema.String;
16
16
  entityKind: typeof Schema.String;
17
17
  /** The versioned DXN of the type of the object. */
18
- typeDxn: typeof Schema.String;
18
+ typeDXN: typeof Schema.String;
19
19
  deleted: typeof Schema.Boolean;
20
20
  source: Schema.NullOr<typeof Schema.String>;
21
21
  target: Schema.NullOr<typeof Schema.String>;
@@ -32,7 +32,7 @@ export interface ObjectMeta extends Schema.Schema.Type<typeof ObjectMeta> {
32
32
  }
33
33
  export declare class ObjectMetaIndex implements Index {
34
34
  migrate: () => Effect.Effect<void, SqlError.SqlError, SqlClient.SqlClient>;
35
- query: (query: Pick<ObjectMeta, "spaceId" | "typeDxn">) => Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient>;
35
+ query: (query: Pick<ObjectMeta, "spaceId" | "typeDXN">) => Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient>;
36
36
  queryAll: (query: {
37
37
  spaceIds: readonly ObjectMeta['spaceId'][];
38
38
  includeAllQueues?: boolean;
@@ -40,7 +40,7 @@ export declare class ObjectMetaIndex implements Index {
40
40
  }) => Effect.Effect<readonly ObjectMeta[], SqlError.SqlError, SqlClient.SqlClient>;
41
41
  queryTypes: (args_0: {
42
42
  spaceIds: readonly ObjectMeta['spaceId'][];
43
- typeDxns: readonly ObjectMeta['typeDxn'][];
43
+ typeDxns: readonly ObjectMeta['typeDXN'][];
44
44
  inverted?: boolean;
45
45
  includeAllQueues?: boolean;
46
46
  queueIds?: readonly string[] | null;