Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specifications #1

Closed
MangelMaxime opened this issue Sep 8, 2022 · 21 comments
Closed

Specifications #1

MangelMaxime opened this issue Sep 8, 2022 · 21 comments

Comments

@MangelMaxime
Copy link
Member

MangelMaxime commented Sep 8, 2022

Introduction

Finding Fable packages has always been a difficult tasks.

Several solutions have been tried to try fixing this problem:

But both of this project had flows, they require external actions beside pushing the code to NuGet in order to make it available. This makes them easily outdated and would require additional work from the package author when they are already have more important stuff to focus on.

A tool to rule unify them all

Hosted under: https://fable.io/packages

The idea behind this tool is to use the NuGet API to find all the Fable packages. This means the list is always up to date with what is published on NuGet.org.

The only requirements is going to add the right tags to the published packages.

There are 2 types of Fable packages:

  1. Libraries

    A library is a collections of pre-written code that user can use.

    Example: Elmish, Feliz, Thoth.Json.

  2. Bindings

    A binding is just here to expose native API to F# world. Sometimes it can also contains some helper code to unwrap/wrap Union types for examples.

    For example, a binding can expose the Browser API to F# world so user can write document.querySelector(...)

    Example: Fable.Browser.*, Glutinum.Fuse

Both libraries or bindings can be:

  • Runtime agnostics works for (.NET, JavaScript, Python, Rust, etc.)

    Examples:
    - Fable.Core
    - FsToolkit.ErrorHandling

  • Runtime specific

    • Thoth.Json only works for JavaScript
    • Thoth.Json.Net only works for .NET

Tags are going to be used to identify if a package is a binding or a library and what runtime is supported

Tags

<target> can be all or the full name of the language

Libraries

Format: fable:<target>

Supported tags:

  • fable:all
  • fable:javascript
  • fable:python
  • fable:rust
  • fable:dart

Bindings

Format: fable-binding:<target>

Supported tags:

  • fable-binding:all
  • fable-binding:javascript
  • fable-binding:python
  • fable-binding:rust
  • fable-binding:dart

Example:

If a library support both Python and JavaScript, then the NuGet package will be published with:

<PackageTags>fable:python;fable:javascript</PackageTags>

Notes

Contrary to what I have said in the past, I don't think we should have a fable tag.

With the proposed tags I think we can support the following scenarios:

  1. Find all Fable related packages search using fable
  2. Find all Fable bindings search using search using fable-binding
  3. Find all Fable libraries for JavaScript search using fable:javascript
  4. Find all Fable bindings for JavaScript search using fable-binding:javascript
  5. Find all Fable packages to working for JavaScript search using fable:javascript AND fable-binding:javascript
@MangelMaxime
Copy link
Member Author

@alexswan10k
Copy link

alexswan10k commented Sep 8, 2022

This looks great.

