@dxos/echo-pipeline 0.8.3 → 0.8.4-main.1c7ec43d41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/dist/lib/{browser/chunk-35I6ERLG.mjs → neutral/chunk-VIHYDCCC.mjs} +313 -709
  2. package/dist/lib/neutral/chunk-VIHYDCCC.mjs.map +7 -0
  3. package/dist/lib/neutral/chunk-YBC6ONBZ.mjs +247 -0
  4. package/dist/lib/neutral/chunk-YBC6ONBZ.mjs.map +7 -0
  5. package/dist/lib/{browser → neutral}/filter/index.mjs +3 -2
  6. package/dist/lib/neutral/index.mjs +7065 -0
  7. package/dist/lib/neutral/index.mjs.map +7 -0
  8. package/dist/lib/neutral/meta.json +1 -0
  9. package/dist/lib/{browser → neutral}/testing/index.mjs +90 -311
  10. package/dist/lib/neutral/testing/index.mjs.map +7 -0
  11. package/dist/types/src/automerge/automerge-host-subduction.test.d.ts +2 -0
  12. package/dist/types/src/automerge/automerge-host-subduction.test.d.ts.map +1 -0
  13. package/dist/types/src/automerge/automerge-host.d.ts +99 -54
  14. package/dist/types/src/automerge/automerge-host.d.ts.map +1 -1
  15. package/dist/types/src/automerge/automerge-repo-subduction.test.d.ts +2 -0
  16. package/dist/types/src/automerge/automerge-repo-subduction.test.d.ts.map +1 -0
  17. package/dist/types/src/automerge/automerge-repo.test.d.ts +1 -1
  18. package/dist/types/src/automerge/automerge-repo.test.d.ts.map +1 -1
  19. package/dist/types/src/automerge/automerge-subduction.test.d.ts +2 -0
  20. package/dist/types/src/automerge/automerge-subduction.test.d.ts.map +1 -0
  21. package/dist/types/src/automerge/collection-synchronizer.d.ts +25 -4
  22. package/dist/types/src/automerge/collection-synchronizer.d.ts.map +1 -1
  23. package/dist/types/src/automerge/echo-data-monitor.d.ts.map +1 -1
  24. package/dist/types/src/automerge/echo-network-adapter.d.ts +23 -10
  25. package/dist/types/src/automerge/echo-network-adapter.d.ts.map +1 -1
  26. package/dist/types/src/automerge/echo-replicator.d.ts +38 -15
  27. package/dist/types/src/automerge/echo-replicator.d.ts.map +1 -1
  28. package/dist/types/src/automerge/handle-state.d.ts +52 -0
  29. package/dist/types/src/automerge/handle-state.d.ts.map +1 -0
  30. package/dist/types/src/automerge/heads-store.d.ts +9 -2
  31. package/dist/types/src/automerge/heads-store.d.ts.map +1 -1
  32. package/dist/types/src/automerge/index.d.ts +2 -1
  33. package/dist/types/src/automerge/index.d.ts.map +1 -1
  34. package/dist/types/src/automerge/leveldb-storage-adapter.d.ts +9 -5
  35. package/dist/types/src/automerge/leveldb-storage-adapter.d.ts.map +1 -1
  36. package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts +9 -8
  37. package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts.map +1 -1
  38. package/dist/types/src/automerge/mesh-echo-replicator.d.ts +6 -5
  39. package/dist/types/src/automerge/mesh-echo-replicator.d.ts.map +1 -1
  40. package/dist/types/src/automerge/network-protocol.d.ts.map +1 -1
  41. package/dist/types/src/automerge/space-collection.d.ts.map +1 -1
  42. package/dist/types/src/common/codec.d.ts +1 -1
  43. package/dist/types/src/common/codec.d.ts.map +1 -1
  44. package/dist/types/src/common/feeds.d.ts.map +1 -1
  45. package/dist/types/src/common/space-id.d.ts.map +1 -1
  46. package/dist/types/src/db-host/automerge-data-source.d.ts +35 -0
  47. package/dist/types/src/db-host/automerge-data-source.d.ts.map +1 -0
  48. package/dist/types/src/db-host/automerge-data-source.test.d.ts +2 -0
  49. package/dist/types/src/db-host/automerge-data-source.test.d.ts.map +1 -0
  50. package/dist/types/src/db-host/automerge-metrics.d.ts.map +1 -1
  51. package/dist/types/src/db-host/data-service.d.ts +8 -7
  52. package/dist/types/src/db-host/data-service.d.ts.map +1 -1
  53. package/dist/types/src/db-host/database-root.d.ts +9 -3
  54. package/dist/types/src/db-host/database-root.d.ts.map +1 -1
  55. package/dist/types/src/db-host/documents-synchronizer.d.ts +9 -8
  56. package/dist/types/src/db-host/documents-synchronizer.d.ts.map +1 -1
  57. package/dist/types/src/db-host/echo-host.d.ts +62 -27
  58. package/dist/types/src/db-host/echo-host.d.ts.map +1 -1
  59. package/dist/types/src/db-host/index.d.ts +2 -0
  60. package/dist/types/src/db-host/index.d.ts.map +1 -1
  61. package/dist/types/src/db-host/invalidation-hint.d.ts +19 -0
  62. package/dist/types/src/db-host/invalidation-hint.d.ts.map +1 -0
  63. package/dist/types/src/db-host/local-queue-service.d.ts +19 -0
  64. package/dist/types/src/db-host/local-queue-service.d.ts.map +1 -0
  65. package/dist/types/src/db-host/query-invalidation.test.d.ts +2 -0
  66. package/dist/types/src/db-host/query-invalidation.test.d.ts.map +1 -0
  67. package/dist/types/src/db-host/query-service.d.ts +19 -11
  68. package/dist/types/src/db-host/query-service.d.ts.map +1 -1
  69. package/dist/types/src/db-host/queue-data-source.d.ts +38 -0
  70. package/dist/types/src/db-host/queue-data-source.d.ts.map +1 -0
  71. package/dist/types/src/db-host/queue-service.test.d.ts +2 -0
  72. package/dist/types/src/db-host/queue-service.test.d.ts.map +1 -0
  73. package/dist/types/src/db-host/space-state-manager.d.ts +4 -3
  74. package/dist/types/src/db-host/space-state-manager.d.ts.map +1 -1
  75. package/dist/types/src/db-host/stub.d.ts +11 -0
  76. package/dist/types/src/db-host/stub.d.ts.map +1 -0
  77. package/dist/types/src/edge/echo-edge-replicator.d.ts +10 -7
  78. package/dist/types/src/edge/echo-edge-replicator.d.ts.map +1 -1
  79. package/dist/types/src/edge/echo-edge-subduction-replicator.d.ts +39 -0
  80. package/dist/types/src/edge/echo-edge-subduction-replicator.d.ts.map +1 -0
  81. package/dist/types/src/edge/echo-edge-subduction-replicator.test.d.ts +2 -0
  82. package/dist/types/src/edge/echo-edge-subduction-replicator.test.d.ts.map +1 -0
  83. package/dist/types/src/edge/index.d.ts +1 -0
  84. package/dist/types/src/edge/index.d.ts.map +1 -1
  85. package/dist/types/src/edge/inflight-request-limiter.d.ts.map +1 -1
  86. package/dist/types/src/filter/filter-match.d.ts +4 -1
  87. package/dist/types/src/filter/filter-match.d.ts.map +1 -1
  88. package/dist/types/src/metadata/metadata-store.d.ts +1 -1
  89. package/dist/types/src/metadata/metadata-store.d.ts.map +1 -1
  90. package/dist/types/src/pipeline/message-selector.d.ts.map +1 -1
  91. package/dist/types/src/pipeline/pipeline.d.ts +3 -3
  92. package/dist/types/src/pipeline/pipeline.d.ts.map +1 -1
  93. package/dist/types/src/pipeline/timeframe-clock.d.ts.map +1 -1
  94. package/dist/types/src/query/errors.d.ts +22 -10
  95. package/dist/types/src/query/errors.d.ts.map +1 -1
  96. package/dist/types/src/query/plan.d.ts +66 -8
  97. package/dist/types/src/query/plan.d.ts.map +1 -1
  98. package/dist/types/src/query/query-executor.d.ts +66 -11
  99. package/dist/types/src/query/query-executor.d.ts.map +1 -1
  100. package/dist/types/src/query/query-planner.d.ts +12 -1
  101. package/dist/types/src/query/query-planner.d.ts.map +1 -1
  102. package/dist/types/src/space/admission-discovery-extension.d.ts.map +1 -1
  103. package/dist/types/src/space/auth.d.ts +3 -3
  104. package/dist/types/src/space/auth.d.ts.map +1 -1
  105. package/dist/types/src/space/control-pipeline.d.ts +5 -4
  106. package/dist/types/src/space/control-pipeline.d.ts.map +1 -1
  107. package/dist/types/src/space/space-manager.d.ts +8 -8
  108. package/dist/types/src/space/space-manager.d.ts.map +1 -1
  109. package/dist/types/src/space/space-protocol.d.ts +10 -9
  110. package/dist/types/src/space/space-protocol.d.ts.map +1 -1
  111. package/dist/types/src/space/space.d.ts +6 -6
  112. package/dist/types/src/space/space.d.ts.map +1 -1
  113. package/dist/types/src/testing/change-metadata.d.ts.map +1 -1
  114. package/dist/types/src/testing/index.d.ts +0 -2
  115. package/dist/types/src/testing/index.d.ts.map +1 -1
  116. package/dist/types/src/testing/test-agent-builder.d.ts +2 -2
  117. package/dist/types/src/testing/test-agent-builder.d.ts.map +1 -1
  118. package/dist/types/src/testing/test-feed-builder.d.ts.map +1 -1
  119. package/dist/types/src/testing/test-network-adapter.d.ts +1 -1
  120. package/dist/types/src/testing/test-network-adapter.d.ts.map +1 -1
  121. package/dist/types/src/testing/test-replicator.d.ts +14 -13
  122. package/dist/types/src/testing/test-replicator.d.ts.map +1 -1
  123. package/dist/types/src/util.d.ts +1 -1
  124. package/dist/types/src/util.d.ts.map +1 -1
  125. package/dist/types/tsconfig.tsbuildinfo +1 -1
  126. package/package.json +56 -54
  127. package/src/automerge/automerge-host-subduction.test.ts +247 -0
  128. package/src/automerge/automerge-host.test.ts +65 -39
  129. package/src/automerge/automerge-host.ts +589 -206
  130. package/src/automerge/automerge-repo-subduction.test.ts +1049 -0
  131. package/src/automerge/automerge-repo.test.ts +349 -91
  132. package/src/automerge/automerge-subduction.test.ts +330 -0
  133. package/src/automerge/collection-synchronizer.test.ts +53 -17
  134. package/src/automerge/collection-synchronizer.ts +119 -36
  135. package/src/automerge/echo-data-monitor.ts +1 -1
  136. package/src/automerge/echo-network-adapter.test.ts +5 -4
  137. package/src/automerge/echo-network-adapter.ts +74 -30
  138. package/src/automerge/echo-replicator.ts +41 -15
  139. package/src/automerge/handle-state.ts +62 -0
  140. package/src/automerge/heads-store.ts +42 -3
  141. package/src/automerge/index.ts +2 -1
  142. package/src/automerge/leveldb-storage-adapter.ts +40 -11
  143. package/src/automerge/mesh-echo-replicator-connection.ts +12 -8
  144. package/src/automerge/mesh-echo-replicator.ts +15 -11
  145. package/src/automerge/storage-adapter.test.ts +1 -1
  146. package/src/common/space-id.ts +1 -1
  147. package/src/db-host/automerge-data-source.test.ts +299 -0
  148. package/src/db-host/automerge-data-source.ts +187 -0
  149. package/src/db-host/data-service.ts +25 -26
  150. package/src/db-host/database-root.ts +20 -8
  151. package/src/db-host/documents-synchronizer.test.ts +143 -14
  152. package/src/db-host/documents-synchronizer.ts +72 -55
  153. package/src/db-host/echo-host.ts +348 -96
  154. package/src/db-host/index.ts +2 -0
  155. package/src/db-host/invalidation-hint.ts +50 -0
  156. package/src/db-host/local-queue-service.ts +127 -0
  157. package/src/db-host/query-invalidation.test.ts +270 -0
  158. package/src/db-host/query-service.ts +111 -106
  159. package/src/db-host/queue-data-source.ts +160 -0
  160. package/src/db-host/queue-service.test.ts +145 -0
  161. package/src/db-host/space-state-manager.ts +12 -10
  162. package/src/db-host/stub.ts +33 -0
  163. package/src/edge/echo-edge-replicator.test.ts +12 -10
  164. package/src/edge/echo-edge-replicator.ts +144 -58
  165. package/src/edge/echo-edge-subduction-replicator.test.ts +158 -0
  166. package/src/edge/echo-edge-subduction-replicator.ts +377 -0
  167. package/src/edge/index.ts +1 -0
  168. package/src/filter/filter-match.test.ts +68 -32
  169. package/src/filter/filter-match.ts +184 -20
  170. package/src/metadata/metadata-store.ts +11 -8
  171. package/src/pipeline/pipeline-stress.test.ts +1 -1
  172. package/src/pipeline/pipeline.test.ts +2 -2
  173. package/src/pipeline/pipeline.ts +9 -7
  174. package/src/query/errors.ts +3 -1
  175. package/src/query/plan.ts +82 -8
  176. package/src/query/query-executor.ts +1199 -164
  177. package/src/query/query-planner.test.ts +993 -81
  178. package/src/query/query-planner.ts +467 -27
  179. package/src/space/admission-discovery-extension.ts +3 -3
  180. package/src/space/auth.ts +6 -6
  181. package/src/space/control-pipeline.test.ts +5 -4
  182. package/src/space/control-pipeline.ts +16 -13
  183. package/src/space/space-manager.browser.test.ts +1 -1
  184. package/src/space/space-manager.ts +16 -20
  185. package/src/space/space-protocol.browser.test.ts +15 -14
  186. package/src/space/space-protocol.test.ts +15 -14
  187. package/src/space/space-protocol.ts +22 -18
  188. package/src/space/space.test.ts +3 -3
  189. package/src/space/space.ts +11 -11
  190. package/src/testing/index.ts +0 -2
  191. package/src/testing/test-agent-builder.ts +3 -3
  192. package/src/testing/test-network-adapter.ts +24 -3
  193. package/src/testing/test-replicator.ts +19 -20
  194. package/src/util.ts +7 -3
  195. package/dist/lib/browser/chunk-35I6ERLG.mjs.map +0 -7
  196. package/dist/lib/browser/chunk-CGS2ULMK.mjs +0 -11
  197. package/dist/lib/browser/chunk-CGS2ULMK.mjs.map +0 -7
  198. package/dist/lib/browser/chunk-TQJTKNMS.mjs +0 -126
  199. package/dist/lib/browser/chunk-TQJTKNMS.mjs.map +0 -7
  200. package/dist/lib/browser/index.mjs +0 -4743
  201. package/dist/lib/browser/index.mjs.map +0 -7
  202. package/dist/lib/browser/meta.json +0 -1
  203. package/dist/lib/browser/testing/index.mjs.map +0 -7
  204. package/dist/lib/node/chunk-HOPOFWAL.cjs +0 -147
  205. package/dist/lib/node/chunk-HOPOFWAL.cjs.map +0 -7
  206. package/dist/lib/node/chunk-JXX6LF5U.cjs +0 -2084
  207. package/dist/lib/node/chunk-JXX6LF5U.cjs.map +0 -7
  208. package/dist/lib/node/chunk-Q7SFCCGT.cjs +0 -33
  209. package/dist/lib/node/chunk-Q7SFCCGT.cjs.map +0 -7
  210. package/dist/lib/node/filter/index.cjs +0 -32
  211. package/dist/lib/node/filter/index.cjs.map +0 -7
  212. package/dist/lib/node/index.cjs +0 -4699
  213. package/dist/lib/node/index.cjs.map +0 -7
  214. package/dist/lib/node/meta.json +0 -1
  215. package/dist/lib/node/testing/index.cjs +0 -753
  216. package/dist/lib/node/testing/index.cjs.map +0 -7
  217. package/dist/lib/node-esm/chunk-5BHLPT24.mjs +0 -2067
  218. package/dist/lib/node-esm/chunk-5BHLPT24.mjs.map +0 -7
  219. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
  220. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +0 -7
  221. package/dist/lib/node-esm/chunk-RVK35BS7.mjs +0 -126
  222. package/dist/lib/node-esm/chunk-RVK35BS7.mjs.map +0 -7
  223. package/dist/lib/node-esm/filter/index.mjs +0 -11
  224. package/dist/lib/node-esm/filter/index.mjs.map +0 -7
  225. package/dist/lib/node-esm/index.mjs +0 -4743
  226. package/dist/lib/node-esm/index.mjs.map +0 -7
  227. package/dist/lib/node-esm/meta.json +0 -1
  228. package/dist/lib/node-esm/testing/index.mjs +0 -742
  229. package/dist/lib/node-esm/testing/index.mjs.map +0 -7
  230. package/dist/types/src/db-host/documents-iterator.d.ts +0 -7
  231. package/dist/types/src/db-host/documents-iterator.d.ts.map +0 -1
  232. package/dist/types/src/testing/test-data.d.ts +0 -18
  233. package/dist/types/src/testing/test-data.d.ts.map +0 -1
  234. package/dist/types/src/testing/test-schema.d.ts +0 -37
  235. package/dist/types/src/testing/test-schema.d.ts.map +0 -1
  236. package/src/db-host/documents-iterator.ts +0 -74
  237. package/src/testing/test-data.ts +0 -127
  238. package/src/testing/test-schema.ts +0 -53
  239. /package/dist/lib/{browser → neutral}/filter/index.mjs.map +0 -0
