@dxos/echo-pipeline 0.8.3 → 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.
Files changed (244) hide show
  1. package/LICENSE +102 -5
  2. package/README.md +1 -1
  3. package/dist/lib/neutral/chunk-46DCRDA7.mjs +271 -0
  4. package/dist/lib/neutral/chunk-46DCRDA7.mjs.map +7 -0
  5. package/dist/lib/{browser/chunk-35I6ERLG.mjs → neutral/chunk-LR7UFVQX.mjs} +313 -709
  6. package/dist/lib/neutral/chunk-LR7UFVQX.mjs.map +7 -0
  7. package/dist/lib/{browser → neutral}/filter/index.mjs +3 -2
  8. package/dist/lib/neutral/index.mjs +7332 -0
  9. package/dist/lib/neutral/index.mjs.map +7 -0
  10. package/dist/lib/neutral/meta.json +1 -0
  11. package/dist/lib/{browser → neutral}/testing/index.mjs +90 -311
  12. package/dist/lib/neutral/testing/index.mjs.map +7 -0
  13. package/dist/types/src/automerge/automerge-host-subduction.test.d.ts +2 -0
  14. package/dist/types/src/automerge/automerge-host-subduction.test.d.ts.map +1 -0
  15. package/dist/types/src/automerge/automerge-host.d.ts +119 -54
  16. package/dist/types/src/automerge/automerge-host.d.ts.map +1 -1
  17. package/dist/types/src/automerge/automerge-repo-subduction.test.d.ts +2 -0
  18. package/dist/types/src/automerge/automerge-repo-subduction.test.d.ts.map +1 -0
  19. package/dist/types/src/automerge/automerge-repo.test.d.ts +1 -1
  20. package/dist/types/src/automerge/automerge-repo.test.d.ts.map +1 -1
  21. package/dist/types/src/automerge/automerge-subduction.test.d.ts +2 -0
  22. package/dist/types/src/automerge/automerge-subduction.test.d.ts.map +1 -0
  23. package/dist/types/src/automerge/collection-synchronizer.d.ts +25 -4
  24. package/dist/types/src/automerge/collection-synchronizer.d.ts.map +1 -1
  25. package/dist/types/src/automerge/echo-data-monitor.d.ts.map +1 -1
  26. package/dist/types/src/automerge/echo-network-adapter.d.ts +23 -10
  27. package/dist/types/src/automerge/echo-network-adapter.d.ts.map +1 -1
  28. package/dist/types/src/automerge/echo-replicator.d.ts +46 -15
  29. package/dist/types/src/automerge/echo-replicator.d.ts.map +1 -1
  30. package/dist/types/src/automerge/errors.d.ts +35 -0
  31. package/dist/types/src/automerge/errors.d.ts.map +1 -0
  32. package/dist/types/src/automerge/handle-state.d.ts +52 -0
  33. package/dist/types/src/automerge/handle-state.d.ts.map +1 -0
  34. package/dist/types/src/automerge/heads-store.d.ts +9 -2
  35. package/dist/types/src/automerge/heads-store.d.ts.map +1 -1
  36. package/dist/types/src/automerge/index.d.ts +3 -1
  37. package/dist/types/src/automerge/index.d.ts.map +1 -1
  38. package/dist/types/src/automerge/leveldb-storage-adapter.d.ts +11 -5
  39. package/dist/types/src/automerge/leveldb-storage-adapter.d.ts.map +1 -1
  40. package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts +9 -8
  41. package/dist/types/src/automerge/mesh-echo-replicator-connection.d.ts.map +1 -1
  42. package/dist/types/src/automerge/mesh-echo-replicator.d.ts +6 -5
  43. package/dist/types/src/automerge/mesh-echo-replicator.d.ts.map +1 -1
  44. package/dist/types/src/automerge/network-protocol.d.ts.map +1 -1
  45. package/dist/types/src/automerge/space-collection.d.ts.map +1 -1
  46. package/dist/types/src/common/codec.d.ts +1 -1
  47. package/dist/types/src/common/codec.d.ts.map +1 -1
  48. package/dist/types/src/common/feeds.d.ts.map +1 -1
  49. package/dist/types/src/common/space-id.d.ts.map +1 -1
  50. package/dist/types/src/db-host/automerge-data-source.d.ts +35 -0
  51. package/dist/types/src/db-host/automerge-data-source.d.ts.map +1 -0
  52. package/dist/types/src/db-host/automerge-data-source.test.d.ts +2 -0
  53. package/dist/types/src/db-host/automerge-data-source.test.d.ts.map +1 -0
  54. package/dist/types/src/db-host/automerge-metrics.d.ts.map +1 -1
  55. package/dist/types/src/db-host/data-service.d.ts +13 -7
  56. package/dist/types/src/db-host/data-service.d.ts.map +1 -1
  57. package/dist/types/src/db-host/database-root.d.ts +9 -3
  58. package/dist/types/src/db-host/database-root.d.ts.map +1 -1
  59. package/dist/types/src/db-host/documents-synchronizer.d.ts +30 -8
  60. package/dist/types/src/db-host/documents-synchronizer.d.ts.map +1 -1
  61. package/dist/types/src/db-host/echo-host.d.ts +74 -28
  62. package/dist/types/src/db-host/echo-host.d.ts.map +1 -1
  63. package/dist/types/src/db-host/index.d.ts +2 -0
  64. package/dist/types/src/db-host/index.d.ts.map +1 -1
  65. package/dist/types/src/db-host/invalidation-hint.d.ts +19 -0
  66. package/dist/types/src/db-host/invalidation-hint.d.ts.map +1 -0
  67. package/dist/types/src/db-host/local-queue-service.d.ts +19 -0
  68. package/dist/types/src/db-host/local-queue-service.d.ts.map +1 -0
  69. package/dist/types/src/db-host/query-invalidation.test.d.ts +2 -0
  70. package/dist/types/src/db-host/query-invalidation.test.d.ts.map +1 -0
  71. package/dist/types/src/db-host/query-service.d.ts +19 -11
  72. package/dist/types/src/db-host/query-service.d.ts.map +1 -1
  73. package/dist/types/src/db-host/queue-data-source.d.ts +38 -0
  74. package/dist/types/src/db-host/queue-data-source.d.ts.map +1 -0
  75. package/dist/types/src/db-host/queue-service.test.d.ts +2 -0
  76. package/dist/types/src/db-host/queue-service.test.d.ts.map +1 -0
  77. package/dist/types/src/db-host/space-state-manager.d.ts +4 -3
  78. package/dist/types/src/db-host/space-state-manager.d.ts.map +1 -1
  79. package/dist/types/src/db-host/stub.d.ts +11 -0
  80. package/dist/types/src/db-host/stub.d.ts.map +1 -0
  81. package/dist/types/src/edge/echo-edge-replicator.d.ts +10 -7
  82. package/dist/types/src/edge/echo-edge-replicator.d.ts.map +1 -1
  83. package/dist/types/src/edge/echo-edge-subduction-replicator.d.ts +39 -0
  84. package/dist/types/src/edge/echo-edge-subduction-replicator.d.ts.map +1 -0
  85. package/dist/types/src/edge/echo-edge-subduction-replicator.test.d.ts +2 -0
  86. package/dist/types/src/edge/echo-edge-subduction-replicator.test.d.ts.map +1 -0
  87. package/dist/types/src/edge/index.d.ts +1 -0
  88. package/dist/types/src/edge/index.d.ts.map +1 -1
  89. package/dist/types/src/edge/inflight-request-limiter.d.ts.map +1 -1
  90. package/dist/types/src/filter/filter-match.d.ts +4 -1
  91. package/dist/types/src/filter/filter-match.d.ts.map +1 -1
  92. package/dist/types/src/metadata/metadata-store.d.ts +1 -1
  93. package/dist/types/src/metadata/metadata-store.d.ts.map +1 -1
  94. package/dist/types/src/pipeline/message-selector.d.ts.map +1 -1
  95. package/dist/types/src/pipeline/pipeline.d.ts +3 -3
  96. package/dist/types/src/pipeline/pipeline.d.ts.map +1 -1
  97. package/dist/types/src/pipeline/timeframe-clock.d.ts.map +1 -1
  98. package/dist/types/src/query/errors.d.ts +22 -10
  99. package/dist/types/src/query/errors.d.ts.map +1 -1
  100. package/dist/types/src/query/plan.d.ts +66 -8
  101. package/dist/types/src/query/plan.d.ts.map +1 -1
  102. package/dist/types/src/query/query-executor.d.ts +66 -11
  103. package/dist/types/src/query/query-executor.d.ts.map +1 -1
  104. package/dist/types/src/query/query-planner.d.ts +12 -1
  105. package/dist/types/src/query/query-planner.d.ts.map +1 -1
  106. package/dist/types/src/space/admission-discovery-extension.d.ts.map +1 -1
  107. package/dist/types/src/space/auth.d.ts +3 -3
  108. package/dist/types/src/space/auth.d.ts.map +1 -1
  109. package/dist/types/src/space/control-pipeline.d.ts +5 -4
  110. package/dist/types/src/space/control-pipeline.d.ts.map +1 -1
  111. package/dist/types/src/space/space-manager.d.ts +8 -8
  112. package/dist/types/src/space/space-manager.d.ts.map +1 -1
  113. package/dist/types/src/space/space-protocol.d.ts +10 -9
  114. package/dist/types/src/space/space-protocol.d.ts.map +1 -1
  115. package/dist/types/src/space/space.d.ts +6 -6
  116. package/dist/types/src/space/space.d.ts.map +1 -1
  117. package/dist/types/src/testing/change-metadata.d.ts.map +1 -1
  118. package/dist/types/src/testing/index.d.ts +0 -2
  119. package/dist/types/src/testing/index.d.ts.map +1 -1
  120. package/dist/types/src/testing/test-agent-builder.d.ts +2 -2
  121. package/dist/types/src/testing/test-agent-builder.d.ts.map +1 -1
  122. package/dist/types/src/testing/test-feed-builder.d.ts.map +1 -1
  123. package/dist/types/src/testing/test-network-adapter.d.ts +1 -1
  124. package/dist/types/src/testing/test-network-adapter.d.ts.map +1 -1
  125. package/dist/types/src/testing/test-replicator.d.ts +14 -13
  126. package/dist/types/src/testing/test-replicator.d.ts.map +1 -1
  127. package/dist/types/src/util.d.ts +1 -1
  128. package/dist/types/src/util.d.ts.map +1 -1
  129. package/dist/types/tsconfig.tsbuildinfo +1 -1
  130. package/package.json +59 -55
  131. package/src/automerge/automerge-host-subduction.test.ts +263 -0
  132. package/src/automerge/automerge-host.test.ts +162 -39
  133. package/src/automerge/automerge-host.ts +679 -201
  134. package/src/automerge/automerge-repo-subduction.test.ts +1054 -0
  135. package/src/automerge/automerge-repo.test.ts +349 -91
  136. package/src/automerge/automerge-subduction.test.ts +330 -0
  137. package/src/automerge/collection-synchronizer.test.ts +98 -17
  138. package/src/automerge/collection-synchronizer.ts +141 -37
  139. package/src/automerge/echo-data-monitor.ts +1 -1
  140. package/src/automerge/echo-network-adapter.test.ts +5 -4
  141. package/src/automerge/echo-network-adapter.ts +76 -31
  142. package/src/automerge/echo-replicator.ts +50 -15
  143. package/src/automerge/errors.ts +10 -0
  144. package/src/automerge/handle-state.ts +62 -0
  145. package/src/automerge/heads-store.ts +42 -3
  146. package/src/automerge/index.ts +3 -1
  147. package/src/automerge/leveldb-storage-adapter.ts +77 -11
  148. package/src/automerge/mesh-echo-replicator-connection.ts +12 -8
  149. package/src/automerge/mesh-echo-replicator.ts +15 -11
  150. package/src/automerge/storage-adapter.test.ts +1 -1
  151. package/src/common/space-id.ts +1 -1
  152. package/src/db-host/automerge-data-source.test.ts +299 -0
  153. package/src/db-host/automerge-data-source.ts +187 -0
  154. package/src/db-host/data-service.ts +35 -26
  155. package/src/db-host/database-root.ts +20 -8
  156. package/src/db-host/documents-synchronizer.test.ts +145 -14
  157. package/src/db-host/documents-synchronizer.ts +163 -51
  158. package/src/db-host/echo-host.ts +384 -99
  159. package/src/db-host/index.ts +2 -0
  160. package/src/db-host/invalidation-hint.ts +50 -0
  161. package/src/db-host/local-queue-service.ts +127 -0
  162. package/src/db-host/query-invalidation.test.ts +270 -0
  163. package/src/db-host/query-service.ts +111 -106
  164. package/src/db-host/queue-data-source.ts +160 -0
  165. package/src/db-host/queue-service.test.ts +145 -0
  166. package/src/db-host/space-state-manager.ts +12 -10
  167. package/src/db-host/stub.ts +33 -0
  168. package/src/edge/echo-edge-replicator.test.ts +12 -10
  169. package/src/edge/echo-edge-replicator.ts +144 -58
  170. package/src/edge/echo-edge-subduction-replicator.test.ts +158 -0
  171. package/src/edge/echo-edge-subduction-replicator.ts +377 -0
  172. package/src/edge/index.ts +1 -0
  173. package/src/filter/filter-match.test.ts +68 -32
  174. package/src/filter/filter-match.ts +230 -21
  175. package/src/metadata/metadata-store.ts +11 -8
  176. package/src/pipeline/pipeline-stress.test.ts +1 -1
  177. package/src/pipeline/pipeline.test.ts +2 -2
  178. package/src/pipeline/pipeline.ts +9 -7
  179. package/src/query/errors.ts +3 -1
  180. package/src/query/plan.ts +84 -9
  181. package/src/query/query-executor.ts +1197 -165
  182. package/src/query/query-planner.test.ts +993 -81
  183. package/src/query/query-planner.ts +503 -27
  184. package/src/space/admission-discovery-extension.ts +3 -3
  185. package/src/space/auth.ts +6 -6
  186. package/src/space/control-pipeline.test.ts +5 -4
  187. package/src/space/control-pipeline.ts +16 -13
  188. package/src/space/space-manager.browser.test.ts +1 -1
  189. package/src/space/space-manager.ts +16 -20
  190. package/src/space/space-protocol.browser.test.ts +15 -14
  191. package/src/space/space-protocol.test.ts +15 -14
  192. package/src/space/space-protocol.ts +22 -18
  193. package/src/space/space.test.ts +3 -3
  194. package/src/space/space.ts +11 -11
  195. package/src/testing/index.ts +0 -2
  196. package/src/testing/test-agent-builder.ts +3 -3
  197. package/src/testing/test-network-adapter.ts +24 -3
  198. package/src/testing/test-replicator.ts +19 -20
  199. package/src/util.ts +7 -3
  200. package/dist/lib/browser/chunk-35I6ERLG.mjs.map +0 -7
  201. package/dist/lib/browser/chunk-CGS2ULMK.mjs +0 -11
  202. package/dist/lib/browser/chunk-CGS2ULMK.mjs.map +0 -7
  203. package/dist/lib/browser/chunk-TQJTKNMS.mjs +0 -126
  204. package/dist/lib/browser/chunk-TQJTKNMS.mjs.map +0 -7
  205. package/dist/lib/browser/index.mjs +0 -4743
  206. package/dist/lib/browser/index.mjs.map +0 -7
  207. package/dist/lib/browser/meta.json +0 -1
  208. package/dist/lib/browser/testing/index.mjs.map +0 -7
  209. package/dist/lib/node/chunk-HOPOFWAL.cjs +0 -147
  210. package/dist/lib/node/chunk-HOPOFWAL.cjs.map +0 -7
  211. package/dist/lib/node/chunk-JXX6LF5U.cjs +0 -2084
  212. package/dist/lib/node/chunk-JXX6LF5U.cjs.map +0 -7
  213. package/dist/lib/node/chunk-Q7SFCCGT.cjs +0 -33
  214. package/dist/lib/node/chunk-Q7SFCCGT.cjs.map +0 -7
  215. package/dist/lib/node/filter/index.cjs +0 -32
  216. package/dist/lib/node/filter/index.cjs.map +0 -7
  217. package/dist/lib/node/index.cjs +0 -4699
  218. package/dist/lib/node/index.cjs.map +0 -7
  219. package/dist/lib/node/meta.json +0 -1
  220. package/dist/lib/node/testing/index.cjs +0 -753
  221. package/dist/lib/node/testing/index.cjs.map +0 -7
  222. package/dist/lib/node-esm/chunk-5BHLPT24.mjs +0 -2067
  223. package/dist/lib/node-esm/chunk-5BHLPT24.mjs.map +0 -7
  224. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
  225. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +0 -7
  226. package/dist/lib/node-esm/chunk-RVK35BS7.mjs +0 -126
  227. package/dist/lib/node-esm/chunk-RVK35BS7.mjs.map +0 -7
  228. package/dist/lib/node-esm/filter/index.mjs +0 -11
  229. package/dist/lib/node-esm/filter/index.mjs.map +0 -7
  230. package/dist/lib/node-esm/index.mjs +0 -4743
  231. package/dist/lib/node-esm/index.mjs.map +0 -7
  232. package/dist/lib/node-esm/meta.json +0 -1
  233. package/dist/lib/node-esm/testing/index.mjs +0 -742
  234. package/dist/lib/node-esm/testing/index.mjs.map +0 -7
  235. package/dist/types/src/db-host/documents-iterator.d.ts +0 -7
  236. package/dist/types/src/db-host/documents-iterator.d.ts.map +0 -1
  237. package/dist/types/src/testing/test-data.d.ts +0 -18
  238. package/dist/types/src/testing/test-data.d.ts.map +0 -1
  239. package/dist/types/src/testing/test-schema.d.ts +0 -37
  240. package/dist/types/src/testing/test-schema.d.ts.map +0 -1
  241. package/src/db-host/documents-iterator.ts +0 -74
  242. package/src/testing/test-data.ts +0 -127
  243. package/src/testing/test-schema.ts +0 -53
  244. /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,10 @@
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 * as semver from 'semver';
6
+
7
+ import { EncodedReference, ObjectStructure, type QueryAST, isEncodedReference } from '@dxos/echo-protocol';
8
+ import { ATTR_META, type ObjectJSON } from '@dxos/echo/internal';
7
9
  import { DXN, type ObjectId, type SpaceId } from '@dxos/keys';
