@dxos/echo 0.8.3-main.672df60 → 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 (57) hide show
  1. package/README.md +25 -23
  2. package/dist/lib/browser/chunk-UYPR62ZB.mjs +624 -0
  3. package/dist/lib/browser/chunk-UYPR62ZB.mjs.map +7 -0
  4. package/dist/lib/browser/index.mjs +11 -280
  5. package/dist/lib/browser/index.mjs.map +4 -4
  6. package/dist/lib/browser/meta.json +1 -1
  7. package/dist/lib/browser/testing/index.mjs +70 -0
  8. package/dist/lib/browser/testing/index.mjs.map +7 -0
  9. package/dist/lib/node/chunk-4HQE2F3L.cjs +644 -0
  10. package/dist/lib/node/chunk-4HQE2F3L.cjs.map +7 -0
  11. package/dist/lib/node/index.cjs +11 -282
  12. package/dist/lib/node/index.cjs.map +4 -4
  13. package/dist/lib/node/meta.json +1 -1
  14. package/dist/lib/node/testing/index.cjs +89 -0
  15. package/dist/lib/node/testing/index.cjs.map +7 -0
  16. package/dist/lib/node-esm/chunk-BYBICDIO.mjs +624 -0
  17. package/dist/lib/node-esm/chunk-BYBICDIO.mjs.map +7 -0
  18. package/dist/lib/node-esm/index.mjs +11 -280
  19. package/dist/lib/node-esm/index.mjs.map +4 -4
  20. package/dist/lib/node-esm/meta.json +1 -1
  21. package/dist/lib/node-esm/testing/index.mjs +70 -0
  22. package/dist/lib/node-esm/testing/index.mjs.map +7 -0
  23. package/dist/types/src/Key.d.ts +2 -0
  24. package/dist/types/src/Key.d.ts.map +1 -0
  25. package/dist/types/src/Obj.d.ts +37 -18
  26. package/dist/types/src/Obj.d.ts.map +1 -1
  27. package/dist/types/src/Ref.d.ts +9 -3
  28. package/dist/types/src/Ref.d.ts.map +1 -1
  29. package/dist/types/src/Relation.d.ts +36 -10
  30. package/dist/types/src/Relation.d.ts.map +1 -1
  31. package/dist/types/src/Type.d.ts +82 -17
  32. package/dist/types/src/Type.d.ts.map +1 -1
  33. package/dist/types/src/index.d.ts +5 -3
  34. package/dist/types/src/index.d.ts.map +1 -1
  35. package/dist/types/src/query/dsl.d.ts +218 -0
  36. package/dist/types/src/query/dsl.d.ts.map +1 -0
  37. package/dist/types/src/query/dsl.test.d.ts +2 -0
  38. package/dist/types/src/query/dsl.test.d.ts.map +1 -0
  39. package/dist/types/src/query/index.d.ts +2 -0
  40. package/dist/types/src/query/index.d.ts.map +1 -0
  41. package/dist/types/src/testing/index.d.ts +2 -0
  42. package/dist/types/src/testing/index.d.ts.map +1 -0
  43. package/dist/types/src/testing/types.d.ts +113 -0
  44. package/dist/types/src/testing/types.d.ts.map +1 -0
  45. package/package.json +56 -12
  46. package/src/Key.ts +5 -0
  47. package/src/Obj.ts +66 -25
  48. package/src/Ref.ts +9 -6
  49. package/src/Relation.ts +75 -13
  50. package/src/Type.ts +122 -18
  51. package/src/index.ts +5 -3
  52. package/src/query/dsl.test.ts +323 -0
  53. package/src/query/dsl.ts +646 -0
  54. package/src/query/index.ts +5 -0
  55. package/src/test/api.test.ts +54 -9
  56. package/src/testing/index.ts +5 -0
  57. package/src/testing/types.ts +91 -0