@@ -4,81 +4,117 @@
4
4
 
5
5
  import { describe, expect, test } from 'vitest';
6
6
 
7
- import { Filter } from '@dxos/echo';
7
+ import { Filter, Ref } from '@dxos/echo';
8
8
  import { ObjectStructure } from '@dxos/echo-protocol';
9
- import { Expando, EXPANDO_TYPENAME, Ref } from '@dxos/echo-schema';
9
+ import { TestSchema } from '@dxos/echo/testing';
10
10
  import { DXN, ObjectId, SpaceId } from '@dxos/keys';
11
11
 
12
- import { filterMatchObject, type MatchedObject } from './filter-match';
12
+ import { type MatchedObject, filterMatchObject } from './filter-match';
13
13
 
14
14
  describe('filterMatch', () => {
15
+ test('everything', () => {
16
+ expect(filterMatchObject(Filter.everything().ast, OBJECT_1)).to.be.true;
17
+ expect(filterMatchObject(Filter.everything().ast, OBJECT_2)).to.be.true;
18
+ });
19
+
20
+ test('nothing', () => {
21
+ expect(filterMatchObject(Filter.nothing().ast, OBJECT_1)).to.be.false;
22
+ expect(filterMatchObject(Filter.nothing().ast, OBJECT_2)).to.be.false;
23
+ });
24
+
15
25
  test('properties', () => {
16
- expect(filterMatchObject(Filter.type(Expando, { title: 'test' }).ast, OBJECT_1)).to.be.true;
17
- expect(filterMatchObject(Filter.type(Expando, { value: 100 }).ast, OBJECT_1)).to.be.true;
18
- expect(filterMatchObject(Filter.type(Expando, { complete: false }).ast, OBJECT_1)).to.be.false;
19
- expect(filterMatchObject(Filter.type(Expando, { missing: undefined }).ast, OBJECT_1)).to.be.true;
26
+ expect(filterMatchObject(Filter.type(TestSchema.Expando, { title: 'test' }).ast, OBJECT_1)).to.be.true;
27
+ expect(filterMatchObject(Filter.type(TestSchema.Expando, { value: 100 }).ast, OBJECT_1)).to.be.true;
28
+ expect(filterMatchObject(Filter.type(TestSchema.Expando, { complete: false }).ast, OBJECT_1)).to.be.false;
29
+ expect(filterMatchObject(Filter.type(TestSchema.Expando, { missing: undefined }).ast, OBJECT_1)).to.be.true;
30
+ expect(filterMatchObject(Filter.type(TestSchema.Expando, { properties: { subject: 'test' } }).ast, OBJECT_1)).to.be
31
+ .true;
32
+ expect(filterMatchObject(Filter.type(TestSchema.Expando, { array: Filter.contains('two') }).ast, OBJECT_1)).to.be
33
+ .true;
20
34
  });
21
35
 
22
36
  test('and', () => {
23
- const filter1 = Filter.type(Expando, { title: 'test' });
24
- const filter2 = Filter.type(Expando, { value: 100 });
25
- const filter3 = Filter.type(Expando, { complete: true });
37
+ const filter1 = Filter.type(TestSchema.Expando, { title: 'test' });
38
+ const filter2 = Filter.type(TestSchema.Expando, { value: 100 });
39
+ const filter3 = Filter.type(TestSchema.Expando, { complete: true });
26
40
 
27
41
  expect(filterMatchObject(Filter.and(filter1, filter2, filter3).ast, OBJECT_1)).to.be.true;
28
42
  });
29
43
 
30
44
  test('or', () => {
31
- const filter1 = Filter.type(Expando, { value: 200 });
32
- const filter2 = Filter.type(Expando, { title: 'test' });
33
- const filter3 = Filter.type(Expando, { complete: false });
45
+ const filter1 = Filter.type(TestSchema.Expando, { value: 200 });
46
+ const filter2 = Filter.type(TestSchema.Expando, { title: 'test' });
47
+ const filter3 = Filter.type(TestSchema.Expando, { complete: false });
34
48
 
35
49
  expect(filterMatchObject(Filter.or(filter1, filter2, filter3).ast, OBJECT_1)).to.be.true;
36
50
  });
37
51
 
38
52
  test('not', () => {
39
- const filter1 = Filter.type(Expando, { title: 'test' });
53
+ const filter1 = Filter.type(TestSchema.Expando, { title: 'test' });
40
54
  const filter2 = Filter.not(filter1);
41
55
 
42
56
  expect(filterMatchObject(filter1.ast, OBJECT_1)).to.be.true;
43
57
  expect(filterMatchObject(filter2.ast, OBJECT_1)).to.be.false;
44
58
  });
45
59
 
60
+ test('contains', () => {
61
+ expect(
62
+ filterMatchObject(
63
+ Filter.type(TestSchema.Expando, { properties: { label: Filter.contains('test') } }).ast,
64
+ OBJECT_1,
65
+ ),
66
+ ).to.be.true;
67
+ expect(
68
+ filterMatchObject(
69
+ Filter.type(TestSchema.Expando, { fields: Filter.contains({ label: 'label', value: 'test' }) }).ast,
70
+ OBJECT_1,
71
+ ),
72
+ ).to.be.true;
73
+ });
74
+
46
75
  test('complex', () => {
47
- const filter1 = Filter.type(Expando, { title: 'bad' });
48
- const filter2 = Filter.type(Expando, { value: 0 });
76
+ const filter1 = Filter.type(TestSchema.Expando, { title: 'bad' });
77
+ const filter2 = Filter.type(TestSchema.Expando, { value: 0 });
49
78
  expect(filterMatchObject(Filter.not(Filter.or(filter1, filter2)).ast, OBJECT_1)).to.be.true;
50
79
  });
51
80
 
52
81
  test('ids', () => {
53
- const filter = Filter.ids(OBJECT_1.id);
82
+ const filter = Filter.id(OBJECT_1.id);
54
83
  expect(filterMatchObject(filter.ast, OBJECT_1)).to.be.true;
55
84
  expect(filterMatchObject(filter.ast, OBJECT_2)).to.be.false;
56
85
  });
57
86
 
58
- test('everything', () => {
59
- expect(filterMatchObject(Filter.everything().ast, OBJECT_1)).to.be.true;
60
- expect(filterMatchObject(Filter.everything().ast, OBJECT_2)).to.be.true;
61
- });
62
-
63
- test('nothing', () => {
64
- expect(filterMatchObject(Filter.nothing().ast, OBJECT_1)).to.be.false;
65
- expect(filterMatchObject(Filter.nothing().ast, OBJECT_2)).to.be.false;
66
- });
67
-
68
87
  test('refs', () => {
69
- const filter = Filter.type(Expando, { parent: Ref.fromDXN(DXN.fromLocalObjectId(OBJECT_1.id)) });
88
+ const filter = Filter.type(TestSchema.Expando, { parent: Ref.fromDXN(DXN.fromLocalObjectId(OBJECT_1.id)) });
70
89
  expect(filterMatchObject(filter.ast, OBJECT_1)).to.be.false;
71
90
  expect(filterMatchObject(filter.ast, OBJECT_2)).to.be.false;
72
91
  expect(filterMatchObject(filter.ast, OBJECT_3)).to.be.true;
73
92
  });
93
+
94
+ test('standalone timestamp filter throws invariant (must be handled at index level)', () => {
95
+ expect(() => filterMatchObject(Filter.updated({ after: Date.now() - 1000 }).ast, OBJECT_1)).to.throw();
96
+ expect(() => filterMatchObject(Filter.created({ before: Date.now() + 1000 }).ast, OBJECT_1)).to.throw();
97
+ });
98
+
99
+ test('and(type, timestamp) throws on timestamp part', () => {
100
+ const filter = Filter.and(Filter.type(TestSchema.Expando), Filter.updated({ after: Date.now() - 1000 }));
101
+ expect(() => filterMatchObject(filter.ast, OBJECT_1)).to.throw();
102
+ });
74
103
  });
