Changelog
View SourceUnreleased
[0.26.3] - 2025-05-29
Fixed
- Allow resetting default order set on schema by passing
default_order: false. 
Upgrade notes
Flop 0.26.2 introduced a new warning when Flop order parameters are set and the
query already has an order by clause. Order by parameters are added to the Flop
struct either by passing the order_by and order_directions parameters to
the validate function or by defining a default_order in the Ecto schema.
To prevent the warning:
- Disable the handling of order parameters by passing 
ordering: false. - Override the default order set in the schema by passing
default_order: false. 
Example:
query = from p in Pet, order_by: :name
flop = Flop.validate!(params, for: Pet, ordering: false, default_order: false)
Flop.run(query, flop)[0.26.2] - 2025-05-28
Added
- Recipe for partial UUID filters.
 
Changed
- Emit warning if ordering parameters are applied and the query already contains
an 
ORDER BYclause. 
Fixed
- Fix "protocol Enumerable not implemented for type X" error when there is a validation error and a filter value is a struct.
 
[0.26.1] - 2024-08-19
Fixed
- Fixed allowed operators for 
Ecto.Enumfields in Ecto 3.12. - Fixed type expansion when passing values with shortened syntax to
Flop.Filter.expand_type/1. - Updated documentation example for setting 
ecto_typeto parameterized types. 
Upgrade Guide
If you pass a parameterized type as ecto_type option, ensure that you use
Ecto.ParameterizedType.init/2 instead of using the tuple representation as
suggested in the documentation before. The tuple representation is an internal
representation of Ecto and was changed in Ecto 3.12.
[
-  ecto_type: {:parameterized, Ecto.Enum, Ecto.Enum.init(values: [:one, :two])}
+  ecto_type: Ecto.ParameterizedType.init(Ecto.Enum, values: [:one, :two])
]For Ecto.Enum specifically, you can also use the short syntax
{:ecto_enum, [:one, :two]}.
[0.26.0] - 2024-08-18
Removed
- The previously deprecated tuple syntax for defining join fields has been removed in favor of a keyword list.
 - The previously deprecated function 
Flop.Schema.field_type/2was removed in favor ofFlop.Schema.field_info/2. 
Fixed
- Fixed a compatibility issue with Ecto 3.12 related to the initialization of
the 
Ecto.Enumtype. 
Upgrade Guide
Replace the tuple syntax for join fields with a keyword list.
@derive {
  Flop.Schema,
  join_fields: [
-    owner_name: {:owner, :name}
+    owner_name: [
+      binding: :owner,
+      field: :name
+    ]
  ]
}[0.25.0] - 2024-01-14
Added
- Added 
Flop.Filter.update_value/3for updating the filter value for a field in a list of filters. 
Fixed
- Determine pagination type if pagination parameter has errors.
 
[0.24.1] - 2023-11-18
Changed
Flop.push_order/3now allows you to use a descending order as the initial sort order.
[0.24.0] - 2023-11-14
Changed
- If an invalid operator is passed in a filter, the error will now include the list of allowed operators for that field.
 
[0.23.0] - 2023-09-26
Added
- Added 
directionsoption toFlop.push_order/3. 
Fixed
- Escape backlash character in queries using one of the 
likeoperators. 
[0.22.1] - 2023-07-18
Fixed
- Updated version requirement for Ecto to 
~> 3.10.3. Flop 0.22.0 relies on a feature added in that version and doesn't compile with lower versions. 
[0.22.0] - 2023-07-17
This release includes a substantial refactoring to lay the groundwork for the upcoming adapter feature. While this release contains deprecations and changes, they are either backward compatible or affect functions that are unlikely to be used by end users. The primary aim has been to ensure a seamless transition and maintain compatibility with previous versions.
Added
- Added a 
Flop.FieldInfostruct that contains metadata for a field for use by adapters. - Added the 
Flop.Schema.field_info/2function, which derives field information and replaces the previousFlop.Schema.field_type/2function with a more standardized and structured output. 
Changed
- The Ecto-specific options 
alias_fields,compound_fields,custom_fields, andjoin_fieldswithinFlop.Schema, as well asrepoandquery_optswithinuse Flop, are now nested under theadapter_optskeyword. The old configuration format is still supported. 
Deprecated
Flop.Schema.field_type/2was deprecated in favor ofFlop.Schema.field_info/2.
Removed
- Removed 
Flop.Schema.apply_order_by/3. - Removed 
Flop.Schema.cursor_dynamic/3. 
Upgrade guide
While the old configuration format is still supported, you are invited to update your application to the new structure to prepare for future versions.
To do this, place the field configuration for Flop.Schema under
adapter_opts:
@derive {
  Flop.Schema,
  filterable: [],
  sortable: [],
-  alias_fields: [],
-  compound_fields: [],
-  custom_fields: [],
-  join_fields: []
+  adapter_opts: [
+    alias_fields: [],
+    compound_fields: [],
+    custom_fields: [],
+    join_fields: []
+  ]
}Similarly for use Flop, you can nest repo and query_opts under
adapter_opts:
use Flop,
  default_limit: 50,
