@dxos/echo-pipeline 0.8.3 → 0.8.4-main.03d5cd7b56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/{browser/chunk-35I6ERLG.mjs → neutral/chunk-VIHYDCCC.mjs} +313 -709
- package/dist/lib/neutral/chunk-VIHYDCCC.mjs.map +7 -0
- package/dist/lib/neutral/chunk-YBC6ONBZ.mjs +247 -0
- package/dist/lib/neutral/chunk-YBC6ONBZ.mjs.map +7 -0
- package/dist/lib/{browser → neutral}/filter/index.mjs +3 -2
- package/dist/lib/neutral/index.mjs +7065 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/{browser → neutral}/testing/index.mjs +90 -311
- package/dist/lib/neutral/testing/index.mjs.map +7 -0
- package/dist/types/src/automerge/automerge-host-subduction.test.d.ts +2 -0
- package/dist/types/src/automerge/automerge-host-subduction.test.d.ts.map +1 -0
- package/dist/types/src/automerge/automerge-host.d.ts +99 -54
- package/dist/types/src/automerge/automerge-host.d.ts.map +1 -1
- package/dist/types/src/automerge/automerge-repo-subduction.test.d.ts +2 -0
- package/dist/types/src/automerge/automerge-repo-subduction.test.d.ts.map +1 -0
- package/dist/types/src/automerge/automerge-repo.test.d.ts +1 -1
- package/dist/types/src/automerge/automerge-repo.test.d.ts.map +1 -1
- package/dist/types/src/automerge/automerge-subduction.test.d.ts +2 -0
- package/dist/types/src/automerge/automerge-subduction.test.d.ts.map +1 -0
- package/dist/types/src/automerge/collection-synchronizer.d.ts +25 -4
- package/dist/types/src/automerge/collection-synchronizer.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-data-monitor.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-network-adapter.d.ts +23 -10
- package/dist/types/src/automerge/echo-network-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/echo-replicator.d.ts +38 -15
- package/dist/types/src/automerge/echo-replicator.d.ts.map +1 -1
- package/dist/types/src/automerge/handle-state.d.ts +52 -0
- package/dist/types/src/automerge/handle-state.d.ts.map +1 -0
- package/dist/types/src/automerge/heads-store.d.ts +9 -2
- package/dist/types/src/automerge/heads-store.d.ts.map +1 -1
- package/dist/types/src/automerge/index.d.ts +2 -1
- package/dist/types/src/automerge/index.d.ts.map +1 -1
- package/dist/types/src/automerge/leveldb-storage-adapter.d.ts +9 -5
- package/dist/types/src/automerge/leveldb-storage-adapter.d.ts.map +1 -1
- package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts +9 -8
- package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts.map +1 -1
- package/dist/types/src/automerge/mesh-echo-replicator.d.ts +6 -5
- package/dist/types/src/automerge/mesh-echo-replicator.d.ts.map +1 -1
- package/dist/types/src/automerge/network-protocol.d.ts.map +1 -1
- package/dist/types/src/automerge/space-collection.d.ts.map +1 -1
- package/dist/types/src/common/codec.d.ts +1 -1
- package/dist/types/src/common/codec.d.ts.map +1 -1
- package/dist/types/src/common/feeds.d.ts.map +1 -1
- package/dist/types/src/common/space-id.d.ts.map +1 -1
- package/dist/types/src/db-host/automerge-data-source.d.ts +35 -0
- package/dist/types/src/db-host/automerge-data-source.d.ts.map +1 -0
- package/dist/types/src/db-host/automerge-data-source.test.d.ts +2 -0
- package/dist/types/src/db-host/automerge-data-source.test.d.ts.map +1 -0
- package/dist/types/src/db-host/automerge-metrics.d.ts.map +1 -1
- package/dist/types/src/db-host/data-service.d.ts +8 -7
- package/dist/types/src/db-host/data-service.d.ts.map +1 -1
- package/dist/types/src/db-host/database-root.d.ts +9 -3
- package/dist/types/src/db-host/database-root.d.ts.map +1 -1
- package/dist/types/src/db-host/documents-synchronizer.d.ts +9 -8
- package/dist/types/src/db-host/documents-synchronizer.d.ts.map +1 -1
- package/dist/types/src/db-host/echo-host.d.ts +62 -27
- package/dist/types/src/db-host/echo-host.d.ts.map +1 -1
- package/dist/types/src/db-host/index.d.ts +2 -0
- package/dist/types/src/db-host/index.d.ts.map +1 -1
- package/dist/types/src/db-host/invalidation-hint.d.ts +19 -0
- package/dist/types/src/db-host/invalidation-hint.d.ts.map +1 -0
- package/dist/types/src/db-host/local-queue-service.d.ts +19 -0
- package/dist/types/src/db-host/local-queue-service.d.ts.map +1 -0
- package/dist/types/src/db-host/query-invalidation.test.d.ts +2 -0
- package/dist/types/src/db-host/query-invalidation.test.d.ts.map +1 -0
- package/dist/types/src/db-host/query-service.d.ts +19 -11
- package/dist/types/src/db-host/query-service.d.ts.map +1 -1
- package/dist/types/src/db-host/queue-data-source.d.ts +38 -0
- package/dist/types/src/db-host/queue-data-source.d.ts.map +1 -0
- package/dist/types/src/db-host/queue-service.test.d.ts +2 -0
- package/dist/types/src/db-host/queue-service.test.d.ts.map +1 -0
- package/dist/types/src/db-host/space-state-manager.d.ts +4 -3
- package/dist/types/src/db-host/space-state-manager.d.ts.map +1 -1
- package/dist/types/src/db-host/stub.d.ts +11 -0
- package/dist/types/src/db-host/stub.d.ts.map +1 -0
- package/dist/types/src/edge/echo-edge-replicator.d.ts +10 -7
- package/dist/types/src/edge/echo-edge-replicator.d.ts.map +1 -1
- package/dist/types/src/edge/echo-edge-subduction-replicator.d.ts +39 -0
- package/dist/types/src/edge/echo-edge-subduction-replicator.d.ts.map +1 -0
- package/dist/types/src/edge/echo-edge-subduction-replicator.test.d.ts +2 -0
- package/dist/types/src/edge/echo-edge-subduction-replicator.test.d.ts.map +1 -0
- package/dist/types/src/edge/index.d.ts +1 -0
- package/dist/types/src/edge/index.d.ts.map +1 -1
- package/dist/types/src/edge/inflight-request-limiter.d.ts.map +1 -1
- package/dist/types/src/filter/filter-match.d.ts +4 -1
- package/dist/types/src/filter/filter-match.d.ts.map +1 -1
- package/dist/types/src/metadata/metadata-store.d.ts +1 -1
- package/dist/types/src/metadata/metadata-store.d.ts.map +1 -1
- package/dist/types/src/pipeline/message-selector.d.ts.map +1 -1
- package/dist/types/src/pipeline/pipeline.d.ts +3 -3
- package/dist/types/src/pipeline/pipeline.d.ts.map +1 -1
- package/dist/types/src/pipeline/timeframe-clock.d.ts.map +1 -1
- package/dist/types/src/query/errors.d.ts +22 -10
- package/dist/types/src/query/errors.d.ts.map +1 -1
- package/dist/types/src/query/plan.d.ts +66 -8
- package/dist/types/src/query/plan.d.ts.map +1 -1
- package/dist/types/src/query/query-executor.d.ts +66 -11
- package/dist/types/src/query/query-executor.d.ts.map +1 -1
- package/dist/types/src/query/query-planner.d.ts +12 -1
- package/dist/types/src/query/query-planner.d.ts.map +1 -1
- package/dist/types/src/space/admission-discovery-extension.d.ts.map +1 -1
- package/dist/types/src/space/auth.d.ts +3 -3
- package/dist/types/src/space/auth.d.ts.map +1 -1
- package/dist/types/src/space/control-pipeline.d.ts +5 -4
- package/dist/types/src/space/control-pipeline.d.ts.map +1 -1
- package/dist/types/src/space/space-manager.d.ts +8 -8
- package/dist/types/src/space/space-manager.d.ts.map +1 -1
- package/dist/types/src/space/space-protocol.d.ts +10 -9
- package/dist/types/src/space/space-protocol.d.ts.map +1 -1
- package/dist/types/src/space/space.d.ts +6 -6
- package/dist/types/src/space/space.d.ts.map +1 -1
- package/dist/types/src/testing/change-metadata.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +0 -2
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/test-agent-builder.d.ts +2 -2
- package/dist/types/src/testing/test-agent-builder.d.ts.map +1 -1
- package/dist/types/src/testing/test-feed-builder.d.ts.map +1 -1
- package/dist/types/src/testing/test-network-adapter.d.ts +1 -1
- package/dist/types/src/testing/test-network-adapter.d.ts.map +1 -1
- package/dist/types/src/testing/test-replicator.d.ts +14 -13
- package/dist/types/src/testing/test-replicator.d.ts.map +1 -1
- package/dist/types/src/util.d.ts +1 -1
- package/dist/types/src/util.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +56 -54
- package/src/automerge/automerge-host-subduction.test.ts +247 -0
- package/src/automerge/automerge-host.test.ts +65 -39
- package/src/automerge/automerge-host.ts +589 -206
- package/src/automerge/automerge-repo-subduction.test.ts +1049 -0
- package/src/automerge/automerge-repo.test.ts +349 -91
- package/src/automerge/automerge-subduction.test.ts +330 -0
- package/src/automerge/collection-synchronizer.test.ts +53 -17
- package/src/automerge/collection-synchronizer.ts +119 -36
- package/src/automerge/echo-data-monitor.ts +1 -1
- package/src/automerge/echo-network-adapter.test.ts +5 -4
- package/src/automerge/echo-network-adapter.ts +74 -30
- package/src/automerge/echo-replicator.ts +41 -15
- package/src/automerge/handle-state.ts +62 -0
- package/src/automerge/heads-store.ts +42 -3
- package/src/automerge/index.ts +2 -1
- package/src/automerge/leveldb-storage-adapter.ts +40 -11
- package/src/automerge/mesh-echo-replicator-connection.ts +12 -8
- package/src/automerge/mesh-echo-replicator.ts +15 -11
- package/src/automerge/storage-adapter.test.ts +1 -1
- package/src/common/space-id.ts +1 -1
- package/src/db-host/automerge-data-source.test.ts +299 -0
- package/src/db-host/automerge-data-source.ts +187 -0
- package/src/db-host/data-service.ts +25 -26
- package/src/db-host/database-root.ts +20 -8
- package/src/db-host/documents-synchronizer.test.ts +143 -14
- package/src/db-host/documents-synchronizer.ts +72 -55
- package/src/db-host/echo-host.ts +348 -96
- package/src/db-host/index.ts +2 -0
- package/src/db-host/invalidation-hint.ts +50 -0
- package/src/db-host/local-queue-service.ts +127 -0
- package/src/db-host/query-invalidation.test.ts +270 -0
- package/src/db-host/query-service.ts +111 -106
- package/src/db-host/queue-data-source.ts +160 -0
- package/src/db-host/queue-service.test.ts +145 -0
- package/src/db-host/space-state-manager.ts +12 -10
- package/src/db-host/stub.ts +33 -0
- package/src/edge/echo-edge-replicator.test.ts +12 -10
- package/src/edge/echo-edge-replicator.ts +144 -58
- package/src/edge/echo-edge-subduction-replicator.test.ts +158 -0
- package/src/edge/echo-edge-subduction-replicator.ts +377 -0
- package/src/edge/index.ts +1 -0
- package/src/filter/filter-match.test.ts +68 -32
- package/src/filter/filter-match.ts +184 -20
- package/src/metadata/metadata-store.ts +11 -8
- package/src/pipeline/pipeline-stress.test.ts +1 -1
- package/src/pipeline/pipeline.test.ts +2 -2
- package/src/pipeline/pipeline.ts +9 -7
- package/src/query/errors.ts +3 -1
- package/src/query/plan.ts +82 -8
- package/src/query/query-executor.ts +1199 -164
- package/src/query/query-planner.test.ts +993 -81
- package/src/query/query-planner.ts +467 -27
- package/src/space/admission-discovery-extension.ts +3 -3
- package/src/space/auth.ts +6 -6
- package/src/space/control-pipeline.test.ts +5 -4
- package/src/space/control-pipeline.ts +16 -13
- package/src/space/space-manager.browser.test.ts +1 -1
- package/src/space/space-manager.ts +16 -20
- package/src/space/space-protocol.browser.test.ts +15 -14
- package/src/space/space-protocol.test.ts +15 -14
- package/src/space/space-protocol.ts +22 -18
- package/src/space/space.test.ts +3 -3
- package/src/space/space.ts +11 -11
- package/src/testing/index.ts +0 -2
- package/src/testing/test-agent-builder.ts +3 -3
- package/src/testing/test-network-adapter.ts +24 -3
- package/src/testing/test-replicator.ts +19 -20
- package/src/util.ts +7 -3
- package/dist/lib/browser/chunk-35I6ERLG.mjs.map +0 -7
- package/dist/lib/browser/chunk-CGS2ULMK.mjs +0 -11
- package/dist/lib/browser/chunk-CGS2ULMK.mjs.map +0 -7
- package/dist/lib/browser/chunk-TQJTKNMS.mjs +0 -126
- package/dist/lib/browser/chunk-TQJTKNMS.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -4743
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/testing/index.mjs.map +0 -7
- package/dist/lib/node/chunk-HOPOFWAL.cjs +0 -147
- package/dist/lib/node/chunk-HOPOFWAL.cjs.map +0 -7
- package/dist/lib/node/chunk-JXX6LF5U.cjs +0 -2084
- package/dist/lib/node/chunk-JXX6LF5U.cjs.map +0 -7
- package/dist/lib/node/chunk-Q7SFCCGT.cjs +0 -33
- package/dist/lib/node/chunk-Q7SFCCGT.cjs.map +0 -7
- package/dist/lib/node/filter/index.cjs +0 -32
- package/dist/lib/node/filter/index.cjs.map +0 -7
- package/dist/lib/node/index.cjs +0 -4699
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
- package/dist/lib/node/testing/index.cjs +0 -753
- package/dist/lib/node/testing/index.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-5BHLPT24.mjs +0 -2067
- package/dist/lib/node-esm/chunk-5BHLPT24.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-RVK35BS7.mjs +0 -126
- package/dist/lib/node-esm/chunk-RVK35BS7.mjs.map +0 -7
- package/dist/lib/node-esm/filter/index.mjs +0 -11
- package/dist/lib/node-esm/filter/index.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -4743
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/testing/index.mjs +0 -742
- package/dist/lib/node-esm/testing/index.mjs.map +0 -7
- package/dist/types/src/db-host/documents-iterator.d.ts +0 -7
- package/dist/types/src/db-host/documents-iterator.d.ts.map +0 -1
- package/dist/types/src/testing/test-data.d.ts +0 -18
- package/dist/types/src/testing/test-data.d.ts.map +0 -1
- package/dist/types/src/testing/test-schema.d.ts +0 -37
- package/dist/types/src/testing/test-schema.d.ts.map +0 -1
- package/src/db-host/documents-iterator.ts +0 -74
- package/src/testing/test-data.ts +0 -127
- package/src/testing/test-schema.ts +0 -53
- /package/dist/lib/{browser → neutral}/filter/index.mjs.map +0 -0
|
@@ -2,19 +2,17 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import { Schema } from 'effect';
|
|
5
|
+
import type * as SqlClient from '@effect/sql/SqlClient';
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
8
7
|
|
|
9
8
|
import { DeferredTask, scheduleMicroTask, synchronized } from '@dxos/async';
|
|
10
9
|
import { Stream } from '@dxos/codec-protobuf/stream';
|
|
11
|
-
import { Context, Resource } from '@dxos/context';
|
|
10
|
+
import { type Context, Resource } from '@dxos/context';
|
|
12
11
|
import { raise } from '@dxos/debug';
|
|
13
|
-
import {
|
|
14
|
-
import { type
|
|
12
|
+
import { QueryAST } from '@dxos/echo-protocol';
|
|
13
|
+
import { type RuntimeProvider } from '@dxos/effect';
|
|
14
|
+
import { type IndexEngine } from '@dxos/index-core';
|
|
15
15
|
import { log } from '@dxos/log';
|
|
16
|
-
import { objectPointerCodec } from '@dxos/protocols';
|
|
17
|
-
import { type IndexConfig } from '@dxos/protocols/proto/dxos/echo/indexing';
|
|
18
16
|
import {
|
|
19
17
|
type QueryRequest,
|
|
20
18
|
type QueryResponse,
|
|
@@ -23,12 +21,14 @@ import {
|
|
|
23
21
|
} from '@dxos/protocols/proto/dxos/echo/query';
|
|
24
22
|
import { trace } from '@dxos/tracing';
|
|
25
23
|
|
|
26
|
-
import type { SpaceStateManager } from './space-state-manager';
|
|
27
24
|
import { type AutomergeHost } from '../automerge';
|
|
28
25
|
import { QueryExecutor } from '../query';
|
|
26
|
+
import { type InvalidationHint, mergeHints } from './invalidation-hint';
|
|
27
|
+
import type { SpaceStateManager } from './space-state-manager';
|
|
29
28
|
|
|
30
|
-
export type
|
|
31
|
-
|
|
29
|
+
export type QueryServiceProps = {
|
|
30
|
+
indexEngine: IndexEngine;
|
|
31
|
+
runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient>;
|
|
32
32
|
automergeHost: AutomergeHost;
|
|
33
33
|
spaceStateManager: SpaceStateManager;
|
|
34
34
|
};
|
|
@@ -53,14 +53,39 @@ type ActiveQuery = {
|
|
|
53
53
|
close: () => Promise<void>;
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
+
type QueryInvalidationStats = {
|
|
57
|
+
totalInvalidations: number;
|
|
58
|
+
catchAllInvalidations: number;
|
|
59
|
+
hintedInvalidations: number;
|
|
60
|
+
totalDirtyQueriesExecuted: number;
|
|
61
|
+
totalExecutionBatches: number;
|
|
62
|
+
averageDirtyPerBatch: number;
|
|
63
|
+
averageQueriesActive: number;
|
|
64
|
+
};
|
|
65
|
+
|
|
56
66
|
@trace.resource()
|
|
57
67
|
export class QueryServiceImpl extends Resource implements QueryService {
|
|
68
|
+
// TODO(dmaretskyi): We need to implement query deduping. Idle composer has 80 queries with only 10 being unique.
|
|
58
69
|
private readonly _queries = new Set<ActiveQuery>();
|
|
59
70
|
|
|
60
71
|
private _updateQueries!: DeferredTask;
|
|
61
72
|
|
|
73
|
+
// 'all' = catch-all; null = no pending hint.
|
|
74
|
+
#pendingHint: InvalidationHint | 'all' | null = null;
|
|
75
|
+
|
|
76
|
+
// Diagnostic counters.
|
|
77
|
+
#stats: QueryInvalidationStats = {
|
|
78
|
+
totalInvalidations: 0,
|
|
79
|
+
catchAllInvalidations: 0,
|
|
80
|
+
hintedInvalidations: 0,
|
|
81
|
+
totalDirtyQueriesExecuted: 0,
|
|
82
|
+
totalExecutionBatches: 0,
|
|
83
|
+
averageDirtyPerBatch: 0,
|
|
84
|
+
averageQueriesActive: 0,
|
|
85
|
+
};
|
|
86
|
+
|
|
62
87
|
// TODO(burdon): OK for options, but not params. Pass separately and type readonly here.
|
|
63
|
-
constructor(private readonly _params:
|
|
88
|
+
constructor(private readonly _params: QueryServiceProps) {
|
|
64
89
|
super();
|
|
65
90
|
|
|
66
91
|
trace.diagnostic({
|
|
@@ -76,12 +101,16 @@ export class QueryServiceImpl extends Resource implements QueryService {
|
|
|
76
101
|
});
|
|
77
102
|
},
|
|
78
103
|
});
|
|
104
|
+
|
|
105
|
+
trace.diagnostic<QueryInvalidationStats>({
|
|
106
|
+
id: 'query-invalidation',
|
|
107
|
+
name: 'Query Invalidation',
|
|
108
|
+
fetch: async () => ({ ...this.#stats }),
|
|
109
|
+
});
|
|
79
110
|
}
|
|
80
111
|
|
|
81
112
|
override async _open(): Promise<void> {
|
|
82
|
-
this.
|
|
83
|
-
|
|
84
|
-
this._updateQueries = new DeferredTask(this._ctx, this._executeQueries.bind(this));
|
|
113
|
+
this._updateQueries = new DeferredTask(this._ctx, () => this._executeQueries(this._ctx));
|
|
85
114
|
}
|
|
86
115
|
|
|
87
116
|
@synchronized
|
|
@@ -90,8 +119,19 @@ export class QueryServiceImpl extends Resource implements QueryService {
|
|
|
90
119
|
await Promise.all(Array.from(this._queries).map((query) => query.close()));
|
|
91
120
|
}
|
|
92
121
|
|
|
93
|
-
|
|
94
|
-
|
|
122
|
+
/**
|
|
123
|
+
* @deprecated No longer needed with SQL-based indexing.
|
|
124
|
+
*/
|
|
125
|
+
async setConfig(): Promise<void> {
|
|
126
|
+
// No-op: SQL indexer doesn't need explicit configuration.
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @deprecated No longer needed with SQL-based indexing.
|
|
131
|
+
*/
|
|
132
|
+
async reindex(): Promise<void> {
|
|
133
|
+
// No-op: SQL indexer handles re-indexing automatically.
|
|
134
|
+
log.warn('reindex() is deprecated and no longer has any effect');
|
|
95
135
|
}
|
|
96
136
|
|
|
97
137
|
execQuery(request: QueryRequest): Stream<QueryResponse> {
|
|
@@ -107,32 +147,20 @@ export class QueryServiceImpl extends Resource implements QueryService {
|
|
|
107
147
|
}
|
|
108
148
|
|
|
109
149
|
/**
|
|
110
|
-
*
|
|
150
|
+
* Schedule re-execution of queries, optionally guided by a targeted hint.
|
|
151
|
+
* When called without a hint, all queries are marked dirty (catch-all invalidation).
|
|
111
152
|
*/
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
log('Collected documents...', { count: ids.size });
|
|
153
|
+
invalidateQueries(hint?: InvalidationHint): void {
|
|
154
|
+
this.#stats.totalInvalidations++;
|
|
155
|
+
if (!hint) {
|
|
156
|
+
this.#pendingHint = 'all';
|
|
157
|
+
this.#stats.catchAllInvalidations++;
|
|
158
|
+
} else {
|
|
159
|
+
this.#stats.hintedInvalidations++;
|
|
160
|
+
if (this.#pendingHint !== 'all') {
|
|
161
|
+
this.#pendingHint = this.#pendingHint ? mergeHints(this.#pendingHint, hint) : hint;
|
|
122
162
|
}
|
|
123
163
|
}
|
|
124
|
-
|
|
125
|
-
log('Marking all documents as dirty...', { count: ids.size });
|
|
126
|
-
await this._params.indexer.reindex(ids);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Schedule re-execution of all queries.
|
|
131
|
-
*/
|
|
132
|
-
invalidateQueries() {
|
|
133
|
-
for (const query of this._queries) {
|
|
134
|
-
query.dirty = true;
|
|
135
|
-
}
|
|
136
164
|
this._updateQueries.schedule();
|
|
137
165
|
}
|
|
138
166
|
|
|
@@ -146,7 +174,8 @@ export class QueryServiceImpl extends Resource implements QueryService {
|
|
|
146
174
|
const parsedQuery = QueryAST.Query.pipe(Schema.decodeUnknownSync)(JSON.parse(request.query));
|
|
147
175
|
const queryEntry: ActiveQuery = {
|
|
148
176
|
executor: new QueryExecutor({
|
|
149
|
-
|
|
177
|
+
indexEngine: this._params.indexEngine,
|
|
178
|
+
runtime: this._params.runtime,
|
|
150
179
|
automergeHost: this._params.automergeHost,
|
|
151
180
|
queryId: request.queryId ?? raise(new Error('query id required')),
|
|
152
181
|
query: parsedQuery,
|
|
@@ -173,17 +202,39 @@ export class QueryServiceImpl extends Resource implements QueryService {
|
|
|
173
202
|
return queryEntry;
|
|
174
203
|
}
|
|
175
204
|
|
|
176
|
-
@trace.span({ showInBrowserTimeline: true })
|
|
177
|
-
private async _executeQueries() {
|
|
178
|
-
|
|
205
|
+
@trace.span({ showInBrowserTimeline: true, showInRemoteTracing: false })
|
|
206
|
+
private async _executeQueries(_ctx: Context) {
|
|
207
|
+
const hint = this.#pendingHint;
|
|
208
|
+
this.#pendingHint = null;
|
|
209
|
+
|
|
210
|
+
// Apply hint to determine which queries need re-execution.
|
|
211
|
+
for (const query of this._queries) {
|
|
212
|
+
if (!query.open) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (query.firstResult) {
|
|
216
|
+
// First run is always executed regardless of hint.
|
|
217
|
+
query.dirty = true;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (hint === 'all') {
|
|
221
|
+
query.dirty = true;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (hint && query.executor.matchesHint(hint)) {
|
|
225
|
+
query.dirty = true;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
179
229
|
const begin = performance.now();
|
|
180
|
-
let
|
|
230
|
+
let dirtyCount = 0;
|
|
231
|
+
const activeCount = this._queries.size;
|
|
181
232
|
await Promise.all(
|
|
182
233
|
Array.from(this._queries).map(async (query) => {
|
|
183
234
|
if (!query.dirty || !query.open) {
|
|
184
235
|
return;
|
|
185
236
|
}
|
|
186
|
-
|
|
237
|
+
dirtyCount++;
|
|
187
238
|
|
|
188
239
|
try {
|
|
189
240
|
const { changed } = await query.executor.execQuery();
|
|
@@ -193,68 +244,22 @@ export class QueryServiceImpl extends Resource implements QueryService {
|
|
|
193
244
|
query.sendResults(query.executor.getResults());
|
|
194
245
|
}
|
|
195
246
|
} catch (err) {
|
|
196
|
-
log.catch(err
|
|
247
|
+
log.catch(err, {
|
|
248
|
+
queryId: query.executor.queryId,
|
|
249
|
+
query: JSON.stringify(query.executor.query),
|
|
250
|
+
});
|
|
251
|
+
query.onError(err as Error);
|
|
197
252
|
}
|
|
198
253
|
}),
|
|
199
254
|
);
|
|
200
|
-
log.verbose('executed queries', { count, duration: performance.now() - begin });
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Factory for `getAllDocuments` iterator.
|
|
206
|
-
*/
|
|
207
|
-
// TODO(dmaretskyi): Get roots from echo-host.
|
|
208
|
-
const createDocumentsIterator = (automergeHost: AutomergeHost) =>
|
|
209
|
-
/**
|
|
210
|
-
* Recursively get all object data blobs from loaded documents from Automerge Repo.
|
|
211
|
-
*/
|
|
212
|
-
// TODO(mykola): Unload automerge handles after usage.
|
|
213
|
-
async function* getAllDocuments(): AsyncGenerator<ObjectSnapshot[], void, void> {
|
|
214
|
-
/** visited automerge handles */
|
|
215
|
-
const visited = new Set<string>();
|
|
216
|
-
|
|
217
|
-
async function* getObjectsFromHandle(handle: DocHandle<DatabaseDirectory>): AsyncGenerator<ObjectSnapshot[]> {
|
|
218
|
-
if (visited.has(handle.documentId) || !handle.isReady()) {
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const doc = handle.doc()!;
|
|
223
|
-
const spaceKey = DatabaseDirectory.getSpaceKey(doc) ?? undefined;
|
|
224
|
-
if (doc.objects) {
|
|
225
|
-
yield Object.entries(doc.objects as { [key: string]: any }).map(([objectId, object]) => {
|
|
226
|
-
return {
|
|
227
|
-
id: objectPointerCodec.encode({ documentId: handle.documentId, objectId, spaceKey }),
|
|
228
|
-
object,
|
|
229
|
-
heads: getHeads(doc),
|
|
230
|
-
};
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (doc.links) {
|
|
235
|
-
for (const id of Object.values(doc.links as { [echoId: string]: string })) {
|
|
236
|
-
const urlString = id.toString();
|
|
237
|
-
if (visited.has(urlString)) {
|
|
238
|
-
continue;
|
|
239
|
-
}
|
|
240
|
-
const linkHandle = await automergeHost.loadDoc<DatabaseDirectory>(Context.default(), urlString as DocumentId);
|
|
241
|
-
for await (const result of getObjectsFromHandle(linkHandle)) {
|
|
242
|
-
yield result;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
255
|
|
|
247
|
-
|
|
248
|
-
|
|
256
|
+
this.#stats.totalExecutionBatches++;
|
|
257
|
+
this.#stats.totalDirtyQueriesExecuted += dirtyCount;
|
|
258
|
+
this.#stats.averageDirtyPerBatch = this.#stats.totalDirtyQueriesExecuted / this.#stats.totalExecutionBatches;
|
|
259
|
+
this.#stats.averageQueriesActive =
|
|
260
|
+
(this.#stats.averageQueriesActive * (this.#stats.totalExecutionBatches - 1) + activeCount) /
|
|
261
|
+
this.#stats.totalExecutionBatches;
|
|
249
262
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
continue;
|
|
254
|
-
}
|
|
255
|
-
for await (const result of getObjectsFromHandle(handle)) {
|
|
256
|
-
yield result;
|
|
257
|
-
}
|
|
258
|
-
visited.add(handle.documentId);
|
|
259
|
-
}
|
|
260
|
-
};
|
|
263
|
+
log.verbose('executed queries', { dirty: dirtyCount, active: activeCount, duration: performance.now() - begin });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import type * as SqlClient from '@effect/sql/SqlClient';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
|
|
8
|
+
import { type Context } from '@dxos/context';
|
|
9
|
+
import { EchoFeedCodec } from '@dxos/echo-protocol';
|
|
10
|
+
import { type ObjectJSON } from '@dxos/echo/internal';
|
|
11
|
+
import { RuntimeProvider } from '@dxos/effect';
|
|
12
|
+
import { type FeedStore } from '@dxos/feed';
|
|
13
|
+
import { type DataSourceCursor, type IndexDataSource, type IndexerObject } from '@dxos/index-core';
|
|
14
|
+
import { failedInvariant } from '@dxos/invariant';
|
|
15
|
+
import type { SpaceId } from '@dxos/keys';
|
|
16
|
+
import { log } from '@dxos/log';
|
|
17
|
+
import { FeedProtocol } from '@dxos/protocols';
|
|
18
|
+
|
|
19
|
+
export type QueueDataSourceOptions = {
|
|
20
|
+
feedStore: FeedStore;
|
|
21
|
+
runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient>;
|
|
22
|
+
/**
|
|
23
|
+
* Callback to get the list of space IDs that should be indexed.
|
|
24
|
+
*/
|
|
25
|
+
getSpaceIds: () => SpaceId[];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Feed namespaces to index.
|
|
29
|
+
* @default [WellKnownNamespaces.data, WellKnownNamespaces.trace]
|
|
30
|
+
*/
|
|
31
|
+
feedNamespaces?: string[];
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export class QueueDataSource implements IndexDataSource {
|
|
35
|
+
readonly sourceName = 'queue';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Feed namespaces to index.
|
|
39
|
+
*/
|
|
40
|
+
private readonly _feedNamespaces: string[];
|
|
41
|
+
private readonly _feedStore: FeedStore;
|
|
42
|
+
private readonly _runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient>;
|
|
43
|
+
private readonly _getSpaceIds: () => SpaceId[];
|
|
44
|
+
|
|
45
|
+
constructor(options: QueueDataSourceOptions) {
|
|
46
|
+
this._feedStore = options.feedStore;
|
|
47
|
+
this._runtime = options.runtime;
|
|
48
|
+
this._getSpaceIds = options.getSpaceIds;
|
|
49
|
+
this._feedNamespaces = options.feedNamespaces ?? [
|
|
50
|
+
FeedProtocol.WellKnownNamespaces.data,
|
|
51
|
+
FeedProtocol.WellKnownNamespaces.trace,
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
getChangedObjects(
|
|
56
|
+
_ctx: Context,
|
|
57
|
+
cursors: DataSourceCursor[],
|
|
58
|
+
opts?: { limit?: number },
|
|
59
|
+
): Effect.Effect<{ objects: IndexerObject[]; cursors: DataSourceCursor[] }> {
|
|
60
|
+
// For queue, the cursor is assumed to have:
|
|
61
|
+
// spaceId = set
|
|
62
|
+
// resourceId = null
|
|
63
|
+
// cursor = feed cursor
|
|
64
|
+
|
|
65
|
+
// We also add new cursors from all previously unindexed spaces.
|
|
66
|
+
|
|
67
|
+
return Effect.gen(this, function* () {
|
|
68
|
+
const objects: IndexerObject[] = [];
|
|
69
|
+
const updatedCursors: DataSourceCursor[] = [];
|
|
70
|
+
|
|
71
|
+
// Delete old cursors that did not have feed namespace set.
|
|
72
|
+
let augmentedCursors = [...cursors].filter((c) => c.resourceId !== null);
|
|
73
|
+
for (const spaceId of this._getSpaceIds()) {
|
|
74
|
+
for (const feedNamespace of this._feedNamespaces) {
|
|
75
|
+
if (!augmentedCursors.some((c) => c.spaceId === spaceId && c.resourceId === feedNamespace)) {
|
|
76
|
+
// Add a new cursor for this space starting from the beginning.
|
|
77
|
+
// Empty string cursor means "start from beginning".
|
|
78
|
+
augmentedCursors.push({
|
|
79
|
+
spaceId,
|
|
80
|
+
resourceId: feedNamespace,
|
|
81
|
+
cursor: '',
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Limit per call, but we might have multiple spaces.
|
|
88
|
+
// We should distribute limit or just fill up to limit.
|
|
89
|
+
let remainingLimit = opts?.limit ?? Infinity;
|
|
90
|
+
|
|
91
|
+
for (const cursor of augmentedCursors) {
|
|
92
|
+
if (remainingLimit <= 0) {
|
|
93
|
+
// Remaining cursors are not processed in this call.
|
|
94
|
+
updatedCursors.push(cursor);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (!cursor.spaceId) {
|
|
98
|
+
// Ignore cursors without spaceId.
|
|
99
|
+
updatedCursors.push(cursor);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (
|
|
103
|
+
FeedProtocol.WellKnownNamespaces.data !== cursor.resourceId &&
|
|
104
|
+
FeedProtocol.WellKnownNamespaces.trace !== cursor.resourceId
|
|
105
|
+
) {
|
|
106
|
+
// TODO(dmaretskyi): Update/remove this check when adding new feed namespaces.
|
|
107
|
+
log.warn('Ignoring cursor with invaliding feed namespace', { namespace: cursor.resourceId });
|
|
108
|
+
// Ignore cursors with unknown resourceId.
|
|
109
|
+
updatedCursors.push(cursor);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Empty string cursor means "start from beginning".
|
|
114
|
+
const currentCursor =
|
|
115
|
+
typeof cursor.cursor === 'string' && cursor.cursor !== ''
|
|
116
|
+
? FeedProtocol.FeedCursor.make(cursor.cursor)
|
|
117
|
+
: undefined;
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const result = yield* this._feedStore.query({
|
|
121
|
+
spaceId: cursor.spaceId,
|
|
122
|
+
feedNamespace: cursor.resourceId,
|
|
123
|
+
cursor: currentCursor,
|
|
124
|
+
limit: remainingLimit,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Process blocks
|
|
128
|
+
for (const block of result.blocks) {
|
|
129
|
+
try {
|
|
130
|
+
const data = EchoFeedCodec.decode(block.data) as ObjectJSON;
|
|
131
|
+
|
|
132
|
+
objects.push({
|
|
133
|
+
spaceId: cursor.spaceId,
|
|
134
|
+
queueId: block.feedId ?? failedInvariant(),
|
|
135
|
+
queueNamespace: cursor.resourceId,
|
|
136
|
+
documentId: null,
|
|
137
|
+
recordId: null,
|
|
138
|
+
data,
|
|
139
|
+
updatedAt: block.timestamp,
|
|
140
|
+
});
|
|
141
|
+
} catch (err) {
|
|
142
|
+
log.warn('Failed to parse block data for indexing', { spaceId: cursor.spaceId, err });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
remainingLimit -= result.blocks.length;
|
|
147
|
+
updatedCursors.push({
|
|
148
|
+
spaceId: cursor.spaceId,
|
|
149
|
+
resourceId: cursor.resourceId,
|
|
150
|
+
cursor: result.nextCursor,
|
|
151
|
+
});
|
|
152
|
+
} catch (error) {
|
|
153
|
+
log.error('Error querying queue for indexing', { spaceId: cursor.spaceId, error });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return { objects, cursors: updatedCursors };
|
|
158
|
+
}).pipe(RuntimeProvider.provide(this._runtime), Effect.withSpan('QueueDataSource.getChangedObjects'), Effect.orDie);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as SqliteClient from '@effect/sql-sqlite-node/SqliteClient';
|
|
6
|
+
import type * as SqlClient from '@effect/sql/SqlClient';
|
|
7
|
+
import { describe, expect, it } from '@effect/vitest';
|
|
8
|
+
import * as Effect from 'effect/Effect';
|
|
9
|
+
import * as Layer from 'effect/Layer';
|
|
10
|
+
|
|
11
|
+
import { RuntimeProvider } from '@dxos/effect';
|
|
12
|
+
import { FeedStore } from '@dxos/feed';
|
|
13
|
+
import { ObjectId, SpaceId } from '@dxos/keys';
|
|
14
|
+
import { FeedProtocol } from '@dxos/protocols';
|
|
15
|
+
import { SqlTransaction } from '@dxos/sql-sqlite';
|
|
16
|
+
|
|
17
|
+
import { LocalQueueServiceImpl } from './local-queue-service';
|
|
18
|
+
|
|
19
|
+
const TestLayer = SqlTransaction.layer.pipe(
|
|
20
|
+
Layer.provideMerge(
|
|
21
|
+
SqliteClient.layer({
|
|
22
|
+
filename: ':memory:',
|
|
23
|
+
}),
|
|
24
|
+
),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
describe('LocalQueueServiceImpl', () => {
|
|
28
|
+
it.effect('should insert and query items', () =>
|
|
29
|
+
Effect.gen(function* () {
|
|
30
|
+
const feedStore = new FeedStore({ localActorId: 'actor-id', assignPositions: true });
|
|
31
|
+
const runtime = yield* RuntimeProvider.currentRuntime<SqlClient.SqlClient | SqlTransaction.SqlTransaction>();
|
|
32
|
+
const service = new LocalQueueServiceImpl(runtime, feedStore);
|
|
33
|
+
yield* feedStore.migrate();
|
|
34
|
+
|
|
35
|
+
const spaceId = SpaceId.random();
|
|
36
|
+
const queueId = ObjectId.random();
|
|
37
|
+
const object1 = { id: 'obj1', data: 'test1' };
|
|
38
|
+
const object2 = { id: 'obj2', data: 'test2' };
|
|
39
|
+
|
|
40
|
+
yield* Effect.promise(() =>
|
|
41
|
+
service.insertIntoQueue({
|
|
42
|
+
subspaceTag: FeedProtocol.WellKnownNamespaces.data,
|
|
43
|
+
spaceId,
|
|
44
|
+
queueId,
|
|
45
|
+
objects: [object1, object2].map((obj) => JSON.stringify(obj)),
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const result = yield* Effect.promise(() =>
|
|
50
|
+
service.queryQueue({
|
|
51
|
+
query: { spaceId, queueIds: [queueId] },
|
|
52
|
+
}),
|
|
53
|
+
);
|
|
54
|
+
expect(JSON.parse(result.objects![0])).toMatchObject(object1);
|
|
55
|
+
expect(JSON.parse(result.objects![1])).toMatchObject(object2);
|
|
56
|
+
}).pipe(Effect.provide(TestLayer)),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
it.effect('should delete items', () =>
|
|
60
|
+
Effect.gen(function* () {
|
|
61
|
+
const runtime = Effect.succeed(yield* Effect.runtime<any>());
|
|
62
|
+
const feedStore = new FeedStore({ localActorId: 'actor-id', assignPositions: true });
|
|
63
|
+
const service = new LocalQueueServiceImpl(runtime, feedStore);
|
|
64
|
+
yield* feedStore.migrate();
|
|
65
|
+
|
|
66
|
+
const spaceId = SpaceId.random();
|
|
67
|
+
const queueId = ObjectId.random();
|
|
68
|
+
const object1Id = ObjectId.random();
|
|
69
|
+
const object1 = { id: object1Id, data: 'test1' };
|
|
70
|
+
|
|
71
|
+
yield* Effect.promise(() =>
|
|
72
|
+
service.insertIntoQueue({
|
|
73
|
+
subspaceTag: FeedProtocol.WellKnownNamespaces.data,
|
|
74
|
+
spaceId,
|
|
75
|
+
queueId,
|
|
76
|
+
objects: [JSON.stringify(object1)],
|
|
77
|
+
}),
|
|
78
|
+
);
|
|
79
|
+
yield* Effect.promise(() =>
|
|
80
|
+
service.deleteFromQueue({
|
|
81
|
+
subspaceTag: FeedProtocol.WellKnownNamespaces.data,
|
|
82
|
+
spaceId,
|
|
83
|
+
queueId,
|
|
84
|
+
objectIds: [object1Id],
|
|
85
|
+
}),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const result = yield* Effect.promise(() =>
|
|
89
|
+
service.queryQueue({
|
|
90
|
+
query: { spaceId, queueIds: [queueId] },
|
|
91
|
+
}),
|
|
92
|
+
);
|
|
93
|
+
expect(result.objects).toHaveLength(2);
|
|
94
|
+
expect(JSON.parse(result.objects![1])).toMatchObject({ id: object1Id, '@deleted': true });
|
|
95
|
+
}).pipe(Effect.provide(TestLayer)),
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
it.effect('should support pagination', () =>
|
|
99
|
+
Effect.gen(function* () {
|
|
100
|
+
const runtime = Effect.succeed(yield* Effect.runtime<any>());
|
|
101
|
+
const feedStore = new FeedStore({ localActorId: 'actor-id', assignPositions: true });
|
|
102
|
+
yield* feedStore.migrate();
|
|
103
|
+
const service = new LocalQueueServiceImpl(runtime, feedStore);
|
|
104
|
+
const spaceId = 'space-1' as SpaceId;
|
|
105
|
+
const queueId = ObjectId.random();
|
|
106
|
+
|
|
107
|
+
// Insert 10 items
|
|
108
|
+
const items = Array.from({ length: 10 }, (_, i) => ({ id: `obj${i}`, data: `test${i}` }));
|
|
109
|
+
yield* Effect.promise(() =>
|
|
110
|
+
service.insertIntoQueue({
|
|
111
|
+
subspaceTag: FeedProtocol.WellKnownNamespaces.data,
|
|
112
|
+
spaceId,
|
|
113
|
+
queueId,
|
|
114
|
+
objects: items.map((item) => JSON.stringify(item)),
|
|
115
|
+
}),
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Query first 5
|
|
119
|
+
const page1 = yield* Effect.promise(() =>
|
|
120
|
+
service.queryQueue({
|
|
121
|
+
query: { spaceId, queueIds: [queueId], limit: 5 },
|
|
122
|
+
}),
|
|
123
|
+
);
|
|
124
|
+
expect(page1.objects).toHaveLength(5);
|
|
125
|
+
expect(JSON.parse(page1.objects![0])).toMatchObject(items[0]);
|
|
126
|
+
expect(JSON.parse(page1.objects![4])).toMatchObject(items[4]);
|
|
127
|
+
expect(page1.nextCursor).toBeDefined();
|
|
128
|
+
|
|
129
|
+
// Query next 5
|
|
130
|
+
const page2 = yield* Effect.promise(() =>
|
|
131
|
+
service.queryQueue({
|
|
132
|
+
query: {
|
|
133
|
+
spaceId,
|
|
134
|
+
queueIds: [queueId],
|
|
135
|
+
limit: 5,
|
|
136
|
+
after: page1.nextCursor!,
|
|
137
|
+
},
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
140
|
+
expect(page2.objects).toHaveLength(5);
|
|
141
|
+
expect(JSON.parse(page2.objects![0])).toMatchObject(items[5]);
|
|
142
|
+
expect(JSON.parse(page2.objects![4])).toMatchObject(items[9]);
|
|
143
|
+
}).pipe(Effect.provide(TestLayer)),
|
|
144
|
+
);
|
|
145
|
+
});
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import isEqual from '
|
|
5
|
+
import { type DocumentId, type DocumentQuery, interpretAsDocumentId } from '@automerge/automerge-repo';
|
|
6
|
+
import isEqual from 'fast-deep-equal';
|
|
7
7
|
|
|
8
8
|
import { Event, UpdateScheduler } from '@dxos/async';
|
|
9
|
-
import {
|
|
9
|
+
import { Context, LifecycleState, Resource } from '@dxos/context';
|
|
10
10
|
import { type DatabaseDirectory } from '@dxos/echo-protocol';
|
|
11
11
|
import { invariant } from '@dxos/invariant';
|
|
12
12
|
import { type SpaceId } from '@dxos/keys';
|
|
@@ -32,6 +32,10 @@ export class SpaceStateManager extends Resource {
|
|
|
32
32
|
return this._roots;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
get spaceIds(): SpaceId[] {
|
|
36
|
+
return Array.from(this._rootBySpace.keys());
|
|
37
|
+
}
|
|
38
|
+
|
|
35
39
|
getRootByDocumentId(documentId: DocumentId): DatabaseRoot | undefined {
|
|
36
40
|
return this._roots.get(documentId);
|
|
37
41
|
}
|
|
@@ -49,13 +53,13 @@ export class SpaceStateManager extends Resource {
|
|
|
49
53
|
return this._roots.get(documentId);
|
|
50
54
|
}
|
|
51
55
|
|
|
52
|
-
async assignRootToSpace(spaceId: SpaceId,
|
|
56
|
+
async assignRootToSpace(spaceId: SpaceId, query: DocumentQuery<DatabaseDirectory>): Promise<DatabaseRoot> {
|
|
53
57
|
let root: DatabaseRoot;
|
|
54
|
-
if (this._roots.has(
|
|
55
|
-
root = this._roots.get(
|
|
58
|
+
if (this._roots.has(query.documentId)) {
|
|
59
|
+
root = this._roots.get(query.documentId)!;
|
|
56
60
|
} else {
|
|
57
|
-
root = new DatabaseRoot(
|
|
58
|
-
this._roots.set(
|
|
61
|
+
root = new DatabaseRoot(query);
|
|
62
|
+
this._roots.set(query.documentId, root);
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
if (this._rootBySpace.get(spaceId) === root.handle.documentId) {
|
|
@@ -73,8 +77,6 @@ export class SpaceStateManager extends Resource {
|
|
|
73
77
|
|
|
74
78
|
this._perRootContext.set(root.handle.documentId, ctx);
|
|
75
79
|
|
|
76
|
-
await root.handle.whenReady();
|
|
77
|
-
|
|
78
80
|
const documentListCheckScheduler = new UpdateScheduler(
|
|
79
81
|
ctx,
|
|
80
82
|
async () => {
|