@dxos/echo 0.8.4-staging.ac66bdf99f → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +102 -5
- package/README.md +7 -7
- package/dist/lib/neutral/Annotation.mjs +37 -6
- package/dist/lib/neutral/Database.mjs +6 -17
- package/dist/lib/neutral/Entity.mjs +31 -20
- package/dist/lib/neutral/Err.mjs +3 -3
- package/dist/lib/neutral/Feed.mjs +23 -19
- package/dist/lib/neutral/Filter.mjs +13 -15
- package/dist/lib/neutral/Format.mjs +23 -3
- package/dist/lib/neutral/JsonSchema.mjs +7 -8
- package/dist/lib/neutral/Key.mjs +9 -5
- package/dist/lib/neutral/Migration.mjs +11 -10
- package/dist/lib/neutral/Obj.mjs +29 -20
- package/dist/lib/neutral/Order.mjs +7 -3
- package/dist/lib/neutral/Query.mjs +17 -17
- package/dist/lib/neutral/QueryResult.mjs +1 -1
- package/dist/lib/neutral/Ref.mjs +10 -9
- package/dist/lib/neutral/Registry.mjs +14 -0
- package/dist/lib/neutral/Relation.mjs +28 -25
- package/dist/lib/neutral/Scope.mjs +12 -0
- package/dist/lib/neutral/Tag.mjs +17 -14
- package/dist/lib/neutral/Type.mjs +54 -26
- package/dist/lib/neutral/{chunk-OMUPQMLR.mjs → chunk-35INCYOE.mjs} +1 -1
- package/dist/lib/neutral/chunk-35INCYOE.mjs.map +7 -0
- package/dist/lib/neutral/chunk-3PBP4V4O.mjs +101 -0
- package/dist/lib/neutral/chunk-3PBP4V4O.mjs.map +7 -0
- package/dist/lib/neutral/chunk-4ZUHOTCG.mjs +184 -0
- package/dist/lib/neutral/chunk-4ZUHOTCG.mjs.map +7 -0
- package/dist/lib/neutral/chunk-5SMDBFVB.mjs +108 -0
- package/dist/lib/neutral/chunk-5SMDBFVB.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-OS35K56T.mjs → chunk-5SUJPHAE.mjs} +3 -3
- package/dist/lib/neutral/{chunk-OS35K56T.mjs.map → chunk-5SUJPHAE.mjs.map} +2 -2
- package/dist/lib/neutral/{chunk-GZQTCRJB.mjs → chunk-6M2Z6WBH.mjs} +22 -2
- package/dist/lib/neutral/chunk-6M2Z6WBH.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-V36VO5SS.mjs → chunk-6YDI3J37.mjs} +32 -40
- package/dist/lib/neutral/chunk-6YDI3J37.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-MOR5ERFM.mjs → chunk-7FPIAJIV.mjs} +701 -1256
- package/dist/lib/neutral/chunk-7FPIAJIV.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-ANHVGJI4.mjs → chunk-7LOUAPYZ.mjs} +9 -5
- package/dist/lib/neutral/chunk-7LOUAPYZ.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-JUXPFOEI.mjs → chunk-7PI7C4EF.mjs} +48 -88
- package/dist/lib/neutral/chunk-7PI7C4EF.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-UBEZSGXY.mjs → chunk-BBFJWWAV.mjs} +6 -6
- package/dist/lib/neutral/chunk-BBFJWWAV.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-UI6MWK5W.mjs → chunk-EVK6XBXO.mjs} +16 -2
- package/dist/lib/neutral/chunk-EVK6XBXO.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-7RO7CPBZ.mjs → chunk-IGK6FN65.mjs} +2 -2
- package/dist/lib/neutral/{chunk-HBUZJNZO.mjs → chunk-LWXVKPPW.mjs} +94 -99
- package/dist/lib/neutral/chunk-LWXVKPPW.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-BVOFLCVF.mjs → chunk-MZ7K3MLL.mjs} +9 -6
- package/dist/lib/neutral/chunk-MZ7K3MLL.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-TBKX6JQO.mjs → chunk-O6BH7EPN.mjs} +30 -3
- package/dist/lib/neutral/chunk-O6BH7EPN.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-EAMSSLZC.mjs → chunk-QQIYS74I.mjs} +83 -46
- package/dist/lib/neutral/chunk-QQIYS74I.mjs.map +7 -0
- package/dist/lib/neutral/chunk-R5W6DXR4.mjs +678 -0
- package/dist/lib/neutral/chunk-R5W6DXR4.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-WAK4DMFV.mjs → chunk-RIVWNMSF.mjs} +12 -7
- package/dist/lib/neutral/chunk-RIVWNMSF.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-T6W2LEZU.mjs → chunk-SBVFRTST.mjs} +73 -38
- package/dist/lib/neutral/chunk-SBVFRTST.mjs.map +7 -0
- package/dist/lib/neutral/chunk-T6E37YIP.mjs +251 -0
- package/dist/lib/neutral/chunk-T6E37YIP.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-DQYLD2RB.mjs → chunk-TFEWTY5A.mjs} +155 -129
- package/dist/lib/neutral/chunk-TFEWTY5A.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-B4BASU6W.mjs → chunk-TYGKCRMK.mjs} +85 -76
- package/dist/lib/neutral/chunk-TYGKCRMK.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-4OIBYSXE.mjs → chunk-UUP46KUQ.mjs} +78 -32
- package/dist/lib/neutral/chunk-UUP46KUQ.mjs.map +7 -0
- package/dist/lib/neutral/chunk-WISOH2XH.mjs +36 -0
- package/dist/lib/neutral/chunk-WISOH2XH.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-G3IQMKF7.mjs → chunk-WTQJHC75.mjs} +111 -112
- package/dist/lib/neutral/chunk-WTQJHC75.mjs.map +7 -0
- package/dist/lib/neutral/chunk-WU3GIANS.mjs +31 -0
- package/dist/lib/neutral/chunk-WU3GIANS.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-TU3GW67D.mjs → chunk-ZGNNFYHS.mjs} +40 -40
- package/dist/lib/neutral/chunk-ZGNNFYHS.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +47 -41
- package/dist/lib/neutral/internal/index.mjs +137 -72
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/lib/neutral/testing/index.mjs +261 -178
- package/dist/lib/neutral/testing/index.mjs.map +4 -4
- package/dist/types/src/Annotation.d.ts +108 -4
- package/dist/types/src/Annotation.d.ts.map +1 -1
- package/dist/types/src/Annotation.test.d.ts +2 -0
- package/dist/types/src/Annotation.test.d.ts.map +1 -0
- package/dist/types/src/Collection.d.ts +2 -3
- package/dist/types/src/Collection.d.ts.map +1 -1
- package/dist/types/src/Database.d.ts +56 -49
- package/dist/types/src/Database.d.ts.map +1 -1
- package/dist/types/src/Dataset.d.ts +16 -6
- package/dist/types/src/Dataset.d.ts.map +1 -1
- package/dist/types/src/Entity.d.ts +101 -28
- package/dist/types/src/Entity.d.ts.map +1 -1
- package/dist/types/src/Err.d.ts +27 -27
- package/dist/types/src/Err.d.ts.map +1 -1
- package/dist/types/src/Feed.d.ts +66 -19
- package/dist/types/src/Feed.d.ts.map +1 -1
- package/dist/types/src/Filter.d.ts +38 -12
- package/dist/types/src/Filter.d.ts.map +1 -1
- package/dist/types/src/Format.d.ts +0 -2
- package/dist/types/src/Format.d.ts.map +1 -1
- package/dist/types/src/Hypergraph.d.ts +14 -9
- package/dist/types/src/Hypergraph.d.ts.map +1 -1
- package/dist/types/src/Json.d.ts +33 -0
- package/dist/types/src/Json.d.ts.map +1 -0
- package/dist/types/src/Json.test.d.ts +2 -0
- package/dist/types/src/Json.test.d.ts.map +1 -0
- package/dist/types/src/JsonSchema.d.ts +2 -2
- package/dist/types/src/JsonSchema.d.ts.map +1 -1
- package/dist/types/src/Key.d.ts +1 -1
- package/dist/types/src/Key.d.ts.map +1 -1
- package/dist/types/src/Migration.d.ts +26 -11
- package/dist/types/src/Migration.d.ts.map +1 -1
- package/dist/types/src/Obj.d.ts +104 -61
- package/dist/types/src/Obj.d.ts.map +1 -1
- package/dist/types/src/Order.d.ts +10 -0
- package/dist/types/src/Order.d.ts.map +1 -1
- package/dist/types/src/Query.d.ts +34 -12
- package/dist/types/src/Query.d.ts.map +1 -1
- package/dist/types/src/QueryResult.d.ts +21 -0
- package/dist/types/src/QueryResult.d.ts.map +1 -1
- package/dist/types/src/Ref.d.ts +15 -7
- package/dist/types/src/Ref.d.ts.map +1 -1
- package/dist/types/src/Registry.d.ts +131 -0
- package/dist/types/src/Registry.d.ts.map +1 -0
- package/dist/types/src/Relation.d.ts +73 -41
- package/dist/types/src/Relation.d.ts.map +1 -1
- package/dist/types/src/Scope.d.ts +35 -0
- package/dist/types/src/Scope.d.ts.map +1 -0
- package/dist/types/src/Tag.d.ts +21 -5
- package/dist/types/src/Tag.d.ts.map +1 -1
- package/dist/types/src/Type.d.ts +362 -95
- package/dist/types/src/Type.d.ts.map +1 -1
- package/dist/types/src/View.d.ts +9 -12
- package/dist/types/src/View.d.ts.map +1 -1
- package/dist/types/src/exemplars.test.d.ts +2 -0
- package/dist/types/src/exemplars.test.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +4 -3
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/internal/Annotation/annotations.d.ts +79 -38
- package/dist/types/src/internal/Annotation/annotations.d.ts.map +1 -1
- package/dist/types/src/internal/Annotation/dictionary.d.ts +24 -0
- package/dist/types/src/internal/Annotation/dictionary.d.ts.map +1 -0
- package/dist/types/src/internal/Annotation/entity-dictionary.d.ts +14 -0
- package/dist/types/src/internal/Annotation/entity-dictionary.d.ts.map +1 -0
- package/dist/types/src/internal/Annotation/index.d.ts +4 -2
- package/dist/types/src/internal/Annotation/index.d.ts.map +1 -1
- package/dist/types/src/internal/Annotation/sorting.d.ts.map +1 -1
- package/dist/types/src/internal/Annotation/util.d.ts +14 -5
- package/dist/types/src/internal/Annotation/util.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/api.d.ts +17 -3
- package/dist/types/src/internal/Entity/api.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/entity.d.ts +72 -8
- package/dist/types/src/internal/Entity/entity.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/guard.d.ts +10 -0
- package/dist/types/src/internal/Entity/guard.d.ts.map +1 -0
- package/dist/types/src/internal/Entity/index.d.ts +2 -0
- package/dist/types/src/internal/Entity/index.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/model.d.ts +21 -17
- package/dist/types/src/internal/Entity/model.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/object.d.ts +3 -3
- package/dist/types/src/internal/Entity/object.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/relation.d.ts +30 -7
- package/dist/types/src/internal/Entity/relation.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/type-kind.d.ts +24 -0
- package/dist/types/src/internal/Entity/type-kind.d.ts.map +1 -0
- package/dist/types/src/internal/Entity/type-uri.d.ts +24 -0
- package/dist/types/src/internal/Entity/type-uri.d.ts.map +1 -0
- package/dist/types/src/internal/Entity/version.d.ts.map +1 -1
- package/dist/types/src/internal/Format/date.d.ts.map +1 -1
- package/dist/types/src/internal/Format/format.d.ts +3 -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/number.d.ts.map +1 -1
- package/dist/types/src/internal/Format/object.d.ts +3 -1
- package/dist/types/src/internal/Format/object.d.ts.map +1 -1
- package/dist/types/src/internal/Format/types.d.ts.map +1 -1
- package/dist/types/src/internal/JsonSchema/json-schema-normalize.d.ts.map +1 -1
- package/dist/types/src/internal/JsonSchema/json-schema-type.d.ts +34 -34
- package/dist/types/src/internal/JsonSchema/json-schema-type.d.ts.map +1 -1
- package/dist/types/src/internal/JsonSchema/json-schema.d.ts +3 -2
- package/dist/types/src/internal/JsonSchema/json-schema.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/atoms.d.ts +38 -0
- package/dist/types/src/internal/Obj/atoms.d.ts.map +1 -0
- package/dist/types/src/internal/Obj/clone.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/common.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/create-object.d.ts +12 -12
- package/dist/types/src/internal/Obj/create-object.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/deleted.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/index.d.ts +1 -1
- package/dist/types/src/internal/Obj/index.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/json-serializer.d.ts +8 -8
- package/dist/types/src/internal/Obj/json-serializer.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/set-value.d.ts +1 -1
- package/dist/types/src/internal/Obj/set-value.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/snapshot.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/typed-object.d.ts +1 -1
- 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.d.ts → Query/pretty.d.ts} +1 -1
- package/dist/types/src/internal/Query/pretty.d.ts.map +1 -0
- package/dist/types/src/internal/Ref/atoms.d.ts +10 -0
- package/dist/types/src/internal/Ref/atoms.d.ts.map +1 -0
- package/dist/types/src/internal/Ref/ref-array.d.ts +2 -2
- package/dist/types/src/internal/Ref/ref.d.ts +50 -19
- package/dist/types/src/internal/Ref/ref.d.ts.map +1 -1
- package/dist/types/src/internal/Ref/utils.d.ts +8 -0
- package/dist/types/src/internal/Ref/utils.d.ts.map +1 -0
- package/dist/types/src/internal/Type/compose.d.ts.map +1 -1
- package/dist/types/src/internal/Type/index.d.ts +1 -2
- package/dist/types/src/internal/Type/index.d.ts.map +1 -1
- package/dist/types/src/internal/Type/manipulation.d.ts +0 -1
- package/dist/types/src/internal/Type/manipulation.d.ts.map +1 -1
- package/dist/types/src/internal/Type/type-schema.d.ts +52 -0
- package/dist/types/src/internal/Type/type-schema.d.ts.map +1 -0
- package/dist/types/src/internal/common/api/meta.d.ts +14 -11
- package/dist/types/src/internal/common/api/meta.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/change-context.d.ts +1 -1
- package/dist/types/src/internal/common/proxy/change-context.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/define-hidden-property.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/errors.d.ts +1 -1
- package/dist/types/src/internal/common/proxy/errors.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/event-batch.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/json-serializer.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/make-object.d.ts +11 -5
- package/dist/types/src/internal/common/proxy/make-object.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/ownership.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/proxy-utils.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/reactive-array.d.ts +1 -1
- package/dist/types/src/internal/common/proxy/reactive.d.ts +1 -1
- package/dist/types/src/internal/common/proxy/reactive.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/reactive.test.d.ts +2 -0
- package/dist/types/src/internal/common/proxy/reactive.test.d.ts.map +1 -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 +18 -2
- package/dist/types/src/internal/common/proxy/typed-handler.d.ts.map +1 -1
- package/dist/types/src/internal/common/types/base.d.ts +4 -4
- package/dist/types/src/internal/common/types/base.d.ts.map +1 -1
- package/dist/types/src/internal/common/types/entity.d.ts +62 -5
- package/dist/types/src/internal/common/types/entity.d.ts.map +1 -1
- package/dist/types/src/internal/common/types/index.d.ts +1 -1
- package/dist/types/src/internal/common/types/index.d.ts.map +1 -1
- package/dist/types/src/internal/common/types/meta.d.ts +33 -12
- package/dist/types/src/internal/common/types/meta.d.ts.map +1 -1
- package/dist/types/src/internal/common/types/model-symbols.d.ts +15 -4
- package/dist/types/src/internal/common/types/model-symbols.d.ts.map +1 -1
- package/dist/types/src/internal/common/types/typename.d.ts +7 -0
- package/dist/types/src/internal/common/types/typename.d.ts.map +1 -1
- package/dist/types/src/internal/common/types/version.d.ts +1 -1
- package/dist/types/src/internal/common/types/well-known-types.d.ts +11 -0
- package/dist/types/src/internal/common/types/well-known-types.d.ts.map +1 -0
- package/dist/types/src/internal/index.d.ts +2 -2
- package/dist/types/src/internal/index.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +1 -0
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/registry.d.ts +9 -0
- package/dist/types/src/testing/registry.d.ts.map +1 -0
- package/dist/types/src/testing/test-data.d.ts +8 -8
- package/dist/types/src/testing/test-data.d.ts.map +1 -1
- package/dist/types/src/testing/test-schema.d.ts +83 -89
- package/dist/types/src/testing/test-schema.d.ts.map +1 -1
- package/dist/types/src/testing/util.d.ts +5 -3
- package/dist/types/src/testing/util.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +26 -24
- package/src/Annotation.test.ts +439 -0
- package/src/Annotation.ts +158 -4
- package/src/Collection.ts +5 -9
- package/src/Database.ts +93 -100
- package/src/Dataset.ts +10 -2
- package/src/Entity.test.ts +116 -6
- package/src/Entity.ts +134 -32
- package/src/Err.ts +4 -4
- package/src/Feed.ts +92 -44
- package/src/Filter.ts +70 -40
- package/src/Format.ts +0 -4
- package/src/Hypergraph.ts +14 -9
- package/src/Json.test.ts +175 -0
- package/src/Json.ts +103 -0
- package/src/Key.ts +1 -1
- package/src/Migration.ts +39 -19
- package/src/Obj.test.ts +122 -20
- package/src/Obj.ts +168 -91
- package/src/Order.ts +22 -0
- package/src/Query.test.ts +183 -154
- package/src/Query.ts +172 -85
- package/src/QueryResult.ts +26 -0
- package/src/Ref.ts +22 -4
- package/src/Registry.ts +155 -0
- package/src/Relation.test.ts +10 -10
- package/src/Relation.ts +116 -69
- package/src/Scope.ts +50 -0
- package/src/Tag.md +88 -0
- package/src/Tag.ts +49 -6
- package/src/Type.test.ts +223 -18
- package/src/Type.ts +609 -131
- package/src/View.ts +14 -23
- package/src/exemplars.test.ts +21 -0
- package/src/index.ts +4 -4
- package/src/internal/Annotation/annotations.test.ts +31 -11
- package/src/internal/Annotation/annotations.ts +143 -111
- package/src/internal/Annotation/dictionary.ts +47 -0
- package/src/internal/Annotation/entity-dictionary.ts +74 -0
- package/src/internal/Annotation/index.ts +4 -2
- package/src/internal/Annotation/util.ts +17 -8
- package/src/internal/Entity/api.ts +54 -7
- package/src/internal/Entity/entity.ts +196 -47
- package/src/internal/Entity/guard.ts +26 -0
- package/src/internal/Entity/index.ts +2 -0
- package/src/internal/Entity/model.ts +38 -28
- package/src/internal/Entity/object.ts +21 -5
- package/src/internal/Entity/relation.ts +68 -34
- package/src/internal/Entity/type-kind.ts +75 -0
- package/src/internal/Entity/type-uri.ts +92 -0
- package/src/internal/Entity/util.ts +9 -9
- package/src/internal/Format/date.ts +0 -4
- package/src/internal/Format/format.test.ts +21 -0
- package/src/internal/Format/index.ts +2 -3
- package/src/internal/Format/object.ts +21 -4
- package/src/internal/Format/types.ts +1 -1
- package/src/internal/JsonSchema/annotations.ts +1 -1
- package/src/internal/JsonSchema/json-schema-type.ts +4 -4
- package/src/internal/JsonSchema/json-schema.test.ts +71 -145
- package/src/internal/JsonSchema/json-schema.ts +49 -35
- package/src/internal/Obj/atoms.ts +244 -0
- package/src/internal/Obj/clone.ts +9 -4
- package/src/internal/Obj/create-object.test.ts +12 -10
- package/src/internal/Obj/create-object.ts +68 -22
- package/src/internal/Obj/index.ts +1 -1
- package/src/internal/Obj/inspect.ts +5 -3
- package/src/internal/Obj/json-serializer.test.ts +101 -22
- package/src/internal/Obj/json-serializer.ts +89 -33
- package/src/internal/Obj/set-value.test.ts +22 -45
- package/src/internal/Obj/set-value.ts +12 -19
- package/src/internal/Obj/snapshot.ts +13 -4
- package/src/internal/Obj/typed-object.test.ts +9 -11
- package/src/internal/Obj/typed-object.ts +1 -1
- package/src/internal/Query/index.ts +5 -0
- package/src/internal/{Query.ts → Query/pretty.ts} +40 -12
- package/src/internal/Ref/atoms.ts +20 -0
- package/src/internal/Ref/ref-array.ts +3 -3
- package/src/internal/Ref/ref.test.ts +18 -27
- package/src/internal/Ref/ref.ts +137 -59
- package/src/internal/Ref/utils.ts +45 -0
- package/src/internal/Type/compose.test.ts +3 -1
- package/src/internal/Type/index.ts +1 -2
- package/src/internal/Type/manipulation.ts +0 -25
- package/src/internal/Type/type-schema.ts +60 -0
- package/src/internal/common/README.md +2 -2
- package/src/internal/common/api/meta.ts +19 -17
- package/src/internal/common/proxy/change-context.ts +1 -1
- package/src/internal/common/proxy/change.test.ts +91 -83
- package/src/internal/common/proxy/errors.ts +2 -2
- package/src/internal/common/proxy/handler.test.ts +1 -1
- package/src/internal/common/proxy/json-serializer.ts +27 -16
- package/src/internal/common/proxy/make-object.ts +44 -20
- package/src/internal/common/proxy/ownership.ts +2 -2
- package/src/internal/common/proxy/reactive-array.ts +1 -1
- package/src/internal/common/proxy/reactive.test.ts +54 -0
- package/src/internal/common/proxy/reactive.ts +11 -2
- package/src/internal/common/proxy/schema.test.ts +48 -86
- package/src/internal/common/proxy/typed-handler.test.ts +12 -11
- package/src/internal/common/proxy/typed-handler.ts +78 -16
- package/src/internal/common/proxy/typed-object.test.ts +16 -28
- package/src/internal/common/types/base.ts +4 -4
- package/src/internal/common/types/entity.ts +80 -1
- package/src/internal/common/types/index.ts +6 -1
- package/src/internal/common/types/meta.ts +62 -20
- package/src/internal/common/types/model-symbols.ts +24 -4
- package/src/internal/common/types/typename.ts +39 -3
- package/src/internal/common/types/well-known-types.ts +15 -0
- package/src/internal/index.ts +6 -4
- package/src/testing/api.test.ts +15 -9
- package/src/testing/index.ts +1 -0
- package/src/testing/registry.ts +44 -0
- package/src/testing/test-data.ts +159 -99
- package/src/testing/test-schema.ts +22 -58
- package/src/testing/util.ts +14 -11
- package/dist/lib/neutral/Extension.mjs +0 -18
- package/dist/lib/neutral/SchemaRegistry.mjs +0 -2
- package/dist/lib/neutral/chunk-2KHZ36F5.mjs +0 -361
- package/dist/lib/neutral/chunk-2KHZ36F5.mjs.map +0 -7
- package/dist/lib/neutral/chunk-4OIBYSXE.mjs.map +0 -7
- package/dist/lib/neutral/chunk-4P3IXBLT.mjs +0 -45
- package/dist/lib/neutral/chunk-4P3IXBLT.mjs.map +0 -7
- package/dist/lib/neutral/chunk-ANHVGJI4.mjs.map +0 -7
- package/dist/lib/neutral/chunk-B4BASU6W.mjs.map +0 -7
- package/dist/lib/neutral/chunk-BNCCGLJN.mjs +0 -7
- package/dist/lib/neutral/chunk-BNCCGLJN.mjs.map +0 -7
- package/dist/lib/neutral/chunk-BVOFLCVF.mjs.map +0 -7
- package/dist/lib/neutral/chunk-DQYLD2RB.mjs.map +0 -7
- package/dist/lib/neutral/chunk-EAMSSLZC.mjs.map +0 -7
- package/dist/lib/neutral/chunk-G3IQMKF7.mjs.map +0 -7
- package/dist/lib/neutral/chunk-GZQTCRJB.mjs.map +0 -7
- package/dist/lib/neutral/chunk-HBUZJNZO.mjs.map +0 -7
- package/dist/lib/neutral/chunk-JUXPFOEI.mjs.map +0 -7
- package/dist/lib/neutral/chunk-MOR5ERFM.mjs.map +0 -7
- package/dist/lib/neutral/chunk-OMUPQMLR.mjs.map +0 -7
- package/dist/lib/neutral/chunk-PHU22NLC.mjs +0 -136
- package/dist/lib/neutral/chunk-PHU22NLC.mjs.map +0 -7
- package/dist/lib/neutral/chunk-ROG4RXXL.mjs +0 -97
- package/dist/lib/neutral/chunk-ROG4RXXL.mjs.map +0 -7
- package/dist/lib/neutral/chunk-T6W2LEZU.mjs.map +0 -7
- package/dist/lib/neutral/chunk-TBKX6JQO.mjs.map +0 -7
- package/dist/lib/neutral/chunk-TU3GW67D.mjs.map +0 -7
- package/dist/lib/neutral/chunk-UBEZSGXY.mjs.map +0 -7
- package/dist/lib/neutral/chunk-UI6MWK5W.mjs.map +0 -7
- package/dist/lib/neutral/chunk-V36VO5SS.mjs.map +0 -7
- package/dist/lib/neutral/chunk-WAK4DMFV.mjs.map +0 -7
- package/dist/lib/neutral/chunk-YAHXAYOW.mjs +0 -56
- package/dist/lib/neutral/chunk-YAHXAYOW.mjs.map +0 -7
- package/dist/lib/neutral/chunk-YS6Q3XAD.mjs +0 -50
- package/dist/lib/neutral/chunk-YS6Q3XAD.mjs.map +0 -7
- package/dist/types/src/Extension.d.ts +0 -80
- package/dist/types/src/Extension.d.ts.map +0 -1
- package/dist/types/src/Extension.test.d.ts +0 -2
- package/dist/types/src/Extension.test.d.ts.map +0 -1
- package/dist/types/src/SchemaRegistry.d.ts +0 -84
- package/dist/types/src/SchemaRegistry.d.ts.map +0 -1
- package/dist/types/src/internal/Obj/ids.d.ts +0 -6
- package/dist/types/src/internal/Obj/ids.d.ts.map +0 -1
- package/dist/types/src/internal/Query.d.ts.map +0 -1
- package/dist/types/src/internal/Type/echo-schema.d.ts +0 -181
- package/dist/types/src/internal/Type/echo-schema.d.ts.map +0 -1
- package/dist/types/src/internal/Type/persistent-schema.d.ts +0 -20
- package/dist/types/src/internal/Type/persistent-schema.d.ts.map +0 -1
- package/src/Extension.test.ts +0 -235
- package/src/Extension.ts +0 -122
- package/src/SchemaRegistry.ts +0 -106
- package/src/internal/Obj/ids.ts +0 -12
- package/src/internal/Type/echo-schema.ts +0 -423
- package/src/internal/Type/persistent-schema.ts +0 -33
- /package/dist/lib/neutral/{Extension.mjs.map → Registry.mjs.map} +0 -0
- /package/dist/lib/neutral/{SchemaRegistry.mjs.map → Scope.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-7RO7CPBZ.mjs.map → chunk-IGK6FN65.mjs.map} +0 -0
package/src/Filter.ts
CHANGED
|
@@ -11,11 +11,13 @@ import type * as Types from 'effect/Types';
|
|
|
11
11
|
|
|
12
12
|
import { type ForeignKey, type QueryAST } from '@dxos/echo-protocol';
|
|
13
13
|
import { assertArgument } from '@dxos/invariant';
|
|
14
|
-
import {
|
|
14
|
+
import { EID, EntityId, type URI } from '@dxos/keys';
|
|
15
15
|
|
|
16
16
|
import * as internal from './internal';
|
|
17
17
|
import type * as Obj from './Obj';
|
|
18
18
|
import * as Ref from './Ref';
|
|
19
|
+
// eslint-disable-next-line @dxos/rules/import-as-namespace
|
|
20
|
+
import type * as Type$ from './Type';
|
|
19
21
|
|
|
20
22
|
export interface Filter<T> {
|
|
21
23
|
// TODO(dmaretskyi): See new effect-schema approach to variance.
|
|
@@ -77,11 +79,11 @@ export const nothing = (): FilterClass => {
|
|
|
77
79
|
};
|
|
78
80
|
|
|
79
81
|
/*
|
|
80
|
-
* Filter by
|
|
82
|
+
* Filter by EntityId.
|
|
81
83
|
*/
|
|
82
|
-
export const id = (...ids:
|
|
84
|
+
export const id = (...ids: EntityId[]): Any => {
|
|
83
85
|
assertArgument(
|
|
84
|
-
ids.every((id) =>
|
|
86
|
+
ids.every((id) => EntityId.isValid(id)),
|
|
85
87
|
'ids',
|
|
86
88
|
'ids must be valid',
|
|
87
89
|
);
|
|
@@ -100,61 +102,89 @@ export const id = (...ids: ObjectId[]): Any => {
|
|
|
100
102
|
|
|
101
103
|
/**
|
|
102
104
|
* Filter by type.
|
|
105
|
+
*
|
|
106
|
+
* Accepts a `Type.Type` entity (the value produced by `Type.makeObject` /
|
|
107
|
+
* `Type.makeRelation`), a `Schema.Union` of such entities (for filtering across a
|
|
108
|
+
* union of ECHO types), or a fully-qualified type URI — an `echo:` EID (stored schema)
|
|
109
|
+
* or a `dxn:` DXN (static schema). To filter by a bare typename, wrap it: `DXN.make(typename)`.
|
|
103
110
|
*/
|
|
104
|
-
export const type
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
111
|
+
export const type: {
|
|
112
|
+
<T extends Type$.AnyEntity>(type: T, props?: Props<Type$.InstanceType<T>>): Filter<Type$.InstanceType<T>>;
|
|
113
|
+
// Schema-side overload restricted to the well-known unknown schemas and to
|
|
114
|
+
// `Schema.Union(...)` of `Type.Type` entities (for filtering across a union
|
|
115
|
+
// of ECHO types). Other raw schemas are rejected.
|
|
116
|
+
<S extends internal.UnknownTypeSchema<any, any>>(
|
|
117
|
+
schema: S,
|
|
118
|
+
props?: Props<Schema.Schema.Type<S>>,
|
|
119
|
+
): Filter<Schema.Schema.Type<S>>;
|
|
120
|
+
<S extends Schema.Union<readonly Schema.Schema.AnyNoContext[]>>(
|
|
121
|
+
union: S,
|
|
122
|
+
props?: Props<Schema.Schema.Type<S>>,
|
|
123
|
+
): Filter<Schema.Schema.Type<S>>;
|
|
124
|
+
(uri: URI.URI, props?: Props<unknown>): Filter<any>;
|
|
125
|
+
// Passthrough overload for callers that hold a `Type.AnyEntity | URI.URI` union
|
|
126
|
+
// (e.g. Query.type / Query.sourceOf / Query.targetOf impls). Listed last so the
|
|
127
|
+
// typed overloads above still win for monomorphic inputs.
|
|
128
|
+
(input: Type$.AnyEntity | URI.URI, props?: Props<unknown>): Filter<unknown>;
|
|
129
|
+
} = (input: Type$.AnyEntity | Schema.Schema.AnyNoContext | URI.URI, props?: Props<unknown>): any => {
|
|
130
|
+
if (Schema.isSchema(input) && SchemaAST.isUnion(input.ast)) {
|
|
131
|
+
const typenames = input.ast.types.map((t) => internal.getTypeURIFromSpecifier(Schema.make(t)));
|
|
110
132
|
return new FilterClass({
|
|
111
133
|
type: 'or',
|
|
112
134
|
filters: typenames.map((typename) => ({
|
|
113
135
|
type: 'object',
|
|
114
|
-
typename
|
|
136
|
+
typename,
|
|
115
137
|
props: {},
|
|
116
138
|
})),
|
|
117
139
|
});
|
|
118
140
|
}
|
|
119
141
|
|
|
120
|
-
const
|
|
142
|
+
const uri = internal.getTypeURIFromSpecifier(input);
|
|
121
143
|
return new FilterClass({
|
|
122
144
|
type: 'object',
|
|
123
|
-
typename:
|
|
145
|
+
typename: uri,
|
|
124
146
|
...propsFilterToAst(props ?? {}),
|
|
125
147
|
});
|
|
126
148
|
};
|
|
127
149
|
|
|
128
150
|
/**
|
|
129
|
-
* Filter by
|
|
151
|
+
* Filter by tag.
|
|
130
152
|
*/
|
|
131
|
-
export const
|
|
132
|
-
assertArgument(!typename.startsWith('dxn:'), 'typename', 'Typename must no be qualified');
|
|
153
|
+
export const tag = (tag: string): Any => {
|
|
133
154
|
return new FilterClass({
|
|
134
|
-
type: '
|
|
135
|
-
|
|
136
|
-
props: {},
|
|
155
|
+
type: 'tag',
|
|
156
|
+
tag,
|
|
137
157
|
});
|
|
138
158
|
};
|
|
139
159
|
|
|
140
160
|
/**
|
|
141
|
-
*
|
|
161
|
+
* Options for {@link key} filter.
|
|
142
162
|
*/
|
|
143
|
-
export
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
163
|
+
export type KeyFilterOptions = {
|
|
164
|
+
/**
|
|
165
|
+
* Optional semver range expression (e.g. `^1.2.3`, `~2.0.0`, `>=1.0.0 <2.0.0`).
|
|
166
|
+
* Matches the object's meta `version` field against the range.
|
|
167
|
+
* If omitted, matches any version (including objects with no version).
|
|
168
|
+
*/
|
|
169
|
+
version?: string;
|
|
149
170
|
};
|
|
150
171
|
|
|
151
172
|
/**
|
|
152
|
-
* Filter by
|
|
173
|
+
* Filter by registry key stored in object meta.
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```ts
|
|
177
|
+
* Filter.key('org.example.type.foo');
|
|
178
|
+
* Filter.key('org.example.type.foo', { version: '^1.2.3' });
|
|
179
|
+
* ```
|
|
153
180
|
*/
|
|
154
|
-
export const
|
|
181
|
+
export const key = (key: string, options?: KeyFilterOptions): Any => {
|
|
155
182
|
return new FilterClass({
|
|
156
|
-
type: '
|
|
157
|
-
|
|
183
|
+
type: 'object',
|
|
184
|
+
typename: null,
|
|
185
|
+
props: {},
|
|
186
|
+
metaKey: key,
|
|
187
|
+
metaVersion: options?.version,
|
|
158
188
|
});
|
|
159
189
|
};
|
|
160
190
|
|
|
@@ -192,14 +222,14 @@ export const text = (
|
|
|
192
222
|
/**
|
|
193
223
|
* Filter by foreign keys.
|
|
194
224
|
*/
|
|
195
|
-
export const foreignKeys = <S extends
|
|
196
|
-
schema: S
|
|
225
|
+
export const foreignKeys = <S extends Type$.AnyEntity | URI.URI>(
|
|
226
|
+
schema: S,
|
|
197
227
|
keys: ForeignKey[],
|
|
198
|
-
): Filter<
|
|
199
|
-
const
|
|
228
|
+
): Filter<S extends Type$.AnyEntity ? Type$.InstanceType<S> : unknown> => {
|
|
229
|
+
const uri = internal.getTypeURIFromSpecifier(schema);
|
|
200
230
|
return new FilterClass({
|
|
201
231
|
type: 'object',
|
|
202
|
-
typename:
|
|
232
|
+
typename: uri,
|
|
203
233
|
props: {},
|
|
204
234
|
foreignKeys: keys,
|
|
205
235
|
});
|
|
@@ -347,7 +377,7 @@ export type ChildOfOptions = {
|
|
|
347
377
|
/**
|
|
348
378
|
* Filter objects that are children of the specified parent(s).
|
|
349
379
|
* Accepts ECHO objects, Refs, or arrays of either.
|
|
350
|
-
* Refs are resolved to DXNs without loading; objects use {@link Obj.
|
|
380
|
+
* Refs are resolved to DXNs without loading; objects use {@link Obj.getURI}.
|
|
351
381
|
* With transitive=true (default), also matches grandchildren and beyond.
|
|
352
382
|
*/
|
|
353
383
|
export const childOf = (
|
|
@@ -357,9 +387,9 @@ export const childOf = (
|
|
|
357
387
|
const items = Array.isArray(parents) ? parents : [parents];
|
|
358
388
|
const dxns = items.map((item) => {
|
|
359
389
|
if (Ref.isRef(item)) {
|
|
360
|
-
return item.
|
|
390
|
+
return EID.parse(item.uri);
|
|
361
391
|
}
|
|
362
|
-
return internal.
|
|
392
|
+
return EID.parse(internal.getUri(item));
|
|
363
393
|
});
|
|
364
394
|
return new FilterClass({
|
|
365
395
|
type: 'child-of',
|
|
@@ -401,7 +431,7 @@ export const or = <Filters extends readonly Any[]>(...filters: Filters): Filter<
|
|
|
401
431
|
// TODO(dmaretskyi): Add `Filter.match` to support pattern matching on string props.
|
|
402
432
|
|
|
403
433
|
const propsFilterToAst = (predicates: Props<any>): Pick<QueryAST.FilterObject, 'id' | 'props'> => {
|
|
404
|
-
let idFilter: readonly
|
|
434
|
+
let idFilter: readonly EntityId[] | undefined;
|
|
405
435
|
if ('id' in predicates) {
|
|
406
436
|
assertArgument(
|
|
407
437
|
typeof predicates.id === 'string' || Array.isArray(predicates.id),
|
|
@@ -409,7 +439,7 @@ const propsFilterToAst = (predicates: Props<any>): Pick<QueryAST.FilterObject, '
|
|
|
409
439
|
'invalid id filter',
|
|
410
440
|
);
|
|
411
441
|
idFilter = typeof predicates.id === 'string' ? [predicates.id] : predicates.id;
|
|
412
|
-
Schema.Array(
|
|
442
|
+
Schema.Array(EntityId).pipe(Schema.validateSync)(idFilter);
|
|
413
443
|
}
|
|
414
444
|
|
|
415
445
|
return {
|
package/src/Format.ts
CHANGED
package/src/Hypergraph.ts
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { type
|
|
5
|
+
import { type URI } from '@dxos/keys';
|
|
6
6
|
|
|
7
7
|
import type * as Database from './Database';
|
|
8
8
|
import type * as Entity from './Entity';
|
|
9
9
|
import type * as internal from './internal';
|
|
10
10
|
import type * as Key from './Key';
|
|
11
11
|
import type * as Ref from './Ref';
|
|
12
|
-
import type * as
|
|
12
|
+
import type * as Registry from './Registry';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Resolution context.
|
|
@@ -22,10 +22,10 @@ export interface RefResolutionContext {
|
|
|
22
22
|
space?: Key.SpaceId;
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
* This
|
|
25
|
+
* Feed that the resolution is happening from.
|
|
26
|
+
* This feed will be searched first, and then the space it belongs to.
|
|
27
27
|
*/
|
|
28
|
-
|
|
28
|
+
feed?: URI.URI;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export interface RefResolverOptions {
|
|
@@ -46,7 +46,12 @@ export interface RefResolverOptions {
|
|
|
46
46
|
* Manages cross-space database interactions.
|
|
47
47
|
*/
|
|
48
48
|
export interface Hypergraph extends Database.Queryable {
|
|
49
|
-
|
|
49
|
+
/**
|
|
50
|
+
* In-process registry of keyed objects and static schema types.
|
|
51
|
+
* Populated at startup via `registry.add(objects)` / `registry.add(schemas)`.
|
|
52
|
+
* Queries that include no explicit from() clause will fan out to this registry automatically.
|
|
53
|
+
*/
|
|
54
|
+
get registry(): Registry.Registry;
|
|
50
55
|
|
|
51
56
|
/**
|
|
52
57
|
* Query objects.
|
|
@@ -57,11 +62,11 @@ export interface Hypergraph extends Database.Queryable {
|
|
|
57
62
|
* Creates a reference to an existing object in the database.
|
|
58
63
|
*
|
|
59
64
|
* NOTE: The reference may be dangling if the object is not present in the database.
|
|
60
|
-
* NOTE: Difference from `Ref.
|
|
61
|
-
* `Ref.
|
|
65
|
+
* NOTE: Difference from `Ref.fromURI`
|
|
66
|
+
* `Ref.fromURI(dxn)` returns an unhydrated reference. The `.load` and `.target` APIs will not work.
|
|
62
67
|
* `db.makeRef(dxn)` is preferable in cases with access to the database.
|
|
63
68
|
*/
|
|
64
|
-
makeRef<T extends Entity.Unknown = Entity.Unknown>(
|
|
69
|
+
makeRef<T extends Entity.Unknown = Entity.Unknown>(uri: URI.URI): Ref.Ref<T>;
|
|
65
70
|
|
|
66
71
|
/**
|
|
67
72
|
* @param hostDb Host database for reference resolution.
|
package/src/Json.test.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from 'vitest';
|
|
6
|
+
|
|
7
|
+
import { DXN, EID, EntityId } from '@dxos/keys';
|
|
8
|
+
import { safeStringify } from '@dxos/util';
|
|
9
|
+
|
|
10
|
+
import * as Database from './Database';
|
|
11
|
+
import * as Json from './Json';
|
|
12
|
+
|
|
13
|
+
/** Mint a random ECHO object id usable as both a stub-db key and a DXN payload. */
|
|
14
|
+
const newId = (): string => EntityId.random();
|
|
15
|
+
|
|
16
|
+
/** Build a fake encoded ref for a local-space object id. */
|
|
17
|
+
const encodeRef = (id: string): { '/': string } => ({ '/': EID.make({ entityId: id }) });
|
|
18
|
+
|
|
19
|
+
/** Minimal stub: `createRefReplacer` only touches `db.getObjectById`. */
|
|
20
|
+
const makeStubDb = (objects: Record<string, unknown>): Database.Database => {
|
|
21
|
+
return {
|
|
22
|
+
getObjectById: (id: string) => objects[id],
|
|
23
|
+
} as unknown as Database.Database;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Run a value through the replacer's public contract — `JSON.stringify(value, replacer)` —
|
|
28
|
+
* then re-parse the result so tests can assert on plain JS shapes. This mirrors the way
|
|
29
|
+
* the JSON highlighter invokes the replacer in production.
|
|
30
|
+
*/
|
|
31
|
+
const stringifyWith = (replacer: Json.JsonReplacer, value: unknown): unknown =>
|
|
32
|
+
JSON.parse(JSON.stringify(value, replacer));
|
|
33
|
+
|
|
34
|
+
describe('createRefReplacer', () => {
|
|
35
|
+
test('passes plain values through unchanged', () => {
|
|
36
|
+
const replacer = Json.createRefReplacer({ db: makeStubDb({}) });
|
|
37
|
+
const subject = { a: 1, b: 'two', c: [3, { d: 4 }] };
|
|
38
|
+
expect(stringifyWith(replacer, subject)).toEqual(subject);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('inlines refs at default depth (1)', () => {
|
|
42
|
+
const id = newId();
|
|
43
|
+
const target = { name: 'inlined' };
|
|
44
|
+
const replacer = Json.createRefReplacer({ db: makeStubDb({ [id]: target }) });
|
|
45
|
+
const subject = { ref: encodeRef(id) };
|
|
46
|
+
expect(stringifyWith(replacer, subject)).toEqual({ ref: target });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('does not follow refs when depth is 0', () => {
|
|
50
|
+
const id = newId();
|
|
51
|
+
const target = { name: 'inlined' };
|
|
52
|
+
const ref = encodeRef(id);
|
|
53
|
+
const replacer = Json.createRefReplacer({ db: makeStubDb({ [id]: target }), depth: 0 });
|
|
54
|
+
expect(stringifyWith(replacer, { ref })).toEqual({ ref });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('inlines refs across multiple levels up to depth', () => {
|
|
58
|
+
const innerId = newId();
|
|
59
|
+
const middleId = newId();
|
|
60
|
+
const inner = { name: 'inner' };
|
|
61
|
+
const middle = { ref: encodeRef(innerId) };
|
|
62
|
+
const outer = { ref: encodeRef(middleId) };
|
|
63
|
+
const db = makeStubDb({ [innerId]: inner, [middleId]: middle });
|
|
64
|
+
|
|
65
|
+
expect(stringifyWith(Json.createRefReplacer({ db, depth: 1 }), outer)).toEqual({
|
|
66
|
+
ref: { ref: encodeRef(innerId) },
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
expect(stringifyWith(Json.createRefReplacer({ db, depth: 2 }), outer)).toEqual({
|
|
70
|
+
ref: { ref: inner },
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('leaves refs encoded when the target is missing in the db', () => {
|
|
75
|
+
const ref = encodeRef(newId());
|
|
76
|
+
const replacer = Json.createRefReplacer({ db: makeStubDb({}) });
|
|
77
|
+
expect(stringifyWith(replacer, { ref })).toEqual({ ref });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('leaves non-DXN single-key { "/": string } objects untouched', () => {
|
|
81
|
+
// Same `{ '/': string }` shape is used by other IPLD-style refs (e.g. CIDs); those should
|
|
82
|
+
// not crash the replacer and should pass through verbatim.
|
|
83
|
+
const cidLike = { '/': 'bafybeibwzifw7izxykxz' };
|
|
84
|
+
const replacer = Json.createRefReplacer({ db: makeStubDb({}) });
|
|
85
|
+
expect(stringifyWith(replacer, { ref: cidLike })).toEqual({ ref: cidLike });
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('leaves malformed dxn strings untouched', () => {
|
|
89
|
+
const malformed = { '/': 'dxn:not-a-real-dxn' };
|
|
90
|
+
const replacer = Json.createRefReplacer({ db: makeStubDb({}) });
|
|
91
|
+
expect(stringifyWith(replacer, { ref: malformed })).toEqual({ ref: malformed });
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('leaves non-echo dxns untouched (e.g. type DXN)', () => {
|
|
95
|
+
// Type DXNs share the `dxn:` prefix but `asEchoDXN()` returns undefined.
|
|
96
|
+
const typeRef = { '/': DXN.make('com.example.Thing') };
|
|
97
|
+
const replacer = Json.createRefReplacer({ db: makeStubDb({}) });
|
|
98
|
+
expect(stringifyWith(replacer, { ref: typeRef })).toEqual({ ref: typeRef });
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('inlines refs inside arrays', () => {
|
|
102
|
+
const idA = newId();
|
|
103
|
+
const idB = newId();
|
|
104
|
+
const a = { name: 'a' };
|
|
105
|
+
const b = { name: 'b' };
|
|
106
|
+
const replacer = Json.createRefReplacer({ db: makeStubDb({ [idA]: a, [idB]: b }) });
|
|
107
|
+
expect(stringifyWith(replacer, { items: [encodeRef(idA), encodeRef(idB), { plain: true }] })).toEqual({
|
|
108
|
+
items: [a, b, { plain: true }],
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('walks nested objects recursively', () => {
|
|
113
|
+
const innerId = newId();
|
|
114
|
+
const inner = { name: 'inner' };
|
|
115
|
+
const replacer = Json.createRefReplacer({ db: makeStubDb({ [innerId]: inner }) });
|
|
116
|
+
const subject = { outer: { mid: { ref: encodeRef(innerId) } } };
|
|
117
|
+
expect(stringifyWith(replacer, subject)).toEqual({ outer: { mid: { ref: inner } } });
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('a single replacer invocation does not recurse on its own', () => {
|
|
121
|
+
// The replacer is per-call; JSON.stringify drives the tree walk. Calling the replacer
|
|
122
|
+
// directly on a cyclic input must therefore return without touching the cycle.
|
|
123
|
+
const replacer = Json.createRefReplacer({ db: makeStubDb({}) });
|
|
124
|
+
const node: any = { name: 'self' };
|
|
125
|
+
node.self = node;
|
|
126
|
+
|
|
127
|
+
expect(() => replacer('', node)).not.toThrow();
|
|
128
|
+
expect(replacer('', node)).toBe(node);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('invokes `toJSON` on resolved targets so refs in the target are re-walked', () => {
|
|
132
|
+
// Simulates the ECHO-object branch: `db.getObjectById` returns a live proxy, the replacer
|
|
133
|
+
// calls `.toJSON()` to get the encoded form, then continues walking that form. A ref nested
|
|
134
|
+
// inside the target should be inlined when there's depth budget remaining.
|
|
135
|
+
const outerId = newId();
|
|
136
|
+
const innerId = newId();
|
|
137
|
+
const inner = { name: 'inner' };
|
|
138
|
+
const target = {
|
|
139
|
+
toJSON: () => ({ nestedRef: encodeRef(innerId) }),
|
|
140
|
+
};
|
|
141
|
+
const replacer = Json.createRefReplacer({ db: makeStubDb({ [outerId]: target, [innerId]: inner }), depth: 2 });
|
|
142
|
+
expect(stringifyWith(replacer, { ref: encodeRef(outerId) })).toEqual({
|
|
143
|
+
ref: { nestedRef: inner },
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('depth budget counts ref hops, not tree depth — a ref deep in a plain tree still resolves', () => {
|
|
148
|
+
// A ref nested under arbitrarily many plain objects is one ref hop from the root, so
|
|
149
|
+
// `depth: 1` resolves it. `depth: 0` leaves it encoded.
|
|
150
|
+
const innerId = newId();
|
|
151
|
+
const inner = { name: 'inner' };
|
|
152
|
+
const subject = { a: { b: { c: { d: { ref: encodeRef(innerId) } } } } };
|
|
153
|
+
|
|
154
|
+
const inlining = Json.createRefReplacer({ db: makeStubDb({ [innerId]: inner }), depth: 1 });
|
|
155
|
+
expect(stringifyWith(inlining, subject)).toEqual({ a: { b: { c: { d: { ref: inner } } } } });
|
|
156
|
+
|
|
157
|
+
const passthrough = Json.createRefReplacer({ db: makeStubDb({ [innerId]: inner }), depth: 0 });
|
|
158
|
+
expect(stringifyWith(passthrough, subject)).toEqual(subject);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('inlines refs when invoked through safeStringify (production path)', () => {
|
|
162
|
+
// `JsonHighlighter` runs the replacer through `@dxos/util/safeStringify`, whose inner
|
|
163
|
+
// wrapper short-circuits the root call without forwarding it to the user's filter. The
|
|
164
|
+
// replacer must therefore work on a per-call basis — not as a one-shot root tree walk.
|
|
165
|
+
// This regression-tests that integration: the `content` ref must inline.
|
|
166
|
+
const targetId = newId();
|
|
167
|
+
const target = { toJSON: () => ({ name: 'README content' }) };
|
|
168
|
+
const document = { id: '01ABC', name: 'README', content: encodeRef(targetId) };
|
|
169
|
+
|
|
170
|
+
const replacer = Json.createRefReplacer({ db: makeStubDb({ [targetId]: target }) });
|
|
171
|
+
const out = JSON.parse(safeStringify(document, replacer, 0)!);
|
|
172
|
+
|
|
173
|
+
expect(out).toEqual({ id: '01ABC', name: 'README', content: { name: 'README content' } });
|
|
174
|
+
});
|
|
175
|
+
});
|
package/src/Json.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { EID } from '@dxos/keys';
|
|
6
|
+
|
|
7
|
+
import * as Database from './Database';
|
|
8
|
+
import * as Obj from './Obj';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* `JSON.stringify` replacer signature.
|
|
12
|
+
*
|
|
13
|
+
* Defined here (rather than re-imported from a UI package) so other ECHO-aware utilities can
|
|
14
|
+
* share a stable signature without creating a dependency edge into the UI tree.
|
|
15
|
+
*/
|
|
16
|
+
export type JsonReplacer = (key: string, value: any) => any;
|
|
17
|
+
|
|
18
|
+
export type CreateRefReplacerOptions = {
|
|
19
|
+
db: Database.Database;
|
|
20
|
+
/** How many ref hops to follow. `0` leaves all refs as-is. Default: `1`. */
|
|
21
|
+
depth?: number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const isEncodedRef = (value: unknown): value is { '/': string } =>
|
|
25
|
+
typeof value === 'object' &&
|
|
26
|
+
value !== null &&
|
|
27
|
+
Object.keys(value as object).length === 1 &&
|
|
28
|
+
typeof (value as { '/': unknown })['/'] === 'string';
|
|
29
|
+
|
|
30
|
+
const toJson = (obj: Obj.Any): unknown => (typeof (obj as any).toJSON === 'function' ? (obj as any).toJSON() : obj);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Returns a {@link JsonReplacer} that inlines ECHO ref objects (`{ "/": "echo:..." }`) up to
|
|
34
|
+
* `depth` ref hops. Beyond that depth refs are left in their encoded form.
|
|
35
|
+
*
|
|
36
|
+
* Implemented as a per-call `JSON.stringify` replacer (not a one-shot tree walk at root) so it
|
|
37
|
+
* composes with wrappers like `safeStringify` that intercept the root call. JSON.stringify
|
|
38
|
+
* already drives the recursion; we only need to (a) detect a ref at the current callback,
|
|
39
|
+
* (b) resolve and return the target if hop budget remains, and (c) tag the returned object
|
|
40
|
+
* with its hop count so children know how far in they are.
|
|
41
|
+
*
|
|
42
|
+
* The hop count is tracked per-object via a `WeakMap`: a ref-resolved target's children inherit
|
|
43
|
+
* `parentHops + 1`; a regular intermediate object's children inherit `parentHops`. This makes the
|
|
44
|
+
* budget count *ref hops*, not tree depth — a ref deep in a tree still resolves once when
|
|
45
|
+
* `depth >= 1`.
|
|
46
|
+
*
|
|
47
|
+
* Note: ECHO objects' `toJSON` runs before the replacer is invoked, so by the time we see a
|
|
48
|
+
* value refs are already encoded as `{ "/": "dxn:..." }`.
|
|
49
|
+
*/
|
|
50
|
+
export const createRefReplacer = ({ db, depth = 1 }: CreateRefReplacerOptions): JsonReplacer => {
|
|
51
|
+
// Per-object hop count. Set when we return an object (via ref resolution or pass-through) so
|
|
52
|
+
// the child callbacks (which carry that object as `this`) can read it.
|
|
53
|
+
const hops = new WeakMap<object, number>();
|
|
54
|
+
|
|
55
|
+
return function (this: any, key: string, value: any) {
|
|
56
|
+
// Hop count for this call: hops at the parent, or 0 for the root.
|
|
57
|
+
const parentHops = this && typeof this === 'object' ? (hops.get(this) ?? 0) : 0;
|
|
58
|
+
if (isEncodedRef(value)) {
|
|
59
|
+
if (parentHops >= depth) {
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// The `{ '/': string }` shape is shared with non-DXN IPLD-style refs (e.g. CIDs);
|
|
64
|
+
// an unparseable string would otherwise crash the whole `JSON.stringify`.
|
|
65
|
+
// Treat any parse miss as "leave as-is" rather than propagating.
|
|
66
|
+
const dxnString = value['/'];
|
|
67
|
+
if (!dxnString.startsWith('dxn:') && !dxnString.startsWith('echo:')) {
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let echoUri: string | undefined;
|
|
72
|
+
try {
|
|
73
|
+
const parsed = EID.tryParse(dxnString);
|
|
74
|
+
echoUri = parsed ? EID.getEntityId(parsed) : undefined;
|
|
75
|
+
} catch {
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!echoUri) {
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
const target = db.getObjectById(echoUri);
|
|
83
|
+
if (!target) {
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const encoded = toJson(target);
|
|
88
|
+
if (encoded != null && typeof encoded === 'object') {
|
|
89
|
+
// Children of the resolved target are one hop deeper.
|
|
90
|
+
hops.set(encoded as object, parentHops + 1);
|
|
91
|
+
}
|
|
92
|
+
return encoded;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Pass-through object: children inherit the parent's hop count (this branch doesn't burn
|
|
96
|
+
// budget).
|
|
97
|
+
if (value != null && typeof value === 'object') {
|
|
98
|
+
hops.set(value, parentHops);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return value;
|
|
102
|
+
};
|
|
103
|
+
};
|
package/src/Key.ts
CHANGED
package/src/Migration.ts
CHANGED
|
@@ -6,23 +6,41 @@
|
|
|
6
6
|
|
|
7
7
|
import type * as Schema from 'effect/Schema';
|
|
8
8
|
|
|
9
|
-
import { type
|
|
9
|
+
import { type URI } from '@dxos/keys';
|
|
10
10
|
|
|
11
11
|
import type * as Database from './Database';
|
|
12
12
|
import type * as Entity from './Entity';
|
|
13
|
-
import {
|
|
13
|
+
import { MetaId, type EntityMeta, getSchemaURI } from './internal';
|
|
14
|
+
import * as Type from './Type';
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Result returned by a migration's `transform` callback.
|
|
18
|
+
* The data shape matches the target schema; the optional `[Obj.Meta]` symbol key lets the
|
|
19
|
+
* transform update the object's meta (e.g. `key` / `version`) atomically with the data swap.
|
|
20
|
+
*/
|
|
21
|
+
type MigrationSchemaInput = Type.AnyEntity;
|
|
22
|
+
|
|
23
|
+
type MigrationInstanceType<S> = S extends Type.AnyEntity
|
|
24
|
+
? Type.InstanceType<S>
|
|
25
|
+
: S extends Schema.Schema.AnyNoContext
|
|
26
|
+
? Schema.Schema.Type<S>
|
|
27
|
+
: never;
|
|
28
|
+
|
|
29
|
+
export type TransformResult<To> = Omit<MigrationInstanceType<To>, 'id' | Entity.KindId> & {
|
|
30
|
+
[MetaId]?: Partial<EntityMeta>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type DefineObjectMigrationOptions<From extends MigrationSchemaInput, To extends MigrationSchemaInput> = {
|
|
16
34
|
from: From;
|
|
17
35
|
to: To;
|
|
18
36
|
/**
|
|
19
37
|
* Pure function that converts the old object data to the new object data.
|
|
38
|
+
*
|
|
39
|
+
* The returned object may include an optional `[Obj.Meta]` entry to update the object's meta
|
|
40
|
+
* (e.g. registry `key` / `version`) atomically with the data swap.
|
|
20
41
|
*/
|
|
21
42
|
// TODO(dmaretskyi): `id` should not be a part of the schema.
|
|
22
|
-
transform: (
|
|
23
|
-
from: Schema.Schema.Type<From>,
|
|
24
|
-
context: ObjectMigrationContext,
|
|
25
|
-
) => Promise<Omit<Schema.Schema.Type<To>, 'id' | Entity.KindId>>;
|
|
43
|
+
transform: (from: MigrationInstanceType<From>, context: ObjectMigrationContext) => Promise<TransformResult<To>>;
|
|
26
44
|
|
|
27
45
|
/**
|
|
28
46
|
* Callback that is called after the object is migrated. Called for every object that is migrated.
|
|
@@ -30,7 +48,7 @@ type DefineObjectMigrationOptions<From extends Schema.Schema.AnyNoContext, To ex
|
|
|
30
48
|
* NOTE: Database mutations performed in this callback are not guaranteed to be idempotent.
|
|
31
49
|
* If multiple peers run the migration separately, the effects may be applied multiple times.
|
|
32
50
|
*/
|
|
33
|
-
onMigration
|
|
51
|
+
onMigration?: (params: OnMigrateProps<From, To>) => Promise<void>;
|
|
34
52
|
};
|
|
35
53
|
|
|
36
54
|
/**
|
|
@@ -40,9 +58,9 @@ export type ObjectMigrationContext = {
|
|
|
40
58
|
db: Database.Database;
|
|
41
59
|
};
|
|
42
60
|
|
|
43
|
-
type OnMigrateProps<From
|
|
44
|
-
before:
|
|
45
|
-
object:
|
|
61
|
+
type OnMigrateProps<From, To> = {
|
|
62
|
+
before: MigrationInstanceType<From>;
|
|
63
|
+
object: MigrationInstanceType<To>;
|
|
46
64
|
db: Database.Database;
|
|
47
65
|
};
|
|
48
66
|
|
|
@@ -50,12 +68,12 @@ type OnMigrateProps<From extends Schema.Schema.AnyNoContext, To extends Schema.S
|
|
|
50
68
|
* Definition of a migration from one object schema version to another.
|
|
51
69
|
*/
|
|
52
70
|
export type ObjectMigration = {
|
|
53
|
-
fromType:
|
|
54
|
-
toType:
|
|
71
|
+
fromType: URI.URI;
|
|
72
|
+
toType: URI.URI;
|
|
55
73
|
fromSchema: Schema.Schema.AnyNoContext;
|
|
56
74
|
toSchema: Schema.Schema.AnyNoContext;
|
|
57
75
|
transform: (from: unknown, context: ObjectMigrationContext) => Promise<unknown>;
|
|
58
|
-
onMigration
|
|
76
|
+
onMigration?: (params: OnMigrateProps<any, any>) => Promise<void>;
|
|
59
77
|
};
|
|
60
78
|
|
|
61
79
|
/**
|
|
@@ -71,14 +89,16 @@ export type ObjectMigration = {
|
|
|
71
89
|
* });
|
|
72
90
|
* ```
|
|
73
91
|
*/
|
|
74
|
-
export const define = <From extends
|
|
92
|
+
export const define = <From extends MigrationSchemaInput, To extends MigrationSchemaInput>(
|
|
75
93
|
options: DefineObjectMigrationOptions<From, To>,
|
|
76
94
|
): ObjectMigration => {
|
|
77
|
-
const
|
|
95
|
+
const fromSchema = Type.getSchema(options.from);
|
|
96
|
+
const toSchema = Type.getSchema(options.to);
|
|
97
|
+
const fromType = getSchemaURI(fromSchema);
|
|
78
98
|
if (!fromType) {
|
|
79
99
|
throw new Error('Invalid from schema');
|
|
80
100
|
}
|
|
81
|
-
const toType =
|
|
101
|
+
const toType = getSchemaURI(toSchema);
|
|
82
102
|
if (!toType) {
|
|
83
103
|
throw new Error('Invalid to schema');
|
|
84
104
|
}
|
|
@@ -86,8 +106,8 @@ export const define = <From extends Schema.Schema.AnyNoContext, To extends Schem
|
|
|
86
106
|
return {
|
|
87
107
|
fromType,
|
|
88
108
|
toType,
|
|
89
|
-
fromSchema
|
|
90
|
-
toSchema
|
|
109
|
+
fromSchema,
|
|
110
|
+
toSchema,
|
|
91
111
|
transform: options.transform as any,
|
|
92
112
|
onMigration: options.onMigration as any,
|
|
93
113
|
};
|