Compare commits

...

14 Commits

Author SHA1 Message Date
Bram 3b927dc460 Update exposed Vault to match internal Vault 2023-12-18 02:11:31 +01:00
Bram ce0f96a74e Merge branch 'develop' into 2-transfer-tools
This operation merges the new update from the `develop` branch in order to get the latest changes.
2023-12-18 02:05:13 +01:00
BramvdnHeuvel 603057568d
Merge pull request #1 from noordstar/1-repo-setup
Task 1: finish repository setup
2023-12-18 02:00:30 +01:00
Bram 2fd2eb4e5a Fix broken link 2023-12-18 01:57:14 +01:00
Bram ee1012f783 Add VSCode workspace settings to .gitignore 2023-12-18 01:34:00 +01:00
Bram 29f9482b74 Add public settings interface 2023-12-18 01:32:57 +01:00
Bram 8b7b2aa312 Add internal Vault type 2023-12-18 01:32:21 +01:00
Bram 5293eb4003 Add Envelope function to extract settings values 2023-12-18 01:30:15 +01:00
Bram f910c0225b Add Envelope JSON coders 2023-12-18 01:08:40 +01:00
Bram 848d83a18e Add JSON coder helper files 2023-12-17 21:29:45 +01:00
Bram 1b0be9bffa Add Envelope type 2023-12-17 15:18:54 +01:00
Bram 70d44c2cb6 Add guidelines to merging branches 2023-12-16 01:49:13 +01:00
Bram van den Heuvel 31d176d110 Add CONTRIBUTING.md 2023-12-15 18:29:25 +01:00
Bram van den Heuvel 037e598b36 elm-format 2023-12-15 15:05:48 +01:00
13 changed files with 696 additions and 4 deletions

2
.gitignore vendored
View File

@ -2,3 +2,5 @@
elm-stuff
# elm-repl generated files
repl-temp-*
# VScode settings
.vscode/

View File

@ -32,3 +32,8 @@ elm install noordstar/elm-matrix-sdk-beta
Keep in mind that the beta versions are intended to develop rapidly. You should
not expect the versions to remain reliable for years! If you need a stable
version, please wait around for a full version.
## Contribute
If you wish to contribute, please read the
[contribution guide](docs/CONTRIBUTING.md).

71
docs/CONTRIBUTING.md Normal file
View File