@@ -0,0 +1,113 @@
1
+ import { Schema } from 'effect';
2
+ import { Type } from '..';
3
+ export declare namespace Testing {
4
+ const _Contact: Type.obj<Schema.SchemaClass<{
5
+ readonly name?: string | undefined;
6
+ readonly email?: string | undefined;
7
+ readonly username?: string | undefined;
8
+ readonly tasks?: import("@dxos/echo-schema").Ref<Task>[] | undefined;
9
+ readonly address?: {
10
+ readonly city?: string | undefined;
11
+ readonly state?: string | undefined;
12
+ readonly zip?: string | undefined;
13
+ readonly coordinates: {
14
+ readonly lat?: number | undefined;
15
+ readonly lng?: number | undefined;
16
+ };
17
+ } | undefined;
18
+ }, {
19
+ readonly name?: string | undefined;
20
+ readonly email?: string | undefined;
21
+ readonly username?: string | undefined;
22
+ readonly tasks?: import("@dxos/echo-protocol").EncodedReference[] | undefined;
23
+ readonly address?: {
24
+ readonly coordinates: {
25
+ readonly lat?: number | undefined;
26
+ readonly lng?: number | undefined;
27
+ };
28
+ readonly city?: string | undefined;
29
+ readonly state?: string | undefined;
30
+ readonly zip?: string | undefined;
31
+ } | undefined;
32
+ }, never>>;
33
+ export interface Contact extends Schema.Schema.Type<typeof _Contact> {
34
+ }
35
+ export const Contact: Schema.Schema<Contact, Schema.Schema.Encoded<typeof _Contact>, never>;
36
+ const _Task: Type.obj<Schema.SchemaClass<{
37
+ readonly description?: string | undefined;
38
+ readonly title?: string | undefined;
39
+ readonly assignee?: import("@dxos/echo-schema").Ref<Contact> | undefined;
40
+ readonly completed?: boolean | undefined;
41
+ readonly previous?: import("@dxos/echo-schema").Ref<Task> | undefined;
42
+ readonly subTasks?: import("@dxos/echo-schema").Ref<Task>[] | undefined;
43
+ }, {
44
+ readonly description?: string | undefined;
45
+ readonly title?: string | undefined;
46
+ readonly assignee?: import("@dxos/echo-protocol").EncodedReference | undefined;
47
+ readonly completed?: boolean | undefined;
48
+ readonly previous?: import("@dxos/echo-protocol").EncodedReference | undefined;
49
+ readonly subTasks?: import("@dxos/echo-protocol").EncodedReference[] | undefined;
50
+ }, never>>;
51
+ export interface Task extends Schema.Schema.Type<typeof _Task> {
52
+ }
53
+ export const Task: Schema.Schema<Task, Schema.Schema.Encoded<typeof _Task>, never>;
54
+ export enum RecordType {
55
+ UNDEFINED = 0,
56
+ PERSONAL = 1,
57
+ WORK = 2
58
+ }
59
+ export const Container: Type.obj<Schema.SchemaClass<{
60
+ readonly records?: {
61
+ readonly type?: RecordType | undefined;
62
+ readonly description?: string | undefined;
63
+ readonly title?: string | undefined;
64
+ readonly contacts?: import("@dxos/echo-schema").Ref<Contact>[] | undefined;
65
+ }[] | undefined;
66
+ readonly objects?: import("@dxos/echo-schema").Ref<Type.Expando>[] | undefined;
67
+ }, {
68
+ readonly records?: {
69
+ readonly type?: RecordType | undefined;
70
+ readonly description?: string | undefined;
71
+ readonly title?: string | undefined;
72
+ readonly contacts?: import("@dxos/echo-protocol").EncodedReference[] | undefined;
73
+ }[] | undefined;
74
+ readonly objects?: import("@dxos/echo-protocol").EncodedReference[] | undefined;
75
+ }, never>>;
76
+ export const WorksFor: Type.relation<Schema.Struct<{
77
+ since: Schema.optional<typeof Schema.String>;
78
+ }>, Schema.Schema<Contact, {
79
+ id: string;
80
+ name?: string | undefined;
81
+ email?: string | undefined;
82
+ username?: string | undefined;
83
+ tasks?: import("@dxos/echo-protocol").EncodedReference[] | undefined;
84
+ address?: {
85
+ readonly coordinates: {
86
+ readonly lat?: number | undefined;
87
+ readonly lng?: number | undefined;
88
+ };
89
+ readonly city?: string | undefined;
90
+ readonly state?: string | undefined;
91
+ readonly zip?: string | undefined;
92
+ } | undefined;
93
+ }, never>, Schema.Schema<Contact, {
94
+ id: string;
95
+ name?: string | undefined;
96
+ email?: string | undefined;
97
+ username?: string | undefined;
98
+ tasks?: import("@dxos/echo-protocol").EncodedReference[] | undefined;
99
+ address?: {
100
+ readonly coordinates: {
101
+ readonly lat?: number | undefined;
102
+ readonly lng?: number | undefined;
103
+ };
104
+ readonly city?: string | undefined;
105
+ readonly state?: string | undefined;
106
+ readonly zip?: string | undefined;
107
+ } | undefined;
108
+ }, never>>;
109
+ export interface WorksFor extends Schema.Schema.Type<typeof WorksFor> {
110
+ }
111
+ export {};
112
+ }
113
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/testing/types.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAG1B,yBAAiB,OAAO,CAAC;IACvB,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAoBb,CAAC;IACF,MAAM,WAAW,OAAQ,SAAQ,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,QAAQ,CAAC;KAAG;IACvE,MAAM,CAAC,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,QAAQ,CAAC,EAAE,KAAK,CAAY,CAAC;IAEvG,MAAM,KAAK;;;;;;;;;;;;;;cAaV,CAAC;IACF,MAAM,WAAW,IAAK,SAAQ,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,KAAK,CAAC;KAAG;IACjE,MAAM,CAAC,MAAM,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,KAAK,CAAC,EAAE,KAAK,CAAS,CAAC;IAE3F,MAAM,MAAM,UAAU;QACpB,SAAS,IAAI;QACb,QAAQ,IAAI;QACZ,IAAI,IAAI;KACT;IAED,MAAM,CAAC,MAAM,SAAS;;;;;;;;;;;;;;;;cAoBrB,CAAC;IAEF,MAAM,CAAC,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cASpB,CAAC;IACF,MAAM,WAAW,QAAS,SAAQ,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,QAAQ,CAAC;KAAG;;CACzE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/echo",
3
- "version": "0.8.3-main.672df60",
3
+ "version": "0.8.3-staging.0fa589b",
4
4
  "description": "ECHO API",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -13,9 +13,53 @@
