@dxos/echo 0.8.4-main.b97322e → 0.8.4-main.c4373fc
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/README.md +1 -1
- package/dist/lib/browser/chunk-MWLA34S5.mjs +3843 -0
- package/dist/lib/browser/chunk-MWLA34S5.mjs.map +7 -0
- package/dist/lib/browser/chunk-OAZJQHVO.mjs +453 -0
- package/dist/lib/browser/chunk-OAZJQHVO.mjs.map +7 -0
- package/dist/lib/browser/chunk-ORIE2FMS.mjs +514 -0
- package/dist/lib/browser/chunk-ORIE2FMS.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +26 -5
- package/dist/lib/browser/internal/index.mjs +326 -0
- package/dist/lib/browser/internal/index.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/query/index.mjs +23 -0
- package/dist/lib/browser/query/index.mjs.map +7 -0
- package/dist/lib/browser/testing/index.mjs +260 -32
- package/dist/lib/browser/testing/index.mjs.map +4 -4
- package/dist/lib/node-esm/chunk-AXWVDOP7.mjs +453 -0
- package/dist/lib/node-esm/chunk-AXWVDOP7.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-L4PBAJDP.mjs +514 -0
- package/dist/lib/node-esm/chunk-L4PBAJDP.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-YTNLFBIK.mjs +3843 -0
- package/dist/lib/node-esm/chunk-YTNLFBIK.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +26 -5
- package/dist/lib/node-esm/internal/index.mjs +326 -0
- package/dist/lib/node-esm/internal/index.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/query/index.mjs +23 -0
- package/dist/lib/node-esm/query/index.mjs.map +7 -0
- package/dist/lib/node-esm/testing/index.mjs +260 -32
- package/dist/lib/node-esm/testing/index.mjs.map +4 -4
- package/dist/types/src/Obj.d.ts +111 -17
- package/dist/types/src/Obj.d.ts.map +1 -1
- package/dist/types/src/Ref.d.ts +1 -1
- package/dist/types/src/Ref.d.ts.map +1 -1
- package/dist/types/src/Relation.d.ts +11 -8
- package/dist/types/src/Relation.d.ts.map +1 -1
- package/dist/types/src/Type.d.ts +17 -18
- package/dist/types/src/Type.d.ts.map +1 -1
- package/dist/types/src/errors.d.ts +72 -0
- package/dist/types/src/errors.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +3 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/internal/ast/annotation-helper.d.ts +8 -0
- package/dist/types/src/internal/ast/annotation-helper.d.ts.map +1 -0
- package/dist/types/src/internal/ast/annotations.d.ts +119 -0
- package/dist/types/src/internal/ast/annotations.d.ts.map +1 -0
- package/dist/types/src/internal/ast/annotations.test.d.ts +2 -0
- package/dist/types/src/internal/ast/annotations.test.d.ts.map +1 -0
- package/dist/types/src/internal/ast/entity-kind.d.ts +10 -0
- package/dist/types/src/internal/ast/entity-kind.d.ts.map +1 -0
- package/dist/types/src/internal/ast/index.d.ts +5 -0
- package/dist/types/src/internal/ast/index.d.ts.map +1 -0
- package/dist/types/src/internal/ast/types.d.ts +6 -0
- package/dist/types/src/internal/ast/types.d.ts.map +1 -0
- package/dist/types/src/internal/formats/date.d.ts +63 -0
- package/dist/types/src/internal/formats/date.d.ts.map +1 -0
- package/dist/types/src/internal/formats/date.test.d.ts +2 -0
- package/dist/types/src/internal/formats/date.test.d.ts.map +1 -0
- package/dist/types/src/internal/formats/format.d.ts +30 -0
- package/dist/types/src/internal/formats/format.d.ts.map +1 -0
- package/dist/types/src/internal/formats/format.test.d.ts +2 -0
- package/dist/types/src/internal/formats/format.test.d.ts.map +1 -0
- package/dist/types/src/internal/formats/index.d.ts +8 -0
- package/dist/types/src/internal/formats/index.d.ts.map +1 -0
- package/dist/types/src/internal/formats/number.d.ts +31 -0
- package/dist/types/src/internal/formats/number.d.ts.map +1 -0
- package/dist/types/src/internal/formats/object.d.ts +35 -0
- package/dist/types/src/internal/formats/object.d.ts.map +1 -0
- package/dist/types/src/internal/formats/select.d.ts +11 -0
- package/dist/types/src/internal/formats/select.d.ts.map +1 -0
- package/dist/types/src/internal/formats/string.d.ts +38 -0
- package/dist/types/src/internal/formats/string.d.ts.map +1 -0
- package/dist/types/src/internal/formats/types.d.ts +68 -0
- package/dist/types/src/internal/formats/types.d.ts.map +1 -0
- package/dist/types/src/internal/index.d.ts +15 -0
- package/dist/types/src/internal/index.d.ts.map +1 -0
- package/dist/types/src/internal/json/annotations.d.ts +19 -0
- package/dist/types/src/internal/json/annotations.d.ts.map +1 -0
- package/dist/types/src/internal/json/effect-schema.test.d.ts +2 -0
- package/dist/types/src/internal/json/effect-schema.test.d.ts.map +1 -0
- package/dist/types/src/internal/json/index.d.ts +2 -0
- package/dist/types/src/internal/json/index.d.ts.map +1 -0
- package/dist/types/src/internal/json/json-schema.d.ts +32 -0
- package/dist/types/src/internal/json/json-schema.d.ts.map +1 -0
- package/dist/types/src/internal/json/json-schema.test.d.ts +2 -0
- package/dist/types/src/internal/json/json-schema.test.d.ts.map +1 -0
- package/dist/types/src/internal/json-schema/index.d.ts +3 -0
- package/dist/types/src/internal/json-schema/index.d.ts.map +1 -0
- package/dist/types/src/internal/json-schema/json-schema-normalize.d.ts +7 -0
- package/dist/types/src/internal/json-schema/json-schema-normalize.d.ts.map +1 -0
- package/dist/types/src/internal/json-schema/json-schema-type.d.ts +250 -0
- package/dist/types/src/internal/json-schema/json-schema-type.d.ts.map +1 -0
- package/dist/types/src/internal/object/accessors.d.ts +29 -0
- package/dist/types/src/internal/object/accessors.d.ts.map +1 -0
- package/dist/types/src/internal/object/common.d.ts +18 -0
- package/dist/types/src/internal/object/common.d.ts.map +1 -0
- package/dist/types/src/internal/object/create.d.ts +40 -0
- package/dist/types/src/internal/object/create.d.ts.map +1 -0
- package/dist/types/src/internal/object/create.test.d.ts +2 -0
- package/dist/types/src/internal/object/create.test.d.ts.map +1 -0
- package/dist/types/src/internal/object/deleted.d.ts +6 -0
- package/dist/types/src/internal/object/deleted.d.ts.map +1 -0
- package/dist/types/src/internal/object/entity.d.ts +22 -0
- package/dist/types/src/internal/object/entity.d.ts.map +1 -0
- package/dist/types/src/internal/object/expando.d.ts +14 -0
- package/dist/types/src/internal/object/expando.d.ts.map +1 -0
- package/dist/types/src/internal/object/ids.d.ts +6 -0
- package/dist/types/src/internal/object/ids.d.ts.map +1 -0
- package/dist/types/src/internal/object/index.d.ts +17 -0
- package/dist/types/src/internal/object/index.d.ts.map +1 -0
- package/dist/types/src/internal/object/inspect.d.ts +2 -0
- package/dist/types/src/internal/object/inspect.d.ts.map +1 -0
- package/dist/types/src/internal/object/json-serializer.d.ts +32 -0
- package/dist/types/src/internal/object/json-serializer.d.ts.map +1 -0
- package/dist/types/src/internal/object/json-serializer.test.d.ts +2 -0
- package/dist/types/src/internal/object/json-serializer.test.d.ts.map +1 -0
- package/dist/types/src/internal/object/meta.d.ts +31 -0
- package/dist/types/src/internal/object/meta.d.ts.map +1 -0
- package/dist/types/src/internal/object/model.d.ts +117 -0
- package/dist/types/src/internal/object/model.d.ts.map +1 -0
- package/dist/types/src/internal/object/relation.d.ts +17 -0
- package/dist/types/src/internal/object/relation.d.ts.map +1 -0
- package/dist/types/src/internal/object/schema-validator.d.ts +15 -0
- package/dist/types/src/internal/object/schema-validator.d.ts.map +1 -0
- package/dist/types/src/internal/object/schema-validator.test.d.ts +2 -0
- package/dist/types/src/internal/object/schema-validator.test.d.ts.map +1 -0
- package/dist/types/src/internal/object/typed-object.d.ts +31 -0
- package/dist/types/src/internal/object/typed-object.d.ts.map +1 -0
- package/dist/types/src/internal/object/typed-object.test.d.ts +2 -0
- package/dist/types/src/internal/object/typed-object.test.d.ts.map +1 -0
- package/dist/types/src/internal/object/typed-relation.d.ts +32 -0
- package/dist/types/src/internal/object/typed-relation.d.ts.map +1 -0
- package/dist/types/src/internal/object/typename.d.ts +15 -0
- package/dist/types/src/internal/object/typename.d.ts.map +1 -0
- package/dist/types/src/internal/object/version.d.ts +14 -0
- package/dist/types/src/internal/object/version.d.ts.map +1 -0
- package/dist/types/src/internal/projection/compose.d.ts +6 -0
- package/dist/types/src/internal/projection/compose.d.ts.map +1 -0
- package/dist/types/src/internal/projection/compose.test.d.ts +2 -0
- package/dist/types/src/internal/projection/compose.test.d.ts.map +1 -0
- package/dist/types/src/internal/projection/index.d.ts +2 -0
- package/dist/types/src/internal/projection/index.d.ts.map +1 -0
- package/dist/types/src/internal/proxy/handler.test.d.ts +2 -0
- package/dist/types/src/internal/proxy/handler.test.d.ts.map +1 -0
- package/dist/types/src/internal/proxy/reactive-object.d.ts +15 -0
- package/dist/types/src/internal/proxy/reactive-object.d.ts.map +1 -0
- package/dist/types/src/internal/proxy/schema.test.d.ts +2 -0
- package/dist/types/src/internal/proxy/schema.test.d.ts.map +1 -0
- package/dist/types/src/internal/proxy/typed-handler.d.ts +44 -0
- package/dist/types/src/internal/proxy/typed-handler.d.ts.map +1 -0
- package/dist/types/src/internal/proxy/typed-handler.test.d.ts +2 -0
- package/dist/types/src/internal/proxy/typed-handler.test.d.ts.map +1 -0
- package/dist/types/src/internal/proxy/typed-object.test.d.ts +2 -0
- package/dist/types/src/internal/proxy/typed-object.test.d.ts.map +1 -0
- package/dist/types/src/internal/query/index.d.ts +2 -0
- package/dist/types/src/internal/query/index.d.ts.map +1 -0
- package/dist/types/src/internal/query/query.d.ts +17 -0
- package/dist/types/src/internal/query/query.d.ts.map +1 -0
- package/dist/types/src/internal/ref/index.d.ts +3 -0
- package/dist/types/src/internal/ref/index.d.ts.map +1 -0
- package/dist/types/src/internal/ref/ref-array.d.ts +21 -0
- package/dist/types/src/internal/ref/ref-array.d.ts.map +1 -0
- package/dist/types/src/internal/ref/ref.d.ts +206 -0
- package/dist/types/src/internal/ref/ref.d.ts.map +1 -0
- package/dist/types/src/internal/ref/ref.test.d.ts +2 -0
- package/dist/types/src/internal/ref/ref.test.d.ts.map +1 -0
- package/dist/types/src/internal/schema/echo-schema.d.ts +168 -0
- package/dist/types/src/internal/schema/echo-schema.d.ts.map +1 -0
- package/dist/types/src/internal/schema/index.d.ts +7 -0
- package/dist/types/src/internal/schema/index.d.ts.map +1 -0
- package/dist/types/src/internal/schema/manipulation.d.ts +10 -0
- package/dist/types/src/internal/schema/manipulation.d.ts.map +1 -0
- package/dist/types/src/internal/schema/runtime-schema-registry.d.ts +18 -0
- package/dist/types/src/internal/schema/runtime-schema-registry.d.ts.map +1 -0
- package/dist/types/src/internal/schema/snapshot.d.ts +6 -0
- package/dist/types/src/internal/schema/snapshot.d.ts.map +1 -0
- package/dist/types/src/internal/schema/stored-schema.d.ts +13 -0
- package/dist/types/src/internal/schema/stored-schema.d.ts.map +1 -0
- package/dist/types/src/internal/testing/index.d.ts +3 -0
- package/dist/types/src/internal/testing/index.d.ts.map +1 -0
- package/dist/types/src/internal/testing/types.d.ts +455 -0
- package/dist/types/src/internal/testing/types.d.ts.map +1 -0
- package/dist/types/src/internal/testing/utils.d.ts +10 -0
- package/dist/types/src/internal/testing/utils.d.ts.map +1 -0
- package/dist/types/src/internal/types/index.d.ts +3 -0
- package/dist/types/src/internal/types/index.d.ts.map +1 -0
- package/dist/types/src/internal/types/types.d.ts +79 -0
- package/dist/types/src/internal/types/types.d.ts.map +1 -0
- package/dist/types/src/internal/types/types.test.d.ts +2 -0
- package/dist/types/src/internal/types/types.test.d.ts.map +1 -0
- package/dist/types/src/internal/types/util.d.ts +5 -0
- package/dist/types/src/internal/types/util.d.ts.map +1 -0
- package/dist/types/src/query/index.d.ts +2 -1
- package/dist/types/src/query/index.d.ts.map +1 -1
- package/dist/types/src/query/{dsl.d.ts → query.d.ts} +44 -15
- package/dist/types/src/query/query.d.ts.map +1 -0
- package/dist/types/src/query/query.test.d.ts +2 -0
- package/dist/types/src/query/query.test.d.ts.map +1 -0
- package/dist/types/src/query/tag.d.ts +18 -0
- package/dist/types/src/query/tag.d.ts.map +1 -0
- package/dist/types/src/testing/echo-schema.d.ts +7 -0
- package/dist/types/src/testing/echo-schema.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/types.d.ts +18 -18
- package/dist/types/src/testing/types.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +34 -16
- package/src/Obj.ts +250 -27
- package/src/Ref.ts +1 -2
- package/src/Relation.ts +25 -13
- package/src/Type.ts +32 -31
- package/src/errors.ts +18 -0
- package/src/index.ts +4 -1
- package/src/internal/ast/annotation-helper.ts +22 -0
- package/src/internal/ast/annotations.test.ts +98 -0
- package/src/internal/ast/annotations.ts +212 -0
- package/src/internal/ast/entity-kind.ts +15 -0
- package/src/internal/ast/index.ts +8 -0
- package/src/internal/ast/types.ts +17 -0
- package/src/internal/formats/date.test.ts +56 -0
- package/src/internal/formats/date.ts +217 -0
- package/src/internal/formats/format.test.ts +77 -0
- package/src/internal/formats/format.ts +52 -0
- package/src/internal/formats/index.ts +12 -0
- package/src/internal/formats/number.ts +89 -0
- package/src/internal/formats/object.ts +80 -0
- package/src/internal/formats/select.ts +16 -0
- package/src/internal/formats/string.ts +76 -0
- package/src/internal/formats/types.ts +175 -0
- package/src/internal/index.ts +22 -0
- package/src/internal/json/annotations.ts +50 -0
- package/src/internal/json/effect-schema.test.ts +143 -0
- package/src/internal/json/index.ts +5 -0
- package/src/internal/json/json-schema.test.ts +726 -0
- package/src/internal/json/json-schema.ts +548 -0
- package/src/internal/json-schema/index.ts +6 -0
- package/src/internal/json-schema/json-schema-normalize.ts +109 -0
- package/src/internal/json-schema/json-schema-type.ts +403 -0
- package/src/internal/object/accessors.ts +115 -0
- package/src/internal/object/common.ts +76 -0
- package/src/internal/object/create.test.ts +118 -0
- package/src/internal/object/create.ts +96 -0
- package/src/internal/object/deleted.ts +19 -0
- package/src/internal/object/entity.ts +204 -0
- package/src/internal/object/expando.ts +21 -0
- package/src/internal/object/ids.ts +12 -0
- package/src/internal/object/index.ts +20 -0
- package/src/internal/object/inspect.ts +48 -0
- package/src/internal/object/json-serializer.test.ts +99 -0
- package/src/internal/object/json-serializer.ts +225 -0
- package/src/internal/object/meta.ts +62 -0
- package/src/internal/object/model.ts +170 -0
- package/src/internal/object/relation.ts +24 -0
- package/src/internal/object/schema-validator.test.ts +186 -0
- package/src/internal/object/schema-validator.ts +241 -0
- package/src/internal/object/typed-object.test.ts +34 -0
- package/src/internal/object/typed-object.ts +81 -0
- package/src/internal/object/typed-relation.ts +85 -0
- package/src/internal/object/typename.ts +61 -0
- package/src/internal/object/version.ts +22 -0
- package/src/internal/projection/compose.test.ts +43 -0
- package/src/internal/projection/compose.ts +36 -0
- package/src/internal/projection/index.ts +5 -0
- package/src/internal/proxy/handler.test.ts +163 -0
- package/src/internal/proxy/reactive-object.ts +108 -0
- package/src/internal/proxy/schema.test.ts +136 -0
- package/src/internal/proxy/typed-handler.test.ts +102 -0
- package/src/internal/proxy/typed-handler.ts +228 -0
- package/src/internal/proxy/typed-object.test.ts +100 -0
- package/src/internal/query/index.ts +5 -0
- package/src/internal/query/query.ts +23 -0
- package/src/internal/ref/index.ts +6 -0
- package/src/internal/ref/ref-array.ts +39 -0
- package/src/internal/ref/ref.test.ts +100 -0
- package/src/internal/ref/ref.ts +521 -0
- package/src/internal/schema/echo-schema.ts +383 -0
- package/src/internal/schema/index.ts +10 -0
- package/src/internal/schema/manipulation.ts +86 -0
- package/src/internal/schema/runtime-schema-registry.ts +78 -0
- package/src/internal/schema/snapshot.ts +25 -0
- package/src/internal/schema/stored-schema.ts +26 -0
- package/src/internal/testing/index.ts +6 -0
- package/src/internal/testing/types.ts +211 -0
- package/src/internal/testing/utils.ts +54 -0
- package/src/internal/types/index.ts +6 -0
- package/src/internal/types/types.test.ts +48 -0
- package/src/internal/types/types.ts +176 -0
- package/src/internal/types/util.ts +9 -0
- package/src/query/index.ts +2 -1
- package/src/query/{dsl.test.ts → query.test.ts} +41 -2
- package/src/query/{dsl.ts → query.ts} +170 -32
- package/src/query/tag.ts +35 -0
- package/src/test/api.test.ts +17 -10
- package/src/testing/echo-schema.ts +39 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/types.ts +1 -1
- package/dist/lib/browser/chunk-EUA7CM23.mjs +0 -619
- package/dist/lib/browser/chunk-EUA7CM23.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-IV6BWGHK.mjs +0 -619
- package/dist/lib/node-esm/chunk-IV6BWGHK.mjs.map +0 -7
- package/dist/types/src/experimental/database.d.ts +0 -8
- package/dist/types/src/experimental/database.d.ts.map +0 -1
- package/dist/types/src/experimental/index.d.ts +0 -1
- package/dist/types/src/experimental/index.d.ts.map +0 -1
- package/dist/types/src/experimental/queue.d.ts +0 -8
- package/dist/types/src/experimental/queue.d.ts.map +0 -1
- package/dist/types/src/experimental/space.d.ts +0 -8
- package/dist/types/src/experimental/space.d.ts.map +0 -1
- package/dist/types/src/query/dsl.d.ts.map +0 -1
- package/dist/types/src/query/dsl.test.d.ts +0 -2
- package/dist/types/src/query/dsl.test.d.ts.map +0 -1
- package/src/experimental/database.ts +0 -11
- package/src/experimental/index.ts +0 -7
- package/src/experimental/queue.ts +0 -11
- package/src/experimental/space.ts +0 -11
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
|
+
import * as SchemaAST from 'effect/SchemaAST';
|
|
7
|
+
|
|
8
|
+
import { invariant } from '@dxos/invariant';
|
|
9
|
+
|
|
10
|
+
import { SchemaId } from './model';
|
|
11
|
+
|
|
12
|
+
// TODO(burdon): Reconcile with @dxos/effect visit().
|
|
13
|
+
|
|
14
|
+
export class SchemaValidator {
|
|
15
|
+
/**
|
|
16
|
+
* Recursively check that schema specifies constructions we can handle.
|
|
17
|
+
* Validates there are no ambiguous discriminated union types.
|
|
18
|
+
*/
|
|
19
|
+
public static validateSchema(schema: Schema.Schema.AnyNoContext): void {
|
|
20
|
+
const visitAll = (nodes: SchemaAST.AST[]) => nodes.forEach((node) => this.validateSchema(Schema.make(node)));
|
|
21
|
+
if (SchemaAST.isUnion(schema.ast)) {
|
|
22
|
+
const typeAstList = schema.ast.types.filter((type) => SchemaAST.isTypeLiteral(type)) as SchemaAST.TypeLiteral[];
|
|
23
|
+
// Check we can handle a discriminated union.
|
|
24
|
+
if (typeAstList.length > 1) {
|
|
25
|
+
getTypeDiscriminators(typeAstList);
|
|
26
|
+
}
|
|
27
|
+
visitAll(typeAstList);
|
|
28
|
+
} else if (SchemaAST.isTupleType(schema.ast)) {
|
|
29
|
+
const positionalTypes = schema.ast.elements.map((t) => t.type);
|
|
30
|
+
const allTypes = positionalTypes.concat(schema.ast.rest.map((t) => t.type));
|
|
31
|
+
visitAll(allTypes);
|
|
32
|
+
} else if (SchemaAST.isTypeLiteral(schema.ast)) {
|
|
33
|
+
visitAll(SchemaAST.getPropertySignatures(schema.ast).map((p) => p.type));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public static hasTypeAnnotation(
|
|
38
|
+
rootObjectSchema: Schema.Schema.AnyNoContext,
|
|
39
|
+
property: string,
|
|
40
|
+
annotation: symbol,
|
|
41
|
+
): boolean {
|
|
42
|
+
try {
|
|
43
|
+
let type = this.getPropertySchema(rootObjectSchema, [property]);
|
|
44
|
+
if (SchemaAST.isTupleType(type.ast)) {
|
|
45
|
+
type = this.getPropertySchema(rootObjectSchema, [property, '0']);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return type.ast.annotations[annotation] != null;
|
|
49
|
+
} catch (err) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public static getPropertySchema(
|
|
55
|
+
rootObjectSchema: Schema.Schema.AnyNoContext,
|
|
56
|
+
propertyPath: KeyPath,
|
|
57
|
+
getProperty: (path: KeyPath) => any = () => null,
|
|
58
|
+
): Schema.Schema.AnyNoContext {
|
|
59
|
+
let schema: Schema.Schema.AnyNoContext = rootObjectSchema;
|
|
60
|
+
for (let i = 0; i < propertyPath.length; i++) {
|
|
61
|
+
const propertyName = propertyPath[i];
|
|
62
|
+
const tupleAst = unwrapArray(schema.ast);
|
|
63
|
+
if (tupleAst != null) {
|
|
64
|
+
schema = getArrayElementSchema(tupleAst, propertyName);
|
|
65
|
+
} else {
|
|
66
|
+
const propertyType = getPropertyType(schema.ast, propertyName.toString(), (propertyName) =>
|
|
67
|
+
getProperty([...propertyPath.slice(0, i), propertyName]),
|
|
68
|
+
);
|
|
69
|
+
if (propertyType == null) {
|
|
70
|
+
throw new TypeError(`unknown property: ${String(propertyName)} on object. Path: ${propertyPath}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
schema = Schema.make(propertyType).annotations(propertyType.annotations);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return schema;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public static getTargetPropertySchema(target: any, prop: string | symbol): Schema.Schema.AnyNoContext {
|
|
81
|
+
const schema: Schema.Schema.AnyNoContext | undefined = (target as any)[SchemaId];
|
|
82
|
+
invariant(schema, 'target has no schema');
|
|
83
|
+
const arrayAst = unwrapArray(schema.ast);
|
|
84
|
+
if (arrayAst != null) {
|
|
85
|
+
return getArrayElementSchema(arrayAst, prop);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const propertyType = getPropertyType(schema.ast, prop.toString(), (prop) => target[prop]);
|
|
89
|
+
if (propertyType == null) {
|
|
90
|
+
return Schema.Any; // TODO(burdon): HACK.
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
invariant(propertyType, `invalid property: ${prop.toString()}`);
|
|
94
|
+
return Schema.make(propertyType);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Tuple AST is used both for:
|
|
100
|
+
* fixed-length tuples ([string, number]) in which case AST will be { elements: [Schema.String, Schema.Number] }
|
|
101
|
+
* variable-length arrays (Array<string | number>) in which case AST will be { rest: [Schema.Union(Schema.String, Schema.Number)] }
|
|
102
|
+
*/
|
|
103
|
+
const getArrayElementSchema = (
|
|
104
|
+
tupleAst: SchemaAST.TupleType,
|
|
105
|
+
property: string | symbol | number,
|
|
106
|
+
): Schema.Schema.AnyNoContext => {
|
|
107
|
+
const elementIndex = typeof property === 'string' ? parseInt(property, 10) : Number.NaN;
|
|
108
|
+
if (Number.isNaN(elementIndex)) {
|
|
109
|
+
invariant(property === 'length', `invalid array property: ${String(property)}`);
|
|
110
|
+
return Schema.Number;
|
|
111
|
+
}
|
|
112
|
+
if (elementIndex < tupleAst.elements.length) {
|
|
113
|
+
const elementType = tupleAst.elements[elementIndex].type;
|
|
114
|
+
return Schema.make(elementType).annotations(elementType.annotations);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const restType = tupleAst.rest;
|
|
118
|
+
return Schema.make(restType[0].type).annotations(restType[0].annotations);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const flattenUnion = (typeAst: SchemaAST.AST): SchemaAST.AST[] =>
|
|
122
|
+
SchemaAST.isUnion(typeAst) ? typeAst.types.flatMap(flattenUnion) : [typeAst];
|
|
123
|
+
|
|
124
|
+
const getProperties = (
|
|
125
|
+
typeAst: SchemaAST.AST,
|
|
126
|
+
getTargetPropertyFn: (propertyName: string) => any,
|
|
127
|
+
): SchemaAST.PropertySignature[] => {
|
|
128
|
+
const astCandidates = flattenUnion(typeAst);
|
|
129
|
+
const typeAstList = astCandidates.filter((type) => SchemaAST.isTypeLiteral(type)) as SchemaAST.TypeLiteral[];
|
|
130
|
+
if (typeAstList.length === 0) {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
if (typeAstList.length === 1) {
|
|
134
|
+
return SchemaAST.getPropertySignatures(typeAstList[0]);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const typeDiscriminators = getTypeDiscriminators(typeAstList);
|
|
138
|
+
const targetPropertyValue = getTargetPropertyFn(String(typeDiscriminators[0].name));
|
|
139
|
+
const typeIndex = typeDiscriminators.findIndex((p) => targetPropertyValue === (p.type as SchemaAST.Literal).literal);
|
|
140
|
+
invariant(typeIndex !== -1, 'discriminator field not set on target');
|
|
141
|
+
return SchemaAST.getPropertySignatures(typeAstList[typeIndex]);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const getPropertyType = (
|
|
145
|
+
ast: SchemaAST.AST,
|
|
146
|
+
propertyName: string,
|
|
147
|
+
getTargetPropertyFn: (propertyName: string) => any,
|
|
148
|
+
): SchemaAST.AST | null => {
|
|
149
|
+
const anyOrObject = unwrapAst(
|
|
150
|
+
ast,
|
|
151
|
+
(candidate) => SchemaAST.isAnyKeyword(candidate) || SchemaAST.isObjectKeyword(candidate),
|
|
152
|
+
);
|
|
153
|
+
if (anyOrObject != null) {
|
|
154
|
+
return ast;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const typeOrDiscriminatedUnion = unwrapAst(ast, (t) => {
|
|
158
|
+
return SchemaAST.isTypeLiteral(t) || (SchemaAST.isUnion(t) && t.types.some((t) => SchemaAST.isTypeLiteral(t)));
|
|
159
|
+
});
|
|
160
|
+
if (typeOrDiscriminatedUnion == null) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const targetProperty = getProperties(typeOrDiscriminatedUnion, getTargetPropertyFn).find(
|
|
165
|
+
(p) => p.name === propertyName,
|
|
166
|
+
);
|
|
167
|
+
if (targetProperty != null) {
|
|
168
|
+
return unwrapAst(targetProperty.type);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const indexSignatureType = unwrapAst(ast, SchemaAST.isTypeLiteral);
|
|
172
|
+
if (
|
|
173
|
+
indexSignatureType &&
|
|
174
|
+
SchemaAST.isTypeLiteral(indexSignatureType) &&
|
|
175
|
+
indexSignatureType.indexSignatures.length > 0
|
|
176
|
+
) {
|
|
177
|
+
return unwrapAst(indexSignatureType.indexSignatures[0].type);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return null;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const getTypeDiscriminators = (typeAstList: SchemaAST.TypeLiteral[]): SchemaAST.PropertySignature[] => {
|
|
184
|
+
const discriminatorPropCandidates = typeAstList
|
|
185
|
+
.flatMap(SchemaAST.getPropertySignatures)
|
|
186
|
+
.filter((p) => SchemaAST.isLiteral(p.type));
|
|
187
|
+
const propertyName = discriminatorPropCandidates[0].name;
|
|
188
|
+
const isValidDiscriminator = discriminatorPropCandidates.every((p) => p.name === propertyName && !p.isOptional);
|
|
189
|
+
const everyTypeHasDiscriminator = discriminatorPropCandidates.length === typeAstList.length;
|
|
190
|
+
const isDiscriminatedUnion = isValidDiscriminator && everyTypeHasDiscriminator;
|
|
191
|
+
invariant(isDiscriminatedUnion, 'type ambiguity: every type in a union must have a single unique-literal field');
|
|
192
|
+
return discriminatorPropCandidates;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Used to check that rootAst is for a type matching the provided predicate.
|
|
197
|
+
* That's not always straightforward because types of optionality and recursive types.
|
|
198
|
+
* const Task = Schema.Struct({
|
|
199
|
+
* ...,
|
|
200
|
+
* previous?: Schema.optional(Schema.suspend(() => Task)),
|
|
201
|
+
* });
|
|
202
|
+
* Here the AST for `previous` field is going to be Union(Suspend(Type), Undefined).
|
|
203
|
+
* SchemaAST.isTypeLiteral(field) will return false, but unwrapAst(field, (ast) => SchemaAST.isTypeLiteral(ast))
|
|
204
|
+
* will return true.
|
|
205
|
+
*/
|
|
206
|
+
const unwrapAst = (rootAst: SchemaAST.AST, predicate?: (ast: SchemaAST.AST) => boolean): SchemaAST.AST | null => {
|
|
207
|
+
let ast: SchemaAST.AST | undefined = rootAst;
|
|
208
|
+
while (ast != null) {
|
|
209
|
+
if (predicate?.(ast)) {
|
|
210
|
+
return ast;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (SchemaAST.isUnion(ast)) {
|
|
214
|
+
const next: any = ast.types.find((t) => (predicate != null && predicate(t)) || SchemaAST.isSuspend(t));
|
|
215
|
+
if (next != null) {
|
|
216
|
+
ast = next;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (SchemaAST.isSuspend(ast)) {
|
|
222
|
+
ast = ast.f();
|
|
223
|
+
} else {
|
|
224
|
+
return predicate == null ? ast : null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return null;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const unwrapArray = (ast: SchemaAST.AST) => unwrapAst(ast, SchemaAST.isTupleType) as SchemaAST.TupleType | null;
|
|
232
|
+
|
|
233
|
+
export const checkIdNotPresentOnSchema = (schema: Schema.Schema<any, any, any>) => {
|
|
234
|
+
invariant(SchemaAST.isTypeLiteral(schema.ast));
|
|
235
|
+
const idProperty = SchemaAST.getPropertySignatures(schema.ast).find((prop) => prop.name === 'id');
|
|
236
|
+
if (idProperty != null) {
|
|
237
|
+
throw new Error('"id" property name is reserved');
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
type KeyPath = readonly (string | number)[];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
|
+
import * as SchemaAST from 'effect/SchemaAST';
|
|
7
|
+
import { describe, expect, test } from 'vitest';
|
|
8
|
+
|
|
9
|
+
import { TypedObject } from './typed-object';
|
|
10
|
+
|
|
11
|
+
class Organization extends TypedObject({
|
|
12
|
+
typename: 'example.com/type/Organization',
|
|
13
|
+
version: '0.1.0',
|
|
14
|
+
})({
|
|
15
|
+
name: Schema.String,
|
|
16
|
+
}) {}
|
|
17
|
+
|
|
18
|
+
describe('EchoObject class DSL', () => {
|
|
19
|
+
test('type is a valid schema', async () => {
|
|
20
|
+
expect(Schema.isSchema(Organization)).to.be.true;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('static typename accessor', async () => {
|
|
24
|
+
expect(Organization.typename).to.eq('example.com/type/Organization');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('expect constructor to throw', async () => {
|
|
28
|
+
expect(() => new Organization()).to.throw();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('expect schema', async () => {
|
|
32
|
+
expect(SchemaAST.isTypeLiteral(Organization.ast)).to.be.true;
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
|
+
|
|
7
|
+
import { invariant } from '@dxos/invariant';
|
|
8
|
+
|
|
9
|
+
import { EntityKind, SchemaVersion, type TypeAnnotation, TypeAnnotationId, type TypeMeta, Typename } from '../ast';
|
|
10
|
+
import { type HasId } from '../types';
|
|
11
|
+
|
|
12
|
+
import { type TypedObjectFields, type TypedObjectOptions, makeTypedEntityClass } from './common';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Definition for an object type that can be stored in an ECHO database.
|
|
16
|
+
* Implements effect schema to define object properties.
|
|
17
|
+
* Has a typename and version.
|
|
18
|
+
*
|
|
19
|
+
* In contrast to {@link EchoSchema} this definition is not recorded in the database.
|
|
20
|
+
*/
|
|
21
|
+
export interface TypedObject<A = any, I = any> extends TypeMeta, Schema.Schema<A, I> {}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Typed object that could be used as a prototype in class definitions.
|
|
25
|
+
* This is an internal API type.
|
|
26
|
+
* Use {@link TypedObject} for the common use-cases.
|
|
27
|
+
*/
|
|
28
|
+
export interface TypedObjectPrototype<A = any, I = any> extends TypedObject<A, I> {
|
|
29
|
+
/** Type constructor. */
|
|
30
|
+
new (): HasId & A;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type TypedObjectProps = TypeMeta & {
|
|
34
|
+
// TODO(dmaretskyi): Remove after all legacy types has been removed.
|
|
35
|
+
disableValidation?: boolean;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Base class factory for typed objects.
|
|
40
|
+
* @deprecated Use Function.pipe(Type.Obj) instead.
|
|
41
|
+
*/
|
|
42
|
+
export const TypedObject = ({
|
|
43
|
+
typename: typenameParam,
|
|
44
|
+
version: versionParam,
|
|
45
|
+
disableValidation,
|
|
46
|
+
}: TypedObjectProps) => {
|
|
47
|
+
const typename = Typename.make(typenameParam, { disableValidation });
|
|
48
|
+
const version = SchemaVersion.make(versionParam, { disableValidation });
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Return class definition factory.
|
|
52
|
+
*/
|
|
53
|
+
return <SchemaFields extends Schema.Struct.Fields, Options extends TypedObjectOptions>(
|
|
54
|
+
fields: SchemaFields,
|
|
55
|
+
options?: Options,
|
|
56
|
+
): TypedObjectPrototype<TypedObjectFields<SchemaFields, Options>, Schema.Struct.Encoded<SchemaFields>> => {
|
|
57
|
+
// Create schema from fields.
|
|
58
|
+
const schema: Schema.Schema.All = options?.record
|
|
59
|
+
? Schema.Struct(fields, { key: Schema.String, value: Schema.Any })
|
|
60
|
+
: Schema.Struct(fields);
|
|
61
|
+
|
|
62
|
+
// Set ECHO object id property.
|
|
63
|
+
const typeSchema = Schema.extend(
|
|
64
|
+
Schema.mutable(options?.partial ? Schema.partial(schema) : schema),
|
|
65
|
+
Schema.Struct({ id: Schema.String }),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// Set ECHO annotations.
|
|
69
|
+
invariant(typeof EntityKind.Object === 'string');
|
|
70
|
+
const annotatedSchema = typeSchema.annotations({
|
|
71
|
+
[TypeAnnotationId]: { kind: EntityKind.Object, typename, version } satisfies TypeAnnotation,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Return class definition.
|
|
76
|
+
* NOTE: Actual reactive ECHO objects must be created via the `live(Type)` function.
|
|
77
|
+
*/
|
|
78
|
+
// TODO(burdon): This is missing fields required by TypedObject (e.g., Type, Encoded, Context)?
|
|
79
|
+
return class TypedObject extends makeTypedEntityClass(typename, version, annotatedSchema as any) {} as any;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
|
+
|
|
7
|
+
import { invariant } from '@dxos/invariant';
|
|
8
|
+
|
|
9
|
+
import { EntityKind, SchemaVersion, type TypeAnnotation, TypeAnnotationId, type TypeMeta, Typename } from '../ast';
|
|
10
|
+
import { type HasId } from '../types';
|
|
11
|
+
|
|
12
|
+
import { type TypedObjectFields, type TypedObjectOptions, makeTypedEntityClass } from './common';
|
|
13
|
+
import { type RelationSourceTargetRefs } from './relation';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Definition for an object type that can be stored in an ECHO database.
|
|
17
|
+
* Implements effect schema to define object properties.
|
|
18
|
+
* Has a typename and version.
|
|
19
|
+
*
|
|
20
|
+
* In contrast to {@link EchoSchema} this definition is not recorded in the database.
|
|
21
|
+
*/
|
|
22
|
+
export interface TypedRelation<A = any, I = any> extends TypeMeta, Schema.Schema<A, I> {}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Typed object that could be used as a prototype in class definitions.
|
|
26
|
+
* This is an internal API type.
|
|
27
|
+
* Use {@link TypedRelation} for the common use-cases.
|
|
28
|
+
*/
|
|
29
|
+
export interface TypedRelationPrototype<A = any, I = any> extends TypedRelation<A, I> {
|
|
30
|
+
/** Type constructor. */
|
|
31
|
+
new (): HasId & A;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type TypedRelationProps = TypeMeta & {
|
|
35
|
+
// TODO(dmaretskyi): Remove after all legacy types has been removed.
|
|
36
|
+
disableValidation?: boolean;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Base class factory for typed objects.
|
|
41
|
+
* @deprecated Use {@link EchoRelation} instead.
|
|
42
|
+
*/
|
|
43
|
+
export const TypedRelation = ({
|
|
44
|
+
typename: typenameParam,
|
|
45
|
+
version: versionParam,
|
|
46
|
+
disableValidation,
|
|
47
|
+
}: TypedRelationProps) => {
|
|
48
|
+
const typename = Typename.make(typenameParam, { disableValidation });
|
|
49
|
+
const version = SchemaVersion.make(versionParam, { disableValidation });
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Return class definition factory.
|
|
53
|
+
*/
|
|
54
|
+
return <SchemaFields extends Schema.Struct.Fields, Options extends TypedObjectOptions>(
|
|
55
|
+
fields: SchemaFields,
|
|
56
|
+
options?: Options,
|
|
57
|
+
): TypedRelationPrototype<
|
|
58
|
+
TypedObjectFields<SchemaFields, Options> & RelationSourceTargetRefs,
|
|
59
|
+
Schema.Struct.Encoded<SchemaFields>
|
|
60
|
+
> => {
|
|
61
|
+
// Create schema from fields.
|
|
62
|
+
const schema: Schema.Schema.All = options?.record
|
|
63
|
+
? Schema.Struct(fields, { key: Schema.String, value: Schema.Any })
|
|
64
|
+
: Schema.Struct(fields);
|
|
65
|
+
|
|
66
|
+
// Set ECHO object id property.
|
|
67
|
+
const typeSchema = Schema.extend(
|
|
68
|
+
Schema.mutable(options?.partial ? Schema.partial(schema) : schema),
|
|
69
|
+
Schema.Struct({ id: Schema.String }),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Set ECHO annotations.
|
|
73
|
+
invariant(typeof EntityKind.Relation === 'string');
|
|
74
|
+
const annotatedSchema = typeSchema.annotations({
|
|
75
|
+
[TypeAnnotationId]: { kind: EntityKind.Relation, typename, version } satisfies TypeAnnotation,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Return class definition.
|
|
80
|
+
* NOTE: Actual reactive ECHO objects must be created via the `live(Type)` function.
|
|
81
|
+
*/
|
|
82
|
+
// TODO(burdon): This is missing fields required by TypedRelation (e.g., Type, Encoded, Context)?
|
|
83
|
+
return class TypedRelation extends makeTypedEntityClass(typename, version, annotatedSchema as any) {} as any;
|
|
84
|
+
};
|
|
85
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { invariant } from '@dxos/invariant';
|
|
6
|
+
import { DXN } from '@dxos/keys';
|
|
7
|
+
|
|
8
|
+
import { getSchemaTypename } from '../ast';
|
|
9
|
+
import { type BaseObject } from '../types';
|
|
10
|
+
|
|
11
|
+
import { getSchema } from './accessors';
|
|
12
|
+
import { TypeId } from './model';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Gets the typename of the object without the version.
|
|
16
|
+
* Returns only the name portion, not the DXN.
|
|
17
|
+
* @example "example.org/type/Contact"
|
|
18
|
+
*/
|
|
19
|
+
export const getTypename = (obj: BaseObject): string | undefined => {
|
|
20
|
+
const schema = getSchema(obj);
|
|
21
|
+
if (schema != null) {
|
|
22
|
+
// Try to extract typename from DXN.
|
|
23
|
+
return getSchemaTypename(schema);
|
|
24
|
+
} else {
|
|
25
|
+
const type = getType(obj);
|
|
26
|
+
return type?.asTypeDXN()?.type;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
// TODO(dmaretskyi): Rename setTypeDXN.
|
|
34
|
+
export const setTypename = (obj: any, typename: DXN) => {
|
|
35
|
+
invariant(typename instanceof DXN, 'Invalid type.');
|
|
36
|
+
Object.defineProperty(obj, TypeId, {
|
|
37
|
+
value: typename,
|
|
38
|
+
writable: false,
|
|
39
|
+
enumerable: false,
|
|
40
|
+
configurable: false,
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @returns Object type as {@link DXN}.
|
|
46
|
+
* @returns undefined if the object doesn't have a type.
|
|
47
|
+
* @example `dxn:example.com/type/Contact:1.0.0`
|
|
48
|
+
*/
|
|
49
|
+
export const getType = (obj: BaseObject): DXN | undefined => {
|
|
50
|
+
if (!obj) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const type = (obj as any)[TypeId];
|
|
55
|
+
if (!type) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
invariant(type instanceof DXN, 'Invalid object.');
|
|
60
|
+
return type;
|
|
61
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//
|
|
2
|
+
// NOTE: Those types must match the ones defined at @dxos/echo. We duplicated them murely due to technical constrains.
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Getter to get object version.
|
|
7
|
+
*/
|
|
8
|
+
//
|
|
9
|
+
// Copyright 2025 DXOS.org
|
|
10
|
+
//
|
|
11
|
+
|
|
12
|
+
export const VersionTypeId: unique symbol = Symbol('@dxos/echo/Version');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Represent object version.
|
|
16
|
+
* May be backed by Automerge.
|
|
17
|
+
* Objects with no history are not versioned.
|
|
18
|
+
*/
|
|
19
|
+
export interface Version {
|
|
20
|
+
[VersionTypeId]: {};
|
|
21
|
+
automergeHeads?: string[];
|
|
22
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
|
+
import { describe, test } from 'vitest';
|
|
7
|
+
|
|
8
|
+
import { FieldPath } from '../ast';
|
|
9
|
+
import { FormatAnnotation, FormatEnum } from '../formats';
|
|
10
|
+
import { toJsonSchema } from '../json';
|
|
11
|
+
import { ECHO_ANNOTATIONS_NS_KEY } from '../json-schema';
|
|
12
|
+
import { TypedObject } from '../object';
|
|
13
|
+
|
|
14
|
+
import { composeSchema } from './compose';
|
|
15
|
+
|
|
16
|
+
describe('schema composition', () => {
|
|
17
|
+
test('schema composition', ({ expect }) => {
|
|
18
|
+
class BaseType extends TypedObject({ typename: 'example.com/Person', version: '0.1.0' })({
|
|
19
|
+
name: Schema.String,
|
|
20
|
+
email: Schema.String,
|
|
21
|
+
}) {}
|
|
22
|
+
|
|
23
|
+
const OverlaySchema = Schema.Struct({
|
|
24
|
+
email: Schema.String.pipe(FieldPath('$.email'), FormatAnnotation.set(FormatEnum.Email)),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const baseSchema = toJsonSchema(BaseType);
|
|
28
|
+
const overlaySchema = toJsonSchema(OverlaySchema);
|
|
29
|
+
const composedSchema = composeSchema(baseSchema, overlaySchema);
|
|
30
|
+
expect(composedSchema.properties).to.deep.eq({
|
|
31
|
+
email: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
format: FormatEnum.Email,
|
|
34
|
+
// TODO(dmaretskyi): Should use the new field.
|
|
35
|
+
[ECHO_ANNOTATIONS_NS_KEY]: {
|
|
36
|
+
meta: {
|
|
37
|
+
path: '$.email',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { invariant } from '@dxos/invariant';
|
|
6
|
+
|
|
7
|
+
import { type JsonSchemaType } from '../json-schema';
|
|
8
|
+
import { getSnapshot } from '../schema';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Creates a composite schema from the source and projection schemas.
|
|
12
|
+
*/
|
|
13
|
+
// TODO(burdon): Use effect schema projections.
|
|
14
|
+
// TODO(burdon): Can avoid having to call this every time we modify any property on the view?
|
|
15
|
+
export const composeSchema = (source: JsonSchemaType, target: JsonSchemaType): JsonSchemaType => {
|
|
16
|
+
const result: JsonSchemaType = getSnapshot(target);
|
|
17
|
+
invariant('type' in result && result.type === 'object', 'source schema must be an object');
|
|
18
|
+
invariant('type' in source && source.type === 'object', 'target schema must be an object');
|
|
19
|
+
|
|
20
|
+
for (const prop in result.properties) {
|
|
21
|
+
const propSchema = source.properties![prop]; // TODO(dmaretskyi): Find by json-path instead.
|
|
22
|
+
const annotations = (propSchema as JsonSchemaType)?.annotations?.meta;
|
|
23
|
+
if (annotations) {
|
|
24
|
+
(result.properties[prop] as JsonSchemaType).annotations ??= {};
|
|
25
|
+
(result.properties[prop] as JsonSchemaType).annotations!.meta ??= {};
|
|
26
|
+
for (const key in annotations) {
|
|
27
|
+
(result.properties[prop] as JsonSchemaType).annotations!.meta![key] ??= {};
|
|
28
|
+
Object.assign((result.properties[prop] as JsonSchemaType).annotations!.meta![key], annotations[key], {
|
|
29
|
+
...(result.properties[prop] as JsonSchemaType).annotations!.meta![key],
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return result;
|
|
36
|
+
};
|