@dxos/echo 0.9.0 → 0.9.1-staging.ee54ba693a
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/neutral/Annotation.mjs +30 -8
- package/dist/lib/neutral/Database.mjs +7 -3
- package/dist/lib/neutral/Entity.mjs +15 -11
- package/dist/lib/neutral/Err.mjs +7 -3
- package/dist/lib/neutral/Feed.mjs +15 -19
- package/dist/lib/neutral/Filter.mjs +13 -11
- package/dist/lib/neutral/Format.mjs +3 -7
- package/dist/lib/neutral/JsonSchema.mjs +7 -7
- package/dist/lib/neutral/Migration.mjs +11 -11
- package/dist/lib/neutral/Obj.mjs +19 -13
- package/dist/lib/neutral/Order.mjs +1 -1
- package/dist/lib/neutral/Query.mjs +17 -17
- package/dist/lib/neutral/QueryResult.mjs +1 -1
- package/dist/lib/neutral/Ref.mjs +6 -6
- package/dist/lib/neutral/Registry.mjs +1 -1
- package/dist/lib/neutral/Relation.mjs +12 -12
- package/dist/lib/neutral/Tag.mjs +15 -15
- package/dist/lib/neutral/Text.mjs +25 -0
- package/dist/lib/neutral/Text.mjs.map +7 -0
- package/dist/lib/neutral/Type.mjs +10 -10
- package/dist/lib/neutral/chunk-2HKGAQM4.mjs +1394 -0
- package/dist/lib/neutral/chunk-2HKGAQM4.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-O6BH7EPN.mjs → chunk-2ZHECXUR.mjs} +5 -4
- package/dist/lib/neutral/chunk-2ZHECXUR.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-QQIYS74I.mjs → chunk-3KTE2Y7Q.mjs} +20 -20
- package/dist/lib/neutral/chunk-3KTE2Y7Q.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-R5W6DXR4.mjs → chunk-3TN2VMI4.mjs} +72 -36
- package/dist/lib/neutral/chunk-3TN2VMI4.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-5SMDBFVB.mjs → chunk-42L4HJ3Z.mjs} +11 -15
- package/dist/lib/neutral/chunk-42L4HJ3Z.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-7PI7C4EF.mjs → chunk-4A2NWKRJ.mjs} +5 -5
- package/dist/lib/neutral/chunk-ACFAOTKJ.mjs +1531 -0
- package/dist/lib/neutral/chunk-ACFAOTKJ.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-SBVFRTST.mjs → chunk-BEFR7LEA.mjs} +20 -10
- package/dist/lib/neutral/chunk-BEFR7LEA.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-6M2Z6WBH.mjs → chunk-BXIKXT7Q.mjs} +2 -6
- package/dist/lib/neutral/{chunk-6M2Z6WBH.mjs.map → chunk-BXIKXT7Q.mjs.map} +1 -1
- package/dist/lib/neutral/{chunk-UUP46KUQ.mjs → chunk-C57XMMBY.mjs} +21 -20
- package/dist/lib/neutral/chunk-C57XMMBY.mjs.map +7 -0
- package/dist/lib/neutral/chunk-C62JYFAD.mjs +183 -0
- package/dist/lib/neutral/chunk-C62JYFAD.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-MZ7K3MLL.mjs → chunk-FSGX2R62.mjs} +3 -3
- package/dist/lib/neutral/chunk-FSGX2R62.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-TYGKCRMK.mjs → chunk-HBXT3GSB.mjs} +37 -15
- package/dist/lib/neutral/chunk-HBXT3GSB.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-WU3GIANS.mjs → chunk-IQR45HAD.mjs} +1 -1
- package/dist/lib/neutral/chunk-IQR45HAD.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-TFEWTY5A.mjs → chunk-KCBW4BR6.mjs} +4 -8
- package/dist/lib/neutral/chunk-KCBW4BR6.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-WTQJHC75.mjs → chunk-L3E5IXAE.mjs} +225 -45
- package/dist/lib/neutral/chunk-L3E5IXAE.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-EVK6XBXO.mjs → chunk-PRC4FEKJ.mjs} +2 -2
- package/dist/lib/neutral/chunk-PRC4FEKJ.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-6YDI3J37.mjs → chunk-SCT6FFCR.mjs} +9 -4
- package/dist/lib/neutral/chunk-SCT6FFCR.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-BBFJWWAV.mjs → chunk-TOCVVFK2.mjs} +26 -2
- package/dist/lib/neutral/chunk-TOCVVFK2.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-RIVWNMSF.mjs → chunk-U53LSQQA.mjs} +3 -3
- package/dist/lib/neutral/{chunk-RIVWNMSF.mjs.map → chunk-U53LSQQA.mjs.map} +1 -1
- package/dist/lib/neutral/{chunk-IGK6FN65.mjs → chunk-UGFFV2FR.mjs} +2 -2
- package/dist/lib/neutral/{chunk-35INCYOE.mjs → chunk-UWLP4RFM.mjs} +1 -1
- package/dist/lib/neutral/chunk-UWLP4RFM.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-LWXVKPPW.mjs → chunk-UZHCQ6NI.mjs} +13 -11
- package/dist/lib/neutral/{chunk-LWXVKPPW.mjs.map → chunk-UZHCQ6NI.mjs.map} +3 -3
- package/dist/lib/neutral/{chunk-5SUJPHAE.mjs → chunk-WKIT2YY5.mjs} +2 -2
- package/dist/lib/neutral/{chunk-T6E37YIP.mjs → chunk-XB6ISRAO.mjs} +38 -18
- package/dist/lib/neutral/chunk-XB6ISRAO.mjs.map +7 -0
- package/dist/lib/neutral/chunk-XXL42WCB.mjs +84 -0
- package/dist/lib/neutral/chunk-XXL42WCB.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-ZGNNFYHS.mjs → chunk-XXWK4J53.mjs} +27 -42
- package/dist/lib/neutral/chunk-XXWK4J53.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +30 -26
- package/dist/lib/neutral/internal/index.mjs +111 -69
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/lib/neutral/testing/index.mjs +79 -60
- package/dist/lib/neutral/testing/index.mjs.map +3 -3
- package/dist/types/src/Annotation.d.ts +35 -1
- package/dist/types/src/Annotation.d.ts.map +1 -1
- package/dist/types/src/Collection.d.ts +9 -8
- package/dist/types/src/Collection.d.ts.map +1 -1
- package/dist/types/src/Database.d.ts +38 -0
- package/dist/types/src/Database.d.ts.map +1 -1
- package/dist/types/src/Dataset.d.ts +1 -16
- package/dist/types/src/Dataset.d.ts.map +1 -1
- package/dist/types/src/Entity.d.ts +2 -0
- package/dist/types/src/Entity.d.ts.map +1 -1
- package/dist/types/src/Err.d.ts +67 -0
- package/dist/types/src/Err.d.ts.map +1 -1
- package/dist/types/src/Feed.d.ts +59 -80
- package/dist/types/src/Feed.d.ts.map +1 -1
- package/dist/types/src/Filter.d.ts +9 -1
- package/dist/types/src/Filter.d.ts.map +1 -1
- package/dist/types/src/Hypergraph.d.ts +2 -9
- package/dist/types/src/Hypergraph.d.ts.map +1 -1
- package/dist/types/src/Migration.d.ts +1 -1
- package/dist/types/src/Migration.d.ts.map +1 -1
- package/dist/types/src/Obj.d.ts +33 -6
- package/dist/types/src/Obj.d.ts.map +1 -1
- package/dist/types/src/Order.d.ts +1 -1
- package/dist/types/src/Order.d.ts.map +1 -1
- package/dist/types/src/Query.d.ts +1 -1
- package/dist/types/src/Query.d.ts.map +1 -1
- package/dist/types/src/QueryResult.d.ts +2 -2
- package/dist/types/src/QueryResult.d.ts.map +1 -1
- package/dist/types/src/Ref.d.ts +10 -6
- package/dist/types/src/Ref.d.ts.map +1 -1
- package/dist/types/src/Registry.d.ts +1 -1
- package/dist/types/src/Relation.d.ts.map +1 -1
- package/dist/types/src/Tag.d.ts +11 -13
- package/dist/types/src/Tag.d.ts.map +1 -1
- package/dist/types/src/Text.d.ts +41 -0
- package/dist/types/src/Text.d.ts.map +1 -0
- package/dist/types/src/Text.test.d.ts +2 -0
- package/dist/types/src/Text.test.d.ts.map +1 -0
- package/dist/types/src/Type.d.ts +38 -21
- package/dist/types/src/Type.d.ts.map +1 -1
- package/dist/types/src/View.d.ts +8 -9
- package/dist/types/src/View.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/internal/Annotation/annotations.d.ts +50 -0
- package/dist/types/src/internal/Annotation/annotations.d.ts.map +1 -1
- package/dist/types/src/internal/Annotation/atoms.d.ts +14 -0
- package/dist/types/src/internal/Annotation/atoms.d.ts.map +1 -0
- package/dist/types/src/internal/Annotation/entity-dictionary.d.ts +13 -0
- package/dist/types/src/internal/Annotation/entity-dictionary.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/api.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/guard.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/model.d.ts +1 -1
- package/dist/types/src/internal/Entity/model.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/object.d.ts +5 -1
- package/dist/types/src/internal/Entity/object.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/relation.d.ts +7 -0
- package/dist/types/src/internal/Entity/relation.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/type-uri.d.ts.map +1 -1
- package/dist/types/src/internal/Filter/index.d.ts +2 -0
- package/dist/types/src/internal/Filter/index.d.ts.map +1 -0
- package/dist/types/src/internal/Filter/match.d.ts +22 -0
- package/dist/types/src/internal/Filter/match.d.ts.map +1 -0
- package/dist/types/src/internal/Format/format.d.ts +0 -2
- package/dist/types/src/internal/Format/format.d.ts.map +1 -1
- package/dist/types/src/internal/Format/index.d.ts +2 -2
- package/dist/types/src/internal/Format/index.d.ts.map +1 -1
- package/dist/types/src/internal/Format/string.d.ts +0 -8
- package/dist/types/src/internal/Format/string.d.ts.map +1 -1
- package/dist/types/src/internal/Format/types.d.ts +2 -1
- package/dist/types/src/internal/Format/types.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/atoms.d.ts +12 -3
- package/dist/types/src/internal/Obj/atoms.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/clone.d.ts +2 -1
- package/dist/types/src/internal/Obj/clone.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/create-object.d.ts +3 -4
- package/dist/types/src/internal/Obj/create-object.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/json-serializer.d.ts.map +1 -1
- package/dist/types/src/internal/Query/index.d.ts +1 -0
- package/dist/types/src/internal/Query/index.d.ts.map +1 -1
- package/dist/types/src/internal/Query/query-result-effect.d.ts +9 -0
- package/dist/types/src/internal/Query/query-result-effect.d.ts.map +1 -0
- package/dist/types/src/internal/Ref/atoms.d.ts +1 -2
- package/dist/types/src/internal/Ref/atoms.d.ts.map +1 -1
- package/dist/types/src/internal/Ref/index.d.ts +1 -0
- package/dist/types/src/internal/Ref/index.d.ts.map +1 -1
- package/dist/types/src/internal/Ref/ref.d.ts +79 -8
- package/dist/types/src/internal/Ref/ref.d.ts.map +1 -1
- package/dist/types/src/internal/Ref/strong-deps.d.ts +35 -0
- package/dist/types/src/internal/Ref/strong-deps.d.ts.map +1 -0
- package/dist/types/src/internal/common/atom-snapshot.d.ts +8 -0
- package/dist/types/src/internal/common/atom-snapshot.d.ts.map +1 -0
- package/dist/types/src/internal/common/proxy/make-object.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/proxy-types.d.ts +12 -0
- package/dist/types/src/internal/common/proxy/proxy-types.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/proxy-utils.d.ts +25 -0
- package/dist/types/src/internal/common/proxy/proxy-utils.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/schema-validator.d.ts +5 -0
- package/dist/types/src/internal/common/proxy/schema-validator.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/typed-handler.d.ts +17 -0
- package/dist/types/src/internal/common/proxy/typed-handler.d.ts.map +1 -1
- package/dist/types/src/internal/common/types/entity.d.ts +4 -4
- package/dist/types/src/internal/common/types/entity.d.ts.map +1 -1
- package/dist/types/src/internal/common/types/meta.d.ts +2 -4
- package/dist/types/src/internal/common/types/meta.d.ts.map +1 -1
- package/dist/types/src/internal/index.d.ts +1 -0
- package/dist/types/src/internal/index.d.ts.map +1 -1
- package/dist/types/src/testing/test-schema.d.ts +42 -171
- package/dist/types/src/testing/test-schema.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +20 -13
- package/src/Annotation.test.ts +257 -10
- package/src/Annotation.ts +58 -4
- package/src/Collection.ts +10 -12
- package/src/Database.ts +55 -21
- package/src/Entity.ts +3 -1
- package/src/Err.ts +22 -0
- package/src/Feed.ts +117 -141
- package/src/Filter.test.ts +38 -0
- package/src/Filter.ts +17 -3
- package/src/Hypergraph.ts +2 -11
- package/src/Migration.ts +1 -1
- package/src/Obj.test.ts +92 -12
- package/src/Obj.ts +38 -5
- package/src/Order.ts +3 -3
- package/src/Query.test.ts +2 -2
- package/src/Query.ts +16 -16
- package/src/QueryResult.ts +2 -2
- package/src/Ref.ts +14 -6
- package/src/Registry.ts +1 -1
- package/src/Relation.ts +1 -1
- package/src/Tag.ts +13 -16
- package/src/Text.test.ts +186 -0
- package/src/Text.ts +116 -0
- package/src/Type.test.ts +52 -24
- package/src/Type.ts +85 -33
- package/src/View.ts +22 -30
- package/src/index.ts +1 -1
- package/src/internal/Annotation/annotations.ts +71 -2
- package/src/internal/Annotation/atoms.ts +107 -0
- package/src/internal/Annotation/entity-dictionary.ts +40 -37
- package/src/internal/Entity/api.ts +1 -1
- package/src/internal/Entity/guard.ts +2 -1
- package/src/internal/Entity/model.ts +2 -2
- package/src/internal/Entity/object.ts +14 -1
- package/src/internal/Entity/relation.ts +19 -0
- package/src/internal/Entity/type-uri.ts +3 -1
- package/src/internal/Filter/index.ts +5 -0
- package/src/internal/Filter/match.ts +284 -0
- package/src/internal/Format/format.ts +0 -2
- package/src/internal/Format/index.ts +2 -2
- package/src/internal/Format/string.ts +0 -12
- package/src/internal/Format/types.ts +4 -2
- package/src/internal/JsonSchema/json-schema.test.ts +82 -54
- package/src/internal/Obj/atoms.ts +62 -18
- package/src/internal/Obj/clone.ts +61 -20
- package/src/internal/Obj/create-object.test.ts +3 -3
- package/src/internal/Obj/create-object.ts +3 -4
- package/src/internal/Obj/inspect.ts +1 -1
- package/src/internal/Obj/json-serializer.test.ts +7 -5
- package/src/internal/Obj/json-serializer.ts +4 -4
- package/src/internal/Obj/schema-validator.test.ts +53 -0
- package/src/internal/Obj/set-value.test.ts +29 -19
- package/src/internal/Query/index.ts +1 -0
- package/src/internal/Query/query-result-effect.ts +35 -0
- package/src/internal/Ref/atoms.ts +23 -3
- package/src/internal/Ref/index.ts +1 -0
- package/src/internal/Ref/ref.test.ts +8 -8
- package/src/internal/Ref/ref.ts +122 -39
- package/src/internal/Ref/strong-deps.ts +107 -0
- package/src/internal/common/atom-snapshot.ts +19 -0
- package/src/internal/common/proxy/make-object.ts +1 -1
- package/src/internal/common/proxy/proxy-types.ts +20 -0
- package/src/internal/common/proxy/proxy-utils.ts +37 -1
- package/src/internal/common/proxy/reactive-array.ts +3 -3
- package/src/internal/common/proxy/reactive.test.ts +1 -1
- package/src/internal/common/proxy/schema-validator.ts +181 -8
- package/src/internal/common/proxy/typed-handler.ts +254 -76
- package/src/internal/common/types/entity.ts +15 -7
- package/src/internal/index.ts +1 -0
- package/src/testing/test-schema.ts +75 -90
- package/dist/lib/neutral/chunk-35INCYOE.mjs.map +0 -7
- package/dist/lib/neutral/chunk-3PBP4V4O.mjs +0 -101
- package/dist/lib/neutral/chunk-3PBP4V4O.mjs.map +0 -7
- package/dist/lib/neutral/chunk-4ZUHOTCG.mjs +0 -184
- package/dist/lib/neutral/chunk-4ZUHOTCG.mjs.map +0 -7
- package/dist/lib/neutral/chunk-5SMDBFVB.mjs.map +0 -7
- package/dist/lib/neutral/chunk-6YDI3J37.mjs.map +0 -7
- package/dist/lib/neutral/chunk-7FPIAJIV.mjs +0 -2279
- package/dist/lib/neutral/chunk-7FPIAJIV.mjs.map +0 -7
- package/dist/lib/neutral/chunk-BBFJWWAV.mjs.map +0 -7
- package/dist/lib/neutral/chunk-EVK6XBXO.mjs.map +0 -7
- package/dist/lib/neutral/chunk-MZ7K3MLL.mjs.map +0 -7
- package/dist/lib/neutral/chunk-O6BH7EPN.mjs.map +0 -7
- package/dist/lib/neutral/chunk-QQIYS74I.mjs.map +0 -7
- package/dist/lib/neutral/chunk-R5W6DXR4.mjs.map +0 -7
- package/dist/lib/neutral/chunk-SBVFRTST.mjs.map +0 -7
- package/dist/lib/neutral/chunk-T6E37YIP.mjs.map +0 -7
- package/dist/lib/neutral/chunk-TFEWTY5A.mjs.map +0 -7
- package/dist/lib/neutral/chunk-TYGKCRMK.mjs.map +0 -7
- package/dist/lib/neutral/chunk-UUP46KUQ.mjs.map +0 -7
- package/dist/lib/neutral/chunk-WTQJHC75.mjs.map +0 -7
- package/dist/lib/neutral/chunk-WU3GIANS.mjs.map +0 -7
- package/dist/lib/neutral/chunk-ZGNNFYHS.mjs.map +0 -7
- package/dist/types/src/exemplars.test.d.ts +0 -2
- package/dist/types/src/exemplars.test.d.ts.map +0 -1
- package/dist/types/src/hierarchy.test.d.ts +0 -2
- package/dist/types/src/hierarchy.test.d.ts.map +0 -1
- package/src/exemplars.test.ts +0 -21
- package/src/hierarchy.test.ts +0 -33
- /package/dist/lib/neutral/{chunk-7PI7C4EF.mjs.map → chunk-4A2NWKRJ.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-IGK6FN65.mjs.map → chunk-UGFFV2FR.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-5SUJPHAE.mjs.map → chunk-WKIT2YY5.mjs.map} +0 -0
|
@@ -14,10 +14,13 @@ import type * as Entity from '../../Entity';
|
|
|
14
14
|
import type * as Obj from '../../Obj';
|
|
15
15
|
import type * as Ref from '../../Ref';
|
|
16
16
|
import type * as Relation from '../../Relation';
|
|
17
|
+
import { getLabel } from '../Annotation';
|
|
18
|
+
import { snapshotForComparison } from '../common/atom-snapshot';
|
|
17
19
|
import { subscribe } from '../common/proxy/reactive';
|
|
18
|
-
import {
|
|
20
|
+
import { getDatabase, isEntity } from '../Entity';
|
|
19
21
|
import { RefTypeId } from '../Ref/ref';
|
|
20
22
|
import { loadRefTarget } from '../Ref/utils';
|
|
23
|
+
import { isDeleted } from './deleted';
|
|
21
24
|
import { getSnapshot } from './snapshot';
|
|
22
25
|
|
|
23
26
|
const isRef = (obj: unknown): obj is Ref.Ref<any> =>
|
|
@@ -62,8 +65,9 @@ const refFamily = Atom.family(<T extends Obj.Unknown>(ref: Ref.Ref<T>): Atom.Ato
|
|
|
62
65
|
const setupTargetSubscription = (target: T): Obj.Snapshot<T> => {
|
|
63
66
|
unsubscribeTarget?.();
|
|
64
67
|
unsubscribeTarget = subscribe(target, () => {
|
|
68
|
+
// Deleted objects resolve to undefined so callers don't need to inspect isDeleted.
|
|
65
69
|
// getSnapshot adds SnapshotKindId brand at runtime; cast bridges static types.
|
|
66
|
-
get.setSelf(getSnapshot(target) as unknown as Obj.Snapshot<T>);
|
|
70
|
+
get.setSelf(isDeleted(target) ? undefined : (getSnapshot(target) as unknown as Obj.Snapshot<T>));
|
|
67
71
|
});
|
|
68
72
|
return getSnapshot(target) as unknown as Obj.Snapshot<T>;
|
|
69
73
|
};
|
|
@@ -76,19 +80,6 @@ const refFamily = Atom.family(<T extends Obj.Unknown>(ref: Ref.Ref<T>): Atom.Ato
|
|
|
76
80
|
}).pipe(Atom.keepAlive);
|
|
77
81
|
});
|
|
78
82
|
|
|
79
|
-
/**
|
|
80
|
-
* Snapshot a value to create a new reference for comparison and React dependency tracking.
|
|
81
|
-
*/
|
|
82
|
-
const snapshotForComparison = <V>(value: V): V => {
|
|
83
|
-
if (Array.isArray(value)) {
|
|
84
|
-
return [...value] as V;
|
|
85
|
-
}
|
|
86
|
-
if (value !== null && typeof value === 'object') {
|
|
87
|
-
return { ...value } as V;
|
|
88
|
-
}
|
|
89
|
-
return value;
|
|
90
|
-
};
|
|
91
|
-
|
|
92
83
|
/**
|
|
93
84
|
* Atom family for ECHO object properties.
|
|
94
85
|
* Uses nested families: outer keyed by object, inner keyed by property key.
|
|
@@ -149,6 +140,20 @@ const refWithReactiveFamily = Atom.family(<T extends Obj.Unknown>(ref: Ref.Ref<T
|
|
|
149
140
|
);
|
|
150
141
|
});
|
|
151
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Atom family for a property of a ref's target object.
|
|
145
|
+
* Resolves the ref (reactively) then projects the property atom, so it fires when the ref resolves or when
|
|
146
|
+
* that property changes. Yields `undefined` while the target is unresolved.
|
|
147
|
+
*/
|
|
148
|
+
const refPropertyFamily = Atom.family(<T extends Obj.Unknown>(ref: Ref.Ref<T>) =>
|
|
149
|
+
Atom.family(<K extends keyof T>(key: K): Atom.Atom<T[K] | undefined> => {
|
|
150
|
+
return Atom.make<T[K] | undefined>((get) => {
|
|
151
|
+
const target = get(refWithReactiveFamily(ref));
|
|
152
|
+
return target ? get(propertyFamily(target)(key)) : undefined;
|
|
153
|
+
}).pipe(Atom.keepAlive);
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
156
|
+
|
|
152
157
|
/**
|
|
153
158
|
* Atom family for any ECHO entity (obj or relation) — returns a snapshot.
|
|
154
159
|
*/
|
|
@@ -200,10 +205,19 @@ export const makeAtom: {
|
|
|
200
205
|
};
|
|
201
206
|
|
|
202
207
|
/**
|
|
203
|
-
* Create a read-only atom for a specific property of a reactive object.
|
|
204
|
-
* Only fires updates when the property value actually changes.
|
|
208
|
+
* Create a read-only atom for a specific property of a reactive object, or of a ref's target.
|
|
209
|
+
* Only fires updates when the property value actually changes. Given a ref, the atom resolves the target
|
|
210
|
+
* first and yields `undefined` until it loads.
|
|
205
211
|
*/
|
|
206
|
-
export const makeProperty
|
|
212
|
+
export const makeProperty: {
|
|
213
|
+
<T extends Obj.Unknown, K extends keyof T>(obj: T, key: K): Atom.Atom<T[K]>;
|
|
214
|
+
<T extends Obj.Unknown, K extends keyof T>(ref: Ref.Ref<T>, key: K): Atom.Atom<T[K] | undefined>;
|
|
215
|
+
} = (objOrRef: Obj.Unknown | Ref.Ref<any>, key: any): Atom.Atom<any> => {
|
|
216
|
+
if (isRef(objOrRef)) {
|
|
217
|
+
return refPropertyFamily(objOrRef as Ref.Ref<any>)(key);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const obj = objOrRef as Obj.Unknown;
|
|
207
221
|
assertArgument(isEntity(obj), 'obj', 'Object must be a reactive object');
|
|
208
222
|
return propertyFamily(obj)(key);
|
|
209
223
|
};
|
|
@@ -242,3 +256,33 @@ export const makeRelation = <T extends Relation.Unknown>(relation: T): Atom.Atom
|
|
|
242
256
|
assertArgument(isEntity(relation), 'relation', 'Must be a reactive ECHO relation');
|
|
243
257
|
return relationFamily(relation);
|
|
244
258
|
};
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Atom family for an entity's label string.
|
|
262
|
+
* Fires only when the computed label actually changes, not on every entity mutation.
|
|
263
|
+
*/
|
|
264
|
+
const labelAtomFamily = Atom.family(<T extends Entity.Unknown>(entity: T): Atom.Atom<string | undefined> => {
|
|
265
|
+
return Atom.make<string | undefined>((get) => {
|
|
266
|
+
let previous = getLabel(entity);
|
|
267
|
+
|
|
268
|
+
const unsubscribe = subscribe(entity, () => {
|
|
269
|
+
const next = getLabel(entity);
|
|
270
|
+
if (next !== previous) {
|
|
271
|
+
previous = next;
|
|
272
|
+
get.setSelf(next);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
get.addFinalizer(() => unsubscribe());
|
|
277
|
+
return previous;
|
|
278
|
+
}).pipe(Atom.keepAlive);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Create a read-only atom for the label of a reactive ECHO entity.
|
|
283
|
+
* Re-evaluates on entity mutation; only propagates when the label string changes.
|
|
284
|
+
*/
|
|
285
|
+
export const makeLabelAtom = <T extends Entity.Unknown>(entity: T): Atom.Atom<string | undefined> => {
|
|
286
|
+
assertArgument(isEntity(entity), 'entity', 'Must be a reactive ECHO entity');
|
|
287
|
+
return labelAtomFamily(entity);
|
|
288
|
+
};
|
|
@@ -3,20 +3,42 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { invariant } from '@dxos/invariant';
|
|
6
|
-
import { deepMapValues } from '@dxos/util';
|
|
6
|
+
import { assumeType, deepMapValues } from '@dxos/util';
|
|
7
7
|
|
|
8
8
|
import type * as Obj from '../../Obj';
|
|
9
9
|
import { makeObject } from '../common/proxy';
|
|
10
|
-
import { getSchema, getStaticTypeSchema, getType } from '../common/types';
|
|
10
|
+
import { ParentId, getSchema, getStaticTypeSchema, getType } from '../common/types';
|
|
11
11
|
import { getMeta } from '../common/types/meta';
|
|
12
|
+
import { type InternalObjectProps } from '../Entity/model';
|
|
12
13
|
import { Ref } from '../Ref';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Clones an object or relation.
|
|
16
|
-
*
|
|
17
|
+
* By default this does not clone referenced objects, only the properties in the object; pass `opts.deep`
|
|
18
|
+
* to recurse into refs (see {@link Obj.CloneOptions}).
|
|
17
19
|
* @returns A new object with the same schema and properties.
|
|
18
20
|
*/
|
|
19
|
-
export const clone = <T extends Obj.Any>(obj: T, opts?: Obj.CloneOptions): T =>
|
|
21
|
+
export const clone = <T extends Obj.Any>(obj: T, opts?: Obj.CloneOptions): T =>
|
|
22
|
+
cloneInner(obj, opts, obj, new Map(), new Set());
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Recursive clone worker threaded with two cycle-guard structures:
|
|
26
|
+
* - `cache`: id → already-finished clone; reused when the same object appears more than once in a DAG.
|
|
27
|
+
* - `inProgress`: ids of objects whose clone is currently being computed; a ref back into this set means
|
|
28
|
+
* a true cycle — the original ref is kept rather than recursing forever.
|
|
29
|
+
*/
|
|
30
|
+
const cloneInner = <T extends Obj.Any>(
|
|
31
|
+
obj: T,
|
|
32
|
+
opts: Obj.CloneOptions | undefined,
|
|
33
|
+
root: Obj.Any,
|
|
34
|
+
cache: Map<string, Obj.Any>,
|
|
35
|
+
inProgress: Set<string>,
|
|
36
|
+
): T => {
|
|
37
|
+
if (cache.has(obj.id)) {
|
|
38
|
+
return cache.get(obj.id) as T;
|
|
39
|
+
}
|
|
40
|
+
inProgress.add(obj.id);
|
|
41
|
+
|
|
20
42
|
const { id, ...data } = obj;
|
|
21
43
|
// Prefer cloning through the type entity so the cloned instance preserves
|
|
22
44
|
// `Obj.getType` identity. Falls back to the raw schema for older instances
|
|
@@ -24,30 +46,49 @@ export const clone = <T extends Obj.Any>(obj: T, opts?: Obj.CloneOptions): T =>
|
|
|
24
46
|
const typeEntity = getType(obj);
|
|
25
47
|
const schema = typeEntity != null ? getStaticTypeSchema(typeEntity) : getSchema(obj);
|
|
26
48
|
invariant(schema != null, 'Object should have a type or schema');
|
|
27
|
-
|
|
49
|
+
|
|
50
|
+
const mapRef = (value: unknown, recurse: (value: unknown) => unknown): unknown => {
|
|
28
51
|
if (Ref.isRef(value)) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
52
|
+
const target = value.target;
|
|
53
|
+
// A ref back to an in-progress object means a cycle — keep the original ref to break it.
|
|
54
|
+
if (
|
|
55
|
+
target != null &&
|
|
56
|
+
!inProgress.has(target.id) &&
|
|
57
|
+
(opts?.deep === 'all' || (opts?.deep === 'parent' && isChildOf(target, root)))
|
|
58
|
+
) {
|
|
59
|
+
return Ref.make(cloneInner(target, opts, root, cache, inProgress));
|
|
32
60
|
}
|
|
33
61
|
return value;
|
|
34
62
|
}
|
|
35
63
|
return recurse(value);
|
|
36
|
-
}
|
|
64
|
+
};
|
|
37
65
|
|
|
66
|
+
const props: any = deepMapValues(data, mapRef);
|
|
38
67
|
if (opts?.retainId) {
|
|
39
68
|
props.id = id;
|
|
40
69
|
}
|
|
41
|
-
const meta = deepMapValues(getMeta(obj),
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
return recurse(value);
|
|
50
|
-
});
|
|
70
|
+
const meta = deepMapValues(getMeta(obj), mapRef);
|
|
71
|
+
const result = makeObject(schema, props, meta, typeEntity as object | undefined);
|
|
72
|
+
|
|
73
|
+
inProgress.delete(obj.id);
|
|
74
|
+
cache.set(obj.id, result);
|
|
75
|
+
return result;
|
|
76
|
+
};
|
|
51
77
|
|
|
52
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Whether `entity`'s parent chain reaches `root` — i.e. it is an owned descendant that cascade-deletes with
|
|
80
|
+
* the root. Walks the `ParentId` back-links (a finite tree); guarded against cycles defensively.
|
|
81
|
+
*/
|
|
82
|
+
const isChildOf = (entity: unknown, root: Obj.Any): boolean => {
|
|
83
|
+
const seen = new Set<InternalObjectProps>();
|
|
84
|
+
assumeType<InternalObjectProps>(entity);
|
|
85
|
+
let parent = entity[ParentId];
|
|
86
|
+
while (parent != null && !seen.has(parent)) {
|
|
87
|
+
if (parent.id === root.id) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
seen.add(parent);
|
|
91
|
+
parent = parent[ParentId];
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
53
94
|
};
|
|
@@ -54,13 +54,13 @@ describe('create (static version)', () => {
|
|
|
54
54
|
|
|
55
55
|
const json = JSON.parse(JSON.stringify(contact));
|
|
56
56
|
expect(json).toEqual({
|
|
57
|
-
id: contact.id,
|
|
57
|
+
'id': contact.id,
|
|
58
58
|
'@type': DXN.make(Type.getTypename(TestSchema.Person), Type.getVersion(TestSchema.Person)),
|
|
59
59
|
'@meta': {
|
|
60
60
|
keys: [],
|
|
61
61
|
},
|
|
62
|
-
name: 'Bot',
|
|
63
|
-
email: 'bot@example.com',
|
|
62
|
+
'name': 'Bot',
|
|
63
|
+
'email': 'bot@example.com',
|
|
64
64
|
});
|
|
65
65
|
expect(objectToJSON(contact)).toStrictEqual(json);
|
|
66
66
|
});
|
|
@@ -55,10 +55,9 @@ export type CreateObjectProps<T> = (T extends { id: string }
|
|
|
55
55
|
*
|
|
56
56
|
* @example
|
|
57
57
|
* ```ts
|
|
58
|
-
*
|
|
59
|
-
* name: Schema.String,
|
|
60
|
-
*
|
|
61
|
-
* }).pipe(Type.makeObject(DXN.make('com.example.type.person', '0.1.0')))
|
|
58
|
+
* class Contact extends Type.makeObject<Contact>(DXN.make('com.example.type.person', '0.1.0'))(
|
|
59
|
+
* Schema.Struct({ name: Schema.String, email: Schema.String }),
|
|
60
|
+
* ) {}
|
|
62
61
|
*
|
|
63
62
|
* const contact = createObject(Contact, {
|
|
64
63
|
* name: "John",
|
|
@@ -7,7 +7,7 @@ import type { InspectOptionsStylized, inspect as inspectFn } from 'node:util';
|
|
|
7
7
|
import { type CustomInspectFunction, inspectCustom } from '@dxos/debug';
|
|
8
8
|
|
|
9
9
|
import { getTypeURI } from '../Annotation';
|
|
10
|
-
import {
|
|
10
|
+
import { type AnyEntity, ATTR_TYPE } from '../common/types';
|
|
11
11
|
import { ATTR_META } from '../common/types/meta';
|
|
12
12
|
import { MetaId } from '../common/types/model-symbols';
|
|
13
13
|
|
|
@@ -10,7 +10,7 @@ import { DXN, EID } from '@dxos/keys';
|
|
|
10
10
|
import * as Obj from '../../Obj';
|
|
11
11
|
import { TestSchema } from '../../testing';
|
|
12
12
|
import * as Type from '../../Type';
|
|
13
|
-
import {
|
|
13
|
+
import { getTypename, getTypeURI } from '../Annotation';
|
|
14
14
|
import { getMetaChecked } from '../common/api';
|
|
15
15
|
import { ATTR_TYPE, EntityKind, KindId, TypeId, getSchema } from '../common/types';
|
|
16
16
|
import { MetaId } from '../common/types/model-symbols';
|
|
@@ -160,10 +160,12 @@ describe('Object JSON serializer', () => {
|
|
|
160
160
|
});
|
|
161
161
|
|
|
162
162
|
describe('Uint8Array', () => {
|
|
163
|
-
const Blob =
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
163
|
+
const Blob = Type.makeObject(DXN.make('com.example.type.blob', '0.1.0'))(
|
|
164
|
+
Schema.Struct({
|
|
165
|
+
name: Schema.String,
|
|
166
|
+
bytes: Schema.Uint8ArrayFromSelf,
|
|
167
|
+
}),
|
|
168
|
+
);
|
|
167
169
|
type Blob = Type.InstanceType<typeof Blob>;
|
|
168
170
|
|
|
169
171
|
test('round-trips Uint8Array field through JSON with schema', async ({ expect }) => {
|
|
@@ -15,9 +15,9 @@ import type * as Obj from '../../Obj';
|
|
|
15
15
|
import { getTypeAnnotation, getTypeURI, setTypename } from '../Annotation';
|
|
16
16
|
import { attachTypedJsonSerializer, defineHiddenProperty, typedJsonSerializer } from '../common/proxy';
|
|
17
17
|
import {
|
|
18
|
+
type AnyEntity,
|
|
18
19
|
ATTR_PARENT,
|
|
19
20
|
ATTR_TYPE,
|
|
20
|
-
type AnyEntity,
|
|
21
21
|
EntityKind,
|
|
22
22
|
KindId,
|
|
23
23
|
ParentId,
|
|
@@ -129,8 +129,8 @@ export const objectFromJSON = async (
|
|
|
129
129
|
const sourceDxn = jsonData[ATTR_RELATION_SOURCE] ?? raise(new TypeError('Missing relation source'));
|
|
130
130
|
const targetDxn = jsonData[ATTR_RELATION_TARGET] ?? raise(new TypeError('Missing relation target'));
|
|
131
131
|
|
|
132
|
-
const source = (await refResolver?.
|
|
133
|
-
const target = (await refResolver?.
|
|
132
|
+
const source = (await refResolver?.resolveLegacy(sourceDxn)) as AnyEntity | undefined;
|
|
133
|
+
const target = (await refResolver?.resolveLegacy(targetDxn)) as AnyEntity | undefined;
|
|
134
134
|
|
|
135
135
|
defineHiddenProperty(obj, KindId, EntityKind.Relation);
|
|
136
136
|
defineHiddenProperty(obj, RelationSourceDXNId, sourceDxn);
|
|
@@ -161,7 +161,7 @@ export const objectFromJSON = async (
|
|
|
161
161
|
|
|
162
162
|
if (jsonData[ATTR_PARENT]) {
|
|
163
163
|
const parentDxn = jsonData[ATTR_PARENT];
|
|
164
|
-
const resolvedParent = (await refResolver?.
|
|
164
|
+
const resolvedParent = (await refResolver?.resolveLegacy(parentDxn)) as Obj.Unknown | undefined;
|
|
165
165
|
defineHiddenProperty(obj, ParentId, resolvedParent);
|
|
166
166
|
} else if (parent) {
|
|
167
167
|
defineHiddenProperty(obj, ParentId, parent);
|
|
@@ -129,6 +129,19 @@ describe('schema-validator', () => {
|
|
|
129
129
|
valueToAssign: { any: 'value' },
|
|
130
130
|
});
|
|
131
131
|
});
|
|
132
|
+
|
|
133
|
+
test('walking into an Unknown-typed value does not throw', ({ expect }) => {
|
|
134
|
+
// Mirrors the annotation Dictionary (Record<string, Unknown>): nested keys/indices under an
|
|
135
|
+
// Unknown slot are all valid, so the validator must not reject while walking them.
|
|
136
|
+
const schema = Schema.Struct({ dict: Schema.Record({ key: Schema.String, value: Schema.Unknown }) });
|
|
137
|
+
for (const path of [
|
|
138
|
+
['dict', 'someType'],
|
|
139
|
+
['dict', 'someType', 'nested'],
|
|
140
|
+
['dict', 'someType', '0'],
|
|
141
|
+
]) {
|
|
142
|
+
SchemaValidator.getPropertySchema(schema, path);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
132
145
|
test('index signatures', () => {
|
|
133
146
|
for (const value of [42, '42']) {
|
|
134
147
|
validateValueToAssign({
|
|
@@ -179,4 +192,44 @@ describe('schema-validator', () => {
|
|
|
179
192
|
}
|
|
180
193
|
});
|
|
181
194
|
});
|
|
195
|
+
|
|
196
|
+
describe('assertExactProperties', () => {
|
|
197
|
+
test('rejects extra properties on a closed struct', () => {
|
|
198
|
+
const Person = Schema.Struct({ name: Schema.String });
|
|
199
|
+
expect(() => SchemaValidator.assertExactProperties(Person, { name: 'Alice', extra: true })).to.throw(
|
|
200
|
+
/Unknown property: extra/,
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('allows extra properties when the schema has an index signature', () => {
|
|
205
|
+
const Expando = Schema.Struct({}, { key: Schema.String, value: Schema.Any });
|
|
206
|
+
expect(() => SchemaValidator.assertExactProperties(Expando, { custom: 'value' })).not.to.throw();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test('rejects extra nested properties', () => {
|
|
210
|
+
const schema = Schema.Struct({
|
|
211
|
+
nested: Schema.Struct({ field: Schema.Number }),
|
|
212
|
+
});
|
|
213
|
+
expect(() => SchemaValidator.assertExactProperties(schema, { nested: { field: 1, extra: true } })).to.throw(
|
|
214
|
+
/Unknown property: nested.extra/,
|
|
215
|
+
);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test('resolves nested discriminated union using target property values', () => {
|
|
219
|
+
const spec = Schema.Union(
|
|
220
|
+
Schema.Struct({ kind: Schema.Literal('feed'), feed: Schema.optional(Schema.String) }),
|
|
221
|
+
Schema.Struct({ kind: Schema.Literal('timer'), cron: Schema.String }),
|
|
222
|
+
);
|
|
223
|
+
const schema = Schema.Struct({ spec });
|
|
224
|
+
const target = { spec: { kind: 'feed' as const, feed: 'echo:/feed', extra: true } };
|
|
225
|
+
expect(() => SchemaValidator.assertExactProperties(schema, target, (path) => getDeep(target, path))).to.throw(
|
|
226
|
+
/Unknown property: spec.extra/,
|
|
227
|
+
);
|
|
228
|
+
expect(() =>
|
|
229
|
+
SchemaValidator.assertExactProperties(schema, { spec: { kind: 'feed' as const } }, (path) =>
|
|
230
|
+
getDeep({ spec: { kind: 'feed' as const } }, path),
|
|
231
|
+
),
|
|
232
|
+
).not.to.throw();
|
|
233
|
+
});
|
|
234
|
+
});
|
|
182
235
|
});
|
|
@@ -59,10 +59,12 @@ describe('Obj.setValue', () => {
|
|
|
59
59
|
value: Schema.optional(Schema.Number),
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
-
const Container =
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
const Container = Type.makeObject(DXN.make('com.test.type.container', '0.1.0'))(
|
|
63
|
+
Schema.Struct({
|
|
64
|
+
name: Schema.String,
|
|
65
|
+
items: Schema.optional(Schema.Array(Item)),
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
66
68
|
|
|
67
69
|
const container = Obj.make(Container, { name: 'box' });
|
|
68
70
|
|
|
@@ -146,9 +148,11 @@ describe('Obj.setValue', () => {
|
|
|
146
148
|
});
|
|
147
149
|
|
|
148
150
|
test('handles two-dimensional arrays', ({ expect }) => {
|
|
149
|
-
const Matrix =
|
|
150
|
-
|
|
151
|
-
|
|
151
|
+
const Matrix = Type.makeObject(DXN.make('com.test.type.matrix', '0.1.0'))(
|
|
152
|
+
Schema.Struct({
|
|
153
|
+
values: Schema.optional(Schema.Array(Schema.Array(Schema.Number))),
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
152
156
|
|
|
153
157
|
const matrix = Obj.make(Matrix, {});
|
|
154
158
|
|
|
@@ -194,10 +198,12 @@ describe('Obj.setValue', () => {
|
|
|
194
198
|
value: Schema.optional(Schema.Number),
|
|
195
199
|
});
|
|
196
200
|
|
|
197
|
-
const Container =
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
+
const Container = Type.makeObject(DXN.make('com.test.type.container', '0.1.0'))(
|
|
202
|
+
Schema.Struct({
|
|
203
|
+
name: Schema.String,
|
|
204
|
+
items: Schema.optional(Schema.Array(Item)),
|
|
205
|
+
}),
|
|
206
|
+
);
|
|
201
207
|
|
|
202
208
|
const container = Obj.make(Container, { name: 'box' });
|
|
203
209
|
|
|
@@ -216,10 +222,12 @@ describe('Obj.setValue', () => {
|
|
|
216
222
|
completed: Schema.optional(Schema.Boolean),
|
|
217
223
|
});
|
|
218
224
|
|
|
219
|
-
const TodoList =
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
225
|
+
const TodoList = Type.makeObject(DXN.make('com.test.type.todoList', '0.1.0'))(
|
|
226
|
+
Schema.Struct({
|
|
227
|
+
name: Schema.String,
|
|
228
|
+
tasks: Schema.optional(Schema.Array(Task)),
|
|
229
|
+
}),
|
|
230
|
+
);
|
|
223
231
|
|
|
224
232
|
const todoList = Obj.make(TodoList, { name: 'My Tasks' });
|
|
225
233
|
|
|
@@ -240,10 +248,12 @@ describe('Obj.setValue', () => {
|
|
|
240
248
|
label: Schema.optional(Schema.String), // Optional - should not be initialized
|
|
241
249
|
});
|
|
242
250
|
|
|
243
|
-
const Container =
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
251
|
+
const Container = Type.makeObject(DXN.make('com.test.type.container', '0.1.0'))(
|
|
252
|
+
Schema.Struct({
|
|
253
|
+
name: Schema.String,
|
|
254
|
+
items: Schema.optional(Schema.Array(Item)),
|
|
255
|
+
}),
|
|
256
|
+
);
|
|
247
257
|
|
|
248
258
|
const container = Obj.make(Container, { name: 'box' });
|
|
249
259
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
import * as Effectable from 'effect/Effectable';
|
|
7
|
+
import * as Option from 'effect/Option';
|
|
8
|
+
|
|
9
|
+
import { EffectEx } from '@dxos/effect';
|
|
10
|
+
|
|
11
|
+
import type * as QueryResult from '../../QueryResult';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Wraps an effect that produces a {@link QueryResult.QueryResult} into a
|
|
15
|
+
* {@link QueryResult.QueryResultEffect}, exposing `.run` and `.first` shorthands that execute the
|
|
16
|
+
* query once. Shared by `Database.query` and `Feed.query` so both surfaces chain identically.
|
|
17
|
+
*/
|
|
18
|
+
export const makeQueryResultEffect = <T, E, R>(
|
|
19
|
+
eff: Effect.Effect<QueryResult.QueryResult<T>, E, R>,
|
|
20
|
+
): QueryResult.QueryResultEffect<T, E, R> => {
|
|
21
|
+
return {
|
|
22
|
+
run: Effect.flatMap(eff, (result) => EffectEx.promiseWithCauseCapture(() => result.run())),
|
|
23
|
+
first: Effect.flatMap(eff, (result) =>
|
|
24
|
+
EffectEx.promiseWithCauseCapture(async () => Option.fromNullable(await result.firstOrUndefined())),
|
|
25
|
+
),
|
|
26
|
+
|
|
27
|
+
// Effect internals: the result is itself an Effect, so it carries the commit prototype.
|
|
28
|
+
...Effectable.CommitPrototype,
|
|
29
|
+
commit() {
|
|
30
|
+
return eff;
|
|
31
|
+
},
|
|
32
|
+
// Cast required: Effect's commit protocol is supplied at runtime via `CommitPrototype` and
|
|
33
|
+
// cannot be expressed by the object literal's static type.
|
|
34
|
+
} as any;
|
|
35
|
+
};
|
|
@@ -4,17 +4,37 @@
|
|
|
4
4
|
|
|
5
5
|
import * as Atom from '@effect-atom/atom/Atom';
|
|
6
6
|
|
|
7
|
+
import { subscribe } from '../common/proxy/reactive';
|
|
8
|
+
import { ObjectDeletedId } from '../common/types/model-symbols';
|
|
7
9
|
import type { Ref } from './ref';
|
|
8
10
|
import { loadRefTarget } from './utils';
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Atom family for ECHO refs.
|
|
12
14
|
* Uses ref reference as key — same ref returns same atom.
|
|
13
|
-
*
|
|
14
|
-
* Use `Obj.atom(ref)` if you need reactive snapshots of ECHO objects via a ref.
|
|
15
|
+
* Subscribes to target object changes and resolves to undefined when the target is deleted.
|
|
15
16
|
*/
|
|
16
17
|
export const refSimpleFamily = Atom.family(<T>(ref: Ref<T>): Atom.Atom<T | undefined> => {
|
|
17
18
|
return Atom.make<T | undefined>((get) => {
|
|
18
|
-
|
|
19
|
+
let unsubscribeTarget: (() => void) | undefined;
|
|
20
|
+
|
|
21
|
+
const setupSubscription = (target: T): T | undefined => {
|
|
22
|
+
// Release any previous subscription before re-subscribing (loadRefTarget may call this more than once).
|
|
23
|
+
// T has no ECHO-proxy constraint at this generic level; `subscribe` and ObjectDeletedId
|
|
24
|
+
// both require the internal proxy shape that cannot be expressed statically here.
|
|
25
|
+
unsubscribeTarget?.();
|
|
26
|
+
unsubscribeTarget = subscribe(target as any, () => {
|
|
27
|
+
const deleted = !!(target as any)[ObjectDeletedId];
|
|
28
|
+
get.setSelf(deleted ? undefined : target);
|
|
29
|
+
});
|
|
30
|
+
const deleted = !!(target as any)[ObjectDeletedId];
|
|
31
|
+
return deleted ? undefined : target;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
get.addFinalizer(() => {
|
|
35
|
+
unsubscribeTarget?.();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return loadRefTarget(ref, get, setupSubscription);
|
|
19
39
|
});
|
|
20
40
|
});
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import * as Schema from 'effect/Schema';
|
|
6
6
|
import { describe, expect, test } from 'vitest';
|
|
7
7
|
|
|
8
|
-
import { EID, EntityId
|
|
8
|
+
import { DXN, EID, EntityId } from '@dxos/keys';
|
|
9
9
|
|
|
10
10
|
import * as Type from '../../Type';
|
|
11
11
|
import { EchoObjectSchema, getObjectEchoUri } from '../Entity';
|
|
@@ -45,16 +45,16 @@ describe('Ref', () => {
|
|
|
45
45
|
|
|
46
46
|
const json = JSON.parse(JSON.stringify(contact));
|
|
47
47
|
expect(json).toEqual({
|
|
48
|
-
id: contact.id,
|
|
48
|
+
'id': contact.id,
|
|
49
49
|
'@type': `dxn:${Type.getTypename(Contact)}:${Type.getVersion(Contact)}`,
|
|
50
50
|
'@meta': {
|
|
51
51
|
keys: [],
|
|
52
52
|
},
|
|
53
|
-
name: 'John Doe',
|
|
54
|
-
tasks: [
|
|
53
|
+
'name': 'John Doe',
|
|
54
|
+
'tasks': [
|
|
55
55
|
{
|
|
56
56
|
'/': getObjectEchoUri(task)!.toString(),
|
|
57
|
-
target: JSON.parse(JSON.stringify(task)),
|
|
57
|
+
'target': JSON.parse(JSON.stringify(task)),
|
|
58
58
|
},
|
|
59
59
|
],
|
|
60
60
|
});
|
|
@@ -66,13 +66,13 @@ describe('Ref', () => {
|
|
|
66
66
|
|
|
67
67
|
const json = JSON.parse(JSON.stringify(contact));
|
|
68
68
|
expect(json).toEqual({
|
|
69
|
-
id: contact.id,
|
|
69
|
+
'id': contact.id,
|
|
70
70
|
'@type': `dxn:${Type.getTypename(Contact)}:${Type.getVersion(Contact)}`,
|
|
71
71
|
'@meta': {
|
|
72
72
|
keys: [],
|
|
73
73
|
},
|
|
74
|
-
name: 'John Doe',
|
|
75
|
-
tasks: [{ '/': getObjectEchoUri(task)!.toString() }],
|
|
74
|
+
'name': 'John Doe',
|
|
75
|
+
'tasks': [{ '/': getObjectEchoUri(task)!.toString() }],
|
|
76
76
|
});
|
|
77
77
|
});
|
|
78
78
|
|