-  repo: MyApp.Repo,
-  query_opts: [prefix: "some-prefix"]
+  adapter_opts: [
+    repo: MyApp.Repo,
+    query_opts: [prefix: "some-prefix"]
+  ][0.21.0] - 2023-07-02
Added
- Introduced 
operatorsas a new option for restricting acceptable operators for a custom field. - Added bindings option for custom fields, allowing required named bindings to
be added via 
Flop.with_named_bindings/4. - The 
ecto_typeoption on join and custom fields now supports references:{:from_schema, MySchema, :some_field}. - The 
ecto_typeoption now supports a convenient syntax for adhoc enums:{:ecto_enum, [:one, :two]}. - Improved documentation with added type definitions: 
Flop.Schema.option/0,Flop.Schema.join_field_option/0,Flop.Schema.custom_field_option/0, andFlop.Schema.ecto_type/0, describing options available when deriving theFlop.Schemaprotocol. 
Changed
- Breaking change: Filter values are now dynamically cast based on the field type and operator, instead of allowing any arbitrary filter value. This change ensures that invalid filter values cause validation errors instead of cast errors.
 - The options for deriving the 
Flop.Schemaprotocol and foruse Flopnow undergo stricter validation withNimbleOptions. Flop.Cursor.encode/1now explicitly sets the minor version option for:erlang.term_to_binary/2to2, aligning with the new default in OTP 26. Before, this option was not set at all.- Added a 
decoded_cursorfield to theFlopstruct. This field temporarily stores the decoded cursor between validation and querying and is discarded when generating the meta data. 
Deprecated
- The tuple syntax for defining join fields has been deprecated in favor of a keyword list.
 