@ -0,0 +1,71 @@
# Contributing to elm-matrix-sdk-beta
Welcome to the elm-matrix-sdk-beta repository! We appreciate your interest in
contributing. Please take a moment to review the following guidelines.
## Table of Contents
1. [How to Contribute](#how-to-contribute)
2. [Bug Reports](#bug-reports)
3. [Code Contributions](#code-contributions)
4. [Documentation Improvements](#documentation-improvements)
5. [Feedback and Tips](#feedback-and-tips)
6. [Development Environment](#development-environment)
7. [Pull Requests](#pull-requests)
8. [Communication](#communication)
9. [License](#license)
---
## How to Contribute
We welcome various forms of contributions, including bug reports, code
contributions through pull requests from forks, suggestions for documentation
improvement, and helpful tips and feedback based on user experience.
## Bug Reports
When reporting bugs, please provide as much detail as possible, including steps
to reproduce, expected behavior, actual behavior, and details about your
environment.
## Code Contributions
1. Fork the repository.
2. Create a new branch from the `develop` branch.
3. Write your code and commit changes.
4. Push your branch to your fork.
5. Submit a pull request to the `develop` branch.
## Documentation Improvements
Feel free to suggest improvements to the documentation. Ensure that your
suggestions are clear and concise.
## Feedback and Tips
We appreciate feedback, tips, and suggestions based on user experience. Share
your thoughts to help us enhance the project.
## Development Environment
To set up your development environment:
1. Install Elm.
2. Use `elm-format` to format your Elm code.
3. Run `elm make --docs=docs.json` to generate documentation.
4. View documentation using an Elm documentation viewer (e.g., [elm-doc-preview](https://elm-doc-preview.netlify.app/)).
5. Expose modules in `elm.json` for documentation.
## Pull Requests
Create a fork, write your code, and submit a pull request to the `develop` branch.
## Communication
- Mastodon: [@elm_matrix_sdk@social.noordstar.me](https://social.noordstar.me/@elm_matrix_sdk)
- Matrix: [#elm-sdk:matrix.org](https://matrix.to/#/#elm-sdk:matrix.org)
## License
This project is licensed under the [EUPL-v1.2](LICENSE). Please review the license file for more details.

29
docs/before-merge.md Normal file
View File

@ -0,0 +1,29 @@
# Before merging to main
⚠️ **Hold up!** Before you merge that pull request, make sure to follow this checklist!
## Any branch to `develop`
If you wish to merge your branch to the `develop` branch, make sure to follow this checklist:
- [ ] Run `elm-format` to ensure the correct formatting of the Elm files.
- [ ] Use `elm-doc-preview` to verify whether the documentation is up to standards.
## The `develop` branch to `main`
The `develop` branch is the only branch that's allowed to merge to `main`. Once the branch merges to `main`, that indicates a new release on the Elm registry.
Before that is being done, however, the following tasks should be done:
- [ ] Run `elm-format` to ensure the correct formatting of the Elm files.
- [ ] Use `elm-doc-preview` to verify whether the documentation is up to standards.
- [ ] Remove exposed modules from `elm.json` that do not need to be exposed modules in the release.
- [ ] Run `elm bump` to update the library's version number
- [ ] Update the version name in the [default values config file](../src/Internal/Config/Default.elm).
## Any branch to any other branch
There are no limitations to merging other branches towards one another, although it is important to keep in mind that:
- Contributors are advised to merge the `develop` branch into their branches regularly to avoid any merge conflicts.
- Merging with branches that haven't been accepted (yet) might result in your branch ending up with code that will not be accepted.

View File

@ -6,12 +6,18 @@
"version": "1.0.0",
"exposed-modules": [
"Matrix",
"Matrix.Settings",
"Internal.Config.Default",
"Internal.Config.Text",
"Internal.Tools.Decode",
"Internal.Tools.Encode",
"Internal.Tools.Hashdict",
"Internal.Tools.Iddict",
"Internal.Tools.Timestamp",
"Internal.Tools.VersionControl"
"Internal.Tools.VersionControl",
"Internal.Values.Envelope",
"Internal.Values.Vault",
"Types"
],
"elm-version": "0.19.0 <= v < 0.20.0",
"dependencies": {

View File

@ -42,7 +42,7 @@ the Elm SDK tolerates being held on hold.
- A high value is good because it significantly reduces traffic between the
user and the homeserver.
- A low value is good because it refuces the risk of
- A low value is good because it reduces the risk of
the connection ending abruptly or unexpectedly.
Nowadays, most libraries use 30 seconds as the standard, as does the Elm SDK.

View File

@ -0,0 +1,155 @@
module Internal.Tools.Decode exposing
( opField, opFieldWithDefault
, map9, map10, map11
)
{-|
# Decode module
This module contains helper functions that help decode JSON.
## Optional field decoders
@docs opField, opFieldWithDefault
## Extended map functions
@docs map9, map10, map11
-}
import Json.Decode as D
{-| Add an optional field decoder. If the field exists, the decoder will fail
if the field doesn't decode properly.
This decoder standard out from `D.maybe <| D.field fieldName decoder` because
that will decode into a `Nothing` if the `decoder` fails. This function will
only decode into a `Nothing` if the field doesn't exist, and will fail if
`decoder` fails.
The function also returns Nothing if the field exists but it is null.
-}
opField : String -> D.Decoder a -> D.Decoder (Maybe a)
opField fieldName decoder =
D.value
|> D.field fieldName
|> D.maybe
|> D.andThen
(\v ->
case v of
Just _ ->
D.oneOf
[ D.null Nothing
, D.map Just decoder
]
|> D.field fieldName
Nothing ->
D.succeed Nothing
)
{-| Add an optional field decoder. If the field is not given, the decoder will
return a default value. If the field exists, the decoder will fail if the field
doesn't decode properly.
-}
opFieldWithDefault : String -> a -> D.Decoder a -> D.Decoder a
opFieldWithDefault fieldName default decoder =
opField fieldName decoder |> D.map (Maybe.withDefault default)
{-| Try 9 decoders and combine the result.
-}
map9 :
(a -> b -> c -> d -> e -> f -> g -> h -> i -> value)
-> D.Decoder a
-> D.Decoder b
-> D.Decoder c
-> D.Decoder d
-> D.Decoder e
-> D.Decoder f
-> D.Decoder g
-> D.Decoder h
-> D.Decoder i
-> D.Decoder value
map9 func da db dc dd de df dg dh di =
D.map8
(\a b c d e f g ( h, i ) ->
func a b c d e f g h i
)
da
db
dc
dd
de
df
dg
(D.map2 Tuple.pair dh di)
{-| Try 10 decoders and combine the result.
-}
map10 :
(a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> value)
-> D.Decoder a
-> D.Decoder b
-> D.Decoder c
-> D.Decoder d
-> D.Decoder e
-> D.Decoder f
-> D.Decoder g
-> D.Decoder h
-> D.Decoder i
-> D.Decoder j
-> D.Decoder value
map10 func da db dc dd de df dg dh di dj =
D.map8
(\a b c d e f ( g, h ) ( i, j ) ->
func a b c d e f g h i j
)
da
db
dc
dd
de
df
(D.map2 Tuple.pair dg dh)
(D.map2 Tuple.pair di dj)
{-| Try 11 decoders and combine the result.
-}
map11 :
(a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> k -> value)
-> D.Decoder a
-> D.Decoder b
-> D.Decoder c
-> D.Decoder d
-> D.Decoder e
-> D.Decoder f
-> D.Decoder g
-> D.Decoder h
-> D.Decoder i
-> D.Decoder j
-> D.Decoder k
-> D.Decoder value
map11 func da db dc dd de df dg dh di dj dk =
D.map8
(\a b c d e ( f, g ) ( h, i ) ( j, k ) ->
func a b c d e f g h i j k
)
da
db
dc
dd
de
(D.map2 Tuple.pair df dg)
(D.map2 Tuple.pair dh di)
(D.map2 Tuple.pair dj dk)

View File

@ -0,0 +1,52 @@
module Internal.Tools.Encode exposing (maybeObject)
{-|
# Encode module
This module contains helper functions that help decode JSON.
# Optional body object
@docs maybeObject
-}
import Json.Encode as E
{-| Create a body object based on optionally provided values.
In other words, the following two variables create the same JSON value:
value1 : Json.Encode.Value
value1 =
maybeObject
[ ( "name", Just (Json.Encode.string "Alice") )
, ( "age", Nothing )
, ( "height", Just (Json.Encode.float 1.61) )
, ( "weight", Nothing )
]
value2 : Json.Encode.Value
value2 =
Json.Encode.object
[ ( "name", Json.Encode.string "Alice" )
, ( "height", Json.Encode.float 1.61 )
]
-}
maybeObject : List ( String, Maybe E.Value ) -> E.Value
maybeObject =
List.filterMap
(\( name, value ) ->
case value of
Just v ->
Just ( name, v )
_ ->
Nothing
)
>> E.object

View File

@ -0,0 +1,250 @@
module Internal.Values.Envelope exposing
( Envelope, init
, map, mapMaybe
, Settings, mapSettings, extractSettings
, getContent, extract
, encode, decoder
)
{-| The Envelope module wraps existing data types with lots of values and
settings that can be adjusted manually.
## Create
@docs Envelope, init
## Manipulate
@docs map, mapMaybe
## Settings
@docs Settings, mapSettings, extractSettings
## Extract
@docs getContent, extract
## JSON coders
@docs encode, decoder
-}
import Internal.Config.Default as Default
import Internal.Tools.Decode as D
import Internal.Tools.Encode as E
import Json.Decode as D
import Json.Encode as E
{-| There are lots of different data types in the Elm SDK, and many of them
need the same values. The Envelope type wraps settings, tokens and values around
each data type so they can all enjoy those values without needing to explicitly
define them in their type.
-}
type Envelope a
= Envelope
{ content : a
, settings : Settings
}
{-| Custom settings that can be manipulated by the user. These serve as a
configuration for how the Elm SDK should behave.
Custom settings are always part of the Envelope, allowing all functions to
behave under the user's preferred settings.
-}
type alias Settings =
{ currentVersion : String
, deviceName : String
, syncTime : Int
}
{-| Decode an enveloped type from a JSON value. The decoder also imports any
potential tokens, values and settings included in the JSON.
-}
decoder : D.Decoder a -> D.Decoder (Envelope a)
decoder xDecoder =
D.map2 (\a b -> Envelope { content = a, settings = b })
(D.field "content" xDecoder)
(D.field "settings" decoderSettings)
{-| Decode settings from a JSON value.
-}
decoderSettings : D.Decoder Settings
decoderSettings =
D.map3 Settings
(D.opFieldWithDefault "currentVersion" Default.currentVersion D.string)
(D.opFieldWithDefault "deviceName" Default.deviceName D.string)
(D.opFieldWithDefault "syncTime" Default.syncTime D.int)
{-| Encode an enveloped type into a JSON value. The function encodes all
non-standard settings, tokens and values.
-}
encode : (a -> E.Value) -> Envelope a -> E.Value
encode encodeX (Envelope data) =
E.object
[ ( "content", encodeX data.content )
, ( "settings", encodeSettings data.settings )
, ( "version", E.string Default.currentVersion )
]
{-| Encode the settings into a JSON value.
-}
encodeSettings : Settings -> E.Value
encodeSettings settings =
let
differentFrom : b -> b -> Maybe b
differentFrom defaultValue currentValue =
if currentValue == defaultValue then
Nothing
else
Just currentValue
in
E.maybeObject
[ ( "currentVersion"
, settings.currentVersion
|> differentFrom Default.currentVersion
|> Maybe.map E.string
)
, ( "deviceName"
, settings.deviceName
|> differentFrom Default.deviceName
|> Maybe.map E.string
)
, ( "syncTime"
, settings.syncTime
|> differentFrom Default.syncTime
|> Maybe.map E.int
)
]
{-| Map a function, then get its content. This is useful for getting information
from a data type inside an Envelope.
type alias User =
{ name : String, age : Int }
getName : Envelope User -> String
getName =
Envelope.extract .name
-}
extract : (a -> b) -> Envelope a -> b
extract f (Envelope data) =
f data.content
{-| Map a function on the settings, effectively getting data that way.
This can be helpful if you have a UI that displays custom settings to a user.
-}
extractSettings : (Settings -> b) -> Envelope a -> b
extractSettings f (Envelope data) =
f data.settings
{-| Get the original item that is stored inside an Envelope.
Make sure that you're only using this if you're interested in the actual value!
If you'd like to get the content, run a function on it, and put it back in an
Envelope, consider using [map](#map) instead.
-}
getContent : Envelope a -> a
getContent =
extract identity
{-| Create a new enveloped data type. All settings are set to default values
from the [Internal.Config.Default](Internal-Config-Default) module.
-}
init : a -> Envelope a
init x =
Envelope
{ content = x
, settings =
{ currentVersion = Default.currentVersion
, deviceName = Default.deviceName
, syncTime = Default.syncTime
}
}
{-| Map a function on the content of the Envelope.
type alias User =
{ name : String, age : Int }
getName : Envelope User -> Envelope String
getName =
Envelope.map .name
-}
map : (a -> b) -> Envelope a -> Envelope b
map f (Envelope data) =
Envelope
{ content = f data.content
, settings = data.settings
}
{-| Update the settings in the Envelope.
setDeviceName : String -> Envelope a -> Envelope a
setDeviceName name envelope =
mapSettings
(\settings ->
{ settings | deviceName = name }
)
envelope
-}
mapSettings : (Settings -> Settings) -> Envelope a -> Envelope a
mapSettings f (Envelope data) =
Envelope
{ content = data.content
, settings = f data.settings
}
{-| Map the contents of a function, where the result is wrapped in a `Maybe`
type. This can be useful when you are not guaranteed to find the value you're
looking for.
type alias User =
{ name : String, age : Int }
type alias UserDatabase =
List User
getFirstUser : Envelope UserDatabase -> Maybe (Envelope User)
getFirstUser envelope =
mapMaybe List.head envelope
-}
mapMaybe : (a -> Maybe b) -> Envelope a -> Maybe (Envelope b)
mapMaybe f =
map f >> toMaybe
toMaybe : Envelope (Maybe a) -> Maybe (Envelope a)
toMaybe (Envelope data) =
Maybe.map
(\content -> map (always content) (Envelope data))
data.content

View File

@ -0,0 +1,15 @@
module Internal.Values.Vault exposing (Vault)
{-| This module hosts the Vault module.
@docs Vault
-}
import Internal.Values.Envelope as Envelope
{-| This is the Vault type.
-}
type alias Vault =
Envelope.Envelope {}

View File

@ -1,5 +1,9 @@
module Matrix exposing (Vault)
{-| # Matrix SDK
{-|
# Matrix SDK
This first version forms a mere basis from which we will create iterative builds
that slowly improve the codebase.
@ -8,13 +12,20 @@ It is generally quite unusual to regularly publish iterative beta versions on
the public registry, but it is also generally quite unusual to exclusively
support a monolithic public registry. (:
## Vault
@docs Vault
-}
import Types
{-| The Vault type stores all relevant information about the Matrix API.
It currently supports no functionality and it will just stay here - for fun.
-}
type Vault = Vault
type alias Vault =
Types.Vault

71
src/Matrix/Settings.elm Normal file
View File

@ -0,0 +1,71 @@
module Matrix.Settings exposing
( getDeviceName, setDeviceName
, getSyncTime, setSyncTime
)
{-| The Matrix Vault has lots of configurable variables that you rarely want to
interact with. Usually, you configure these variables only when creating a new
Vault, or when a user explicitly changes one of their preferred settings.
## Device name
The default device name that is being communicated with the Matrix API.
This is mostly useful for users who are logged in with multiple sessions. They
will see device names like "Element for Android" or "Element on iOS". For the
Elm SDK, they will by default see the Elm SDK with its version included. If you
are writing a custom client, however, you are free to change this to something
more meaningful to the user.
@docs getDeviceName, setDeviceName
## Sync time
Whenever the Matrix API has nothing new to report, the Elm SDK is kept on
hold until something new happens. The `syncTime` indicates a timeout to how long
the Elm SDK tolerates being held on hold.
- A high value is good because it significantly reduces traffic between the
user and the homeserver.
- A low value is good because it reduces the risk of
the connection ending abruptly or unexpectedly.
Nowadays, most libraries use 30 seconds as the standard, as does the Elm SDK.
The value is in miliseconds, so it is set at 30,000.
@docs getSyncTime, setSyncTime
-}
import Internal.Values.Envelope as Envelope
import Types exposing (Vault(..))
{-| Determine the device name.
-}
getDeviceName : Vault -> String
getDeviceName (Vault vault) =
Envelope.extractSettings .deviceName vault
{-| Override the device name.
-}
setDeviceName : String -> Vault -> Vault
setDeviceName name (Vault vault) =
Vault <| Envelope.mapSettings (\s -> { s | deviceName = name }) vault
{-| Determine the sync timeout value.
-}
getSyncTime : Vault -> Int
getSyncTime (Vault vault) =
Envelope.extractSettings .syncTime vault
{-| Override the sync timeout value.
-}
setSyncTime : Int -> Vault -> Vault
setSyncTime time (Vault vault) =
Vault <| Envelope.mapSettings (\s -> { s | syncTime = time }) vault

25
src/Types.elm Normal file
View File

@ -0,0 +1,25 @@
module Types exposing (Vault(..))
{-| The Elm SDK uses a lot of records and values that are easy to manipulate.
Yet, the [Elm design guidelines](https://package.elm-lang.org/help/design-guidelines#keep-tags-and-record-constructors-secret)
highly recommend using opaque types in order to avoid breaking everyone's code
in a future major release.
This module forms as a protective layer between the internal modules and the
exposed modules, hiding all exposed types behind opaque types so the user cannot
access their content directly.
The opaque types are placed in a central module so all exposed modules can
safely access all exposed data types without risking to create circular imports.
@docs Vault
-}
import Internal.Values.Vault as Vault
{-| Opaque type for Matrix Vault
-}
type Vault
= Vault Vault.Vault