@opentermsarchive/engine 0.15.0 → 0.17.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.
Files changed (64) hide show
  1. package/package.json +7 -1
  2. package/src/tracker/index.js +1 -1
  3. package/.env.example +0 -3
  4. package/.eslintrc.yaml +0 -116
  5. package/.github/workflows/deploy.yml +0 -50
  6. package/.github/workflows/release.yml +0 -64
  7. package/.github/workflows/test.yml +0 -77
  8. package/CHANGELOG.md +0 -14
  9. package/CODE_OF_CONDUCT.md +0 -128
  10. package/CONTRIBUTING.md +0 -143
  11. package/MIGRATING.md +0 -42
  12. package/Vagrantfile +0 -38
  13. package/ansible.cfg +0 -13
  14. package/decision-records/0001-service-name-and-id.md +0 -73
  15. package/decision-records/0002-service-history.md +0 -212
  16. package/decision-records/0003-snapshots-database.md +0 -123
  17. package/ops/README.md +0 -280
  18. package/ops/app.yml +0 -5
  19. package/ops/infra.yml +0 -6
  20. package/ops/inventories/dev.yml +0 -7
  21. package/ops/inventories/production.yml +0 -27
  22. package/ops/roles/infra/defaults/main.yml +0 -2
  23. package/ops/roles/infra/files/.gitconfig +0 -3
  24. package/ops/roles/infra/files/mongod.conf +0 -18
  25. package/ops/roles/infra/files/ota-bot-key.private_key +0 -26
  26. package/ops/roles/infra/tasks/main.yml +0 -78
  27. package/ops/roles/infra/tasks/mongo.yml +0 -40
  28. package/ops/roles/infra/templates/ssh_config.j2 +0 -5
  29. package/ops/roles/ota/defaults/main.yml +0 -14
  30. package/ops/roles/ota/files/.env +0 -21
  31. package/ops/roles/ota/tasks/database.yml +0 -65
  32. package/ops/roles/ota/tasks/main.yml +0 -110
  33. package/ops/site.yml +0 -6
  34. package/pm2.config.cjs +0 -20
  35. package/test/fixtures/service_A.js +0 -22
  36. package/test/fixtures/service_A_terms.md +0 -10
  37. package/test/fixtures/service_A_terms_snapshot.html +0 -14
  38. package/test/fixtures/service_B.js +0 -22
  39. package/test/fixtures/service_with_declaration_history.js +0 -65
  40. package/test/fixtures/service_with_filters_history.js +0 -155
  41. package/test/fixtures/service_with_history.js +0 -188
  42. package/test/fixtures/service_with_multipage_document.js +0 -100
  43. package/test/fixtures/service_without_history.js +0 -31
  44. package/test/fixtures/services.js +0 -19
  45. package/test/fixtures/terms.pdf +0 -0
  46. package/test/fixtures/termsFromPDF.md +0 -25
  47. package/test/fixtures/termsModified.pdf +0 -0
  48. package/test/services/service_A.json +0 -9
  49. package/test/services/service_B.json +0 -9
  50. package/test/services/service_with_declaration_history.filters.js +0 -7
  51. package/test/services/service_with_declaration_history.history.json +0 -17
  52. package/test/services/service_with_declaration_history.json +0 -13
  53. package/test/services/service_with_filters_history.filters.history.js +0 -29
  54. package/test/services/service_with_filters_history.filters.js +0 -7
  55. package/test/services/service_with_filters_history.json +0 -13
  56. package/test/services/service_with_history.filters.history.js +0 -29
  57. package/test/services/service_with_history.filters.js +0 -7
  58. package/test/services/service_with_history.history.json +0 -26
  59. package/test/services/service_with_history.json +0 -17
  60. package/test/services/service_with_multipage_document.filters.js +0 -7
  61. package/test/services/service_with_multipage_document.history.json +0 -37
  62. package/test/services/service_with_multipage_document.json +0 -28
  63. package/test/services/service_without_history.filters.js +0 -7
  64. package/test/services/service_without_history.json +0 -13
