@dxos/echo 0.8.3-main.7f5a14c → 0.8.3-staging.0fa589b

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 (53) hide show
  1. package/dist/lib/browser/chunk-UYPR62ZB.mjs +624 -0
  2. package/dist/lib/browser/chunk-UYPR62ZB.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +10 -282
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/testing/index.mjs +70 -0
  7. package/dist/lib/browser/testing/index.mjs.map +7 -0
  8. package/dist/lib/node/chunk-4HQE2F3L.cjs +644 -0
  9. package/dist/lib/node/chunk-4HQE2F3L.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +9 -282
  11. package/dist/lib/node/index.cjs.map +4 -4
  12. package/dist/lib/node/meta.json +1 -1
  13. package/dist/lib/node/testing/index.cjs +89 -0
  14. package/dist/lib/node/testing/index.cjs.map +7 -0
  15. package/dist/lib/node-esm/chunk-BYBICDIO.mjs +624 -0
  16. package/dist/lib/node-esm/chunk-BYBICDIO.mjs.map +7 -0
  17. package/dist/lib/node-esm/index.mjs +10 -282
  18. package/dist/lib/node-esm/index.mjs.map +4 -4
  19. package/dist/lib/node-esm/meta.json +1 -1
  20. package/dist/lib/node-esm/testing/index.mjs +70 -0
  21. package/dist/lib/node-esm/testing/index.mjs.map +7 -0
  22. package/dist/types/src/Obj.d.ts +23 -8
  23. package/dist/types/src/Obj.d.ts.map +1 -1
  24. package/dist/types/src/Ref.d.ts +1 -1
  25. package/dist/types/src/Ref.d.ts.map +1 -1
  26. package/dist/types/src/Relation.d.ts +36 -10
  27. package/dist/types/src/Relation.d.ts.map +1 -1
  28. package/dist/types/src/Type.d.ts +78 -15
  29. package/dist/types/src/Type.d.ts.map +1 -1
  30. package/dist/types/src/index.d.ts +1 -1
  31. package/dist/types/src/index.d.ts.map +1 -1
  32. package/dist/types/src/query/dsl.d.ts +218 -0
  33. package/dist/types/src/query/dsl.d.ts.map +1 -0
  34. package/dist/types/src/query/dsl.test.d.ts +2 -0
  35. package/dist/types/src/query/dsl.test.d.ts.map +1 -0
  36. package/dist/types/src/query/index.d.ts +2 -0
  37. package/dist/types/src/query/index.d.ts.map +1 -0
  38. package/dist/types/src/testing/index.d.ts +2 -0
  39. package/dist/types/src/testing/index.d.ts.map +1 -0
  40. package/dist/types/src/testing/types.d.ts +113 -0
  41. package/dist/types/src/testing/types.d.ts.map +1 -0
  42. package/package.json +56 -12
  43. package/src/Obj.ts +44 -6
  44. package/src/Ref.ts +1 -1
  45. package/src/Relation.ts +75 -13
  46. package/src/Type.ts +113 -14
  47. package/src/index.ts +1 -1
  48. package/src/query/dsl.test.ts +323 -0
  49. package/src/query/dsl.ts +646 -0
  50. package/src/query/index.ts +5 -0
  51. package/src/test/api.test.ts +37 -7
  52. package/src/testing/index.ts +5 -0
  53. package/src/testing/types.ts +91 -0
@@ -0,0 +1,323 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Schema } from 'effect';
6
+ import { describe, expect, test } from 'vitest';
7
+
8
+ import { QueryAST } from '@dxos/echo-protocol';
9
+ import { DXN } from '@dxos/keys';
10
+ import { log } from '@dxos/log';
11
+
12
+ import { Filter, Query } from './dsl';
13
+ import * as Obj from '../Obj';
14
+ import * as Ref from '../Ref';
15
+ import * as Type from '../Type';
16
+
17
+ //
18
+ // Example schema
19
+ //
20
+
21
+ // TODO(dmaretskyi): Need common set of test types.
22
+ const Person = Schema.Struct({
23
+ name: Schema.String,
24
+ email: Schema.optional(Schema.String),
25
+ age: Schema.optional(Schema.Number),
26
+ }).pipe(
27
+ Type.Obj({
28
+ typename: 'dxos.org/type/Person',
29
+ version: '0.1.0',
30
+ }),
31
+ );
32
+ interface Person extends Schema.Schema.Type<typeof Person> {}
33
+
34
+ const Organization = Schema.Struct({
35
+ name: Schema.String,
36
+ }).pipe(
37
+ Type.Obj({
38
+ typename: 'dxos.org/type/Organization',
39
+ version: '0.1.0',
40
+ }),
41
+ );
42
+ interface Organization extends Schema.Schema.Type<typeof Organization> {}
43
+
44
+ const WorksFor = Schema.Struct({
45
+ since: Schema.String,
46
+ }).pipe(
47
+ Type.Relation({
48
+ typename: 'dxos.org/type/WorksFor',
49
+ version: '0.1.0',
50
+ source: Person,
51
+ target: Organization,
52
+ }),
53
+ );
54
+ interface WorksFor extends Schema.Schema.Type<typeof WorksFor> {}
55
+
56
+ const Task = Schema.Struct({
57
+ title: Schema.String,
58
+ createdAt: Schema.String,
59
+ assignee: Schema.optional(Type.Ref(Person)),
60
+ }).pipe(Type.Obj({ typename: 'dxos.org/type/Task', version: '0.1.0' }));
61
+ interface Task extends Schema.Schema.Type<typeof Task> {}
62
+
63
+ //
64
+ // Example queries
65
+ //
66
+
67
+ describe('query api', () => {
68
+ test('get all people', () => {
69
+ const getAllPeople = Query.type(Person);
70
+
71
+ log('query', { ast: getAllPeople.ast });
72
+ Schema.validateSync(QueryAST.Query)(getAllPeople.ast);
73
+ console.log('getAllPeople', JSON.stringify(getAllPeople.ast, null, 2));
74
+ });
75
+
76
+ test('get all people named Fred', () => {
77
+ const PeopleNamedFred = Query.select(Filter.type(Person, { name: 'Fred' }));
78
+
79
+ log('query', { ast: PeopleNamedFred.ast });
80
+ Schema.validateSync(QueryAST.Query)(PeopleNamedFred.ast);
81
+ console.log('PeopleNamedFred', JSON.stringify(PeopleNamedFred.ast, null, 2));
82
+ });
83
+
84
+ test('get all orgs Fred worked for since 2020', () => {
85
+ const fred = Obj.make(Person, { name: 'Fred' });
86
+ const OrganizationsFredWorkedForSince2020 = Query.select(Filter.type(Person, { id: fred.id }))
87
+ .sourceOf(WorksFor, { since: Filter.gt('2020') })
88
+ .target();
89
+
90
+ log('query', { ast: OrganizationsFredWorkedForSince2020.ast });
91
+ Schema.validateSync(QueryAST.Query)(OrganizationsFredWorkedForSince2020.ast);
92
+ console.log(
93
+ 'OrganizationsFredWorkedForSince2020',
94
+ JSON.stringify(OrganizationsFredWorkedForSince2020.ast, null, 2),
95
+ );
96
+ });
97
+
98
+ test('get all tasks for Fred', () => {
99
+ const fred = Obj.make(Person, { name: 'Fred' });
100
+ const TasksForFred = Query.select(Filter.type(Person, { id: fred.id })).referencedBy(Task, 'assignee');
101
+
102
+ log('query', { ast: TasksForFred.ast });
103
+ Schema.validateSync(QueryAST.Query)(TasksForFred.ast);
104
+ console.log('TasksForFred', JSON.stringify(TasksForFred.ast, null, 2));
105
+ });
106
+
107
+ test('get all tasks for employees of Cyberdyne', () => {
108
+ const TasksForEmployeesOfCyberdyne = Query.select(Filter.type(Organization, { name: 'Cyberdyne' }))
109
+ .targetOf(WorksFor)
110
+ .source()
111
+ .referencedBy(Task, 'assignee');
112
+
113
+ log('query', { ast: TasksForEmployeesOfCyberdyne.ast });
114
+ Schema.validateSync(QueryAST.Query)(TasksForEmployeesOfCyberdyne.ast);
115
+ console.log('TasksForEmployeesOfCyberdyne', JSON.stringify(TasksForEmployeesOfCyberdyne.ast, null, 2));
116
+ });
117
+
118
+ test('get all people or orgs', () => {
119
+ const PeopleOrOrganizations = Query.all(Query.select(Filter.type(Person)), Query.select(Filter.type(Organization)));
120
+
121
+ log('query', { ast: PeopleOrOrganizations.ast });
122
+ Schema.validateSync(QueryAST.Query)(PeopleOrOrganizations.ast);
123
+ console.log('PeopleOrOrganizations', JSON.stringify(PeopleOrOrganizations.ast, null, 2));
124
+ });
125
+
126
+ test('get all people not in orgs', () => {
127
+ const PeopleNotInOrganizations = Query.without(
128
+ Query.select(Filter.type(Person)),
129
+ Query.select(Filter.type(Person)).sourceOf(WorksFor).source(),
130
+ );
131
+
132
+ log('query', { ast: PeopleNotInOrganizations.ast });
133
+ Schema.validateSync(QueryAST.Query)(PeopleNotInOrganizations.ast);
134
+ console.log('PeopleNotInOrganizations', JSON.stringify(PeopleNotInOrganizations.ast, null, 2));
135
+ });
136
+
137
+ test('get assignees of all tasks created after 2020', () => {
138
+ const AssigneesOfAllTasksCreatedAfter2020 = Query.select(
139
+ Filter.type(Task, { createdAt: Filter.gt('2020') }),
140
+ ).reference('assignee');
141
+
142
+ log('query', { ast: AssigneesOfAllTasksCreatedAfter2020.ast });
143
+ Schema.validateSync(QueryAST.Query)(AssigneesOfAllTasksCreatedAfter2020.ast);
144
+ console.log(
145
+ 'AssigneesOfAllTasksCreatedAfter2020',
146
+ JSON.stringify(AssigneesOfAllTasksCreatedAfter2020.ast, null, 2),
147
+ );
148
+ });
149
+
150
+ test('untyped full-text search', () => {
151
+ const contactFullTextSearch = Query.select(Filter.text('Bill'));
152
+
153
+ log('query', { ast: contactFullTextSearch.ast });
154
+ Schema.validateSync(QueryAST.Query)(contactFullTextSearch.ast);
155
+ expect(contactFullTextSearch.ast).toMatchInlineSnapshot(`
156
+ {
157
+ "filter": {
158
+ "searchKind": undefined,
159
+ "text": "Bill",
160
+ "type": "text-search",
161
+ },
162
+ "type": "select",
163
+ }
164
+ `);
165
+ });
166
+
167
+ test('typed full-text search', () => {
168
+ const contactFullTextSearch = Query.select(Filter.type(Person)).select(Filter.text('Bill'));
169
+
170
+ log('query', { ast: contactFullTextSearch.ast });
171
+ Schema.validateSync(QueryAST.Query)(contactFullTextSearch.ast);
172
+ expect(contactFullTextSearch.ast).toMatchInlineSnapshot(`
173
+ {
174
+ "filter": {
175
+ "searchKind": undefined,
176
+ "text": "Bill",
177
+ "type": "text-search",
178
+ },
179
+ "selection": {
180
+ "filter": {
181
+ "id": undefined,
182
+ "props": {},
183
+ "type": "object",
184
+ "typename": "dxn:type:dxos.org/type/Person:0.1.0",
185
+ },
186
+ "type": "select",
187
+ },
188
+ "type": "filter",
189
+ }
190
+ `);
191
+ });
192
+
193
+ test('filter by ref', () => {
194
+ const fred = Obj.make(Person, { name: 'Fred' });
195
+ const tasksByFred = Filter.type(Task, { assignee: Ref.make(fred) });
196
+ expect(tasksByFred.ast).toEqual({
197
+ props: {
198
+ assignee: {
199
+ operator: 'eq',
200
+ type: 'compare',
201
+ value: {
202
+ '/': DXN.fromLocalObjectId(fred.id).toString(),
203
+ },
204
+ },
205
+ },
206
+ type: 'object',
207
+ typename: 'dxn:type:dxos.org/type/Task:0.1.0',
208
+ });
209
+ console.log('tasksByFred', JSON.stringify(tasksByFred.ast, null, 2));
210
+ });
211
+
212
+ test('select orgs and people', () => {
213
+ const orgsAndPeople = Query.select(Filter.or(Filter.type(Organization), Filter.type(Person)));
214
+
215
+ Schema.validateSync(QueryAST.Query)(orgsAndPeople.ast);
216
+ expect(orgsAndPeople.ast).toMatchInlineSnapshot(`
217
+ {
218
+ "filter": {
219
+ "filters": [
220
+ {
221
+ "id": undefined,
222
+ "props": {},
223
+ "type": "object",
224
+ "typename": "dxn:type:dxos.org/type/Organization:0.1.0",
225
+ },
226
+ {
227
+ "id": undefined,
228
+ "props": {},
229
+ "type": "object",
230
+ "typename": "dxn:type:dxos.org/type/Person:0.1.0",
231
+ },
232
+ ],
233
+ "type": "or",
234
+ },
235
+ "type": "select",
236
+ }
237
+ `);
238
+ });
239
+
240
+ test('select everything but orgs and people', () => {
241
+ const everythingButOrgsAndPeople = Query.select(
242
+ Filter.not(Filter.or(Filter.type(Organization), Filter.type(Person))),
243
+ );
244
+
245
+ Schema.validateSync(QueryAST.Query)(everythingButOrgsAndPeople.ast);
246
+ expect(everythingButOrgsAndPeople.ast).toMatchInlineSnapshot(`
247
+ {
248
+ "filter": {
249
+ "filter": {
250
+ "filters": [
251
+ {
252
+ "id": undefined,
253
+ "props": {},
254
+ "type": "object",
255
+ "typename": "dxn:type:dxos.org/type/Organization:0.1.0",
256
+ },
257
+ {
258
+ "id": undefined,
259
+ "props": {},
260
+ "type": "object",
261
+ "typename": "dxn:type:dxos.org/type/Person:0.1.0",
262
+ },
263
+ ],
264
+ "type": "or",
265
+ },
266
+ "type": "not",
267
+ },
268
+ "type": "select",
269
+ }
270
+ `);
271
+ });
272
+
273
+ test('select deleted tasks', () => {
274
+ const deletedTasks = Query.select(Filter.type(Task)).options({
275
+ deleted: 'only',
276
+ });
277
+
278
+ Schema.validateSync(QueryAST.Query)(deletedTasks.ast);
279
+ expect(deletedTasks.ast).toMatchInlineSnapshot(`
280
+ {
281
+ "options": {
282
+ "deleted": "only",
283
+ },
284
+ "query": {
285
+ "filter": {
286
+ "id": undefined,
287
+ "props": {},
288
+ "type": "object",
289
+ "typename": "dxn:type:dxos.org/type/Task:0.1.0",
290
+ },
291
+ "type": "select",
292
+ },
293
+ "type": "options",
294
+ }
295
+ `);
296
+ });
297
+
298
+ test.skip('chain', () => {
299
+ // NOTE: Can't support props without type since they can't be inferred.
300
+ // const f1: Filter<Person> = Filter.props({ name: 'Fred' });
301
+
302
+ // const x = Query.select(Filter.props({ id: '123' }));
303
+ const y = Query.select(Filter.type(Person));
304
+
305
+ const or = Filter.or(Filter.type(Person, { id: Filter.in('1', '2', '3') }), Filter.type(Organization));
306
+
307
+ const and = Filter.and(
308
+ Filter.type(Person, { id: Filter.in('1', '2', '3') }),
309
+ Filter.type(Person, { name: 'Fred' }),
310
+ );
311
+
312
+ const q = Query
313
+ //
314
+ // NOTE: Can't support functions since they can't be serialized (to server).
315
+ // .filter((object) => Math.random() > 0.5)
316
+ .select(Filter.type(Person))
317
+ .select(Filter.type(Person, { name: 'Fred' }))
318
+ .select({ age: Filter.between(20, 40) })
319
+ .select(Filter.and(Filter.type(Person), Filter.type(Person, { name: Filter.in('bob', 'bill') })));
320
+
321
+ log('stuff', { fOr: or, fAnd: and, q, y });
322
+ });
323
+ });