13
13
  "types": "./dist/types/src/index.d.ts",
14
14
  "browser": "./dist/lib/browser/index.mjs",
15
15
  "node": "./dist/lib/node-esm/index.mjs"
16
+ },
17
+ "./Type": {
18
+ "types": "./dist/types/src/Type.d.ts",
19
+ "browser": "./dist/lib/browser/Type.mjs",
20
+ "node": "./dist/lib/node-esm/Type.mjs"
21
+ },
22
+ "./Obj": {
23
+ "types": "./dist/types/src/Obj.d.ts",
24
+ "browser": "./dist/lib/browser/Obj.mjs",
25
+ "node": "./dist/lib/node-esm/Obj.mjs"
26
+ },
27
+ "./Relation": {
28
+ "types": "./dist/types/src/Relation.d.ts",
29
+ "browser": "./dist/lib/browser/Relation.mjs",
30
+ "node": "./dist/lib/node-esm/Relation.mjs"
31
+ },
32
+ "./Ref": {
33
+ "types": "./dist/types/src/Ref.d.ts",
34
+ "browser": "./dist/lib/browser/Ref.mjs",
35
+ "node": "./dist/lib/node-esm/Ref.mjs"
36
+ },
37
+ "./testing": {
38
+ "types": "./dist/types/src/testing/types.d.ts",
39
+ "browser": "./dist/lib/browser/testing/index.mjs",
40
+ "node": "./dist/lib/node-esm/testing/index.mjs"
16
41
  }
17
42
  },
18
43
  "types": "dist/types/src/index.d.ts",