package/Vagrantfile DELETED
@@ -1,38 +0,0 @@
1
- # -*- mode: ruby -*-
2
- # vi: set ft=ruby :
3
-
4
- Vagrant.configure("2") do |config|
5
- config.vm.hostname = "vagrant"
6
-
7
- config.vm.box = "debian/bullseye64" # Unable to locate package mongodb-org
8
-
9
- # in order to have the same config for both Docker and VirtualBox providers, we load the key manually
10
- # if necessary, create the key with `ssh-keygen -f ~/.ssh/ota-vagrant -q -N ""`
11
- # CAUTION: use of `~` in path causes problems with ssh
12
- config.vm.provision "file", source: File.join(ENV['HOME'], ".ssh", "ota-vagrant.pub"), destination: "/home/vagrant/.ssh/authorized_keys"
13
-
14
- # based on https://github.com/rofrano/vagrant-docker-provider#example-vagrantfile
15
- config.vm.provider :docker do |docker, override|
16
- override.vm.box = nil
17
- docker.image = "rofrano/vagrant-provider:debian"
18
- docker.remains_running = true
19
- docker.has_ssh = true
20
- docker.privileged = true
21
- docker.volumes = ["/sys/fs/cgroup:/sys/fs/cgroup:rw"]
22
- docker.create_args = ["--cgroupns=host"]
23
-
24
- # python is not installed by default in the vagrant-provider image
25
- # and deploying results in /bin/sh: 1: /usr/bin/python: not found
26
- # use a provision to fix that
27
- # only with debian, no need with ubuntu
28
- # Also need to name the provisioner, so that it runs only once https://github.com/hashicorp/vagrant/issues/7685#issuecomment-308281283
29
- config.vm.provision "install_python3", type: "shell", inline: $installPython3
30
- end
31
- end
32
-
33
- $installPython3 = <<-SCRIPT
34
- echo Updating apt...
35
- sudo apt-get update --fix-missing # Needed to fix "No package matching 'chromium' is available"
36
- echo Installing python...
37
- sudo apt-get --assume-yes install python3 python3-pip
38
- SCRIPT
package/ansible.cfg DELETED
@@ -1,13 +0,0 @@
1
- [defaults]
2
-
3
- inventory = ops/inventories/dev.yml
4
- roles_path = ops/roles
5
-
6
-
7
- # The two following lines allow to have human readable output
8
- # Use the YAML callback plugin.
9
- stdout_callback = yaml
10
- # Use the stdout_callback when running ad-hoc commands.
11
- bin_ansible_callbacks = true
12
-
13
- vault_password_file = vault.key
@@ -1,73 +0,0 @@
1
- # Choosing service name and service ID
2
-
3
- - Date: 2020-10-14
4
-
5
- ## Context and Problem Statement
6
-
7
- To scale up from 50 to 5,000 services, we need a clear way for choosing the service name and the service ID.
8
-
9
- ### We need
10
-
11
- A name that reflects the common name used by the provider itself, to be exposed in a GUI. This name is currently exposed as the name property in the service declaration.
12
- An ID of sorts that can be represented in the filesystem. This ID is currently exposed as the filename of the service declaration, without the .json extension.
13
-
14
- ### Use cases
15
-
16
- The service name is presented to end users. It should reflect as closely as possible the official service name, so that it can be identified easily.
17
- The ID is used internally and exposed for analysis. It should be easy to handle with scripts and other tools.
18
-
19
- ### Constraints for the ID
20
-
21
- As long as this ID is stored in the filesystem:
22
-
23
- - No `/` for UNIX.
24
- - No `\` for Windows.
25
- - No `:` for APFS and HFS.
26
- - No case-sensitive duplicates to support case-insensitive filesystems.
27
- - No more than 255 characters to support transfer over [FAT32](https://en.wikipedia.org/wiki/File_Allocation_Table#FAT32).
28
-
29
- UTF, spaces and capitals are all supported, even on case-insensitive filesystems.
30
-
31
- ### However
32
-
33
- - UTF in filenames can be [a (fixable) problem with Git and HFS+](https://stackoverflow.com/questions/5581857/git-and-the-umlaut-problem-on-mac-os-x).
34
- - UTF in filenames is by default quoted in Git, leading for example `été.txt` to be displayed as `"\303\251t\303\251.txt"`.
35
- - Most online services align their brand name with their domain name. Even though UTF is now officially supported in domain names, support is limited and most services, even non-Western, have an official ASCII transliteration used at least in their domain name (e.g. “qq” by Tencent, “rzd.ru” for “РЖД”, “yahoo” for “Yahoo!”).
36
- - We currently use GitHub as a GUI, so the service ID is presented to the user instead of the service name. The name is used in email notifications.
37
-
38
- ## Decision Outcome
39
-
40
- 1. The service name should be the one used by the service itself, no matter the alphabet.
41
-
42
- - _Example: `туту.ру`_.
43
-
44
- 2. We don't support non-ASCII characters in service IDs, at least as long as the database is Git and the filesystem, in order to minimise risk. Service IDs are derived from the service name through normalization into ASCII.
45
-
46
- - _Example: `туту.ру` → `tutu.ru`_.
47
- - _Example: `historielærer.dk` → `historielaerer.dk`_.
48
- - _Example: `RTÉ` → `RTE`_.
49
-
50
- 3. We support punctuation, except characters that have meaning at filesystem level (`:`, `/`, `\`). These are replaced with a dash (`-`).
51
-
52
- - _Example: `Yahoo!` → `Yahoo!`_.
53
- - _Example: `Last.fm` → `Last.fm`_.
54
- - _Example: `re:start` → `re-start`_.
55
- - _Example: `we://` → `we---`_.
56
-
57
- 4. We support capitals. Casing is expected to reflect the official service name casing.
58
-
59
- - _Example: `hi5` → `hi5`_.
60
- - _Example: `DeviantArt` → `DeviantArt`_.
61
- - _Example: `LINE` → `LINE`_.
62
-
63
- 5. We support spaces. Spaces are expected to reflect the official service name spacing.
64
-
65
- - _Example: `App Store` → `App Store`_.
66
- - _Example: `DeviantArt` → `DeviantArt`_.
67
-
68
- 6. We prefix the service name by the provider name when self-references are ambiguous, separated by a space. For example, Facebook refers to their Self-serve Ads service simply as “Ads”, which we cannot use in a shared database. We thus call the service “Facebook Ads”.
69
-
70
- - _Example: `Ads` (by Facebook) → `Facebook Ads`_.
71
- - _Example: `Analytics` (by Google) → `Google Analytics`_.
72
- - _Example: `Firebase` (by Google) → `Firebase`_.
73
- - _Example: `App Store` (by Apple) → `App Store`_.
@@ -1,212 +0,0 @@
1
- # Defining a service history system
2
-
3
- - Date: 2020-11-23
4
-
5
- ## Context and Problem Statement
6
-
7
- We need to be able to regenerate versions from snapshots. As documents is aim to change over time (location or filters) we can't rely on the last version of the declaration to regenerate the version from an old snapshot. So we need a system to keep track of declaration changes, that's what we called declarations and filters versioning.
8
-
9
- ## Solutions considered
10
-
11
- At this time, we see three solutions which have in common the following rules:
12
-
13
- - `history` is optional
14
- - the current valid declaration has no date and should be clearly identifiable
15
- - the `valid_until` date is an inclusive expiration date. It should be the exact authored date of the last snapshot commit for which the declaration is still valid.
16
-
17
- ## Option 1: Add an `history` field in service declaration
18
-
19
- In `services/ASKfm.json`:
20
-
21
- ```
22
- {
23
- "name": "ASKfm",
24
- "documents": {
25
- "Terms of Service": {
26
- "fetch": "https://ask.fm/docs/terms_of_use/?lang=en",
27
- "select": ".selection",
28
- "filter": [ "add" ]
29
- "history": [
30
- {
31
- "fetch": "https://ask.fm/docs/terms_of_use/?lang=en",
32
- "select": "body",
33
- "filter": [ "add" ]
34
- "valid_until": "2020-08-24T14:02:39Z"
35
- },
36
- {
37
- "fetch": "https://ask.fm/docs/terms_of_use/?lang=en",
38
- "select": "body",
39
- "valid_until": "2020-08-23T14:02:39Z"
40
- }
41
- ]
42
- }
43
- }
44
- }
45
- ```
46
-
47
- Note: When no historisation is needed the file may have no mention of history.
48
-
49
- **Pros:**
50
-
51
- - Everything is in the same file:
52
- - might prevent to forget to update existing history
53
- - might help user to know that history is a thing and encourage them to learn about it if they feel the need
54
- - no (pseudo-)hidden knowledge about history
55
-
56
- **Cons:**
57
-
58
- - Apparent complexity can discourage new contributors
59
- - With time, the file can become huge
60
-
61
- ## Option 2: Add an `serviceId.history.json` file
62
-
63
- In `services/ASKfm.json`:
64
-
65
- ```
66
- {
67
- "name": "ASKfm",
68
- "documents": {
69
- "Terms of Service": {
70
- "fetch": "https://ask.fm/docs/terms_of_use/?lang=en",
71
- "select": ".selection",
72
- "filter": [ "add" ]
73
- }
74
- }
75
- }
76
- ```
77
-
78
- In `services/ASKfm.history.json`:
79
-
80
- ```
81
- {
82
- "name": "ASKfm",
83
- "documents": {
84
- "Terms of Service": [
85
- {
86
- "fetch": "https://ask.fm/docs/terms_of_use/?lang=en",
87
- "select": "body",
88
- "filter": [ "add" ]
89
- "valid_until": "2020-08-24T14:02:39Z"
90
- },
91
- {
92
- "fetch": "https://ask.fm/docs/terms_of_use/?lang=en",
93
- "select": "body",
94
- "valid_until": "2020-08-23T14:02:39Z"
95
- }
96
- ]
97
- }
98
- }
99
- ```
100
-
101
- **Pros:**
102
-
103
- - Service declaration stay small and simple
104
- - History file is kept close to the service declaration so users might see them
105
-
106
- **Cons:**
107
-
108
- - Make the discovery of history capacities less easy
109
- - Increase the probability of forgetting to update history file when making a change in the service discovery
110
-
111
- ## Option 2A
112
-
113
- Same as option 2, but the history file should only contain the document declarations to avoid divergence on service properties with the one in the original file.
114
-
115
- In `services/ASKfm.json`, **called the “service declaration”**:
116
-
117
- ```
118
- {
119
- "name": "ASKfm",
120
- "documents": {
121
- "Terms of Service": {
122
- "fetch": "https://ask.fm/docs/terms_of_use/?lang=en",
123
- "select": ".selection",
124
- "filter": [ "add" ]
125
- }
126
- }
127
- }
128
- ```
129
-
130
- In `services/ASKfm.history.json`, **called the “service history”**:
131
-
132
- ```
133
- {
134
- "Terms of Service": [
135
- {
136
- "fetch": "https://ask.fm/docs/terms_of_use/?lang=en",
137
- "select": "body",
138
- "filter": [ "add" ]
139
- "valid_until": "2020-08-24T14:02:39Z"
140
- },
141
- {
142
- "fetch": "https://ask.fm/docs/terms_of_use/?lang=en",
143
- "select": "body",
144
- "valid_until": "2020-08-23T14:02:39Z"
145
- }
146
- ]
147
- }
148
- ```
149
-
150
- ## Option 3: Add an history service declaration file in `services/history` folder
151
-
152
- In `services/ASKfm.json`:
153
-
154
- ```
155
- {
156
- "name": "ASKfm",
157
- "documents": {
158
- "Terms of Service": {
159
- "fetch": "https://ask.fm/docs/terms_of_use/?lang=en",
160
- "select": ".selection",
161
- "filter": [ "add" ]
162
- }
163
- }
164
- }
165
- ```
166
-
167
- In `services/history/ASKfm.json`:
168
-
169
- ```
170
- {
171
- "name": "ASKfm",
172
- "documents": {
173
- "Terms of Service": [
174
- {
175
- "fetch": "https://ask.fm/docs/terms_of_use/?lang=en",
176
- "select": "body",
177
- "filter": [ "add" ]
178
- "valid_until": "2020-08-24T14:02:39Z"
179
- },
180
- {
181
- "fetch": "https://ask.fm/docs/terms_of_use/?lang=en",
182
- "select": "body",
183
- "valid_until": "2020-08-23T14:02:39Z"
184
- }
185
- ]
186
- }
187
- }
188
- ```
189
-
190
- **Pros:**
191
-
192
- - Service declaration stay small and simple
193
- - All history updates are reserved to users with the knowledge that might work as gatekeepers
194
-
195
- **Cons:**
196
-
197
- - All history updates are reserved to users with the knowledge that might work as gatekeepers :)
198
- - Need to rely on people with knowledge to keep the history
199
-
200
- ## Some thoughts
201
-
202
- ### Community
203
-
204
- The choice might have implication on the community that will grow around the project.
205
-
206
- _Option 1_ shows everything to everyone, it might frightened some contributors with some apparent complexity (once there are history in the declaration file), but it might also encourage them to learn about it if they want or feel the need to. All contributors will share the same view and knowledge about the system. This might encourage collaboration between them to learn and improve together.
207
-
208
- _Option 2_ and _Option 3_ hide the complexity of history management in separate files and only most adventurous contributors will find them by themselves. Contribution to those files will probably be done by specific contributors that will be taught to manage those file. Thus creating two different kind of contributors: those who will stay with the basic service declaration, not knowing that more complex options exist, and those who will have the knowledge of history management whose work might stay in the shadow or work as gatekeeper.
209
-
210
- ## Decision Outcome
211
-
212
- [After consulting the community](https://github.com/ambanum/OpenTermsArchive/issues/156), the options 2A is retained as it hide complexity (compared to Option 1) of the history while increasing its discoverability (compared to Option 3) for contributors who might become more “adventurous”.
@@ -1,123 +0,0 @@
1
- # Determining an appropriate database system to store snapshots
2
-
3
- - Date: 2021-10-20
4
-
5
- ## Context and Problem Statement
6
-
7
- ### Context
8
-
9
- The Versions repository has several purposes:
10
-
11
- - Display differences between two versions, in particular when users receive a notification of change, so that they can simply see the changes.
12
- - Explore significant changes in tracked documents.
13
- - Offer a corpus of the latest versions of all the documents of the monitored services.
14
- - Serve as a dataset for research.
15
-
16
- It is therefore important that repository constitutes a quality dataset, to provide relevant information to users.
17
-
18
- For this purpose, the following constraints are considered necessary:
19
-
20
- - Versions must be ordered chronologically, so that navigation through the history of a document is intuitive.
21
- - Versions should not contain noise, only significant changes.
22
- - Each version must contain a link to the snapshot that was used to generate it.
23
-
24
- Currently, the following problems with the repository of Versions are identified:
25
-
26
- - Noise in the versions: URL or structure changes in the tracked documents.
27
- - Presence of refilter commits: related to URL and selector updates in service declarations or to Open Terms Archive code evolution.
28
- - Presence of commits due to code changes: type renaming, service renaming, documentation changes in the repository.
29
- - Presence of unordered commits: consequence of the import of the ToSBack history in snapshots or to the import of snapshots corresponding to archived documents provided by the services themselves.
30
-
31
- The solution considered in order to provide a quality dataset therefore consists of being able to regenerate the `versions` from the `snapshots`, that's what we call rewriting history.
32
-
33
- #### Rewriting history
34
-
35
- To rewrite history, we go through the snapshot commits one by one after reordering them (in memory) and we create a version commit each time, avoiding commits corresponding to noise and performing any renaming.
36
-
37
- This implies being able to version the service filters (used to generate the version from the snapshot).
38
- See https://github.com/ambanum/OpenTermsArchive/issues/156.
39
-
40
- ### Problem
41
-
42
- Currently, `git` is used as database for storing snapshots and versions.
43
- One year ago, the process to rewrite history was estimated to take about 16 hours for 100,000 commits. It has also been noted that the evolution of the time is not linear, the more commits there are in `snapshots` the more the average time per commit increases.
44
-
45
- It appears that the most costly operation is accessing the contents of a commit (checkout).
46
- It also appears that the older the commit is in the git history, the longer this operation takes.
47
-
48
- > For example, on a history containing about 100,000 commits, accessing the contents of the oldest commit takes about 1,000 ms while accessing the most recent commit takes only 100 ms.
49
-
50
- At the date of this document, the number of commits entries approaches the million and to iterate over these snapshots, to rewrite versions history, it currently takes more or less 3 months.
51
-
52
-
53
- Also, `git` implies to store data in a hash tree in the form of chronologically ordered commits. So to insert snapshots in the history, it implies to rewrite the whole snapshots history which also takes the same time as reading them.
54
-
55
- As described previously, we need to be able to regenerate versions from snapshots (for example to [rename services](https://github.com/ambanum/OpenTermsArchive/issues/314)) and to be able to insert snapshots in the history (for example to [import databases](https://github.com/ambanum/OpenTermsArchive/pull/214)).
56
- **This cannot take 6 months.**
57
-
58
- Moreover, as the number of snapshots will keep on growing, we need a system which allows scaling, potentially across multiple servers.
59
-
60
- Thus, we need a database management system meeting the following requirements:
61
-
62
- - Access time to a snapshot should be constant and independent from its authoring date.
63
- - Inserting time of a snapshot should be constant and independent from its authoring date.
64
- - Support concurrent access.
65
- - Scale horizontally.
66
-
67
- ### Solutions considered
68
-
69
- #### 1. Keep the system under git
70
-
71
- ##### Splitting into sub-repos
72
-
73
- Since accessing the contents of a commit takes longer the older it is in the history considered, the idea would be to work successively on ordered subsets of this history.
74
- This means truncating the history, browsing the remaining commits and regenerating the corresponding versions. Then creating another subset of the history which contains an arbitrary number of commits following the commits already browsed and perform the processing.
75
-
76
- To create a history subset with git :
77
- - Create a clone of a subset of N commits from the local snapshot: `git clone --depth <N> "file://local/path/snapshots" snapshots-tmp` with `N` corresponding to the position of the first commit you want in the block relative to the last commit in the history
78
- - Remove all commits older than the last commit you want to keep in the block: `git reset --hard <sha>` with `sha` corresponding to the id of the last commit you want to have in the block.
79
- - Clean up git to ensure that history navigation is efficient: `git gc`.
80
-
81
- So we need to split the history into chronologically ordered blocks, which leads us to the next problem.
82
-
83
- ##### Splitting and reordering blocks of snapshots
84
-
85
- Because snapshot commits are unordered, we can't simply create blocks of a fixed size from the git history (otherwise we'd process commits out of order).
86
- It is necessary to create blocks whose commits are ordered within the block but also in relation to the other blocks: for example, all the commits of the first block processed must be older than the commits of all the other blocks.
87
-
88
- The solution would be to create blocks in order: from the git history, we look for commits that are not in their place (whose date is earlier than that of its predecessor).
89
-
90
- Each of these commits represents the first commit of a block. This block extends to the previous one, the starting point of the next block.
91
- We thus obtain blocks whose commits are ordered.
92
-
93
- We still have to order the blocks between them (note, it is possible to have to cut a block to be able to place another).
94
-
95
- These chronologically ordered commit blocks, without overlap, can then be used with the previous approach (it may be necessary to re-split these blocks so that they have a reasonable size).
96
-
97
- #### 2. Move snapshots to a document-oriented database
98
-
99
- The idea of this solution is to keep the `versions` under git in order to continue to enjoy the benefits that GitHub provides in terms of browsing and viewing diffs, but to save the snapshots in a database, since we don't really need to browse the snapshots via a graphical interface nor to see the diff between two snapshots, which would allow us to be able to access the content more efficiently.
100
-
101
- MongoDB seems to meet the constraints:
102
-
103
- - It natively allows horizontal scaling with [replica sets](https://docs.mongodb.com/manual/replication/) and [sharding](https://docs.mongodb.com/manual/sharding/).
104
- - It supports concurrent access.
105
- - It has [In-Memory storage engine](https://docs.mongodb.com/manual/core/inmemory/) as an option for performance.
106
-
107
- We also did a simple test to ensure that access time and insert time also meets the requirements. We populated a database with one million entries and tried accessing snapshots with random dates and we found that access times remained stable. In our test on 1000 sequential access to random snapshot, the average access time was ~3.5ms with a maximum of ~50ms.
108
-
109
- Moreover, MongoDB has the following benefits:
110
-
111
- - Easy to use: offers a simple query syntax SQL and has a quick learning curve, especially for JavaScript developers.
112
- - Flexible and evolutive: allows to manage data of any structure, not just tabular structures defined in advance.
113
- - Widely used in the JavaScript ecosystem.
114
-
115
- As downside, joining documents in MongoDB is no easy task and pulling data from several collections requires a number of queries, which will lead to long turn-around times. This is not a problem in our case as we do not currently envision a need for such complex queries.
116
-
117
- ## Decision Outcome
118
-
119
- As MongoDB meets the requirements it is retained as a solution.
120
-
121
- ### Benchmark
122
-
123
- With MongoDB implementation, refilter takes around ~3m where it took around ~1h20 with the Git version.