Couple of minor observations

  • I am not sure fable-binding:all makes sense. To the best of my knowledge, bindings are always bespoke to a particular package for a target ecosystem. I can't think of any examples where there are packages that could share a common api. The only one that springs to mind is something like Rx and RxJs perhaps, which do differ somewhat already.
  • It might be worth clarifying exactly when you are safe to tag your package with fable:all in preference to fable:<target('s)>. In a nutshell I believe it is roughly as follows:
    • Package has no references to Emit
    • Package does not call target-specific code with import or importAll such as .js/.ts/.py/.rs files
    • Package has no dependencies to packages that are not fable:all
    • Package does not use target specific namespaces eg Fable.Core.Rust... or Fable.Core.Js...
    • Edit - Package does not use conditional compilation for enabling besoke code for each target eg ```#if FABLE_JAVASCRIPT
    • Package ships with source .fs files (true for all I guess)
  • On the legacy fable tag, keeping the old tag is probably harmless, but I do agree they probably should no longer be used for searching, as the new tags provide far better granularity.
  • Not really given this much thought, but Is it worth considering if there is overlap between typescript and javascript? They are technically separate targets but share the same library & one is a superset of the other etc.

@MangelMaxime
Copy link
Member Author

  • I am not sure fable-binding:all makes sense. To the best of my knowledge, bindings are always bespoke to a particular package for a target ecosystem. I can't think of any examples where there are packages that could share a common api. The only one that springs to mind is something like Rx and RxJs perhaps, which do differ somewhat already.

Originally, there was a note to tell that this tag probably will never exist in reality.

It might be worth clarifying exactly when you are safe to tag your package with fable:all in preference to fable:<target('s)>. In a nutshell I believe it is roughly as follows:

  • Package has no references to Emit

  • Package does not call target-specific code with import or importAll such as .js/.ts/.py/.rs files

  • Package has no dependencies to packages that are not fable:all

  • Package does not use target specific namespaces eg Fable.Core.Rust... or Fable.Core.Js...

  • Package ships with source .fs files (true for all I guess)

I don't think this is true because you can use compiler directives if you need to shadow/mock API from different target.

Imagine, you are creating a Fable.IO library compatible with all the targets. You will have target specific code hidden behind code like:

let readFile (filePath : string) =
#if FABLE_JAVASCRIPT
	node.api.readFileSync(filePath)
#endif
#if FABLE_RUST
	rust.readFile(filePath) // Pseudo code as I don't know the rust instruction
#endif
// etc.

In the documentation, I think we will teach library authors which tag to use.

  • On the legacy fable tag, keeping the old tag is probably harmless, but I do agree they probably should no longer be used for searching, as the new tags provide far better granularity.

The old tag is harmless indeed, it just doesn't allow for precise search.

  • Not really given this much thought, but Is it worth considering if there is overlap between typescript and javascript? They are technically separate targets but share the same library & one is a superset of the other etc.

I know I was forgetting one language in my list but couldn't find which one ahah.

Hum, well theory any package written fable:javascript is compatible with TypeScript target and vice versa.

I think it is would be easier to make only fable:javascript exist as this is the actual runtime.
For JavaScript/TypeScript language distinction, I believe this more for the end user to decide which file he needs for his use case.

@alexswan10k
Copy link

I think this makes sense. All packages are basically javascript with potential typescript type annotations, so I guess for a binding, it would always be js. With supported languages though, this is a little more vague since Emit can now export type information. Type information will obviously not compile if you are targeting javascript and webpack-ing .js files.

With respect to conditional compilation, I do not think it is best to encourage package authors to tag with fable:all, because this method explicitly requires code changes in order to support yet-to-be-invented future targets. My intent here was that an existing package will just work out of the box with any future language/target without any code change or package version bump.

For compilation switches, I would suggest authors explicitly tag the languages they support (fable:js, fable:rust etc). As they roll out new switches, they can extend the list of tags accordingly. This also assures each version will have exactly the tags matching their supported targets, and nothing is assumed.

@MangelMaxime
Copy link
Member Author

Testing how tag search work on NuGet.org is actually possible with their website/URL.

On their website, you can use Tags:"fable" in the search box to search by tag.

image

The corresponding URL is https://www.nuget.org/packages?q=Tags%3A%22fable%22

This discovery made me remove the part of the section

Contrary to what fable-compiler/Fable#3089 (comment), I don't think we should have a fable tag.

My reasoning behind that is I am not sure how NuGet search engine works but if it works with a "contains" rules and we are looking for packages with tag: fable it is possible that fable:javascript AND fable-binding:javascript are both a match.

Because the rules for tags doesn't seems to be contains but exact.

If you search for the tag fabl (not having the last e is voluntary), you see that no packages are found.

image

Also if you search using 2 tags the search seems to be an OR and sort the result by relevance but even if Fable.Core is the package which has both tags it is not ranked first.

image


With this new discovery I think in order to have a user experience which allow to search by:

  • Find Fable related packages
  • Package type (binding or library)
  • Target (JavaScript, Rust, Python, etc.)
  • Package type and target

we are going to have specialised tags.

Fable

Use the tag fable

Package type tags

The packages should have one of these two tags to make it possible search by package type:

  • fable-library
  • fable-binding

Target tags

  • fable-all
  • fable-javascript
  • fable-rust
  • fable-python
  • fable-dart

If we want to be really declarative we could go with fable-target-<lang> but I suppose that fable-<lang> is enough.

Package & Target tags

  • fable-library-all

  • fable-library-javascript

  • fable-library-rust

  • fable-library-python

  • fable-library-dart

  • fable-binding-all

  • fable-library-javascript

  • fable-library-rust

  • fable-library-python

  • fable-library-dart

Tests the search scenario

  • Find all Fable related packages search using the tag fable
  • Find all Fable bindings search using search using the tag fable-binding
  • Find all Fable libraries for JavaScript search using the tag fable-library-javascript
  • Find all Fable bindings for JavaScript search using the tag fable-binding-javascript
  • Find all Fable packages working for JavaScript search using fable-javascript

I used - separator everywhere for consistency but perhaps using : before the language is possible.

Example: fable-library:rust

User experience

In regard of the user experience when using https://fable.io/packages tool the complexity/verbosity of the tags are going to be hidden.

We will not propose to the users the different tags but more general search option like:

  • Package type: Library / Binding
  • Target: JavaScript / Rust / Python

By default, the tool will search for package with the tag fable as we don't know what the user want.

If the user select Package type: Library we search for tag fable-library

If the user select Target: JavaScript we search for tag fable-javascript

If the user select Package type: Library & Target: JavaScript we search for tag fable-library-javascript

If the user select Package type: Library & Target: JavaScript & Target: Rust we search for tag fable-library-javascript fable-library-rust.

Author experience

For the author, the complexity cannot really be hidden but we can work on having great 😊 documentation.

And we can provide a similar tool as for the standard user search, which ask the author what his library does and generate the tags to add to the package.

@MangelMaxime
Copy link
Member Author

With respect to conditional compilation, I do not think it is best to encourage package authors to tag with fable:all, because this method explicitly requires code changes in order to support yet-to-be-invented future targets. My intent here was that an existing package will just work out of the box with any future language/target without any code change or package version bump.

For compilation switches, I would suggest authors explicitly tag the languages they support (fable:js, fable:rust etc). As they roll out new switches, they can extend the list of tags accordingly. This also assures each version will have exactly the tags matching their supported targets, and nothing is assumed.

You are right indeed, I was not thinking far enough in the future.

@alexswan10k
Copy link

alexswan10k commented Sep 8, 2022

Just made a discovery. Looks like you can do And with the following query:

Tags:"fable","javascript","js"

Perhaps composition is an option after all

@MangelMaxime
Copy link
Member Author

@alexswan10k Nice find.

Indeed, if we can do AND query I think we can we can go back to the original specification.

Ironically, there is one target that we kept forget which is dotnet runtime for the library.

For example, FsToolkit.ErrorHandling works both on JavaScript and dotnet currently. And I believe it will also work for all the others targets too.

@dbrattli
Copy link

dbrattli commented Sep 8, 2022

This sounds like a great idea! I've been unsure if I should call my packages Fable.Giraffe, Fable.Giraffe.Python, Giraffe.Python or even Fable.Pyraffe but these tags should do the trick!

@MangelMaxime
Copy link
Member Author

@dbrattli In your case, I would probably go with Fable.Giraffe.Python or Giraffe.Python for the name because I suppose your implementation is python specific.

Fable.Giraffe kind of imply that it will works for all Fable target to me.

But indeed, the tags will help us know what a package does and make it easier to discover them (I hope so).

@MangelMaxime
Copy link
Member Author

I started the work on the website and the more I think about it the more I like my second proposition with specialised tags.

Fable

Use the tag fable

Package type tags

The packages should have one of these two tags to make it possible search by package type:

* `fable-library`
* `fable-binding`

Target tags

* `fable-all`
* `fable-javascript`
* `fable-rust`
* `fable-python`
* `fable-dart`

This also make it really easy to implements the query generation:

[<RequireQualifiedAccess>]
type Target =
    | Dotnet
    | JavaScript
    | Rust
    | Python
    | Dart
    | All

type SearchFormResult = {
    SearchForBinding: bool
    SearchForLibrary: bool
    Targets: Target list
}

let targetToTag (target : Target) =
    match target with
    | Target.Dotnet -> "fable-dotnet"
    | Target.JavaScript -> "fable-javascript"
    | Target.Rust -> "fable-rust"
    | Target.Python -> "fable-python"
    | Target.Dart -> "fable-dart"
    | Target.All -> "fable-all"

let searchToUrl (request : SearchFormResult) =
    let tags =
        [
            "fable"
            if request.SearchForBinding then
                "fable-binding"
            if request.SearchForLibrary then
                "fable-library"
            yield! List.map targetToTag request.Targets
        ]
        |> List.map (fun tag ->
            $"\"%s{tag}\""
        )
        |> String.concat ","
        |> window.encodeURIComponent

    $"https://azuresearch-usnc.nuget.org?q=Tags%%3A%s{tags}"

If the user select Package type: Library we search for tag fable-library

If the user select Target: JavaScript we search for tag fable-javascript

If the user select Package type: Library & Target: JavaScript we search for tag fable-library fable-javascript

If the user select Package type: Library & Target: JavaScript & Target: Rust we search for tag fable-library fable-javascript fable-rust.


We could probably simplify fable-<lang> to <lang>

Example: fable-javascript --> javascript

But I kind of like the fable prefix.

@MangelMaxime
Copy link
Member Author

Today, I was able to make great progress on designing the interface for the tool.

Not everything is functional yet, but I think the design is starting to look good. And is enough to give you an idea of the direction I took.

fable_packages_demo.mp4

@MangelMaxime
Copy link
Member Author

Just made a discovery. Looks like you can do And with the following query:

Tags:"fable","javascript","js"

Perhaps composition is an option after all

It looks like this syntax is actually not doing a "AND query over the tags".

But looking for package with the tag fable and then a search in the title/description (perhaps somewhere too) with the other provided term.

I released a version of Glutinum.Fuse with one of the suggestion I made for the naming to give it a try.

image

The tags applied to the package are:

  • fable
  • fable-binding
  • fable-javascript

If you query for Tags:"fable","fable-binding" you will see that there are 100 match on the NuGet site.

image

If you query for Tags:"fable",Tags:"fable-binding" you get 360 and Glutinum.Fuse is not in the first position.

image

If you query for Tags:"fable-binding",Tags:"fable" or Tags:"fable-binding" you get 1 match which is Glutinum.Fuse. So I think it take into consideration only 1 tag.

I tried other syntaxes like Tags:"fable-binding,fable" without much success.


NuGet v3 API is not Rate limited so I guess we are going to need to handle search on our side.

image

Source

My idea is to use the search syntax for what works well like searching in Text + options like "include prerelease" etc. And then once we get that result, fetch all the packages informations and apply the tag filters on the client side.

I will make a naive implementation to test this idea. If needed later it is always possible to add caching mechanism etc.

@MangelMaxime
Copy link
Member Author

Hello,

My suspicion about AND queries not working on tags has been confirmed by the NuGet team.

During the last days I made a prototype which export the search responsibility to the user browser.

fable_packages_indexing_demo.mp4

Indexing is taking a bit of time around 10 sec here for 360 packages. It is possible to reduce the time by sending more request in parallel but I think having a progress bar instead of a generic loading animation is better for the user.

Also, I am planning to use Web Worker to cache the request result so on future visit the indexing should be much faste.

Last point, my vision for the tool is that in theory user will not have to go to NuGet.org website as we should be able to provide the important information on Fable.Packages directly 😇

There are a few limitations that exist but in theory we have some time before reaching them. The limitation will be documented both in the code and in the README or an issue.

@alexswan10k
Copy link

alexswan10k commented Sep 15, 2022

Hmm interesting.

So to be precise, they are not actually saying that it is OR. They are saying each term is independently scored and aggregated (lucene query behaviour), so adding two terms Tag:A, Tag:B means each tag contributes to the score. If this is accurate, you do not need an absolute filter such as AND, you simply need something with multiple matched terms to be scored higher, so these results appear at the top. According to the response, this appears to be the case. What I believe is confusing matters is the fact the download count is massively contributing to the score also, thus throwing off the results.

It would be nice to have an additional filter to throw out everything non-fable, but I guess this gets us into the And/Or dilemma discussed in the response. I am not sure this is the end of the world though since the fundamental behaviour for scoring tags is expected.

As to keeping an index of the whole nuget repository locally - it's a nice POC, but not something I would generally recommend unless there really is no other way, as there are a lot of moving parts to go wrong.

Looking good other than that though.

@MangelMaxime
Copy link
Member Author

As to keeping an index of the whole nuget repository locally - it's a nice POC, but not something I would generally recommend unless there really is no other way, as there are a lot of moving parts to go wrong.

We don't have any other way to filter correctly the packages. Their search engine gives too much weight to the download count and because it doesn't do an AND on field scoped term this cause problem.

The main query which retrieve all the Fable tagged package is never cached and will always be up to date with NuGet repository when the user visit the website. That query currently takes around one second to execute, we are forced to do it in one query because of when using the pagination the retrieved list of packages is not stable and you end up with duplicates and missing packages...

The response that are going to be cached is the response for a specific version of a package because we don't have access to the Published date in the response of the main search query. These information are pretty stable because the only thing that can change from a published packages is if it is listed or unlisted.

Because we are going to use a stale while re-validate cache this should not cause any problem.

@MangelMaxime
Copy link
Member Author

I just realised a version of Fable.Packages online.

You can find it here https://fable.io/packages/

This version has more or less all the features I could imagine. It will also serve as a V1 to gather feedbacks and see what features needs improvement or is missing.

V2 will be a complete rewrite of the project using a server to fix the performance problem we have with the indexing process.

I will take the time in the next days to write the specification that has been implemented. And also the list of all the features that this tools has.

This will serve a base for the V2 rewrite and help keep track of if something is supported or not.

@alexswan10k
Copy link

Looks great. Indexing is not as slow as I expected actually, although 350 packages is not exactly a big dataset, so if it grows substantially I guess the V2 bit is more interesting.

Nice work.

@MangelMaxime
Copy link
Member Author

I published a new version which has a really basic cache which works for the duration of the tab life.

Previously, when the user was doing:

  1. Access https://fable.io/packages/
  2. Go to a package
  3. Click on a tag
  4. User is on https://fable.io/packages/ once again

Then he was having the indexing process happen twice (on step 1 & 4).

Now, it will only happen on step 1 and not on step 4.

If the user reload the tab, the cache is destroyed.

@MangelMaxime
Copy link
Member Author

Documentation about how the tags system works is available here

fable-compiler/fable-compiler.github.io#142

@MangelMaxime
Copy link
Member Author

Closing as the specification is now hosted on Fable docs and the announcement will be made tomorrow

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants