@dxos/echo 0.10.0 → 0.11.0
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/Annotation.mjs +3 -0
- package/dist/lib/Blob.mjs +160 -0
- package/dist/lib/Blob.mjs.map +1 -0
- package/dist/lib/Database.mjs +2 -0
- package/dist/lib/Entity.mjs +231 -0
- package/dist/lib/Entity.mjs.map +1 -0
- package/dist/lib/Err.mjs +105 -0
- package/dist/lib/Err.mjs.map +1 -0
- package/dist/lib/Feed.mjs +338 -0
- package/dist/lib/Feed.mjs.map +1 -0
- package/dist/lib/Filter.mjs +380 -0
- package/dist/lib/Filter.mjs.map +1 -0
- package/dist/lib/Format.mjs +47 -0
- package/dist/lib/Format.mjs.map +1 -0
- package/dist/lib/JsonSchema.mjs +21 -0
- package/dist/lib/JsonSchema.mjs.map +1 -0
- package/dist/lib/Key.mjs +15 -0
- package/dist/lib/Key.mjs.map +1 -0
- package/dist/lib/Migration.mjs +38 -0
- package/dist/lib/Migration.mjs.map +1 -0
- package/dist/lib/Obj.mjs +660 -0
- package/dist/lib/Obj.mjs.map +1 -0
- package/dist/lib/Order.mjs +65 -0
- package/dist/lib/Order.mjs.map +1 -0
- package/dist/lib/Query.mjs +336 -0
- package/dist/lib/Query.mjs.map +1 -0
- package/dist/lib/QueryResult.mjs +7 -0
- package/dist/lib/QueryResult.mjs.map +1 -0
- package/dist/lib/Ref.mjs +44 -0
- package/dist/lib/Ref.mjs.map +1 -0
- package/dist/lib/Registry.mjs +35 -0
- package/dist/lib/Registry.mjs.map +1 -0
- package/dist/lib/Relation.mjs +397 -0
- package/dist/lib/Relation.mjs.map +1 -0
- package/dist/lib/Scope.mjs +51 -0
- package/dist/lib/Scope.mjs.map +1 -0
- package/dist/lib/Tag.mjs +70 -0
- package/dist/lib/Tag.mjs.map +1 -0
- package/dist/lib/Text.mjs +82 -0
- package/dist/lib/Text.mjs.map +1 -0
- package/dist/lib/Type.mjs +371 -0
- package/dist/lib/Type.mjs.map +1 -0
- package/dist/lib/chunk-Annotation.mjs +221 -0
- package/dist/lib/chunk-Annotation.mjs.map +1 -0
- package/dist/lib/chunk-Database.mjs +918 -0
- package/dist/lib/chunk-Database.mjs.map +1 -0
- package/dist/lib/chunk-atom-snapshot.mjs +249 -0
- package/dist/lib/chunk-atom-snapshot.mjs.map +1 -0
- package/dist/lib/chunk-internal.mjs +2775 -0
- package/dist/lib/chunk-internal.mjs.map +1 -0
- package/dist/lib/chunk-json-schema.mjs +660 -0
- package/dist/lib/chunk-json-schema.mjs.map +1 -0
- package/dist/lib/chunk-number.mjs +164 -0
- package/dist/lib/chunk-number.mjs.map +1 -0
- package/dist/lib/chunk-ref-array.mjs +31 -0
- package/dist/lib/chunk-ref-array.mjs.map +1 -0
- package/dist/lib/chunk-ref.mjs +590 -0
- package/dist/lib/chunk-ref.mjs.map +1 -0
- package/dist/lib/chunk-rolldown-runtime.mjs +13 -0
- package/dist/lib/chunk-select.mjs +186 -0
- package/dist/lib/chunk-select.mjs.map +1 -0
- package/dist/lib/chunk-src.mjs +234 -0
- package/dist/lib/chunk-src.mjs.map +1 -0
- package/dist/lib/chunk-util.mjs +140 -0
- package/dist/lib/chunk-util.mjs.map +1 -0
- package/dist/lib/index.mjs +24 -0
- package/dist/lib/internal.mjs +10 -0
- package/dist/lib/testing.mjs +346 -0
- package/dist/lib/testing.mjs.map +1 -0
- package/dist/types/src/Aggregate.d.ts +86 -0
- package/dist/types/src/Aggregate.d.ts.map +1 -0
- package/dist/types/src/Blob.d.ts +153 -0
- package/dist/types/src/Blob.d.ts.map +1 -0
- package/dist/types/src/Blob.test.d.ts +2 -0
- package/dist/types/src/Blob.test.d.ts.map +1 -0
- package/dist/types/src/Database.d.ts +153 -4
- package/dist/types/src/Database.d.ts.map +1 -1
- package/dist/types/src/Err.d.ts +116 -0
- package/dist/types/src/Err.d.ts.map +1 -1
- package/dist/types/src/Feed.d.ts +116 -28
- package/dist/types/src/Feed.d.ts.map +1 -1
- package/dist/types/src/Feed.test.d.ts +2 -0
- package/dist/types/src/Feed.test.d.ts.map +1 -0
- package/dist/types/src/Filter.d.ts +14 -6
- package/dist/types/src/Filter.d.ts.map +1 -1
- package/dist/types/src/Hypergraph.d.ts +18 -0
- package/dist/types/src/Hypergraph.d.ts.map +1 -1
- package/dist/types/src/Json.d.ts.map +1 -1
- package/dist/types/src/Obj.d.ts +14 -1
- package/dist/types/src/Obj.d.ts.map +1 -1
- package/dist/types/src/Order.d.ts +9 -3
- package/dist/types/src/Order.d.ts.map +1 -1
- package/dist/types/src/Query.d.ts +107 -26
- package/dist/types/src/Query.d.ts.map +1 -1
- package/dist/types/src/Relation.d.ts +6 -0
- package/dist/types/src/Relation.d.ts.map +1 -1
- package/dist/types/src/Type.d.ts.map +1 -1
- package/dist/types/src/View.d.ts +1 -1
- package/dist/types/src/index.d.ts +2 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/api.d.ts +6 -0
- package/dist/types/src/internal/Entity/api.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/model.d.ts +3 -2
- package/dist/types/src/internal/Entity/model.d.ts.map +1 -1
- package/dist/types/src/internal/Filter/match.d.ts.map +1 -1
- package/dist/types/src/internal/JsonSchema/json-schema-type.d.ts +34 -8
- package/dist/types/src/internal/JsonSchema/json-schema-type.d.ts.map +1 -1
- package/dist/types/src/internal/JsonSchema/json-schema.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/atoms.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/json-serializer.d.ts.map +1 -1
- package/dist/types/src/internal/Query/pretty.d.ts.map +1 -1
- package/dist/types/src/internal/Query/query-result-effect.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/make-object.d.ts +13 -0
- package/dist/types/src/internal/common/proxy/make-object.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/reactive.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/symbols.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/typed-handler.d.ts +12 -0
- package/dist/types/src/internal/common/proxy/typed-handler.d.ts.map +1 -1
- package/dist/types/src/internal/common/types/index.d.ts +1 -0
- package/dist/types/src/internal/common/types/index.d.ts.map +1 -1
- package/dist/types/src/internal/common/types/model-symbols.d.ts +5 -0
- package/dist/types/src/internal/common/types/model-symbols.d.ts.map +1 -1
- package/dist/types/src/internal/common/types/projection.d.ts +32 -0
- package/dist/types/src/internal/common/types/projection.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +40 -35
- package/src/Aggregate.ts +105 -0
- package/src/Blob.test.ts +45 -0
- package/src/Blob.ts +205 -0
- package/src/Database.ts +207 -17
- package/src/Err.ts +36 -0
- package/src/Feed.test.ts +188 -0
- package/src/Feed.ts +238 -62
- package/src/Filter.ts +28 -10
- package/src/Hypergraph.ts +18 -0
- package/src/Json.ts +1 -0
- package/src/Obj.test.ts +2 -2
- package/src/Obj.ts +21 -1
- package/src/Order.ts +16 -9
- package/src/Query.test.ts +319 -2
- package/src/Query.ts +165 -51
- package/src/Relation.ts +14 -0
- package/src/Type.ts +5 -6
- package/src/index.ts +2 -0
- package/src/internal/Entity/api.ts +11 -1
- package/src/internal/Entity/model.ts +3 -0
- package/src/internal/Filter/match.ts +30 -1
- package/src/internal/JsonSchema/json-schema-type.ts +14 -2
- package/src/internal/JsonSchema/json-schema.test.ts +28 -1
- package/src/internal/JsonSchema/json-schema.ts +33 -6
- package/src/internal/Obj/atoms.ts +4 -2
- package/src/internal/Obj/json-serializer.test.ts +162 -2
- package/src/internal/Obj/json-serializer.ts +6 -0
- package/src/internal/Query/pretty.ts +19 -1
- package/src/internal/Query/query-result-effect.ts +2 -4
- package/src/internal/Ref/atoms.ts +1 -1
- package/src/internal/common/proxy/change-context.ts +1 -1
- package/src/internal/common/proxy/make-object.ts +42 -3
- package/src/internal/common/proxy/reactive.ts +5 -0
- package/src/internal/common/proxy/symbols.ts +1 -0
- package/src/internal/common/proxy/typed-handler.ts +71 -29
- package/src/internal/common/types/index.ts +1 -0
- package/src/internal/common/types/model-symbols.ts +6 -0
- package/src/internal/common/types/projection.ts +45 -0
- package/src/testing/test-data.ts +14 -14
- package/dist/lib/neutral/Annotation.mjs +0 -90
- package/dist/lib/neutral/Annotation.mjs.map +0 -7
- package/dist/lib/neutral/Database.mjs +0 -42
- package/dist/lib/neutral/Database.mjs.map +0 -7
- package/dist/lib/neutral/Entity.mjs +0 -76
- package/dist/lib/neutral/Entity.mjs.map +0 -7
- package/dist/lib/neutral/Err.mjs +0 -16
- package/dist/lib/neutral/Err.mjs.map +0 -7
- package/dist/lib/neutral/Feed.mjs +0 -50
- package/dist/lib/neutral/Feed.mjs.map +0 -7
- package/dist/lib/neutral/Filter.mjs +0 -72
- package/dist/lib/neutral/Filter.mjs.map +0 -7
- package/dist/lib/neutral/Format.mjs +0 -82
- package/dist/lib/neutral/Format.mjs.map +0 -7
- package/dist/lib/neutral/JsonSchema.mjs +0 -18
- package/dist/lib/neutral/JsonSchema.mjs.map +0 -7
- package/dist/lib/neutral/Key.mjs +0 -16
- package/dist/lib/neutral/Key.mjs.map +0 -7
- package/dist/lib/neutral/Migration.mjs +0 -18
- package/dist/lib/neutral/Migration.mjs.map +0 -7
- package/dist/lib/neutral/Obj.mjs +0 -128
- package/dist/lib/neutral/Obj.mjs.map +0 -7
- package/dist/lib/neutral/Order.mjs +0 -16
- package/dist/lib/neutral/Order.mjs.map +0 -7
- package/dist/lib/neutral/Query.mjs +0 -38
- package/dist/lib/neutral/Query.mjs.map +0 -7
- package/dist/lib/neutral/QueryResult.mjs +0 -2
- package/dist/lib/neutral/QueryResult.mjs.map +0 -7
- package/dist/lib/neutral/Ref.mjs +0 -25
- package/dist/lib/neutral/Ref.mjs.map +0 -7
- package/dist/lib/neutral/Registry.mjs +0 -14
- package/dist/lib/neutral/Registry.mjs.map +0 -7
- package/dist/lib/neutral/Relation.mjs +0 -97
- package/dist/lib/neutral/Relation.mjs.map +0 -7
- package/dist/lib/neutral/Scope.mjs +0 -12
- package/dist/lib/neutral/Scope.mjs.map +0 -7
- package/dist/lib/neutral/Tag.mjs +0 -32
- package/dist/lib/neutral/Tag.mjs.map +0 -7
- package/dist/lib/neutral/Text.mjs +0 -25
- package/dist/lib/neutral/Text.mjs.map +0 -7
- package/dist/lib/neutral/Type.mjs +0 -65
- package/dist/lib/neutral/Type.mjs.map +0 -7
- package/dist/lib/neutral/chunk-2HKGAQM4.mjs +0 -1394
- package/dist/lib/neutral/chunk-2HKGAQM4.mjs.map +0 -7
- package/dist/lib/neutral/chunk-2ZHECXUR.mjs +0 -95
- package/dist/lib/neutral/chunk-2ZHECXUR.mjs.map +0 -7
- package/dist/lib/neutral/chunk-3KTE2Y7Q.mjs +0 -336
- package/dist/lib/neutral/chunk-3KTE2Y7Q.mjs.map +0 -7
- package/dist/lib/neutral/chunk-3TN2VMI4.mjs +0 -714
- package/dist/lib/neutral/chunk-3TN2VMI4.mjs.map +0 -7
- package/dist/lib/neutral/chunk-42L4HJ3Z.mjs +0 -104
- package/dist/lib/neutral/chunk-42L4HJ3Z.mjs.map +0 -7
- package/dist/lib/neutral/chunk-4A2NWKRJ.mjs +0 -747
- package/dist/lib/neutral/chunk-4A2NWKRJ.mjs.map +0 -7
- package/dist/lib/neutral/chunk-7LOUAPYZ.mjs +0 -25
- package/dist/lib/neutral/chunk-7LOUAPYZ.mjs.map +0 -7
- package/dist/lib/neutral/chunk-ACFAOTKJ.mjs +0 -1531
- package/dist/lib/neutral/chunk-ACFAOTKJ.mjs.map +0 -7
- package/dist/lib/neutral/chunk-BEFR7LEA.mjs +0 -170
- package/dist/lib/neutral/chunk-BEFR7LEA.mjs.map +0 -7
- package/dist/lib/neutral/chunk-BXIKXT7Q.mjs +0 -89
- package/dist/lib/neutral/chunk-BXIKXT7Q.mjs.map +0 -7
- package/dist/lib/neutral/chunk-C57XMMBY.mjs +0 -206
- package/dist/lib/neutral/chunk-C57XMMBY.mjs.map +0 -7
- package/dist/lib/neutral/chunk-C62JYFAD.mjs +0 -183
- package/dist/lib/neutral/chunk-C62JYFAD.mjs.map +0 -7
- package/dist/lib/neutral/chunk-FSGX2R62.mjs +0 -48
- package/dist/lib/neutral/chunk-FSGX2R62.mjs.map +0 -7
- package/dist/lib/neutral/chunk-HBXT3GSB.mjs +0 -459
- package/dist/lib/neutral/chunk-HBXT3GSB.mjs.map +0 -7
- package/dist/lib/neutral/chunk-IQR45HAD.mjs +0 -31
- package/dist/lib/neutral/chunk-IQR45HAD.mjs.map +0 -7
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs +0 -10
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +0 -7
- package/dist/lib/neutral/chunk-KCBW4BR6.mjs +0 -424
- package/dist/lib/neutral/chunk-KCBW4BR6.mjs.map +0 -7
- package/dist/lib/neutral/chunk-L3E5IXAE.mjs +0 -575
- package/dist/lib/neutral/chunk-L3E5IXAE.mjs.map +0 -7
- package/dist/lib/neutral/chunk-PRC4FEKJ.mjs +0 -56
- package/dist/lib/neutral/chunk-PRC4FEKJ.mjs.map +0 -7
- package/dist/lib/neutral/chunk-SCT6FFCR.mjs +0 -351
- package/dist/lib/neutral/chunk-SCT6FFCR.mjs.map +0 -7
- package/dist/lib/neutral/chunk-TOCVVFK2.mjs +0 -75
- package/dist/lib/neutral/chunk-TOCVVFK2.mjs.map +0 -7
- package/dist/lib/neutral/chunk-U53LSQQA.mjs +0 -41
- package/dist/lib/neutral/chunk-U53LSQQA.mjs.map +0 -7
- package/dist/lib/neutral/chunk-UGFFV2FR.mjs +0 -27
- package/dist/lib/neutral/chunk-UGFFV2FR.mjs.map +0 -7
- package/dist/lib/neutral/chunk-UWLP4RFM.mjs +0 -7
- package/dist/lib/neutral/chunk-UWLP4RFM.mjs.map +0 -7
- package/dist/lib/neutral/chunk-UZHCQ6NI.mjs +0 -283
- package/dist/lib/neutral/chunk-UZHCQ6NI.mjs.map +0 -7
- package/dist/lib/neutral/chunk-WISOH2XH.mjs +0 -36
- package/dist/lib/neutral/chunk-WISOH2XH.mjs.map +0 -7
- package/dist/lib/neutral/chunk-WKIT2YY5.mjs +0 -34
- package/dist/lib/neutral/chunk-WKIT2YY5.mjs.map +0 -7
- package/dist/lib/neutral/chunk-XB6ISRAO.mjs +0 -271
- package/dist/lib/neutral/chunk-XB6ISRAO.mjs.map +0 -7
- package/dist/lib/neutral/chunk-XXL42WCB.mjs +0 -84
- package/dist/lib/neutral/chunk-XXL42WCB.mjs.map +0 -7
- package/dist/lib/neutral/chunk-XXWK4J53.mjs +0 -117
- package/dist/lib/neutral/chunk-XXWK4J53.mjs.map +0 -7
- package/dist/lib/neutral/index.mjs +0 -115
- package/dist/lib/neutral/index.mjs.map +0 -7
- package/dist/lib/neutral/internal/index.mjs +0 -581
- package/dist/lib/neutral/internal/index.mjs.map +0 -7
- package/dist/lib/neutral/meta.json +0 -1
- package/dist/lib/neutral/testing/index.mjs +0 -477
- package/dist/lib/neutral/testing/index.mjs.map +0 -7
package/src/Query.test.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { QueryAST } from '@dxos/echo-protocol';
|
|
|
9
9
|
import { DXN, EID, EntityId, SpaceId } from '@dxos/keys';
|
|
10
10
|
import { log } from '@dxos/log';
|
|
11
11
|
|
|
12
|
+
import * as Aggregate from './Aggregate';
|
|
12
13
|
import * as Dataset from './Dataset';
|
|
13
14
|
import * as Feed from './Feed';
|
|
14
15
|
import * as Filter from './Filter';
|
|
@@ -588,7 +589,7 @@ describe('query api', () => {
|
|
|
588
589
|
{ uri: feedDxn },
|
|
589
590
|
)) as Feed.Feed;
|
|
590
591
|
|
|
591
|
-
const
|
|
592
|
+
const expectedFeedUri = Feed.getFeedUri(feed);
|
|
592
593
|
|
|
593
594
|
const query = Query.type(TestSchema.Person).from(feed);
|
|
594
595
|
Schema.validateSync(QueryAST.Query)(query.ast);
|
|
@@ -596,7 +597,7 @@ describe('query api', () => {
|
|
|
596
597
|
type: 'from',
|
|
597
598
|
from: {
|
|
598
599
|
_tag: 'scope',
|
|
599
|
-
scopes: [{ _tag: 'feed', feedUri:
|
|
600
|
+
scopes: [{ _tag: 'feed', feedUri: expectedFeedUri?.toString() }],
|
|
600
601
|
},
|
|
601
602
|
query: {
|
|
602
603
|
type: 'select',
|
|
@@ -697,6 +698,82 @@ describe('query api', () => {
|
|
|
697
698
|
});
|
|
698
699
|
});
|
|
699
700
|
|
|
701
|
+
describe('Filter.in (subquery projection)', () => {
|
|
702
|
+
test('Query.project builds a Projection carrying the query ast and property', () => {
|
|
703
|
+
const subquery = Query.select(Filter.type(TestSchema.Task, { completed: true }));
|
|
704
|
+
const projection = subquery.project('title');
|
|
705
|
+
|
|
706
|
+
expect(projection.property).toBe('title');
|
|
707
|
+
expect(projection.query).toEqual(subquery.ast);
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
test('standalone Query.project is equivalent to the instance method', () => {
|
|
711
|
+
const subquery = Query.select(Filter.type(TestSchema.Task, { completed: true }));
|
|
712
|
+
expect(Query.project(subquery, 'title')).toEqual(subquery.project('title'));
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
test('Filter.in(projection) builds an in-query AST node', () => {
|
|
716
|
+
const subquery = Query.select(Filter.type(TestSchema.Task, { completed: true }));
|
|
717
|
+
const filter = Filter.in(subquery.project('title'));
|
|
718
|
+
|
|
719
|
+
expect(filter.ast).toMatchObject({
|
|
720
|
+
type: 'in-query',
|
|
721
|
+
subquery: subquery.ast,
|
|
722
|
+
property: 'title',
|
|
723
|
+
});
|
|
724
|
+
Schema.validateSync(QueryAST.Filter)(filter.ast);
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
test('Filter.in(...) with literal values still builds a plain in node', () => {
|
|
728
|
+
const filter = Filter.in('1', '2', '3');
|
|
729
|
+
expect(filter.ast).toEqual({ type: 'in', values: ['1', '2', '3'] });
|
|
730
|
+
Schema.validateSync(QueryAST.Filter)(filter.ast);
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
test('nested Filter.in(projection) composes inside Filter.type', () => {
|
|
734
|
+
const subquery = Query.select(Filter.type(TestSchema.Task, { completed: true }));
|
|
735
|
+
const query = Query.select(Filter.type(TestSchema.Task, { title: Filter.in(subquery.project('title')) }));
|
|
736
|
+
|
|
737
|
+
Schema.validateSync(QueryAST.Query)(query.ast);
|
|
738
|
+
expect(query.ast).toMatchObject({
|
|
739
|
+
type: 'select',
|
|
740
|
+
filter: {
|
|
741
|
+
type: 'object',
|
|
742
|
+
props: {
|
|
743
|
+
title: {
|
|
744
|
+
type: 'in-query',
|
|
745
|
+
property: 'title',
|
|
746
|
+
subquery: subquery.ast,
|
|
747
|
+
},
|
|
748
|
+
},
|
|
749
|
+
},
|
|
750
|
+
});
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
test('Query.pretty renders the subquery form', () => {
|
|
754
|
+
const subquery = Query.select(Filter.type(TestSchema.Task, { completed: true }));
|
|
755
|
+
const query = Query.select(Filter.type(TestSchema.Task, { title: Filter.in(subquery.project('title')) }));
|
|
756
|
+
const pretty = Query.pretty(query);
|
|
757
|
+
|
|
758
|
+
expect(pretty).toContain('Filter.in(');
|
|
759
|
+
expect(pretty).toContain('.project("title")');
|
|
760
|
+
expect(pretty).toContain('Query.select(Filter.type(');
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
test('project is strictly typed to the query type properties', () => {
|
|
764
|
+
const subquery = Query.select(Filter.type(TestSchema.Task, { completed: true }));
|
|
765
|
+
|
|
766
|
+
// `title` is `Schema.optional(Schema.String)` on Task — the projection and the filter it
|
|
767
|
+
// feeds both carry that exact value type, not `unknown`, through Query.project -> Filter.in.
|
|
768
|
+
expectTypeOf(subquery.project('title')).toEqualTypeOf<Query.Projection<string | undefined>>();
|
|
769
|
+
expectTypeOf(Query.project(subquery, 'title')).toEqualTypeOf<Query.Projection<string | undefined>>();
|
|
770
|
+
expectTypeOf(Filter.in(subquery.project('title'))).toEqualTypeOf<Filter.Filter<string | undefined>>();
|
|
771
|
+
|
|
772
|
+
// @ts-expect-error - 'nope' is not a property of Task.
|
|
773
|
+
subquery.project('nope');
|
|
774
|
+
});
|
|
775
|
+
});
|
|
776
|
+
|
|
700
777
|
describe('Filter.childOf', () => {
|
|
701
778
|
test('childOf with Ref', () => {
|
|
702
779
|
const parentDxn = EID.make({ spaceId: SpaceId.random(), entityId: EntityId.random() });
|
|
@@ -803,6 +880,246 @@ describe('query api', () => {
|
|
|
803
880
|
expect(filter.ast.type === 'child-of' && filter.ast.parents.length).toBe(2);
|
|
804
881
|
Schema.validateSync(QueryAST.Filter)(filter.ast);
|
|
805
882
|
});
|
|
883
|
+
|
|
884
|
+
describe('.aggregate', () => {
|
|
885
|
+
test('group by a single property', () => {
|
|
886
|
+
const query = Query.select(Filter.type(TestSchema.Person)).aggregate({
|
|
887
|
+
email: Aggregate.group('email'),
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
expect(query.ast).toMatchObject({
|
|
891
|
+
type: 'aggregate',
|
|
892
|
+
aggregates: [{ name: 'email', kind: 'group', property: 'email' }],
|
|
893
|
+
});
|
|
894
|
+
Schema.validateSync(QueryAST.Query)(query.ast);
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
test('group by multiple properties forms a composite key', () => {
|
|
898
|
+
const query = Query.select(Filter.type(TestSchema.Person)).aggregate({
|
|
899
|
+
name: Aggregate.group('name'),
|
|
900
|
+
email: Aggregate.group('email'),
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
expect(query.ast).toMatchObject({
|
|
904
|
+
type: 'aggregate',
|
|
905
|
+
aggregates: [
|
|
906
|
+
{ name: 'name', kind: 'group', property: 'name' },
|
|
907
|
+
{ name: 'email', kind: 'group', property: 'email' },
|
|
908
|
+
],
|
|
909
|
+
});
|
|
910
|
+
Schema.validateSync(QueryAST.Query)(query.ast);
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
test('aggregate-everything (no group entries) is valid', () => {
|
|
914
|
+
const query = Query.select(Filter.type(TestSchema.Person)).aggregate({ latest: Aggregate.max('name') });
|
|
915
|
+
|
|
916
|
+
expect(query.ast).toMatchObject({
|
|
917
|
+
type: 'aggregate',
|
|
918
|
+
aggregates: [{ name: 'latest', kind: 'max', property: 'name' }],
|
|
919
|
+
});
|
|
920
|
+
Schema.validateSync(QueryAST.Query)(query.ast);
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
test("aggregate wraps the preceding query as the aggregate node's inner query", () => {
|
|
924
|
+
const inner = Query.select(Filter.type(TestSchema.Person));
|
|
925
|
+
const query = inner.aggregate({ email: Aggregate.group('email') });
|
|
926
|
+
|
|
927
|
+
expect(query.ast).toMatchObject({ type: 'aggregate', query: inner.ast });
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
test('from()/options() may follow aggregate, keeping it the outermost data clause', () => {
|
|
931
|
+
const grouped = Query.select(Filter.type(TestSchema.Person)).aggregate({ email: Aggregate.group('email') });
|
|
932
|
+
|
|
933
|
+
const withOptions = grouped.options({ debugLabel: 'grouped' });
|
|
934
|
+
expect(withOptions.ast).toMatchObject({ type: 'options', query: grouped.ast });
|
|
935
|
+
Schema.validateSync(QueryAST.Query)(withOptions.ast);
|
|
936
|
+
|
|
937
|
+
const withDebugLabel = grouped.debugLabel('grouped-2');
|
|
938
|
+
expect(withDebugLabel.ast).toMatchObject({ type: 'options', options: { debugLabel: 'grouped-2' } });
|
|
939
|
+
Schema.validateSync(QueryAST.Query)(withDebugLabel.ast);
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
test('items carries its limit', () => {
|
|
943
|
+
const query = Query.select(Filter.type(TestSchema.Person)).aggregate({
|
|
944
|
+
email: Aggregate.group('email'),
|
|
945
|
+
items: Aggregate.items({ limit: 20 }),
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
expect(query.ast).toMatchObject({
|
|
949
|
+
type: 'aggregate',
|
|
950
|
+
aggregates: [
|
|
951
|
+
{ name: 'email', kind: 'group', property: 'email' },
|
|
952
|
+
{ name: 'items', kind: 'items', limit: 20 },
|
|
953
|
+
],
|
|
954
|
+
});
|
|
955
|
+
Schema.validateSync(QueryAST.Query)(query.ast);
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
test('items carries its own per-group order, independent of the query-level orderBy', () => {
|
|
959
|
+
const query = Query.select(Filter.type(TestSchema.Person)).aggregate({
|
|
960
|
+
email: Aggregate.group('email'),
|
|
961
|
+
items: Aggregate.items({ limit: 20, order: [Order.property('name', 'asc')] }),
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
expect(query.ast).toMatchObject({
|
|
965
|
+
type: 'aggregate',
|
|
966
|
+
aggregates: [
|
|
967
|
+
{ name: 'email', kind: 'group', property: 'email' },
|
|
968
|
+
{
|
|
969
|
+
name: 'items',
|
|
970
|
+
kind: 'items',
|
|
971
|
+
limit: 20,
|
|
972
|
+
order: [{ kind: 'property', property: 'name', direction: 'asc' }],
|
|
973
|
+
},
|
|
974
|
+
],
|
|
975
|
+
});
|
|
976
|
+
Schema.validateSync(QueryAST.Query)(query.ast);
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
test('aggregate declares named aggregates on the aggregate node', () => {
|
|
980
|
+
const query = Query.select(Filter.type(TestSchema.Person)).aggregate({
|
|
981
|
+
email: Aggregate.group('email'),
|
|
982
|
+
latest: Aggregate.max('name'),
|
|
983
|
+
earliest: Aggregate.min('name'),
|
|
984
|
+
total: Aggregate.count(),
|
|
985
|
+
items: Aggregate.items(),
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
expect(query.ast).toMatchObject({
|
|
989
|
+
type: 'aggregate',
|
|
990
|
+
aggregates: [
|
|
991
|
+
{ name: 'email', kind: 'group', property: 'email' },
|
|
992
|
+
{ name: 'latest', kind: 'max', property: 'name' },
|
|
993
|
+
{ name: 'earliest', kind: 'min', property: 'name' },
|
|
994
|
+
{ name: 'total', kind: 'count' },
|
|
995
|
+
{ name: 'items', kind: 'items' },
|
|
996
|
+
],
|
|
997
|
+
});
|
|
998
|
+
Schema.validateSync(QueryAST.Query)(query.ast);
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
test('orderBy(Order.property) after aggregate orders by the aggregate field', () => {
|
|
1002
|
+
const grouped = Query.select(Filter.type(TestSchema.Person)).aggregate({
|
|
1003
|
+
email: Aggregate.group('email'),
|
|
1004
|
+
latest: Aggregate.max('name'),
|
|
1005
|
+
});
|
|
1006
|
+
const ordered = grouped.orderBy(Order.property('latest', 'desc'));
|
|
1007
|
+
|
|
1008
|
+
expect(ordered.ast).toMatchObject({
|
|
1009
|
+
type: 'order',
|
|
1010
|
+
query: grouped.ast,
|
|
1011
|
+
order: [{ kind: 'property', property: 'latest', direction: 'desc' }],
|
|
1012
|
+
});
|
|
1013
|
+
Schema.validateSync(QueryAST.Query)(ordered.ast);
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
test('Query.pretty renders aggregates (incl. group + items limit) and aggregate ordering', () => {
|
|
1017
|
+
const query = Query.select(Filter.type(TestSchema.Person))
|
|
1018
|
+
.aggregate({
|
|
1019
|
+
email: Aggregate.group('email'),
|
|
1020
|
+
latest: Aggregate.max('name'),
|
|
1021
|
+
items: Aggregate.items({ limit: 20 }),
|
|
1022
|
+
})
|
|
1023
|
+
.orderBy(Order.property('latest', 'desc'));
|
|
1024
|
+
const pretty = Query.pretty(query);
|
|
1025
|
+
expect(pretty).toContain(
|
|
1026
|
+
'.aggregate({ "email": Aggregate.group("email"), "latest": Aggregate.max("name"), "items": Aggregate.items({ limit: 20 }) })',
|
|
1027
|
+
);
|
|
1028
|
+
expect(pretty).toContain('.orderBy(Order.property("latest", "desc"))');
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
test('type-level: aggregates surface as flat top-level fields', () => {
|
|
1032
|
+
const query = Query.type(TestSchema.Person).aggregate({
|
|
1033
|
+
email: Aggregate.group('email'),
|
|
1034
|
+
latest: Aggregate.max('name'),
|
|
1035
|
+
total: Aggregate.count(),
|
|
1036
|
+
items: Aggregate.items(),
|
|
1037
|
+
});
|
|
1038
|
+
expectTypeOf<Query.Type<typeof query>>().toHaveProperty('email');
|
|
1039
|
+
expectTypeOf<Query.Type<typeof query>>().toHaveProperty('latest');
|
|
1040
|
+
expectTypeOf<Query.Type<typeof query>>().toHaveProperty('total');
|
|
1041
|
+
expectTypeOf<Query.Type<typeof query>>().toHaveProperty('items');
|
|
1042
|
+
// The flat record has no nested `key` wrapper.
|
|
1043
|
+
expectTypeOf<Query.Type<typeof query>>().not.toHaveProperty('key');
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
test('type-level: Aggregate.group/max/min property is checked against the element type', () => {
|
|
1047
|
+
const query = Query.type(TestSchema.Person);
|
|
1048
|
+
// Valid element properties compile.
|
|
1049
|
+
query.aggregate({
|
|
1050
|
+
email: Aggregate.group('email'),
|
|
1051
|
+
latest: Aggregate.max('name'),
|
|
1052
|
+
oldest: Aggregate.min('age'),
|
|
1053
|
+
});
|
|
1054
|
+
// @ts-expect-error - 'nope' is not a property of Person.
|
|
1055
|
+
query.aggregate({ bad: Aggregate.group('nope') });
|
|
1056
|
+
// @ts-expect-error - 'nope' is not a property of Person.
|
|
1057
|
+
query.aggregate({ bad: Aggregate.max('nope') });
|
|
1058
|
+
// @ts-expect-error - min is checked too.
|
|
1059
|
+
query.aggregate({ bad: Aggregate.min('nope') });
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
test('type-level: aggregate value type tracks its kind and element property type', () => {
|
|
1063
|
+
const query = Query.type(TestSchema.Person).aggregate({
|
|
1064
|
+
email: Aggregate.group('email'),
|
|
1065
|
+
latest: Aggregate.max('name'),
|
|
1066
|
+
oldest: Aggregate.min('age'),
|
|
1067
|
+
total: Aggregate.count(),
|
|
1068
|
+
items: Aggregate.items(),
|
|
1069
|
+
});
|
|
1070
|
+
type Group = Query.Type<typeof query>;
|
|
1071
|
+
// `email`/`name`/`age` are optional on Person (partial schema); group/max/min are additionally
|
|
1072
|
+
// nullable (missing/non-scalar members bucket under null) — so the value tracks the property type.
|
|
1073
|
+
expectTypeOf<Group['email']>().toEqualTypeOf<string | undefined | null>();
|
|
1074
|
+
expectTypeOf<Group['latest']>().toEqualTypeOf<string | undefined | null>();
|
|
1075
|
+
expectTypeOf<Group['oldest']>().toEqualTypeOf<number | undefined | null>();
|
|
1076
|
+
expectTypeOf<Group['total']>().toEqualTypeOf<number>();
|
|
1077
|
+
// `items` is an array of the element type.
|
|
1078
|
+
expectTypeOf<Group['items']>().toBeArray();
|
|
1079
|
+
expectTypeOf<Group['items'][number]>().toHaveProperty('name');
|
|
1080
|
+
|
|
1081
|
+
// Assignment-level proof that it is a real string/number union and not `any`.
|
|
1082
|
+
// @ts-expect-error - a string aggregate does not accept a number.
|
|
1083
|
+
const badName: Group['latest'] = 42;
|
|
1084
|
+
// @ts-expect-error - a number aggregate does not accept a string.
|
|
1085
|
+
const badAge: Group['oldest'] = 'nope';
|
|
1086
|
+
void badName;
|
|
1087
|
+
void badAge;
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
test('type-level: Order.property after aggregate is checked against the flat record fields', () => {
|
|
1091
|
+
const grouped = Query.type(TestSchema.Person).aggregate({
|
|
1092
|
+
email: Aggregate.group('email'),
|
|
1093
|
+
latest: Aggregate.max('name'),
|
|
1094
|
+
total: Aggregate.count(),
|
|
1095
|
+
});
|
|
1096
|
+
// Declared group/aggregate fields compile.
|
|
1097
|
+
grouped.orderBy(Order.property('email', 'asc'));
|
|
1098
|
+
grouped.orderBy(Order.property('latest', 'desc'));
|
|
1099
|
+
grouped.orderBy(Order.property('total', 'asc'));
|
|
1100
|
+
// @ts-expect-error - 'earliest' was never declared as an aggregate.
|
|
1101
|
+
grouped.orderBy(Order.property('earliest', 'desc'));
|
|
1102
|
+
// @ts-expect-error - 'name' is an element property, not a result field, so it is not orderable here.
|
|
1103
|
+
grouped.orderBy(Order.property('name', 'desc'));
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1106
|
+
test('type-level: Order.property follows the current result shape across aggregate', () => {
|
|
1107
|
+
const ungrouped = Query.type(TestSchema.Person);
|
|
1108
|
+
// Before aggregate: element properties are orderable.
|
|
1109
|
+
ungrouped.orderBy(Order.property('name', 'desc'));
|
|
1110
|
+
// @ts-expect-error - 'latest' is not an element property before aggregating.
|
|
1111
|
+
ungrouped.orderBy(Order.property('latest', 'desc'));
|
|
1112
|
+
|
|
1113
|
+
const grouped = Query.type(TestSchema.Person).aggregate({
|
|
1114
|
+
email: Aggregate.group('email'),
|
|
1115
|
+
latest: Aggregate.max('name'),
|
|
1116
|
+
});
|
|
1117
|
+
// After aggregate: the result fields are orderable, element properties are not.
|
|
1118
|
+
grouped.orderBy(Order.property('latest', 'desc'));
|
|
1119
|
+
// @ts-expect-error - 'name' is an element property, not a result field.
|
|
1120
|
+
grouped.orderBy(Order.property('name', 'desc'));
|
|
1121
|
+
});
|
|
1122
|
+
});
|
|
806
1123
|
});
|
|
807
1124
|
|
|
808
1125
|
describe('Filter', () => {
|