75
104
 
76
105
  const OBJECT_1: MatchedObject = {
77
106
  id: ObjectId.make('01JVS9YYT5VMVJW0GGTM1YHCCH'),
78
107
  spaceId: SpaceId.make('B2NJDFNVZIW77OQSXUBNAD7BUMBD3G5PO'),
79
108
  doc: ObjectStructure.makeObject({
80
- type: DXN.fromTypenameAndVersion(EXPANDO_TYPENAME, '0.1.0').toString(),
81
- data: { title: 'test', value: 100, complete: true },
109
+ type: DXN.fromTypenameAndVersion(TestSchema.Expando.typename, '0.1.0').toString(),
110
+ data: {
111
+ title: 'test',
112
+ value: 100,
113
+ complete: true,
114
+ array: ['one', 'two', 'three'],
115
+ properties: { label: ['test'], subject: 'test' },
116
+ fields: [{ label: 'label', value: 'test' }],
117
+ },
82
118
  }),
83
119
  };
84
120
 
@@ -86,7 +122,7 @@ const OBJECT_2: MatchedObject = {
86
122
  id: ObjectId.make('01JT5TD6K9FFJ3VNM5FGMS5C0Q'),
87
123
  spaceId: SpaceId.make('B2NJDFNVZIW77OQSXUBNAD7BUMBD3G5PO'),
88
124
  doc: ObjectStructure.makeObject({
89
- type: DXN.fromTypenameAndVersion(EXPANDO_TYPENAME, '0.1.0').toString(),
125
+ type: DXN.fromTypenameAndVersion(TestSchema.Expando.typename, '0.1.0').toString(),
90
126
  data: { title: 'test', value: 100, complete: true },
91
127
  deleted: true,
92
128
  }),
@@ -96,7 +132,7 @@ const OBJECT_3: MatchedObject = {
96
132
  id: ObjectId.make('01JT5TD6K9FFJ3VNM5FGMS5C0Q'),
97
133
  spaceId: SpaceId.make('B2NJDFNVZIW77OQSXUBNAD7BUMBD3G5PO'),
98
134
  doc: ObjectStructure.makeObject({
99
- type: DXN.fromTypenameAndVersion(EXPANDO_TYPENAME, '0.1.0').toString(),
135
+ type: DXN.fromTypenameAndVersion(TestSchema.Expando.typename, '0.1.0').toString(),
100
136
  data: { title: 'test', value: 100, complete: true, parent: { '/': DXN.fromLocalObjectId(OBJECT_1.id).toString() } },
101
137
  }),
102
138
  };
@@ -2,8 +2,8 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { decodeReference, isEncodedReference, type QueryAST, type ObjectStructure } from '@dxos/echo-protocol';
6
- import { EXPANDO_TYPENAME } from '@dxos/echo-schema';
5
+ import { EncodedReference, ObjectStructure, type QueryAST, isEncodedReference } from '@dxos/echo-protocol';
6
+ import { ATTR_META, type ObjectJSON } from '@dxos/echo/internal';
7
7
  import { DXN, type ObjectId, type SpaceId } from '@dxos/keys';
8
8
 
9
9
  export type MatchedObject = {
@@ -14,35 +14,32 @@ export type MatchedObject = {
14
14
 
15
15
  /**
16
16
  * Matches an object against a filter AST.
17
+ * @param obj object structure as stored in automerge.
17
18
  */
18
19
  export const filterMatchObject = (filter: QueryAST.Filter, obj: MatchedObject): boolean => {
19
20
  switch (filter.type) {
20
21
  case 'object': {
21
- // Check typename if specified
22
+ // Check typename if specified.
22
23
  if (filter.typename !== null) {
23
24
  // TODO(dmaretskyi): `system` is missing in some cases.
24
25
  if (!obj.doc.system?.type?.['/']) {
25
- // Objects with no type are considered to be expando objects
26
- const expectedDXN = DXN.parse(filter.typename).asTypeDXN();
27
- if (expectedDXN?.type !== EXPANDO_TYPENAME) {
28
- return false;
29
- }
26
+ // Objects with no type are deprecated.
27
+ return false;
30
28
  } else {
31
29
  const actualDXN = DXN.parse(obj.doc.system.type['/']);
32
30
  const expectedDXN = DXN.parse(filter.typename);
33
-
34
31
  if (!compareTypename(expectedDXN, actualDXN)) {
35
32
  return false;
36
33
  }
37
34
  }
38
35
  }
39
36
 
40
- // Check IDs if specified
37
+ // Check IDs if specified.
41
38
  if (filter.id && filter.id.length > 0 && !filter.id.includes(obj.id)) {
42
39
  return false;
43
40
  }
44
41
 
45
- // Check properties
42
+ // Check properties.
46
43
  if (filter.props) {
47
44
  for (const [key, valueFilter] of Object.entries(filter.props)) {
48
45
  const value = obj.doc.data[key];
@@ -52,7 +49,7 @@ export const filterMatchObject = (filter: QueryAST.Filter, obj: MatchedObject):
52
49
  }
53
50
  }
54
51
 
55
- // Check foreign keys if specified
52
+ // Check foreign keys if specified.
56
53
  if (filter.foreignKeys && filter.foreignKeys.length > 0) {
57
54
  const hasMatchingKey = filter.foreignKeys.some((filterKey) =>
58
55
  obj.doc.meta.keys.some((objKey) => objKey.source === filterKey.source && objKey.id === filterKey.id),
@@ -65,11 +62,24 @@ export const filterMatchObject = (filter: QueryAST.Filter, obj: MatchedObject):
65
62
  return true;
66
63
  }
67
64
 
65
+ case 'tag': {
66
+ const tags = ObjectStructure.getTags(obj.doc);
67
+ return tags.some((tag) => tag === filter.tag);
68
+ }
69
+
68
70
  case 'text-search': {
69
- // TODO: Implement text search
71
+ // TODO(???): Implement text search.
70
72
  return false;
71
73
  }
72
74
 
75
+ case 'timestamp': {
76
+ throw new Error('Timestamp filters must be handled at the index level, not in-memory matching.');
77
+ }
78
+
79
+ case 'child-of': {
80
+ throw new Error('child-of filters must be handled at the executor level, not in-memory matching.');
81
+ }
82
+
73
83
  case 'not': {
74
84
  return !filterMatchObject(filter.filter, obj);
75
85
  }
@@ -87,6 +97,128 @@ export const filterMatchObject = (filter: QueryAST.Filter, obj: MatchedObject):
87
97
  }
88
98
  };
89
99
 
100
+ // TODO(burdon): Reconcile with filterMatchObject.
101
+ export const filterMatchObjectJSON = (filter: QueryAST.Filter, obj: ObjectJSON): boolean => {
102
+ switch (filter.type) {
103
+ case 'object': {
104
+ // Check typename if specified
105
+ if (filter.typename !== null) {
106
+ // TODO(dmaretskyi): `system` is missing in some cases.
107
+ if (!obj['@type']) {
108
+ // Objects with no type are deprecated.
109
+ return false;
110
+ } else {
111
+ const actualDXN = DXN.parse(obj['@type']);
112
+ const expectedDXN = DXN.parse(filter.typename);
113
+ if (!compareTypename(expectedDXN, actualDXN)) {
114
+ return false;
115
+ }
116
+ }
117
+ }
118
+
119
+ // Check IDs if specified
120
+ if (filter.id && filter.id.length > 0 && !filter.id.includes(obj.id)) {
121
+ return false;
122
+ }
123
+
124
+ // Check properties
125
+ if (filter.props) {
126
+ for (const [key, valueFilter] of Object.entries(filter.props)) {
127
+ if (key.startsWith('@')) {
128
+ // ignore meta properties
129
+ continue;
130
+ }
131
+ const value = (obj as any)[key];
132
+ if (!filterMatchValue(valueFilter, value)) {
133
+ return false;
134
+ }
135
+ }
136
+ }
137
+
138
+ // Check foreign keys if specified
139
+ if (filter.foreignKeys && filter.foreignKeys.length > 0) {
140
+ const hasMatchingKey = filter.foreignKeys.some((filterKey) =>
141
+ obj['@meta']?.keys?.some((objKey) => objKey.source === filterKey.source && objKey.id === filterKey.id),
142
+ );
143
+ if (!hasMatchingKey) {
144
+ return false;
145
+ }
146
+ }
147
+
148
+ return true;
149
+ }
150
+
151
+ case 'tag': {
152
+ const tags = obj[ATTR_META]?.tags ?? [];
153
+ return tags.some((tag) => tag === filter.tag);
154
+ }
155
+
156
+ // TODO: Implement text search.
157
+ case 'text-search': {
158
+ return false;
159
+ }
160
+
161
+ case 'timestamp': {
162
+ throw new Error('Timestamp filters must be handled at the index level, not in-memory matching.');
163
+ }
164
+
165
+ case 'child-of': {
166
+ throw new Error('child-of filters must be handled at the executor level, not in-memory matching.');
167
+ }
168
+
169
+ case 'not': {
170
+ return !filterMatchObjectJSON(filter.filter, obj);
171
+ }
172
+
173
+ case 'and': {
174
+ return filter.filters.every((f) => filterMatchObjectJSON(f, obj));
175
+ }
176
+
177
+ case 'or': {
178
+ return filter.filters.some((f) => filterMatchObjectJSON(f, obj));
179
+ }
180
+
181
+ default:
182
+ return false;
183
+ }
184
+ };
185
+
186
+ /**
187
+ * Performs structural matching between a filter object and a target object.
188
+ * This handles nested object comparison for array matching scenarios.
189
+ */
190
+ // TODO(wittjosiah): Add ast support for non-strict matching.
191
+ const structuralMatch = (filterObj: any, targetObj: any, strict = true): boolean => {
192
+ if (typeof filterObj !== 'object' || filterObj === null) {
193
+ return filterObj === targetObj;
194
+ }
195
+
196
+ if (typeof targetObj !== 'object' || targetObj === null) {
197
+ return false;
198
+ }
199
+
200
+ // Prohibit extra keys in targetObj.
201
+ const filterKeys = Object.keys(filterObj);
202
+ const targetKeys = Object.keys(targetObj);
203
+ if (strict && filterKeys.length !== targetKeys.length) {
204
+ return false;
205
+ }
206
+
207
+ return filterKeys.every((key) => {
208
+ if (!(key in targetObj)) {
209
+ return false;
210
+ }
211
+ const filterValue = filterObj[key];
212
+ const targetValue = targetObj[key];
213
+
214
+ if (typeof filterValue === 'object' && filterValue !== null) {
215
+ return structuralMatch(filterValue, targetValue);
216
+ }
217
+
218
+ return filterValue === targetValue;
219
+ });
220
+ };
221
+
90
222
  export const filterMatchValue = (filter: QueryAST.Filter, value: unknown): boolean => {
91
223
  switch (filter.type) {
92
224
  case 'compare': {
@@ -97,7 +229,7 @@ export const filterMatchValue = (filter: QueryAST.Filter, value: unknown): boole
97
229
  if (!isEncodedReference(value)) {
98
230
  return false;
99
231
  }
100
- return DXN.equals(decodeReference(value).toDXN(), decodeReference(compareValue).toDXN());
232
+ return DXN.equals(EncodedReference.toDXN(value), EncodedReference.toDXN(compareValue));
101
233
  }
102
234
  return value === compareValue;
103
235
  case 'neq':
@@ -110,12 +242,44 @@ export const filterMatchValue = (filter: QueryAST.Filter, value: unknown): boole
110
242
  return (value as any) < compareValue;
111
243
  case 'lte':
112
244
  return (value as any) <= compareValue;
245
+ default:
246
+ return false;
247
+ }
248
+ }
249
+ case 'object': {
250
+ // Handle nested object filters for property matching
251
+ if (typeof value !== 'object' || value === null) {
252
+ return false;
253
+ }
254
+
255
+ // Check properties
256
+ if (filter.props) {
257
+ for (const [key, valueFilter] of Object.entries(filter.props)) {
258
+ const nestedValue = (value as any)[key];
259
+ if (!filterMatchValue(valueFilter, nestedValue)) {
260
+ return false;
261
+ }
262
+ }
113
263
  }
114
- break;
264
+
265
+ return true;
115
266
  }
116
267
  case 'in': {
117
268
  return filter.values.includes(value);
118
269
  }
270
+ case 'contains': {
271
+ if (!Array.isArray(value)) {
272
+ return false;
273
+ }
274
+
275
+ return value.some((element) => {
276
+ if (typeof filter.value === 'object' && filter.value !== null && !Array.isArray(filter.value)) {
277
+ return structuralMatch(filter.value, element);
278
+ }
279
+
280
+ return element === filter.value;
281
+ });
282
+ }
119
283
  case 'range': {
120
284
  return (value as any) >= filter.from && (value as any) <= filter.to;
121
285
  }
@@ -143,11 +307,11 @@ export const filterMatchValue = (filter: QueryAST.Filter, value: unknown): boole
143
307
  *
144
308
  * Examples: (expected) (actual)
145
309
  *
146
- * dxn:type:example.org/type/Task !== dxn:type:example.org/type/Contact
147
- * dxn:type:example.org/type/Task === dxn:type:example.org/type/Task
148
- * dxn:type:example.org/type/Task:0.1.0 !== dxn:type:example.org/type/Task:0.2.0
149
- * dxn:type:example.org/type/Task === dxn:type:example.org/type/Task:0.1.0
150
- * dxn:type:example.org/type/Task:0.1.0 === dxn:type:example.org/type/Task
310
+ * dxn:type:com.example.type.task !== dxn:type:com.example.type.contact
311
+ * dxn:type:com.example.type.task === dxn:type:com.example.type.task
312
+ * dxn:type:com.example.type.task:0.1.0 !== dxn:type:com.example.type.task:0.2.0
313
+ * dxn:type:com.example.type.task === dxn:type:com.example.type.task:0.1.0
314
+ * dxn:type:com.example.type.task:0.1.0 === dxn:type:com.example.type.task
151
315
  *
152
316
  */
153
317
  const compareTypename = (expectedDXN: DXN, actualDXN: DXN): boolean => {
@@ -16,11 +16,11 @@ import { Invitation, SpaceState } from '@dxos/protocols/proto/dxos/client/servic
16
16
  import {
17
17
  type ControlPipelineSnapshot,
18
18
  type EchoMetadata,
19
- type SpaceMetadata,
19
+ type EdgeReplicationSetting,
20
20
  type IdentityRecord,
21
- type SpaceCache,
22
21
  type LargeSpaceMetadata,
23
- type EdgeReplicationSetting,
22
+ type SpaceCache,
23
+ type SpaceMetadata,
24
24
  } from '@dxos/protocols/proto/dxos/echo/metadata';
25
25
  import { type Directory, type File } from '@dxos/random-access-storage';
26
26
  import { type Timeframe } from '@dxos/timeframe';
@@ -91,14 +91,17 @@ export class MetadataStore {
91
91
  log('loaded', { size: dataSize, checksum, name: file.filename });
92
92
 
93
93
  if (fileLength < dataSize + 8) {
94
- throw new DataCorruptionError('Metadata size is smaller than expected.', { fileLength, dataSize });
94
+ throw new DataCorruptionError({
95
+ message: 'Metadata size is smaller than expected.',
96
+ context: { fileLength, dataSize },
97
+ });
95
98
  }
96
99
 
97
100
  const data = await file.read(8, dataSize);
98
101
 
99
102
  const calculatedChecksum = CRC32.buf(data);
100
103
  if (calculatedChecksum !== checksum) {
101
- throw new DataCorruptionError('Metadata checksum is invalid.');
104
+ throw new DataCorruptionError({ message: 'Metadata checksum is invalid.' });
102
105
  }
103
106
 
104
107
  return codec.decode(data);
@@ -363,9 +366,9 @@ const fromBytesInt32 = (buf: Buffer) => buf.readInt32LE(0);
363
366
  export const hasInvitationExpired = (invitation: Invitation): boolean => {
364
367
  return Boolean(
365
368
  invitation.created &&
366
- invitation.lifetime &&
367
- invitation.lifetime !== 0 &&
368
- invitation.created.getTime() + invitation.lifetime * 1000 < Date.now(),
369
+ invitation.lifetime &&
370
+ invitation.lifetime !== 0 &&
371
+ invitation.created.getTime() + invitation.lifetime * 1000 < Date.now(),
369
372
  );
370
373
  };
371
374
 
@@ -15,8 +15,8 @@ import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
15
15
  import { Timeframe } from '@dxos/timeframe';
16
16
  import { range } from '@dxos/util';
17
17
 
18
- import { Pipeline } from './pipeline';
19
18
  import { TestFeedBuilder } from '../testing';
19
+ import { Pipeline } from './pipeline';
20
20
 
21
21
  const NUM_AGENTS = 2;
22
22
  const NUM_MESSAGES = 10;
@@ -2,15 +2,15 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import { describe, expect, test, onTestFinished } from 'vitest';
5
+ import { describe, expect, onTestFinished, test } from 'vitest';
6
6
 
7
7
  import { Event, sleep } from '@dxos/async';
8
8
  import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
9
9
  import { Timeframe } from '@dxos/timeframe';
10
10
  import { range } from '@dxos/util';
11
11
 
12
- import { Pipeline } from './pipeline';
13
12
  import { TestFeedBuilder } from '../testing';
13
+ import { Pipeline } from './pipeline';
14
14
 
15
15
  const TEST_MESSAGE: FeedMessage = {
16
16
  timeframe: new Timeframe(),
@@ -2,7 +2,7 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import { Event, sleepWithContext, synchronized, Trigger } from '@dxos/async';
5
+ import { Event, Trigger, sleepWithContext, synchronized } from '@dxos/async';
6
6
  import { Context, rejectOnDispose } from '@dxos/context';
7
7
  import { failUndefined } from '@dxos/debug';
8
8
  import { FeedSetIterator, type FeedWrapper, type FeedWriter } from '@dxos/feed-store';
@@ -14,11 +14,11 @@ import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
14
14
  import { Timeframe } from '@dxos/timeframe';
15
15
  import { ComplexMap } from '@dxos/util';
16
16
 
17
- import { createMessageSelector } from './message-selector';
18
- import { mapFeedIndexesToTimeframe, startAfter, TimeframeClock } from './timeframe-clock';
19
17
  import { createMappedFeedWriter } from '../common';
18
+ import { createMessageSelector } from './message-selector';
19
+ import { TimeframeClock, mapFeedIndexesToTimeframe, startAfter } from './timeframe-clock';
20
20
 
21
- export type WaitUntilReachedTargetParams = {
21
+ export type WaitUntilReachedTargetProps = {
22
22
  /**
23
23
  * For cancellation.
24
24
  */
@@ -40,8 +40,10 @@ export class PipelineState {
40
40
  */
41
41
  _ctx = new Context();
42
42
 
43
- // TODO(dmaretskyi): Remove?.
44
- public readonly timeframeUpdate = this._timeframeClock.update;
43
+ // TODO(dmaretskyi): Remove?. Avoid accessing `_timeframeClock` before constructor initialization.
44
+ public get timeframeUpdate() {
45
+ return this._timeframeClock.update;
46
+ }
45
47
 
46
48
  public readonly stalled = new Event();
47
49
 
@@ -129,7 +131,7 @@ export class PipelineState {
129
131
  ctx = new Context(),
130
132
  timeout,
131
133
  breakOnStall = true,
132
- }: WaitUntilReachedTargetParams = {}): Promise<void> {
134
+ }: WaitUntilReachedTargetProps = {}): Promise<void> {
133
135
  log('waitUntilReachedTargetTimeframe', {
134
136
  timeout,
135
137
  current: this.timeframe,
@@ -4,4 +4,6 @@
4
4
 
5
5
  import { BaseError } from '@dxos/errors';
6
6
 
7
- export class QueryError extends BaseError.extend('QUERY_ERROR') {}
7
+ export class QueryError extends BaseError.extend('QueryError') {}
8
+
9
+ export class InvalidQueryError extends QueryError.extend('InvalidQueryError') {}