├── .github
└── workflows
│ ├── ci.yml
│ └── clean.yml
├── .gitignore
├── .mergify.yml
├── .scalafmt.conf
├── CHANGELOG.md
├── LICENSE
├── NOTICE
├── README.md
├── build.sbt
├── core
└── src
│ ├── main
│ └── scala
│ │ └── org
│ │ └── typelevel
│ │ └── vault
│ │ ├── InvariantMapping.scala
│ │ ├── Key.scala
│ │ ├── Locker.scala
│ │ └── Vault.scala
│ └── test
│ └── scala
│ └── org
│ └── typelevel
│ └── vault
│ ├── KeySuite.scala
│ └── VaultSuite.scala
├── docs
└── index.md
├── licenses
└── LICENSE_vault
└── project
├── build.properties
└── plugins.sbt
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | # This file was automatically generated by sbt-github-actions using the
2 | # githubWorkflowGenerate task. You should add and commit this file to
3 | # your git repository. It goes without saying that you shouldn't edit
4 | # this file by hand! Instead, if you wish to make changes, you should
5 | # change your sbt build configuration to revise the workflow description
6 | # to meet your needs, then regenerate this file.
7 |
8 | name: Continuous Integration
9 |
10 | on:
11 | pull_request:
12 | branches: ['**', '!update/**', '!pr/**']
13 | push:
14 | branches: ['**', '!update/**', '!pr/**']
15 | tags: [v*]
16 |
17 | env:
18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
19 |
20 |
21 | concurrency:
22 | group: ${{ github.workflow }} @ ${{ github.ref }}
23 | cancel-in-progress: true
24 |
25 | jobs:
26 | build:
27 | name: Test
28 | strategy:
29 | matrix:
30 | os: [ubuntu-22.04]
31 | scala: [2.12, 3, 2.13]
32 | java: [temurin@8, temurin@17]
33 | project: [rootJS, rootJVM, rootNative]
34 | exclude:
35 | - scala: 2.12
36 | java: temurin@17
37 | - scala: 3
38 | java: temurin@17
39 | - project: rootJS
40 | java: temurin@17
41 | - project: rootNative
42 | java: temurin@17
43 | runs-on: ${{ matrix.os }}
44 | timeout-minutes: 60
45 | steps:
46 | - name: Checkout current branch (full)
47 | uses: actions/checkout@v4
48 | with:
49 | fetch-depth: 0
50 |
51 | - name: Setup sbt
52 | uses: sbt/setup-sbt@v1
53 |
54 | - name: Setup Java (temurin@8)
55 | id: setup-java-temurin-8
56 | if: matrix.java == 'temurin@8'
57 | uses: actions/setup-java@v4
58 | with:
59 | distribution: temurin
60 | java-version: 8
61 | cache: sbt
62 |
63 | - name: sbt update
64 | if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false'
65 | run: sbt +update
66 |
67 | - name: Setup Java (temurin@17)
68 | id: setup-java-temurin-17
69 | if: matrix.java == 'temurin@17'
70 | uses: actions/setup-java@v4
71 | with:
72 | distribution: temurin
73 | java-version: 17
74 | cache: sbt
75 |
76 | - name: sbt update
77 | if: matrix.java == 'temurin@17' && steps.setup-java-temurin-17.outputs.cache-hit == 'false'
78 | run: sbt +update
79 |
80 | - name: Check that workflows are up to date
81 | run: sbt githubWorkflowCheck
82 |
83 | - name: Check headers and formatting
84 | if: matrix.java == 'temurin@8' && matrix.os == 'ubuntu-22.04'
85 | run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' headerCheckAll scalafmtCheckAll 'project /' scalafmtSbtCheck
86 |
87 | - name: scalaJSLink
88 | if: matrix.project == 'rootJS'
89 | run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' Test/scalaJSLinkerResult
90 |
91 | - name: nativeLink
92 | if: matrix.project == 'rootNative'
93 | run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' Test/nativeLink
94 |
95 | - name: Test
96 | run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' test
97 |
98 | - name: Check binary compatibility
99 | if: matrix.java == 'temurin@8' && matrix.os == 'ubuntu-22.04'
100 | run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' mimaReportBinaryIssues
101 |
102 | - name: Generate API documentation
103 | if: matrix.java == 'temurin@8' && matrix.os == 'ubuntu-22.04'
104 | run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' doc
105 |
106 | - name: Make target directories
107 | if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
108 | run: mkdir -p core/.native/target core/.js/target core/.jvm/target project/target
109 |
110 | - name: Compress target directories
111 | if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
112 | run: tar cf targets.tar core/.native/target core/.js/target core/.jvm/target project/target
113 |
114 | - name: Upload target directories
115 | if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
116 | uses: actions/upload-artifact@v4
117 | with:
118 | name: target-${{ matrix.os }}-${{ matrix.java }}-${{ matrix.scala }}-${{ matrix.project }}
119 | path: targets.tar
120 |
121 | publish:
122 | name: Publish Artifacts
123 | needs: [build]
124 | if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
125 | strategy:
126 | matrix:
127 | os: [ubuntu-22.04]
128 | java: [temurin@8]
129 | runs-on: ${{ matrix.os }}
130 | steps:
131 | - name: Checkout current branch (full)
132 | uses: actions/checkout@v4
133 | with:
134 | fetch-depth: 0
135 |
136 | - name: Setup sbt
137 | uses: sbt/setup-sbt@v1
138 |
139 | - name: Setup Java (temurin@8)
140 | id: setup-java-temurin-8
141 | if: matrix.java == 'temurin@8'
142 | uses: actions/setup-java@v4
143 | with:
144 | distribution: temurin
145 | java-version: 8
146 | cache: sbt
147 |
148 | - name: sbt update
149 | if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false'
150 | run: sbt +update
151 |
152 | - name: Setup Java (temurin@17)
153 | id: setup-java-temurin-17
154 | if: matrix.java == 'temurin@17'
155 | uses: actions/setup-java@v4
156 | with:
157 | distribution: temurin
158 | java-version: 17
159 | cache: sbt
160 |
161 | - name: sbt update
162 | if: matrix.java == 'temurin@17' && steps.setup-java-temurin-17.outputs.cache-hit == 'false'
163 | run: sbt +update
164 |
165 | - name: Download target directories (2.12, rootJS)
166 | uses: actions/download-artifact@v4
167 | with:
168 | name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-rootJS
169 |
170 | - name: Inflate target directories (2.12, rootJS)
171 | run: |
172 | tar xf targets.tar
173 | rm targets.tar
174 |
175 | - name: Download target directories (2.12, rootJVM)
176 | uses: actions/download-artifact@v4
177 | with:
178 | name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-rootJVM
179 |
180 | - name: Inflate target directories (2.12, rootJVM)
181 | run: |
182 | tar xf targets.tar
183 | rm targets.tar
184 |
185 | - name: Download target directories (2.12, rootNative)
186 | uses: actions/download-artifact@v4
187 | with:
188 | name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-rootNative
189 |
190 | - name: Inflate target directories (2.12, rootNative)
191 | run: |
192 | tar xf targets.tar
193 | rm targets.tar
194 |
195 | - name: Download target directories (3, rootJS)
196 | uses: actions/download-artifact@v4
197 | with:
198 | name: target-${{ matrix.os }}-${{ matrix.java }}-3-rootJS
199 |
200 | - name: Inflate target directories (3, rootJS)
201 | run: |
202 | tar xf targets.tar
203 | rm targets.tar
204 |
205 | - name: Download target directories (3, rootJVM)
206 | uses: actions/download-artifact@v4
207 | with:
208 | name: target-${{ matrix.os }}-${{ matrix.java }}-3-rootJVM
209 |
210 | - name: Inflate target directories (3, rootJVM)
211 | run: |
212 | tar xf targets.tar
213 | rm targets.tar
214 |
215 | - name: Download target directories (3, rootNative)
216 | uses: actions/download-artifact@v4
217 | with:
218 | name: target-${{ matrix.os }}-${{ matrix.java }}-3-rootNative
219 |
220 | - name: Inflate target directories (3, rootNative)
221 | run: |
222 | tar xf targets.tar
223 | rm targets.tar
224 |
225 | - name: Download target directories (2.13, rootJS)
226 | uses: actions/download-artifact@v4
227 | with:
228 | name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-rootJS
229 |
230 | - name: Inflate target directories (2.13, rootJS)
231 | run: |
232 | tar xf targets.tar
233 | rm targets.tar
234 |
235 | - name: Download target directories (2.13, rootJVM)
236 | uses: actions/download-artifact@v4
237 | with:
238 | name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-rootJVM
239 |
240 | - name: Inflate target directories (2.13, rootJVM)
241 | run: |
242 | tar xf targets.tar
243 | rm targets.tar
244 |
245 | - name: Download target directories (2.13, rootNative)
246 | uses: actions/download-artifact@v4
247 | with:
248 | name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-rootNative
249 |
250 | - name: Inflate target directories (2.13, rootNative)
251 | run: |
252 | tar xf targets.tar
253 | rm targets.tar
254 |
255 | - name: Import signing key
256 | if: env.PGP_SECRET != '' && env.PGP_PASSPHRASE == ''
257 | env:
258 | PGP_SECRET: ${{ secrets.PGP_SECRET }}
259 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }}
260 | run: echo $PGP_SECRET | base64 -d -i - | gpg --import
261 |
262 | - name: Import signing key and strip passphrase
263 | if: env.PGP_SECRET != '' && env.PGP_PASSPHRASE != ''
264 | env:
265 | PGP_SECRET: ${{ secrets.PGP_SECRET }}
266 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }}
267 | run: |
268 | echo "$PGP_SECRET" | base64 -d -i - > /tmp/signing-key.gpg
269 | echo "$PGP_PASSPHRASE" | gpg --pinentry-mode loopback --passphrase-fd 0 --import /tmp/signing-key.gpg
270 | (echo "$PGP_PASSPHRASE"; echo; echo) | gpg --command-fd 0 --pinentry-mode loopback --change-passphrase $(gpg --list-secret-keys --with-colons 2> /dev/null | grep '^sec:' | cut --delimiter ':' --fields 5 | tail -n 1)
271 |
272 | - name: Publish
273 | env:
274 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
275 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
276 | SONATYPE_CREDENTIAL_HOST: ${{ secrets.SONATYPE_CREDENTIAL_HOST }}
277 | run: sbt tlCiRelease
278 |
279 | dependency-submission:
280 | name: Submit Dependencies
281 | if: github.event.repository.fork == false && github.event_name != 'pull_request'
282 | strategy:
283 | matrix:
284 | os: [ubuntu-22.04]
285 | java: [temurin@8]
286 | runs-on: ${{ matrix.os }}
287 | steps:
288 | - name: Checkout current branch (full)
289 | uses: actions/checkout@v4
290 | with:
291 | fetch-depth: 0
292 |
293 | - name: Setup sbt
294 | uses: sbt/setup-sbt@v1
295 |
296 | - name: Setup Java (temurin@8)
297 | id: setup-java-temurin-8
298 | if: matrix.java == 'temurin@8'
299 | uses: actions/setup-java@v4
300 | with:
301 | distribution: temurin
302 | java-version: 8
303 | cache: sbt
304 |
305 | - name: sbt update
306 | if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false'
307 | run: sbt +update
308 |
309 | - name: Setup Java (temurin@17)
310 | id: setup-java-temurin-17
311 | if: matrix.java == 'temurin@17'
312 | uses: actions/setup-java@v4
313 | with:
314 | distribution: temurin
315 | java-version: 17
316 | cache: sbt
317 |
318 | - name: sbt update
319 | if: matrix.java == 'temurin@17' && steps.setup-java-temurin-17.outputs.cache-hit == 'false'
320 | run: sbt +update
321 |
322 | - name: Submit Dependencies
323 | uses: scalacenter/sbt-dependency-submission@v2
324 | with:
325 | modules-ignore: rootjs_2.12 rootjs_3 rootjs_2.13 docs_2.12 docs_3 docs_2.13 rootjvm_2.12 rootjvm_3 rootjvm_2.13 rootnative_2.12 rootnative_3 rootnative_2.13
326 | configs-ignore: test scala-tool scala-doc-tool test-internal
327 |
328 | site:
329 | name: Generate Site
330 | strategy:
331 | matrix:
332 | os: [ubuntu-22.04]
333 | java: [temurin@17]
334 | runs-on: ${{ matrix.os }}
335 | steps:
336 | - name: Checkout current branch (full)
337 | uses: actions/checkout@v4
338 | with:
339 | fetch-depth: 0
340 |
341 | - name: Setup sbt
342 | uses: sbt/setup-sbt@v1
343 |
344 | - name: Setup Java (temurin@8)
345 | id: setup-java-temurin-8
346 | if: matrix.java == 'temurin@8'
347 | uses: actions/setup-java@v4
348 | with:
349 | distribution: temurin
350 | java-version: 8
351 | cache: sbt
352 |
353 | - name: sbt update
354 | if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false'
355 | run: sbt +update
356 |
357 | - name: Setup Java (temurin@17)
358 | id: setup-java-temurin-17
359 | if: matrix.java == 'temurin@17'
360 | uses: actions/setup-java@v4
361 | with:
362 | distribution: temurin
363 | java-version: 17
364 | cache: sbt
365 |
366 | - name: sbt update
367 | if: matrix.java == 'temurin@17' && steps.setup-java-temurin-17.outputs.cache-hit == 'false'
368 | run: sbt +update
369 |
370 | - name: Generate site
371 | run: sbt docs/tlSite
372 |
373 | - name: Publish site
374 | if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
375 | uses: peaceiris/actions-gh-pages@v4.0.0
376 | with:
377 | github_token: ${{ secrets.GITHUB_TOKEN }}
378 | publish_dir: site/target/docs/site
379 | keep_files: true
380 |
--------------------------------------------------------------------------------
/.github/workflows/clean.yml:
--------------------------------------------------------------------------------
1 | # This file was automatically generated by sbt-github-actions using the
2 | # githubWorkflowGenerate task. You should add and commit this file to
3 | # your git repository. It goes without saying that you shouldn't edit
4 | # this file by hand! Instead, if you wish to make changes, you should
5 | # change your sbt build configuration to revise the workflow description
6 | # to meet your needs, then regenerate this file.
7 |
8 | name: Clean
9 |
10 | on: push
11 |
12 | jobs:
13 | delete-artifacts:
14 | name: Delete Artifacts
15 | runs-on: ubuntu-latest
16 | env:
17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
18 | steps:
19 | - name: Delete artifacts
20 | run: |
21 | # Customize those three lines with your repository and credentials:
22 | REPO=${GITHUB_API_URL}/repos/${{ github.repository }}
23 |
24 | # A shortcut to call GitHub API.
25 | ghapi() { curl --silent --location --user _:$GITHUB_TOKEN "$@"; }
26 |
27 | # A temporary file which receives HTTP response headers.
28 | TMPFILE=/tmp/tmp.$$
29 |
30 | # An associative array, key: artifact name, value: number of artifacts of that name.
31 | declare -A ARTCOUNT
32 |
33 | # Process all artifacts on this repository, loop on returned "pages".
34 | URL=$REPO/actions/artifacts
35 | while [[ -n "$URL" ]]; do
36 |
37 | # Get current page, get response headers in a temporary file.
38 | JSON=$(ghapi --dump-header $TMPFILE "$URL")
39 |
40 | # Get URL of next page. Will be empty if we are at the last page.
41 | URL=$(grep '^Link:' "$TMPFILE" | tr ',' '\n' | grep 'rel="next"' | head -1 | sed -e 's/.*/' -e 's/>.*//')
42 | rm -f $TMPFILE
43 |
44 | # Number of artifacts on this page:
45 | COUNT=$(( $(jq <<<$JSON -r '.artifacts | length') ))
46 |
47 | # Loop on all artifacts on this page.
48 | for ((i=0; $i < $COUNT; i++)); do
49 |
50 | # Get name of artifact and count instances of this name.
51 | name=$(jq <<<$JSON -r ".artifacts[$i].name?")
52 | ARTCOUNT[$name]=$(( $(( ${ARTCOUNT[$name]} )) + 1))
53 |
54 | id=$(jq <<<$JSON -r ".artifacts[$i].id?")
55 | size=$(( $(jq <<<$JSON -r ".artifacts[$i].size_in_bytes?") ))
56 | printf "Deleting '%s' #%d, %'d bytes\n" $name ${ARTCOUNT[$name]} $size
57 | ghapi -X DELETE $REPO/actions/artifacts/$id
58 | done
59 | done
60 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | .idea/
3 | # vim
4 | *.sw?
5 |
6 | # Ignore [ce]tags files
7 | tags
8 |
9 | .bloop
10 | .metals
11 | .vscode
12 | .bsp
13 | .jekyll-cache/
14 |
15 | # metals
16 | metals.sbt
--------------------------------------------------------------------------------
/.mergify.yml:
--------------------------------------------------------------------------------
1 | pull_request_rules:
2 | - name: automatically merge scala-steward's PRs
3 | conditions:
4 | - author=scala-steward
5 | - status-success=Travis CI - Pull Request
6 | - body~=labels:.*semver-patch.*
7 | actions:
8 | merge:
9 | method: merge
10 |
--------------------------------------------------------------------------------
/.scalafmt.conf:
--------------------------------------------------------------------------------
1 | version=3.9.4
2 | runner.dialect = scala213source3
3 | align.openParenCallSite = true
4 | align.openParenDefnSite = true
5 | maxColumn = 120
6 | continuationIndent.defnSite = 2
7 | assumeStandardLibraryStripMargin = true
8 | danglingParentheses.preset = true
9 | rewrite.rules = [AvoidInfix, SortImports, RedundantParens, SortModifiers]
10 | newlines.afterCurlyLambda = preserve
11 | docstrings.style = Asterisk
12 | docstrings.oneline = unfold
13 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # changelog
2 |
3 | This file summarizes **notable** changes for each release, but does not describe internal changes unless they are particularly exciting. This change log is ordered chronologically, so each release contains all changes described below it.
4 |
5 | ----
6 |
7 | ## Unreleased Changes
8 |
9 | ## New and Noteworthy for Version 3.0.0-RC1
10 |
11 | * Built for cats-effect-3.0.0-RC1
12 | * Uses Cats-Effect-3's `Unique` instead of our internal one
13 |
14 | ## New and Noteworthy for Version 3.0.0-M1
15 |
16 | * Built for cats-effect-3.0.0-M5
17 | * Internal, private copy of Unique
18 |
19 | ## New and Noteworthy for Version 2.1.7
20 |
21 | * Add support for Dotty 3.0.0-RC1
22 | * Drop support for Dotty 3.0.0-M2
23 |
24 | ## New and Noteworthy for Version 2.1.0
25 |
26 | * cats-2.4.2
27 | * cats-effect-2.3.3
28 | * unique-2.1.1
29 |
30 | ## New and Noteworthy for Version 2.1.0-M2
31 |
32 | * Now publishes under `org.typelevel`
33 | * Package renamed to `org.typelevel.vault`
34 | * Support for Dotty 3.0.0-M2 and 3.0.0-M3
35 |
36 | ## New and Noteworthy for Version 1.0.0
37 |
38 | Stable Release of `vault`. This library will maintain binary compatibility moving forward for the forseeable future.
39 |
40 | - [#39](https://github.com/ChristopherDavenport/vault/pull/39) Scala 2.13 Integration
41 |
42 | Upgrades:
43 |
44 | - cats 1.6.0
45 | - cats-effect 1.2.0
46 | - unique 1.0.0
47 |
48 | ## New and Noteworthy for Version 0.1.0
49 |
50 | Baseline Vault implementation for an implementation of a type-safe, persistent storage of values of arbitrary types. Dependencies on `cats` 1.x, `cats-effect` 1.x, and `unique` 0.1.x.
51 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2018 Christopher Davenport
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | vault
2 | Copyright 2018 Christopher Davenport
3 | Licensed under the MIT License (see LICENSE)
4 |
5 | This software contains portions of code derived from HeinrichApfelmus/vault
6 | https://github.com/HeinrichApfelmus/vault
7 | Copyright (c)2011, Heinrich Apfelmus
8 | Licensed under BSD3 (see licenses/LICENSE_vault)
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vault [](https://maven-badges.herokuapp.com/maven-central/org.typelevel/vault_2.12) 
2 |
3 | Vault is a tiny library that provides a single data structure called vault.
4 |
5 | Inspiration was drawn from [HeinrichApfelmus/vault](https://github.com/HeinrichApfelmus/vault) and the original [blog post](https://apfelmus.nfshost.com/blog/2011/09/04-vault.html)
6 |
7 | A vault is a type-safe, persistent storage for values of arbitrary types. Like `Ref`, it should be capable of storing values of any type in it, but unlike `Ref`, behave like a persistent, first-class data structure.
8 |
9 | It is analogous to a bank vault, where you can access different bank boxes with different keys; hence the name.
10 |
11 | ## Microsite
12 |
13 | Head on over [to the microsite](https://typelevel.org/vault/)
14 |
15 | ## Quick Start
16 |
17 | To use vault in an existing SBT project with Scala 2.12 or a later version, add the following dependencies to your
18 | `build.sbt` depending on your needs:
19 |
20 | ```scala
21 | libraryDependencies ++= Seq(
22 | "org.typelevel" %% "vault" % ""
23 | )
24 | ```
25 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | val Scala212 = "2.12.20"
2 | val Scala213 = "2.13.16"
3 | val Scala3 = "3.3.6"
4 |
5 | ThisBuild / tlBaseVersion := "3.6"
6 | ThisBuild / crossScalaVersions := Seq(Scala212, Scala3, Scala213)
7 | ThisBuild / tlVersionIntroduced := Map("3" -> "3.0.3")
8 | ThisBuild / tlMimaPreviousVersions ~= (_.filterNot(_ == "3.2.0"))
9 | ThisBuild / licenses := List("MIT" -> url("http://opensource.org/licenses/MIT"))
10 | ThisBuild / startYear := Some(2021)
11 | ThisBuild / tlSiteApiUrl := Some(url("https://www.javadoc.io/doc/org.typelevel/vault_2.13/latest/org/typelevel/vault/"))
12 |
13 | ThisBuild / developers := List(
14 | tlGitHubDev("christopherdavenport", "Christopher Davenport")
15 | )
16 |
17 | val JDK8 = JavaSpec.temurin("8")
18 | val JDK17 = JavaSpec.temurin("17")
19 |
20 | ThisBuild / githubWorkflowJavaVersions := Seq(JDK8, JDK17)
21 |
22 | lazy val root = tlCrossRootProject.aggregate(core)
23 |
24 | lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform)
25 | .crossType(CrossType.Pure)
26 | .in(file("core"))
27 | .settings(
28 | name := "vault",
29 | libraryDependencies ++= Seq(
30 | "org.typelevel" %%% "cats-core" % catsV,
31 | "org.typelevel" %%% "cats-effect" % catsEffectV,
32 | "org.typelevel" %%% "cats-laws" % catsV % Test,
33 | "org.typelevel" %%% "discipline-munit" % disciplineMunitV % Test,
34 | "org.typelevel" %%% "scalacheck-effect-munit" % scalacheckEffectV % Test,
35 | "org.typelevel" %%% "munit-cats-effect" % munitCatsEffectV % Test
36 | )
37 | )
38 | .nativeSettings(
39 | tlVersionIntroduced := List("2.12", "2.13", "3").map(_ -> "3.2.2").toMap
40 | )
41 |
42 | lazy val docs = project
43 | .in(file("site"))
44 | .settings(tlFatalWarnings := false)
45 | .dependsOn(core.jvm)
46 | .enablePlugins(TypelevelSitePlugin)
47 |
48 | val catsV = "2.11.0"
49 | val catsEffectV = "3.6.1"
50 | val disciplineMunitV = "2.0.0-M3"
51 | val scalacheckEffectV = "2.0.0-M2"
52 | val munitCatsEffectV = "2.1.0"
53 | val kindProjectorV = "0.13.3"
54 |
55 | // Scalafmt
56 | addCommandAlias("fmt", "; Compile / scalafmt; Test / scalafmt; scalafmtSbt")
57 | addCommandAlias("fmtCheck", "; Compile / scalafmtCheck; Test / scalafmtCheck; scalafmtSbtCheck")
58 |
--------------------------------------------------------------------------------
/core/src/main/scala/org/typelevel/vault/InvariantMapping.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2021 Typelevel
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of
5 | * this software and associated documentation files (the "Software"), to deal in
6 | * the Software without restriction, including without limitation the rights to
7 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8 | * the Software, and to permit persons to whom the Software is furnished to do so,
9 | * subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package org.typelevel.vault
23 |
24 | import cats.data.AndThen
25 |
26 | private[vault] trait InvariantMapping[A] { outer =>
27 | type I
28 | def in: A => I
29 | def out: I => A
30 | def imap[B](f: A => B)(g: B => A): InvariantMapping[B] =
31 | new InvariantMapping[B] {
32 | type I = outer.I
33 | val in = AndThen(g).andThen(outer.in)
34 | val out = AndThen(outer.out).andThen(f)
35 | }
36 | }
37 |
38 | private[vault] object InvariantMapping {
39 | def id[A]: InvariantMapping[A] =
40 | new InvariantMapping[A] {
41 | type I = A
42 | val in = identity
43 | val out = identity
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/core/src/main/scala/org/typelevel/vault/Key.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2021 Typelevel
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of
5 | * this software and associated documentation files (the "Software"), to deal in
6 | * the Software without restriction, including without limitation the rights to
7 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8 | * the Software, and to permit persons to whom the Software is furnished to do so,
9 | * subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package org.typelevel.vault
23 |
24 | import cats.Contravariant
25 | import cats.Functor
26 | import cats.effect.kernel.Unique
27 | import cats.Hash
28 | import cats.implicits._
29 | import cats.Invariant
30 | import cats.data.AndThen
31 |
32 | /**
33 | * A unique value tagged with a specific type to that unique. Since it can only be created as a result of that, it links
34 | * a Unique identifier to a type known by the compiler.
35 | */
36 | final class Key[A] private (
37 | private[vault] val unique: Unique.Token,
38 | private[vault] val imapping: InvariantMapping[A]
39 | ) extends InsertKey[A]
40 | with LookupKey[A] {
41 |
42 | // Delegates, for convenience.
43 | private[vault] type I = imapping.I
44 | private[vault] val in = imapping.in
45 | private[vault] val out = imapping.out
46 |
47 | // Overloaded ctor to preserve bincompat
48 | def this(unique: Unique.Token) =
49 | this(unique, InvariantMapping.id[A])
50 |
51 | /**
52 | * Create a copy of this key that references the same underlying vault element, transformed from type `B` before
53 | * insert, and to `B` after lookup.
54 | */
55 | def imap[B](f: A => B)(g: B => A): Key[B] =
56 | new Key(unique, imapping.imap(f)(g))
57 |
58 | override def hashCode(): Int = unique.hashCode()
59 |
60 | }
61 |
62 | sealed trait DeleteKey {
63 | private[vault] def unique: Unique.Token
64 | }
65 |
66 | sealed trait InsertKey[-A] extends DeleteKey { outer =>
67 | private[vault] type I
68 | private[vault] def in: A => I
69 |
70 | def contramap[B](f: B => A): InsertKey[B] =
71 | new InsertKey[B] {
72 | val unique = outer.unique
73 | type I = outer.I
74 | val in = AndThen(f).andThen(outer.in)
75 | }
76 | }
77 |
78 | object InsertKey {
79 | implicit val ContravariantInsertKey: Contravariant[InsertKey] =
80 | new Contravariant[InsertKey] {
81 | def contramap[A, B](fa: InsertKey[A])(f: B => A): InsertKey[B] =
82 | fa.contramap(f)
83 | }
84 | }
85 |
86 | sealed trait LookupKey[+A] extends DeleteKey { outer =>
87 | private[vault] def unique: Unique.Token
88 | private[vault] type I
89 | private[vault] def out: I => A
90 |
91 | def map[B](f: A => B): LookupKey[B] =
92 | new LookupKey[B] {
93 | val unique = outer.unique
94 | type I = outer.I
95 | val out = AndThen(outer.out).andThen(f)
96 | }
97 | }
98 |
99 | object LookupKey {
100 | implicit val FunctorLookupKey: Functor[LookupKey] =
101 | new Functor[LookupKey] {
102 | def map[A, B](fa: LookupKey[A])(f: A => B): LookupKey[B] =
103 | fa.map(f)
104 | }
105 | }
106 |
107 | object Key {
108 |
109 | /**
110 | * Create A Typed Key
111 | */
112 | def newKey[F[_]: Functor: Unique, A]: F[Key[A]] = Unique[F].unique.map(new Key[A](_))
113 |
114 | implicit def keyInstances[A]: Hash[Key[A]] = new Hash[Key[A]] {
115 | // Members declared in cats.kernel.Eq
116 | def eqv(x: Key[A], y: Key[A]): Boolean =
117 | x.unique === y.unique
118 |
119 | // Members declared in cats.kernel.Hash
120 | def hash(x: Key[A]): Int = Hash[Unique.Token].hash(x.unique)
121 | }
122 |
123 | implicit val InvariantKey: Invariant[Key] =
124 | new Invariant[Key] {
125 | def imap[A, B](fa: Key[A])(f: A => B)(g: B => A): Key[B] =
126 | fa.imap(f)(g)
127 | }
128 |
129 | }
130 |
--------------------------------------------------------------------------------
/core/src/main/scala/org/typelevel/vault/Locker.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2021 Typelevel
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of
5 | * this software and associated documentation files (the "Software"), to deal in
6 | * the Software without restriction, including without limitation the rights to
7 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8 | * the Software, and to permit persons to whom the Software is furnished to do so,
9 | * subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package org.typelevel.vault
23 |
24 | import cats.implicits._
25 | import cats.effect.kernel.Unique
26 |
27 | /**
28 | * Locker - A persistent store for a single value. This utilizes the fact that a unique is linked to a type. Since the
29 | * key is linked to a type, then we can cast the value to Any, and join it to the Unique. Then if we are then asked to
30 | * unlock this locker with the same unique, we know that the type MUST be the type of the Key, so we can bring it back
31 | * as that type safely.
32 | */
33 | final class Locker private (private val unique: Unique.Token, private val a: Any) {
34 |
35 | /**
36 | * Retrieve the value from the Locker. If the reference equality instance backed by a `Unique` value is the same then
37 | * allows conversion to that type, otherwise as it does not match then this will be `None`
38 | *
39 | * @param k
40 | * The key to check, if the internal Unique value matches then this Locker can be unlocked as the specifed value
41 | */
42 | def unlock[A](k: LookupKey[A]): Option[A] =
43 | if (k.unique === unique) Some(k.out(a.asInstanceOf[k.I]))
44 | else None
45 |
46 | /**
47 | * Retrieve the value from the Locker. If the reference equality instance backed by a `Unique` value is the same then
48 | * allows conversion to that type, otherwise as it does not match then this will be `None`
49 | *
50 | * @param k
51 | * The key to check, if the internal Unique value matches then this Locker can be unlocked as the specifed value
52 | */
53 | private[vault] def unlock[A](k: Key[A]): Option[A] = unlock(k: LookupKey[A])
54 | }
55 |
56 | object Locker {
57 |
58 | /**
59 | * Put a single value into a Locker
60 | */
61 | def apply[A](k: InsertKey[A], a: A): Locker = new Locker(k.unique, k.in(a))
62 |
63 | /**
64 | * Put a single value into a Locker
65 | */
66 | @deprecated("Use Locker(k, a)", since = "3.1.0")
67 | def lock[A](k: Key[A], a: A): Locker = Locker(k, a)
68 |
69 | /**
70 | * Retrieve the value from the Locker. If the reference equality instance backed by a `Unique` value is the same then
71 | * allows conversion to that type, otherwise as it does not match then this will be `None`
72 | *
73 | * @param k
74 | * The key to check, if the internal Unique value matches then this Locker can be unlocked as the specifed value
75 | * @param l
76 | * The locked to check against
77 | */
78 | @deprecated("Use l.unlock(k)", since = "3.1.0")
79 | def unlock[A](k: Key[A], l: Locker): Option[A] = l.unlock(k)
80 | }
81 |
--------------------------------------------------------------------------------
/core/src/main/scala/org/typelevel/vault/Vault.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2021 Typelevel
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of
5 | * this software and associated documentation files (the "Software"), to deal in
6 | * the Software without restriction, including without limitation the rights to
7 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8 | * the Software, and to permit persons to whom the Software is furnished to do so,
9 | * subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package org.typelevel.vault
23 |
24 | import cats.effect.kernel.Unique
25 |
26 | /**
27 | * Vault - A persistent store for values of arbitrary types. This extends the behavior of the locker, into a Map that
28 | * maps Keys to Lockers, creating a heterogenous store of values, accessible by keys. Such that the Vault has no type
29 | * information, all the type information is contained in the keys.
30 | */
31 | final class Vault private (private val m: Map[Unique.Token, Locker]) {
32 |
33 | /**
34 | * Empty this Vault
35 | */
36 | def empty: Vault = Vault.empty
37 |
38 | /**
39 | * Lookup the value of a key in this vault
40 | */
41 | def lookup[A](k: LookupKey[A]): Option[A] = m.get(k.unique).flatMap(_.unlock(k))
42 |
43 | /**
44 | * Lookup the value of a key in this vault
45 | */
46 | private[vault] def lookup[A](k: Key[A]): Option[A] = lookup(k: LookupKey[A])
47 |
48 | /**
49 | * Checks if the value of a key is in this vault.
50 | *
51 | * @param k
52 | * the key to lookup can be any LookupKey, InsertKey, or DeleteKey
53 | */
54 | def contains(k: DeleteKey): Boolean = m.contains(k.unique)
55 |
56 | /**
57 | * Insert a value for a given key. Overwrites any previous value.
58 | */
59 | def insert[A](k: InsertKey[A], a: A): Vault = new Vault(m + (k.unique -> Locker(k, a)))
60 |
61 | /**
62 | * Insert a value for a given key. Overwrites any previous value.
63 | */
64 | private[vault] def insert[A](k: Key[A], a: A): Vault = insert(k: InsertKey[A], a)
65 |
66 | /**
67 | * Checks whether this Vault is empty
68 | */
69 | def isEmpty: Boolean = m.isEmpty
70 |
71 | /**
72 | * Delete a key from the vault
73 | */
74 | // Keeping unused type parameter for source compat
75 | def delete[A](k: DeleteKey): Vault = new Vault(m - k.unique)
76 |
77 | /**
78 | * Delete a key from the vault
79 | */
80 | private[vault] def delete[A](k: Key[A]): Vault = delete(k: DeleteKey)
81 |
82 | /**
83 | * Adjust the value for a given key if it's present in the vault.
84 | */
85 | def adjust[A](k: Key[A], f: A => A): Vault = lookup(k).fold(this)(a => insert(k, f(a)))
86 |
87 | /**
88 | * Merge Two Vaults. `that` is prioritized.
89 | */
90 | def ++(that: Vault): Vault = new Vault(this.m ++ that.m)
91 |
92 | /**
93 | * The size of the vault
94 | */
95 | def size: Int = m.size
96 | }
97 | object Vault {
98 |
99 | /**
100 | * The Empty Vault
101 | */
102 | def empty = new Vault(Map.empty)
103 |
104 | /**
105 | * Lookup the value of a key in the vault
106 | */
107 | @deprecated("Use v.lookup(k)", "3.1.0")
108 | def lookup[A](k: Key[A], v: Vault): Option[A] =
109 | v.lookup(k)
110 |
111 | /**
112 | * Insert a value for a given key. Overwrites any previous value.
113 | */
114 | @deprecated("Use v.insert(k, a)", "3.1.0")
115 | def insert[A](k: Key[A], a: A, v: Vault): Vault =
116 | v.insert(k, a)
117 |
118 | /**
119 | * Checks whether the given Vault is empty
120 | */
121 | @deprecated("Use v.isEmpty", "3.1.0")
122 | def isEmpty(v: Vault): Boolean = v.isEmpty
123 |
124 | /**
125 | * Delete a key from the vault
126 | */
127 | @deprecated("Use v.delete(k)", "3.1.0")
128 | def delete[A](k: Key[A], v: Vault): Vault = v.delete(k)
129 |
130 | /**
131 | * Adjust the value for a given key if it's present in the vault.
132 | */
133 | @deprecated("Use v.adjust(k, f)", "3.1.0")
134 | def adjust[A](k: Key[A], f: A => A, v: Vault): Vault =
135 | v.adjust(k, f)
136 |
137 | /**
138 | * Merge Two Vaults. v2 is prioritized.
139 | */
140 | @deprecated("Use v2 ++ v2", "3.1.0")
141 | def union(v1: Vault, v2: Vault): Vault = v1 ++ v2
142 |
143 | }
144 |
--------------------------------------------------------------------------------
/core/src/test/scala/org/typelevel/vault/KeySuite.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2021 Typelevel
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of
5 | * this software and associated documentation files (the "Software"), to deal in
6 | * the Software without restriction, including without limitation the rights to
7 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8 | * the Software, and to permit persons to whom the Software is furnished to do so,
9 | * subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package org.typelevel.vault
23 |
24 | import org.scalacheck._
25 | import cats.effect.SyncIO
26 | import cats.kernel.laws.discipline.{EqTests, HashTests}
27 | import munit.DisciplineSuite
28 | import cats.laws.discipline.InvariantTests
29 |
30 | class KeySuite extends DisciplineSuite {
31 | implicit def functionArbitrary[B, A: Arbitrary]: Arbitrary[B => A] = Arbitrary {
32 | for {
33 | a <- Arbitrary.arbitrary[A]
34 | } yield { (_: B) => a }
35 | }
36 |
37 | implicit def uniqueKey[A]: Arbitrary[Key[A]] = Arbitrary {
38 | Arbitrary.arbitrary[Unit].map(_ => Key.newKey[SyncIO, A].unsafeRunSync())
39 | }
40 |
41 | checkAll("Key", HashTests[Key[Int]].hash)
42 | checkAll("Key", EqTests[Key[Int]].eqv)
43 | checkAll("Key", InvariantTests[Key].invariant[Int, String, Boolean])
44 | }
45 |
--------------------------------------------------------------------------------
/core/src/test/scala/org/typelevel/vault/VaultSuite.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2021 Typelevel
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of
5 | * this software and associated documentation files (the "Software"), to deal in
6 | * the Software without restriction, including without limitation the rights to
7 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8 | * the Software, and to permit persons to whom the Software is furnished to do so,
9 | * subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | package org.typelevel.vault
23 |
24 | import cats.effect._
25 | import munit.{CatsEffectSuite, ScalaCheckEffectSuite}
26 | import org.scalacheck.effect.PropF
27 |
28 | class VaultSuite extends CatsEffectSuite with ScalaCheckEffectSuite {
29 | test("Vault should contain a single value correctly") {
30 | PropF.forAllF { (i: Int) =>
31 | val emptyVault: Vault = Vault.empty
32 | val test = Key.newKey[IO, Int].map(k => emptyVault.insert(k, i).lookup(k))
33 |
34 | assertIO(test, Some(i))
35 | }
36 | }
37 |
38 | test("Vault should contain only the last value after inserts") {
39 | PropF.forAllF { (l: List[String]) =>
40 | val emptyVault: Vault = Vault.empty
41 | val test = Key.newKey[IO, String].map { k =>
42 | l.reverse.foldLeft(emptyVault)((v, a) => v.insert(k, a)).lookup(k)
43 | }
44 |
45 | assertIO(test, l.headOption)
46 | }
47 | }
48 |
49 | test("Vault should contain no value after being emptied") {
50 | PropF.forAllF { (l: List[String]) =>
51 | val emptyVault: Vault = Vault.empty
52 | val test: IO[Option[String]] = Key.newKey[IO, String].map { k =>
53 | l.reverse.foldLeft(emptyVault)((v, a) => v.insert(k, a)).empty.lookup(k)
54 | }
55 |
56 | assertIO(test, None)
57 | }
58 | }
59 |
60 | test("Vault should not be accessible via a different key") {
61 | PropF.forAllF { (i: Int) =>
62 | val test = for {
63 | key1 <- Key.newKey[IO, Int]
64 | key2 <- Key.newKey[IO, Int]
65 | } yield Vault.empty.insert(key1, i).lookup(key2)
66 |
67 | assertIO(test, None)
68 | }
69 | }
70 |
71 | test("Vault should contain mapped value inserted with unmapped key") {
72 | PropF.forAllF { (i: Int) =>
73 | val emptyVault: Vault = Vault.empty
74 | val test =
75 | for {
76 | k <- Key.newKey[IO, Int]
77 | kʹ = k.imap(_.toString)(_.toInt) // create a mapped key
78 | vʹ = emptyVault.insert(k, i) // insert using unmapped key
79 | s = vʹ.lookup(kʹ) // read using mapped key
80 | } yield s
81 | assertIO(test, Some(i.toString))
82 | }
83 | }
84 |
85 | test("Vault should contain unmapped value inserted with mapped key") {
86 | PropF.forAllF { (i: Int) =>
87 | val emptyVault: Vault = Vault.empty
88 | val test =
89 | for {
90 | k <- Key.newKey[IO, Int]
91 | kʹ = k.imap(_.toString)(_.toInt) // create a mapped key
92 | vʹ = emptyVault.insert(kʹ, i.toString) // insert using mapped key
93 | n = vʹ.lookup(k) // read using unmapped key
94 | } yield n
95 | assertIO(test, Some(i))
96 | }
97 | }
98 |
99 | test("Vault should contain mapped value inserted with mapped key") {
100 | PropF.forAllF { (i: Int) =>
101 | val emptyVault: Vault = Vault.empty
102 | val test =
103 | for {
104 | k <- Key.newKey[IO, Int]
105 | kʹ = k.imap(_.toString)(_.toInt) // create a mapped key
106 | vʹ = emptyVault.insert(kʹ, i.toString) // insert using mapped key
107 | n = vʹ.lookup(kʹ) // read using mapped key
108 | } yield n
109 | assertIO(test, Some(i.toString))
110 | }
111 | }
112 |
113 | test("Vault contains should be true for inserted values") {
114 | PropF.forAllF { (i: Int) =>
115 | val emptyVault: Vault = Vault.empty
116 | val test = Key.newKey[IO, Int].map(k => emptyVault.insert(k, i).contains(k))
117 |
118 | assertIO(test, true)
119 | }
120 | }
121 |
122 | test("Vault contains should be false for keys not inserted") {
123 | PropF.forAllF { (i: Int) =>
124 | val test = for {
125 | key1 <- Key.newKey[IO, Int]
126 | key2 <- Key.newKey[IO, Int]
127 | } yield Vault.empty.insert(key1, i).contains(key2)
128 |
129 | assertIO(test, false)
130 | }
131 | }
132 |
133 | test("Vault contains should be false when empty") {
134 | val emptyVault: Vault = Vault.empty
135 | val test = Key.newKey[IO, Int].map(k => emptyVault.contains(k))
136 |
137 | assertIO(test, false)
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # vault
2 |
3 | [](https://maven-badges.herokuapp.com/maven-central/io.chrisdavenport/vault-core_2.12)
4 |
5 | ## Project Goals
6 |
7 | Vault is a tiny library that provides a single data structure called vault.
8 |
9 | Inspiration was drawn from [HeinrichApfelmus/vault](https://github.com/HeinrichApfelmus/vault) and the original [blog post](https://apfelmus.nfshost.com/blog/2011/09/04-vault.html)
10 |
11 | A vault is a type-safe, persistent storage for values of arbitrary types. Like `Ref`, it should be capable of storing values of any type in it, but unlike `Ref`, behave like a persistent, first-class data structure.
12 |
13 | It is analogous to a bank vault, where you can access different bank boxes with different keys; hence the name.
14 |
15 | ## Quick Start
16 |
17 | To use vault in an existing SBT project with Scala 2.11 or a later version, add the following dependencies to your
18 | `build.sbt` depending on your needs:
19 |
20 | ```scala
21 | libraryDependencies ++= Seq(
22 | "org.typelevel" %% "vault" % "@VERSION@",
23 | )
24 | ```
25 |
26 | First the imports
27 |
28 | ```scala mdoc:silent
29 | import cats.effect._
30 | import cats.implicits._
31 | import org.typelevel.vault._
32 |
33 | // Importing global cats-effect runtime to allow .unsafeRunSync();
34 | // In real code you should follow cats-effect advice on obtaining a runtime
35 | import cats.effect.unsafe.implicits.global
36 | ```
37 |
38 | Then some basic operations
39 |
40 | ```scala mdoc:silent
41 | case class Bar(a: String, b: Int, c: Long)
42 |
43 | // Creating keys are effects, but interacting with the vault
44 | // not, it acts like a simple persistent store.
45 |
46 | val basicLookup = for {
47 | key <- Key.newKey[IO, Bar]
48 | } yield {
49 | Vault.empty
50 | .insert(key, Bar("", 1, 2L))
51 | .lookup(key)
52 | }
53 | ```
54 |
55 | ```scala mdoc
56 | basicLookup.unsafeRunSync()
57 | ```
58 |
59 | ```scala mdoc:silent
60 | val containsKey = for {
61 | key <- Key.newKey[IO, Bar]
62 | } yield {
63 | Vault.empty
64 | .insert(key, Bar("", 1, 2L))
65 | .contains(key)
66 | }
67 | ```
68 |
69 | ```scala mdoc
70 | containsKey.unsafeRunSync()
71 | ```
72 |
73 | ```scala mdoc:silent
74 | val lookupValuesOfDifferentTypes = for {
75 | key1 <- Key.newKey[IO, Bar]
76 | key2 <- Key.newKey[IO, String]
77 | key3 <- Key.newKey[IO, String]
78 | } yield {
79 | val myvault = Vault.empty
80 | .insert(key1, Bar("", 1, 2L))
81 | .insert(key2, "I'm at Key2")
82 | .insert(key3, "Key3 Reporting for Duty!")
83 |
84 | (myvault.lookup(key1), myvault.lookup(key2), myvault.lookup(key3))
85 | .mapN((_,_,_))
86 | }
87 | ```
88 |
89 | ```scala mdoc
90 | lookupValuesOfDifferentTypes.unsafeRunSync()
91 | ```
92 |
93 | ```scala mdoc:silent
94 | val emptyLookup = for {
95 | key <- Key.newKey[IO, Bar]
96 | } yield {
97 | Vault.empty
98 | .lookup(key)
99 | }
100 | ```
101 |
102 | ```scala mdoc
103 | emptyLookup.unsafeRunSync()
104 | ```
105 |
106 | ```scala mdoc:silent
107 | val doubleInsertTakesMostRecent = for {
108 | key <- Key.newKey[IO, Bar]
109 | } yield {
110 | Vault.empty
111 | .insert(key, Bar("", 1, 2L))
112 | .insert(key, Bar("Monkey", 7, 5L))
113 | .lookup(key)
114 | }
115 | ```
116 |
117 | ```scala mdoc
118 | doubleInsertTakesMostRecent.unsafeRunSync()
119 | ```
120 |
121 | ```scala mdoc:silent
122 | val mergedVaultsTakesLatter = for {
123 | key <- Key.newKey[IO, Bar]
124 | } yield {
125 | (
126 | Vault.empty.insert(key, Bar("", 1, 2L)) ++
127 | Vault.empty.insert(key, Bar("Monkey", 7, 5L))
128 | ).lookup(key)
129 | }
130 | ```
131 |
132 | ```scala mdoc
133 | mergedVaultsTakesLatter.unsafeRunSync()
134 | ```
135 |
136 | ```scala mdoc:silent
137 | val deletedKeyIsMissing = for {
138 | key <- Key.newKey[IO, Bar]
139 | } yield {
140 | Vault.empty
141 | .insert(key, Bar("", 1, 2L))
142 | .delete(key)
143 | .lookup(key)
144 | }
145 | ```
146 |
147 | ```scala mdoc
148 | deletedKeyIsMissing.unsafeRunSync()
149 | ```
150 |
151 | We can also interact with a single value `locker` instead of the
152 | larger datastructure that a `vault` enables.
153 |
154 | ```scala mdoc:silent
155 | val lockerExample = for {
156 | key <- Key.newKey[IO, Bar]
157 | } yield {
158 | Locker(key, Bar("", 1, 2L))
159 | .unlock(key)
160 | }
161 | ```
162 |
163 | ```scala mdoc
164 | lockerExample.unsafeRunSync()
165 | ```
166 |
167 | ```scala mdoc:silent
168 | val wrongLockerExample = for {
169 | key <- Key.newKey[IO, Bar]
170 | key2 <- Key.newKey[IO, Bar]
171 | } yield {
172 | Locker(key, Bar("", 1, 2L))
173 | .unlock(key2)
174 | }
175 | ```
176 |
177 | ```scala mdoc
178 | wrongLockerExample.unsafeRunSync()
179 | ```
180 |
--------------------------------------------------------------------------------
/licenses/LICENSE_vault:
--------------------------------------------------------------------------------
1 | Copyright (c)2011, Heinrich Apfelmus
2 |
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | * Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 |
11 | * Redistributions in binary form must reproduce the above
12 | copyright notice, this list of conditions and the following
13 | disclaimer in the documentation and/or other materials provided
14 | with the distribution.
15 |
16 | * Neither the name of Heinrich Apfelmus nor the names of other
17 | contributors may be used to endorse or promote products derived
18 | from this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.11.2
2 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.18.2")
2 | val sbtTypelevelVersion = "0.8.0"
3 | addSbtPlugin("org.typelevel" % "sbt-typelevel" % sbtTypelevelVersion)
4 | addSbtPlugin("org.typelevel" % "sbt-typelevel-site" % sbtTypelevelVersion)
5 | addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.17")
6 | addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.3.2")
7 |
--------------------------------------------------------------------------------