8
10
 
9
11
  export type MatchedObject = {
@@ -14,35 +16,32 @@ export type MatchedObject = {
14
16
 
15
17
  /**
16
18
  * Matches an object against a filter AST.
19
+ * @param obj object structure as stored in automerge.
17
20
  */
18
21
  export const filterMatchObject = (filter: QueryAST.Filter, obj: MatchedObject): boolean => {
19
22
  switch (filter.type) {
20
23
  case 'object': {
21
- // Check typename if specified
24
+ // Check typename if specified.
22
25
  if (filter.typename !== null) {
23
26
  // TODO(dmaretskyi): `system` is missing in some cases.
24
- 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
- }
27
+ if (!obj.doc?.system?.type?.['/']) {
28
+ // Objects with no type are deprecated.
29
+ return false;
30
30
  } else {
31
31
  const actualDXN = DXN.parse(obj.doc.system.type['/']);
32
32
  const expectedDXN = DXN.parse(filter.typename);
33
-
34
33
  if (!compareTypename(expectedDXN, actualDXN)) {
35
34
  return false;
36
35
  }
37
36
  }
38
37
  }
39
38
 
40
- // Check IDs if specified
39
+ // Check IDs if specified.
41
40
  if (filter.id && filter.id.length > 0 && !filter.id.includes(obj.id)) {
42
41
  return false;
43
42
  }
44
43
 
45
- // Check properties
44
+ // Check properties.
46
45
  if (filter.props) {
47
46
  for (const [key, valueFilter] of Object.entries(filter.props)) {
48
47
  const value = obj.doc.data[key];
@@ -52,7 +51,7 @@ export const filterMatchObject = (filter: QueryAST.Filter, obj: MatchedObject):
52
51
  }
53
52
  }
54
53
 
55
- // Check foreign keys if specified
54
+ // Check foreign keys if specified.
56
55
  if (filter.foreignKeys && filter.foreignKeys.length > 0) {
57
56
  const hasMatchingKey = filter.foreignKeys.some((filterKey) =>
58
57
  obj.doc.meta.keys.some((objKey) => objKey.source === filterKey.source && objKey.id === filterKey.id),
@@ -62,14 +61,35 @@ export const filterMatchObject = (filter: QueryAST.Filter, obj: MatchedObject):
62
61
  }
63
62
  }
64
63
 
64
+ // Check registry meta key / version if specified.
65
+ if (
66
+ filter.metaKey !== undefined &&
67
+ !matchMetaKey(filter.metaKey, filter.metaVersion, obj.doc.meta.key, obj.doc.meta.version)
68
+ ) {
69
+ return false;
70
+ }
71
+
65
72
  return true;
66
73
  }
67
74
 
75
+ case 'tag': {
76
+ const tags = ObjectStructure.getTags(obj.doc);
77
+ return tags.some((tag) => tag === filter.tag);
78
+ }
79
+
68
80
  case 'text-search': {
69
- // TODO: Implement text search
81
+ // TODO(???): Implement text search.
70
82
  return false;
71
83
  }
72
84
 
85
+ case 'timestamp': {
86
+ throw new Error('Timestamp filters must be handled at the index level, not in-memory matching.');
87
+ }
88
+
89
+ case 'child-of': {
90
+ throw new Error('child-of filters must be handled at the executor level, not in-memory matching.');
91
+ }
92
+
73
93
  case 'not': {
74
94
  return !filterMatchObject(filter.filter, obj);
75
95
  }
@@ -87,6 +107,163 @@ export const filterMatchObject = (filter: QueryAST.Filter, obj: MatchedObject):
87
107
  }
88
108
  };
89
109
 
110
+ /**
111
+ * Matches a meta `key` / `version` constraint against an object's meta `key` and `version`.
112
+ * - `key` must match exactly.
113
+ * - If `versionRange` is set, the object's `version` must satisfy it (semver).
114
+ * Objects without a `version` or with an invalid `version` do not match a version-constrained filter.
115
+ */
116
+ const matchMetaKey = (
117
+ key: string,
118
+ versionRange: string | undefined,
119
+ objKey: string | undefined,
120
+ objVersion: string | undefined,
121
+ ): boolean => {
122
+ if (objKey !== key) {
123
+ return false;
124
+ }
125
+ if (versionRange === undefined) {
126
+ return true;
127
+ }
128
+ if (objVersion === undefined || semver.valid(objVersion) === null) {
129
+ return false;
130
+ }
131
+ if (semver.validRange(versionRange) === null) {
132
+ return false;
133
+ }
134
+ return semver.satisfies(objVersion, versionRange, { includePrerelease: true });
135
+ };
136
+
137
+ // TODO(burdon): Reconcile with filterMatchObject.
138
+ export const filterMatchObjectJSON = (filter: QueryAST.Filter, obj: ObjectJSON): boolean => {
139
+ switch (filter.type) {
140
+ case 'object': {
141
+ // Check typename if specified
142
+ if (filter.typename !== null) {
143
+ // TODO(dmaretskyi): `system` is missing in some cases.
144
+ if (!obj['@type']) {
145
+ // Objects with no type are deprecated.
146
+ return false;
147
+ } else {
148
+ const actualDXN = DXN.parse(obj['@type']);
149
+ const expectedDXN = DXN.parse(filter.typename);
150
+ if (!compareTypename(expectedDXN, actualDXN)) {
151
+ return false;
152
+ }
153
+ }
154
+ }
155
+
156
+ // Check IDs if specified
157
+ if (filter.id && filter.id.length > 0 && !filter.id.includes(obj.id)) {
158
+ return false;
159
+ }
160
+
161
+ // Check properties
162
+ if (filter.props) {
163
+ for (const [key, valueFilter] of Object.entries(filter.props)) {
164
+ if (key.startsWith('@')) {
165
+ // ignore meta properties
166
+ continue;
167
+ }
168
+ const value = (obj as any)[key];
169
+ if (!filterMatchValue(valueFilter, value)) {
170
+ return false;
171
+ }
172
+ }
173
+ }
174
+
175
+ // Check foreign keys if specified
176
+ if (filter.foreignKeys && filter.foreignKeys.length > 0) {
177
+ const hasMatchingKey = filter.foreignKeys.some((filterKey) =>
178
+ obj['@meta']?.keys?.some((objKey) => objKey.source === filterKey.source && objKey.id === filterKey.id),
179
+ );
180
+ if (!hasMatchingKey) {
181
+ return false;
182
+ }
183
+ }
184
+
185
+ // Check registry meta key / version if specified.
186
+ if (
187
+ filter.metaKey !== undefined &&
188
+ !matchMetaKey(filter.metaKey, filter.metaVersion, obj[ATTR_META]?.key, obj[ATTR_META]?.version)
189
+ ) {
190
+ return false;
191
+ }
192
+
193
+ return true;
194
+ }
195
+
196
+ case 'tag': {
197
+ const tags = obj[ATTR_META]?.tags ?? [];
198
+ return tags.some((tag) => tag === filter.tag);
199
+ }
200
+
201
+ // TODO: Implement text search.
202
+ case 'text-search': {
203
+ return false;
204
+ }
205
+
206
+ case 'timestamp': {
207
+ throw new Error('Timestamp filters must be handled at the index level, not in-memory matching.');
208
+ }
209
+
210
+ case 'child-of': {
211
+ throw new Error('child-of filters must be handled at the executor level, not in-memory matching.');
212
+ }
213
+
214
+ case 'not': {
215
+ return !filterMatchObjectJSON(filter.filter, obj);
216
+ }
217
+
218
+ case 'and': {
219
+ return filter.filters.every((f) => filterMatchObjectJSON(f, obj));
220
+ }
221
+
222
+ case 'or': {
223
+ return filter.filters.some((f) => filterMatchObjectJSON(f, obj));
224
+ }
225
+
226
+ default:
227
+ return false;
228
+ }
229
+ };
230
+
231
+ /**
232
+ * Performs structural matching between a filter object and a target object.
233
+ * This handles nested object comparison for array matching scenarios.
234
+ */
235
+ // TODO(wittjosiah): Add ast support for non-strict matching.
236
+ const structuralMatch = (filterObj: any, targetObj: any, strict = true): boolean => {
237
+ if (typeof filterObj !== 'object' || filterObj === null) {
238
+ return filterObj === targetObj;
239
+ }
240
+
241
+ if (typeof targetObj !== 'object' || targetObj === null) {
242
+ return false;
243
+ }
244
+
245
+ // Prohibit extra keys in targetObj.
246
+ const filterKeys = Object.keys(filterObj);
247
+ const targetKeys = Object.keys(targetObj);
248
+ if (strict && filterKeys.length !== targetKeys.length) {
249
+ return false;
250
+ }
251
+
252
+ return filterKeys.every((key) => {
253
+ if (!(key in targetObj)) {
254
+ return false;
255
+ }
256
+ const filterValue = filterObj[key];
257
+ const targetValue = targetObj[key];
258
+
259
+ if (typeof filterValue === 'object' && filterValue !== null) {
260
+ return structuralMatch(filterValue, targetValue);
261
+ }
262
+
263
+ return filterValue === targetValue;
264
+ });
265
+ };
266
+
90
267
  export const filterMatchValue = (filter: QueryAST.Filter, value: unknown): boolean => {
91
268
  switch (filter.type) {
92
269
  case 'compare': {
@@ -97,7 +274,7 @@ export const filterMatchValue = (filter: QueryAST.Filter, value: unknown): boole
97
274
  if (!isEncodedReference(value)) {
98
275
  return false;
99
276
  }
100
- return DXN.equals(decodeReference(value).toDXN(), decodeReference(compareValue).toDXN());
277
+ return DXN.equals(EncodedReference.toDXN(value), EncodedReference.toDXN(compareValue));
101
278
  }
102
279
  return value === compareValue;
103
280
  case 'neq':
@@ -110,12 +287,44 @@ export const filterMatchValue = (filter: QueryAST.Filter, value: unknown): boole
110
287
  return (value as any) < compareValue;
111
288
  case 'lte':
112
289
  return (value as any) <= compareValue;
290
+ default:
291
+ return false;
292
+ }
293
+ }
294
+ case 'object': {
295
+ // Handle nested object filters for property matching
296
+ if (typeof value !== 'object' || value === null) {
297
+ return false;
113
298
  }
114
- break;
299
+
300
+ // Check properties
301
+ if (filter.props) {
302
+ for (const [key, valueFilter] of Object.entries(filter.props)) {
303
+ const nestedValue = (value as any)[key];
304
+ if (!filterMatchValue(valueFilter, nestedValue)) {
305
+ return false;
306
+ }
307
+ }
308
+ }
309
+
310
+ return true;
115
311
  }
116
312
  case 'in': {
117
313
  return filter.values.includes(value);
118
314
  }
315
+ case 'contains': {
316
+ if (!Array.isArray(value)) {
317
+ return false;
318
+ }
319
+
320
+ return value.some((element) => {
321
+ if (typeof filter.value === 'object' && filter.value !== null && !Array.isArray(filter.value)) {
322
+ return structuralMatch(filter.value, element);
323
+ }
324
+
325
+ return element === filter.value;
326
+ });
327
+ }
119
328
  case 'range': {
120
329
  return (value as any) >= filter.from && (value as any) <= filter.to;
121
330
  }
@@ -143,11 +352,11 @@ export const filterMatchValue = (filter: QueryAST.Filter, value: unknown): boole
143
352
  *
144
353
  * Examples: (expected) (actual)
145
354
  *
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
355
+ * dxn:type:com.example.type.task !== dxn:type:com.example.type.contact
356
+ * dxn:type:com.example.type.task === dxn:type:com.example.type.task
357
+ * dxn:type:com.example.type.task:0.1.0 !== dxn:type:com.example.type.task:0.2.0
358
+ * dxn:type:com.example.type.task === dxn:type:com.example.type.task:0.1.0
359
+ * dxn:type:com.example.type.task:0.1.0 === dxn:type:com.example.type.task
151
360
  *
152
361
  */
153
362
  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') {}