44
+ "typesVersions": {
45
+ "*": {
46
+ "Type": [
47
+ "dist/types/src/Type.d.ts"
48
+ ],
49
+ "Obj": [
50
+ "dist/types/src/Obj.d.ts"
51
+ ],
52
+ "Relation": [
53
+ "dist/types/src/Relation.d.ts"
54
+ ],
55
+ "Ref": [
56
+ "dist/types/src/Ref.d.ts"
57
+ ],
58
+ "testing": [
59
+ "dist/types/src/testing/index.d.ts"
60
+ ]
61
+ }
62
+ },
19
63
  "files": [
20
64
  "dist",
21
65
  "src"
@@ -23,17 +67,17 @@
23
67
  "dependencies": {
24
68
  "@preact/signals-core": "^1.9.0",
25
69
  "effect": "3.14.21",
26
- "@dxos/debug": "0.8.3-main.672df60",
27
- "@dxos/echo-protocol": "0.8.3-main.672df60",
28
- "@dxos/echo-schema": "0.8.3-main.672df60",
29
- "@dxos/echo-signals": "0.8.3-main.672df60",
30
- "@dxos/effect": "0.8.3-main.672df60",
31
- "@dxos/invariant": "0.8.3-main.672df60",
32
- "@dxos/keys": "0.8.3-main.672df60",
33
- "@dxos/live-object": "0.8.3-main.672df60",
34
- "@dxos/node-std": "0.8.3-main.672df60",
35
- "@dxos/log": "0.8.3-main.672df60",
36
- "@dxos/util": "0.8.3-main.672df60"
70
+ "@dxos/debug": "0.8.3-staging.0fa589b",
71
+ "@dxos/echo-protocol": "0.8.3-staging.0fa589b",
72
+ "@dxos/echo-schema": "0.8.3-staging.0fa589b",
73
+ "@dxos/effect": "0.8.3-staging.0fa589b",
74
+ "@dxos/echo-signals": "0.8.3-staging.0fa589b",
75
+ "@dxos/invariant": "0.8.3-staging.0fa589b",
76
+ "@dxos/keys": "0.8.3-staging.0fa589b",
77
+ "@dxos/live-object": "0.8.3-staging.0fa589b",
78
+ "@dxos/log": "0.8.3-staging.0fa589b",
79
+ "@dxos/node-std": "0.8.3-staging.0fa589b",
80
+ "@dxos/util": "0.8.3-staging.0fa589b"
37
81
  },
38
82
  "publishConfig": {
39
83
  "access": "public"
package/src/Key.ts ADDED
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export { SpaceId, ObjectId } from '@dxos/keys';
package/src/Obj.ts CHANGED
@@ -6,34 +6,80 @@ import { Schema } from 'effect';
6
6
 
7
7
  import * as EchoSchema from '@dxos/echo-schema';
8
8
  import { assertArgument, invariant } from '@dxos/invariant';
9
- import type { DXN } from '@dxos/keys';
10
- import * as LiveObject from '@dxos/live-object';
9
+ import { type DXN } from '@dxos/keys';
10
+ import type * as LiveObject from '@dxos/live-object';
11
+ import { live } from '@dxos/live-object';
12
+ import { assumeType } from '@dxos/util';
11
13
 
12
14
  import type * as Ref from './Ref';
15
+ import type * as Relation from './Relation';
13
16
  import type * as Type from './Type';
14
17
 
15
- export type Any = EchoSchema.AnyEchoObject;
18
+ // NOTE: Don't export: Obj.Any and Obj.Obj form the public API.
19
+ interface ObjBase extends Type.OfKind<EchoSchema.EntityKind.Object> {
20
+ readonly id: EchoSchema.ObjectId;
21
+ }
16
22
 
17
- export const make = LiveObject.live;
23
+ /**
24
+ * Object type with specific properties.
25
+ */
26
+ export type Obj<Props> = ObjBase & Props;
27
+
28
+ /**
29
+ * Base type for all ECHO objects.
30
+ */
31
+ export interface Any extends ObjBase {}
32
+
33
+ type MakeProps<T> = {
34
+ id?: EchoSchema.ObjectId;
35
+ } & Type.Properties<T>;
36
+
37
+ /**
38
+ * Creates new object.
39
+ */
40
+ // TODO(dmaretskyi): Move meta into props.
41
+ export const make = <S extends Type.Obj.Any>(
42
+ schema: S,
43
+ props: NoInfer<MakeProps<Schema.Schema.Type<S>>>,
44
+ meta?: EchoSchema.ObjectMeta,
45
+ ): LiveObject.Live<Schema.Schema.Type<S>> => {
46
+ assertArgument(
47
+ EchoSchema.getTypeAnnotation(schema)?.kind === EchoSchema.EntityKind.Object,
48
+ 'Expected an object schema',
49
+ );
50
+
51
+ if (props[EchoSchema.MetaId] != null) {
52
+ meta = props[EchoSchema.MetaId] as any;
53
+ delete props[EchoSchema.MetaId];
54
+ }
55
+
56
+ return live<Schema.Schema.Type<S>>(schema, props as any, meta);
57
+ };
18
58
 
19
- // TODO(dmaretskyi): Currently broken
20
59
  export const isObject = (obj: unknown): obj is Any => {
21
- return LiveObject.isLiveObject(obj);
60
+ assumeType<EchoSchema.InternalObjectProps>(obj);
61
+ return typeof obj === 'object' && obj !== null && obj[EchoSchema.EntityKindId] === EchoSchema.EntityKind.Object;
22
62
  };
23
63
 
24
64
  /**
25
- * Check that object or relation is an instance of a schema.
65
+ * Test if object or relation is an instance of a schema.
26
66
  * @example
27
67
  * ```ts
28
- * const person = Obj.make(Person, { name: 'John' });
68
+ * const john = Obj.make(Person, { name: 'John' });
69
+ * const johnIsPerson = Obj.instanceOf(Person)(john);
70
+ *
29
71
  * const isPerson = Obj.instanceOf(Person);
30
- * isPerson(person); // true
72
+ * if(isPerson(john)) {
73
+ * // john is Person
74
+ * }
31
75
  * ```
32
76
  */
33
77
  export const instanceOf: {
34
- <S extends Type.Relation.Any | Type.Obj.Any>(schema: S): (value: unknown) => value is S;
35
- <S extends Type.Relation.Any | Type.Obj.Any>(schema: S, value: unknown): value is S;
36
- } = ((...args: any[]) => {
78
+ <S extends Type.Relation.Any | Type.Obj.Any>(schema: S): (value: unknown) => value is Schema.Schema.Type<S>;
79
+ <S extends Type.Relation.Any | Type.Obj.Any>(schema: S, value: unknown): value is Schema.Schema.Type<S>;
80
+ } = ((
81
+ ...args: [schema: Type.Relation.Any | Type.Obj.Any, value: unknown] | [schema: Type.Relation.Any | Type.Obj.Any]
82
+ ) => {
37
83
  if (args.length === 1) {
38
84
  return (obj: unknown) => EchoSchema.isInstanceOf(args[0], obj);
39
85
  }
@@ -55,12 +101,8 @@ export const getDXN = (obj: Any): DXN => {
55
101
  * @returns The DXN of the object's type.
56
102
  * @example dxn:example.com/type/Contact:1.0.0
57
103
  */
58
- // TODO(dmaretskyi): Allow returning undefined.
59
- export const getSchemaDXN = (obj: Any): DXN => {
60
- const type = EchoSchema.getType(obj);
61
- invariant(type != null, 'Invalid object.');
62
- return type;
63
- };
104
+ // TODO(burdon): Expando does not have a type.
105
+ export const getTypeDXN = EchoSchema.getType;
64
106
 
65
107
  /**
66
108
  * @returns The typename of the object's type.
@@ -70,7 +112,7 @@ export const getTypename = (obj: Any): string | undefined => {
70
112
  const schema = getSchema(obj);
71
113
  if (schema == null) {
72
114
  // Try to extract typename from DXN.
73
- return getSchemaDXN(obj)?.asTypeDXN()?.type;
115
+ return EchoSchema.getType(obj)?.asTypeDXN()?.type;
74
116
  }
75
117
 
76
118
  return EchoSchema.getSchemaTypename(schema);
@@ -103,18 +145,17 @@ export const getLabel = (obj: Any): string | undefined => {
103
145
  export type JSON = EchoSchema.ObjectJSON;
104
146
 
105
147
  /**
106
- * Converts object to it's JSON representation.
148
+ * Converts object to its JSON representation.
107
149
  *
108
150
  * The same algorithm is used when calling the standard `JSON.stringify(obj)` function.
109
151
  */
110
- export const toJSON = (obj: Any): JSON => EchoSchema.objectToJSON(obj);
152
+ export const toJSON = (obj: Any | Relation.Any): JSON => EchoSchema.objectToJSON(obj);
111
153
 
112
154
  /**
113
- * Creates an object from it's json representation.
114
- * Performs schema validation.
115
- * References and schema will be resolvable if the `refResolver` is provided.
155
+ * Creates an object from its json representation, performing schema validation.
156
+ * References and schemas will be resolvable if the `refResolver` is provided.
116
157
  *
117
158
  * The function need to be async to support resolving the schema as well as the relation endpoints.
118
159
  */
119
160
  export const fromJSON: (json: unknown, options?: { refResolver?: Ref.Resolver }) => Promise<Any> =
120
- EchoSchema.objectFromJSON;
161
+ EchoSchema.objectFromJSON as any;
package/src/Ref.ts CHANGED
@@ -6,14 +6,10 @@ import * as EchoSchema from '@dxos/echo-schema';
6
6
 
7
7
  import type * as Obj from './Obj';
8
8
 
9
+ export type Ref<T> = EchoSchema.Ref<T>;
9
10
  export type Any = EchoSchema.Ref<Obj.Any>;
10
11
 
11
- export const make = EchoSchema.Ref.make;
12
-
13
- export const isRef: (value: unknown) => value is Any = EchoSchema.Ref.isRef;
14
-
15
- // TODO(dmaretskyi): Consider just allowing `make` to accept DXN.
16
- export const fromDXN = EchoSchema.Ref.fromDXN;
12
+ export const Array = EchoSchema.RefArray;
17
13
 
18
14
  /**
19
15
  * Extract reference target.
@@ -24,3 +20,10 @@ export type Target<R extends Any> = R extends EchoSchema.Ref<infer T> ? T : neve
24
20
  * Reference resolver.
25
21
  */
26
22
  export type Resolver = EchoSchema.RefResolver;
23
+
24
+ export const isRef: (value: unknown) => value is Any = EchoSchema.Ref.isRef;
25
+
26
+ export const make = EchoSchema.Ref.make;
27
+
28
+ // TODO(dmaretskyi): Consider just allowing `make` to accept DXN.
29
+ export const fromDXN = EchoSchema.Ref.fromDXN;
package/src/Relation.ts CHANGED
@@ -2,18 +2,78 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
+ import { type Schema } from 'effect';
6
+
7
+ import { raise } from '@dxos/debug';
5
8
  import * as EchoSchema from '@dxos/echo-schema';
6
9
  import { assertArgument, invariant } from '@dxos/invariant';
7
10
  import { DXN } from '@dxos/keys';
8
- import * as LiveObject from '@dxos/live-object';
11
+ import { type Live, live } from '@dxos/live-object';
9
12
  import { assumeType } from '@dxos/util';
10
13
 
11
- export type Any = EchoSchema.AnyEchoObject & EchoSchema.RelationSourceTargetRefs;
14
+ import type * as Obj from './Obj';
15
+ import type * as Type from './Type';
16
+
17
+ // NOTE: Don't export: Relation.Relation and Relation.Any form the public API.
18
+ interface RelationBase<Source, Target>
19
+ extends Type.Relation.Endpoints<Source, Target>,
20
+ Type.OfKind<EchoSchema.EntityKind.Relation> {
21
+ readonly id: EchoSchema.ObjectId;
22
+ }
23
+
24
+ /**
25
+ * Relation type with specific properties.
26
+ */
27
+ export type Relation<Source extends Obj.Any, Target extends Obj.Any, Props> = RelationBase<Source, Target> & Props;
28
+
29
+ /**
30
+ * Base type for all ECHO relations.
31
+ */
32
+ export interface Any extends RelationBase<Obj.Any, Obj.Any> {}
33
+
34
+ // TODO(dmaretskyi): Has to be `unique symbol`.
35
+ export const Source: unique symbol = EchoSchema.RelationSourceId as any;
36
+ export type Source = typeof Source;
37
+ export const Target: unique symbol = EchoSchema.RelationTargetId as any;
38
+ export type Target = typeof Target;
39
+
40
+ type MakeProps<T extends Any> = {
41
+ id?: EchoSchema.ObjectId;
42
+ [Source]: T[Source];
43
+ [Target]: T[Target];
44
+ } & Type.Properties<T>;
45
+
46
+ /**
47
+ * Creates new relation.
48
+ * @param schema - Relation schema.
49
+ * @param props - Relation properties. Endpoints are passed as [Relation.Source] and [Relation.Target] keys.
50
+ * @param meta - Relation metadata.
51
+ * @returns
52
+ */
53
+ // NOTE: Writing the definition this way (with generic over schema) makes typescript perfer to infer the type from the first param (this schema) rather than the second param (the props).
54
+ // TODO(dmaretskyi): Move meta into props.
55
+ export const make = <S extends Type.Relation.Any>(
56
+ schema: S,
57
+ props: NoInfer<MakeProps<Schema.Schema.Type<S>>>,
58
+ meta?: EchoSchema.ObjectMeta,
59
+ ): Live<Schema.Schema.Type<S> & Type.OfKind<EchoSchema.EntityKind.Relation>> => {
60
+ assertArgument(
61
+ EchoSchema.getTypeAnnotation(schema)?.kind === EchoSchema.EntityKind.Relation,
62
+ 'Expected a relation schema',
63
+ );
64
+
65
+ if (props[EchoSchema.MetaId] != null) {
66
+ meta = props[EchoSchema.MetaId] as any;
67
+ delete props[EchoSchema.MetaId];
68
+ }
12
69
 
13
- export const Source = EchoSchema.RelationSourceId;
14
- export const Target = EchoSchema.RelationTargetId;
70
+ const sourceDXN = EchoSchema.getObjectDXN(props[Source]) ?? raise(new Error('Unresolved relation source'));
71
+ const targetDXN = EchoSchema.getObjectDXN(props[Target]) ?? raise(new Error('Unresolved relation target'));
72
+ (props as any)[EchoSchema.RelationSourceDXNId] = sourceDXN;
73
+ (props as any)[EchoSchema.RelationTargetDXNId] = targetDXN;
15
74
 
16
- export const make = LiveObject.live;
75
+ return live<Schema.Schema.Type<S>>(schema, props as any, meta);
76
+ };
17
77
 
18
78
  export const isRelation = (value: unknown): value is Any => {
19
79
  if (typeof value !== 'object' || value === null) {
@@ -34,7 +94,7 @@ export const isRelation = (value: unknown): value is Any => {
34
94
  export const getSourceDXN = (value: Any): DXN => {
35
95
  assertArgument(isRelation(value), 'Expected a relation');
36
96
  assumeType<EchoSchema.InternalObjectProps>(value);
37
- const dxn = value[EchoSchema.RelationSourceDXNId];
97
+ const dxn = (value as EchoSchema.InternalObjectProps)[EchoSchema.RelationSourceDXNId];
38
98
  invariant(dxn instanceof DXN);
39
99
  return dxn;
40
100
  };
@@ -46,7 +106,7 @@ export const getSourceDXN = (value: Any): DXN => {
46
106
  export const getTargetDXN = (value: Any): DXN => {
47
107
  assertArgument(isRelation(value), 'Expected a relation');
48
108
  assumeType<EchoSchema.InternalObjectProps>(value);
49
- const dxn = value[EchoSchema.RelationTargetDXNId];
109
+ const dxn = (value as EchoSchema.InternalObjectProps)[EchoSchema.RelationTargetDXNId];
50
110
  invariant(dxn instanceof DXN);
51
111
  return dxn;
52
112
  };
@@ -55,20 +115,22 @@ export const getTargetDXN = (value: Any): DXN => {
55
115
  * @returns Relation source.
56
116
  * @throws If the object is not a relation.
57
117
  */
58
- export const getSource = <T extends Any>(relation: T): EchoSchema.RelationSource<T> => {
118
+ export const getSource = <T extends Any>(relation: T): Type.Relation.Source<T> => {
59
119
  assertArgument(isRelation(relation), 'Expected a relation');
60
- const obj = relation[EchoSchema.RelationSourceId];
120
+ assumeType<EchoSchema.InternalObjectProps>(relation);
121
+ const obj = (relation as EchoSchema.InternalObjectProps)[EchoSchema.RelationSourceId];
61
122
  invariant(obj !== undefined, `Invalid source: ${relation.id}`);
62
- return obj;
123
+ return obj as Type.Relation.Source<T>;
63
124
  };
64
125
 
65
126
  /**
66
127
  * @returns Relation target.
67
128
  * @throws If the object is not a relation.
68
129
  */
69
- export const getTarget = <T extends Any>(relation: T): EchoSchema.RelationTarget<T> => {
130
+ export const getTarget = <T extends Any>(relation: T): Type.Relation.Target<T> => {
70
131
  assertArgument(isRelation(relation), 'Expected a relation');
71
- const obj = relation[EchoSchema.RelationTargetId];
132
+ assumeType<EchoSchema.InternalObjectProps>(relation);
133
+ const obj = (relation as EchoSchema.InternalObjectProps)[EchoSchema.RelationTargetId];
72
134
  invariant(obj !== undefined, `Invalid target: ${relation.id}`);
73
- return obj;
135
+ return obj as Type.Relation.Target<T>;
74
136
  };