Fixed
- Resolved an issue where setting 
replace_invalid_paramstotruestill caused validation errors for pagination and sorting parameters due to cast errors, instead of defaulting to valid parameters. - Fixed the type specification for 
Flop.Filter.allowed_operators/1. 
Upgrade notes
The newly implemented dynamic casting of filter values could impact your code:
- Filter values failing to cast into the determined type will now yield a
validation error or result in the removal of the invalid filter if the
replace_invalid_paramsoption is enabled. - The 
valuefield of theFlop.Filterstruct now holds the cast value instead of the original parameter value. For instance, while handling parameters generated via an HTML form with Flop, previously all filter values would be represented as strings in the struct. However, they may now be integers,DateTimestructs, and so forth. Look out for this if you are directly reading or manipulatingFlop.Filterstructs. - For join and custom fields, the type is determined with the 
ecto_typeoption. Previously, this option was only used for operator validation. Ensure the correct Ecto type is set. If the option is omitted, the filter values will continue to use their incoming format. - Manual casting of filter values in a custom filter function is no longer
required if the 
ecto_typeoption is set. - If join fields point to 
Ecto.Enumfields, previously you could simply setecto_typeto string. This will continue to work if the filter value is passed as a string, but passing it as an atom will cause an error. Make sure to correctly reference the schema field ({:from_schema, MySchema, :some_field}) or directly pass the Enum values ({:ecto_enum, [:one, :two}). - To enable 
Flop.Phoenixto build a query string for filter parameters, the filter value must be convertible into a string viato_string/1. Ifecto_typeis set to a custom Ecto type that casts values into a struct, theString.Charsprotocol must be implemented for that struct. - If you use the result of 
Flop.Phoenix.to_query/2in a~psigil for verified routes or in a route helper function, Phoenix converts filter values into a string using thePhoenix.Paramprotocol. If you useDate,DateTime,NaiveDateTime,Timefilters, or filters using custom structs, you need to implement that protocol for these structs in your application. 
Please review the newly added "Ecto type option" section in the Flop.Schema
module documentation.
Join field syntax
If you are using tuples to define join fields when deriving Flop.Schema,
update the configuration to use keyword lists instead:
@derive {
  Flop.Schema,
  join_fields: [
-    owner_name: {:owner, :name}
+    owner_name: [binding: :owner, field: :name]
  ]
}[0.20.3] - 2023-06-23
Changed
Flop.count/3will now wrap queries that haveGROUP BYclauses in a subquery.
Fixed
- Fixed cursor-based pagination on composite types.
 
[0.20.2] - 2023-06-09
Changed
- Added nutrition facts about 
use Flopand@derive Flop.Schema. - The minimum Elixir version is now 1.11.
 
Fixed
- Fixed a deprecation warning about 
Logger.warn/1. - Fixed a deprecation warning about passing an MFA to 
:within cast_assoc/cast_embed introduced in Ecto 3.10.2. 
[0.20.1] - 2023-05-19
Added
- Added the 
:countoverride option toFlop.count/3. 
Changed
- The 
default_pagination_typecan now be set in the schema. 
Fixed
- Don't raise function clause error in 
Flop.to_previous_cursor/1andFlop.to_next_cursor/1when the start cursor or end cursor arenil. 
[0.20.0] - 2023-03-21
Added
- Added 
Flop.unnest_filters/3as a reverse operation ofFlop.nest_filters/3after retrieving data from the database. - Added 
Flop.Filter.fetch_value/2,Flop.Filter.get_value/2,Flop.Filter.put_value/4,Flop.Filter.put_new_value/4,Flop.Filter.pop_value/3andFlop.Filter.pop_first_value/3. 
Changed
- Several of the functions for manipulating lists of filters in the
Flop.Filtermodule now accept lists of maps with atom keys, lists of maps with string keys, and indexed maps as produced by Phoenix HTML forms as argument. - The 
emptyandnot_emptyoperators now treat empty maps as empty values on map fields and empty arrays as empty values on array fields. %and_characters in filter values for thelike,ilikeand=~operators are now escaped.
Fixed
- Fixed an issue that caused filter conditions for 
like_and,like_or,ilike_andandilike_orto be incorrectly combined when applied to compound fields. 
[0.19.0] - 2023-01-15
Added
- Support for custom fields. These fields allow you to run custom filter functions for anything that cannot be expressed with Flop filters.
 - Added 
Flop.with_named_bindings/4for dynamically adding bindings needed for a Flop query. - Added 
fetch,get,get_all,delete,delete_first,drop,new,take,pop,pop_first,putandput_newfunctions toFlop.Filter. - Added 
Flop.Meta.with_errors/3. - Added 
ecto_typeoption to join fields. - Added 
not_likeandnot_ilikefilter operators. - Added a cheatsheet for schema configuration.
 - Added 
optsfield toFlop.Metastruct. 
Changed
- Renamed 
Flop.bindings/3toFlop.named_bindings/3. Flop.Filter.allowed_operators/2now tries to determine the Ecto type by reading the Flop field type from the schema module. This function is used during parameter validation, which means the validation step will be a bit stricter now. For join and custom fields, the Ecto type is determined via the newecto_typeoption. If the option is not set, the function returns all operators as before. For compound fields, only the supported operators are returned.
[0.18.4] - 2022-11-17
Changed
- The 
:ilike_and,:ilike_or,:like_andand:like_orfilter operators can now also be used with a list of strings as filter value. 
[0.18.3] - 2022-10-27
Fixed
default_pagination_typecan be overridden by passingfalsenow.
[0.18.2] - 2022-10-19
Fixed
Flop.bindings/3did not consider join fields that are used as part of a compound field.
[0.18.1] - 2022-10-14
Changed
- If the given map already has a 
:filters/"filters"key,Flop.nest_filters/3will now merge the derived filters into the existing filters. If the existing filters are formatted as a map (as produced by an HTML form), they are converted to a list first. use Flopwill now also compilevalidate/2andvalidate!/2functions that apply the options of your config module.- Allow setting 
default_limitandmax_limittofalse, which removes the default/max limit without falling back to global options. 
Fixed
Flop.bindings/3was returning bindings for filters withnilvalues.
[0.18.0] - 2022-10-10
Added
- Added 
alias_fieldsoption toFlop.Schema, which allows you to sort by field aliases defined withEcto.Query.API.selected_as/2. - Added 
aliases/2for getting the alias fields needed for a query. - Added documentation example for filtering by calculated values.
 - New option 
renameforFlop.map_to_filter_params/2andFlop.nest_filters/3. - New option 
:replace_invalid_params. This option can be passed to thevalidateandvalidate_and_runfunctions or set in the global configuration or in a config module. Setting the value totruewill cause Flop to replace invalid parameters with default values where possible or remove the parameter otherwise during the validation step, instead of returning validation errors. 
Changed
- Require 
ecto ~> 3.9.0. Flop.Schemadoes not raise an error anymore if a compound or join field is defined with the same name as a regular Ecto schema field. This was done so that you can add virtual fields with the same name. It is not possible to differentiate between non-virtual and virtual fields at compile time (at least I don't know how), so we cannot differentiate in the validation step.- Flop applies a default limit of 
50and a max limit of1000now, unless other values are set. - In offset/limit based pagination, the 
limitparameter is now required, in line with the other pagination types. If not set, it will fall back to a default limit. 
[0.17.2] - 2022-10-03
Fixed
- Fixed an issue where the 
repooption was not read from a backend module. 
[0.17.1] - 2022-10-02
Added
- Added a 
backendfield to theFlop.Metastruct. 
Fixed
- Fixed an issue where the schema options were overridden by the backend module options.
 
[0.17.0] - 2022-08-26
Added
- Added the filter operators 
not_inandnot_contains. - Added examples for integration with Relay to the documentation.
 - Added examples for the parameter format to the documentation.
 
Changed
- Refactored the query builder. This does not affect users of the library, but makes the code base more readable and lays the groundwork for upcoming features.
 - Added the 
:query_optsoption to Flop callbacks to pass on options to the Ecto repo on query execution. If you are already using the:prefixoption you now have to pass this through:query_opts. 
If you configured the Repo :prefix in the application config:
config :flop,
-  prefix: "some-prefix"
+  query_opts: [prefix: "some-prefix"]If you set the :prefix when calling the Flop functions:
- Flop.validate_and_run(Pet, params, prefix: "some-prefix")
+ Flop.validate_and_run(Pet, params, query_opts: [prefix: "some-prefix"])[0.16.1] - 2022-04-05
Fixed
- Wrong type spec for 
Flop.Schema.default_order/1callback. 
[0.16.0] - 2022-03-22
Added
- You can now define a configuration module with 
use Flopto set defaults instead of or in addition to the application configuration. This makes it easier to work with multiple Ecto repos. - The new function 
Flop.bindings/3returns the necessary bindings for a given Flop query. You can use it in case you want to optimize your queries by only joining tables that are actually needed. - Added a 
count_queryoption to override the count query used byFlop.run/3,Flop.validate_and_run/3andFlop.validate_and_run!/3. - You can get a list of allowed operators for a given Ecto type or a given
schema field with 
Flop.Filter.allowed_operators/1andFlop.Filter.allowed_operators/2now. 
Changed
- Breaking: The 
:emptyand:not_emptyfilters now require a boolean value. If no value is passed, the filter is ignored, just as it is handled for all other filter operators. This change was necessary to make the integration with filter forms (checkboxes) easier. - Breaking: The default order needs to be passed as a map now when deriving
Flop.Schema. The previous implementation already converted the two separate configuration keys to a map. This meant that the configuration passed when derivingFlop.Schemahad a different format from the one you had to pass when overriding the default order with theopts. With this change, the configuration format is the same everywhere. A compile time exception is raised if you are still using the old format, guiding you in the update. - It is now validated that the filter operator matches the field type.
 - The compile time validation of the options passed when deriving 
Flop.Schemahas been improved. - Allow passing page as string to 
Flop.set_page/2. - Allow passing offset as string to 
Flop.set_offset/2. 
[0.15.0] - 2021-11-14
Added
- Add 
Flop.reset_filters/1andFlop.reset_order/1. - Add 
Flop.current_order/2to retrieve the order of a given field. - Add 
Flop.to_next_page/2andFlop.to_previous_page/1. - Add 
Flop.set_cursor/2,Flop.to_next_cursor/1andFlop.to_previous_cursor/1. - Add 
Flop.set_offset/2,Flop.to_previous_offset/1,Flop.to_next_offset_2andFlop.reset_cursors/2. - Add 
Flop.nest_filters/3for converting filters between a key/value map and a list ofFlop.Filterparameters. - You can now set the 
default_pagination_typeoption, which forces a certain set of parameters when defaults are applied and the pagination type cannot be determined from the given parameters. - Add optional 
defaultargument toget_option. - Add 
paginationoption. If set totrue, pagination parameters are not cast. 
Changed
Flop.map_to_filter_params/2returns maps with string keys if the original map has string keys now.- The 
has_previous_page?value of theFlop.Metastruct is now alwaystrueiffirstis used withafter.has_next_page?is alwaystruewhenlastis used withbefore. push_order/2resets the:afterand:beforeparameters now, since the cursors depend on the order.validate_and_run/3andvalidate_and_run!/3pass all given options to the validate functions now, allowing you to override defaults set in the schema.- If the 
pagination_typesoption is used, parameters for other pagination types will not be cast now instead of casting them and returning validation errors. 
Removed
- Remove 
Flop.Cursor.get_cursor_from_map/2. UseFlop.Cursor.get_cursor_from_node/2instead. 
[0.14.0] - 2021-11-08
Added
- Add 
:containsoperator. - Add 
Flop.map_to_filter_params/2. 
Changed
Flop.validate/2andFlop.validate_and_run/3return{:error, Flop.Meta.t}instead of{:error, Ecto.Changeset.t}now. The Meta struct has the new fields:errorsand:params, which are set when validation errors occur. This accompanies the changes inFlop.Phoenix, which include the implementation of thePhoenix.HTML.FormDataprotocol for theFlop.Metastruct.Flop.validate!/2andFlop.validate_and_run!/3raise aFlop.InvalidParamsErrorinstead of anEcto.InvalidChangesetErrornow.- Add 
:schemakey toFlop.Meta. This field points to the schema module set by passing the:foroption. - Minimum Ecto version changed to 3.5.
 - Replace 
OperatorandOrderDirectioncustom Ecto types with Ecto.Enum. - Update 
Flop.Metastruct default values for the fields:flop,:has_next_page?and:has_previous_page?. 
[0.13.2] - 2021-10-16
Fixed
- Fix error when sorting by a compound field that consists of at least one join field.
 - Fix import conflict when importing 
Ecto.Changesetin a module that derivesFlop.Schemaand configures a compound field. 
[0.13.1] - 2021-08-23
Fixed
- Wrong type spec for cursor_dynamic/3 callback.
 
[0.13.0] - 2021-08-22
Added
- Support ordering by join fields.
 - Support ordering by compound fields.
 - Support join fields as cursor fields.
 - New function 
Flop.Schema.get_field/2. Flop.Cursor.get_cursor_from_edge/2andFlop.Cursor.get_cursor_from_node/2can get cursor values from join and compound fields now.
Changed
To get the pagination cursor value from a join field, Flop needs to know how to access the field value from the returned struct or map. The configuration format for join fields has been changed to allow specifying the path to the nested field.
Before:
@derive {
  Flop.Schema,
  join_fields: [
    owner_name: {:owner, :name}
  ]
}After:
@derive {
  Flop.Schema,
  join_fields: [
    owner_name: [binding: :owner, field: :name, path: [:owner, :name]]
  ]
}The :path is optional and inferred from the :binding and :field options,
if omitted.
The old configuration format is still accepted. All of these settings are equivalent:
[owner_name: {:owner, :name}]
[owner_name: [binding: :owner, field: :name]]
[owner_name: [binding: :owner, field: :name, path: [:owner, :name]]]Fixed
- Cursor pagination failed when one of the cursor field values was 
nil. 
[0.12.0] - 2021-08-11
Added
- Allow to define join fields in 
Flop.Schema. - Allow to define compound fields in 
Flop.Schema. - Support filtering by join fields.
 - Support filtering by compound fields.
 - New filter operator 
empty. - New filter operator 
not_empty. - New function 
Flop.set_page/2. 
Changed
- Rename option 
get_cursor_value_functocursor_value_func. - Silently ignore filters with 
nilvalue for the field or the value instead of raising anArgumentError. - Allow passing a string as the second argument to 
Flop.push_order/2. 
[0.11.0] - 2021-06-13
Added
- New functions 
Flop.Cursor.get_cursor_from_node/2andFlop.Cursor.get_cursor_from_edge/2. - New function 
Flop.get_option/2. - Support Ecto prefixes.
 
Changed
- Use 
Flop.Cursor.get_cursor_from_node/2as default for the:get_cursor_value_funcoption. Flop.Relay.edges_from_result/2can now handlenilinstead of a map as edge information in a query result.
Deprecated
- Deprecate 
Flop.Cursor.get_cursor_from_map/2. UseFlop.Cursor.get_cursor_from_node/2instead. 
[0.10.0] - 2021-05-03
Added
- Add function 
Flop.push_order/2for updating theorder_byandorder_directionsvalues of a Flop struct. 
[0.9.1] - 2020-10-21
Fixed
- Fixed type spec of 
Flop.Schema.default_order/1. 
[0.9.0] - 2020-10-16
Added
- Add 
like,like_and,like_or,ilike,ilike_andandilike_orfilter operators. - Add option to disable pagination types globally, for a schema or locally.
 - Add options to disable ordering or filtering.
 - Allow global configuration of 
get_cursor_value_func,max_limitanddefault_limit. - Add 
Flop.optiontype, improve documentation of available options. - Add 
Flop.Cursor.decode!/1. 
Changed
- Refactored the parameter validation. Default limits are now applied to all
pagination types. Added validation for the 
after/beforecursor values. Flop.Cursor.decode/1returns:oktuple or:errornow instead of raising an error if the cursor is invalid.Flop.Cursor.decode/1returns an error if the decoded cursor value is not a map with atom keys.- Improved documentation.
 
[0.8.4] - 2020-10-14
Fixed
- Default limit was overriding 
first/lastparameters when building query. 
[0.8.3] - 2020-10-14
Fixed
- Cursor-based pagination: 
has_next_page?was set when querying withlastbased onbeforebeing set. Likewise,has_previous_page?was set when querying withfirstbased onafterbeing set. Both assumptions are wrong. In both cases, the values are always set tofalsenow. 
[0.8.2] - 2020-10-08
Changed
- Order directions are not restricted anymore for cursor-based pagination.
 
Fixed
- Query for cursor-based pagination returned wrong results when using more than one cursor field.
 - Query for cursor-based pagination returned wrong results when using
last/before. 
[0.8.1] - 2020-10-07
Changed
- Allow structs in cursor values.
 
[0.8.0] - 2020-10-07
Added
- Support for cursor-based pagination. Thanks to @bunker-inspector.
 - Add functions to turn query results into Relay connection format when using cursor-based pagination.
 
[0.7.1] - 2020-09-04
Fixed
- Calculation of 
has_next_page?was wrong. 
[0.7.0] - 2020-08-04
Added
Flop.Schemanow allows to set a default sort order.
Changed
- Passing a limit without an offset will now set the offset to 0.
 - Passing a page size without a page will now set the page to 1.
 
[0.6.1] - 2020-06-17
Changed
- Add Flop to Meta struct.
 
Fixed
- Type 
Flop.Filter.opdidn't include all operators. 
[0.6.0] - 2020-06-14
Added
- New struct 
Flop.Meta. - New function 
Flop.all/3. - New function 
Flop.count/3. - New function 
Flop.meta/3. - New function 
Flop.run/3. - New function 
Flop.validate_and_run/3. - New function 
Flop.validate_and_run!/3. 
[0.5.0] - 2020-05-28
Added
- New function 
Flop.validate!/2. - New filter operator 
:in. 
Fixed
- Filter validation was using sortable fields instead of filterable fields.
 
[0.4.0] - 2020-05-27
Added
- Added 
=~filter operator. 
Fixed
- Query function wasn't generating valid where clauses for filters.
 
[0.3.0] - 2020-05-22
Added
- Added a 
default_limitoption toFlop.Schema. 
[0.2.0] - 2020-05-20
Added
- Added a 
max_limitoption toFlop.Schema. When set, Flop validates that thelimitandpage_sizeparameters don't exceed the configured max limit. 
[0.1.0] - 2019-10-19
initial release