├── website ├── data │ └── .gitkeep ├── assets │ ├── images │ │ └── .gitkeep │ ├── js │ │ ├── vendor │ │ │ └── .gitkeep │ │ ├── app.js │ │ └── index.js │ ├── lambda │ │ ├── .gitignore │ │ └── hi-from-lambda.js │ └── scss │ │ ├── vendor │ │ └── .gitkeep │ │ ├── layouts │ │ ├── _footer.scss │ │ ├── _posts.scss │ │ ├── _pages.scss │ │ └── _sidebar.scss │ │ ├── components │ │ ├── _forms.scss │ │ ├── _comments.scss │ │ ├── _code.scss │ │ ├── _images.scss │ │ ├── _alerts.scss │ │ ├── _buttons.scss │ │ ├── _search.scss │ │ └── _syntax.scss │ │ ├── app.scss │ │ └── common │ │ ├── _fonts.scss │ │ └── _variables.scss ├── config │ ├── staging │ │ └── .gitkeep │ ├── production │ │ └── .gitkeep │ ├── postcss.config.js │ └── _default │ │ ├── params.toml │ │ ├── menus.toml │ │ └── config.toml ├── static │ ├── fonts │ │ └── vendor │ │ │ ├── .gitkeep │ │ │ └── jost │ │ │ ├── jost-v4-latin-500.woff │ │ │ ├── jost-v4-latin-700.woff │ │ │ ├── jost-v4-latin-500.woff2 │ │ │ ├── jost-v4-latin-700.woff2 │ │ │ ├── jost-v4-latin-italic.woff │ │ │ ├── jost-v4-latin-italic.woff2 │ │ │ ├── jost-v4-latin-regular.woff │ │ │ ├── jost-v4-latin-regular.woff2 │ │ │ ├── jost-v4-latin-500italic.woff │ │ │ ├── jost-v4-latin-500italic.woff2 │ │ │ ├── jost-v4-latin-700italic.woff │ │ │ └── jost-v4-latin-700italic.woff2 │ └── api │ │ ├── lib │ │ ├── ownerbg.gif │ │ ├── ownerbg2.gif │ │ ├── ownderbg2.gif │ │ ├── class_diagram.png │ │ ├── trait_diagram.png │ │ ├── type_diagram.png │ │ ├── object_diagram.png │ │ ├── lato-v11-latin-100.eot │ │ ├── lato-v11-latin-100.ttf │ │ ├── lato-v11-latin-100.woff │ │ ├── MaterialIcons-Regular.eot │ │ ├── MaterialIcons-Regular.ttf │ │ ├── MaterialIcons-Regular.woff │ │ ├── lato-v11-latin-regular.eot │ │ ├── lato-v11-latin-regular.ttf │ │ ├── lato-v11-latin-regular.woff │ │ ├── open-sans-v13-latin-700.eot │ │ ├── open-sans-v13-latin-700.ttf │ │ ├── open-sans-v13-latin-400i.eot │ │ ├── open-sans-v13-latin-400i.ttf │ │ ├── open-sans-v13-latin-400i.woff │ │ ├── open-sans-v13-latin-700.woff │ │ ├── open-sans-v13-latin-700i.eot │ │ ├── open-sans-v13-latin-700i.ttf │ │ ├── open-sans-v13-latin-700i.woff │ │ ├── open-sans-v13-latin-regular.eot │ │ ├── open-sans-v13-latin-regular.ttf │ │ ├── open-sans-v13-latin-regular.woff │ │ ├── source-code-pro-v6-latin-700.eot │ │ ├── source-code-pro-v6-latin-700.ttf │ │ ├── source-code-pro-v6-latin-700.woff │ │ ├── source-code-pro-v6-latin-regular.eot │ │ ├── source-code-pro-v6-latin-regular.ttf │ │ ├── source-code-pro-v6-latin-regular.woff │ │ ├── print.css │ │ ├── ref-index.css │ │ ├── class.svg │ │ ├── object.svg │ │ ├── package.svg │ │ ├── trait.svg │ │ ├── annotation.svg │ │ ├── abstract_type.svg │ │ ├── class_comp.svg │ │ ├── object_comp.svg │ │ ├── trait_comp.svg │ │ ├── annotation_comp.svg │ │ ├── object_comp_trait.svg │ │ ├── diagrams.css │ │ ├── object_comp_annotation.svg │ │ └── scheduler.js │ │ └── index.html ├── layouts │ ├── _default │ │ ├── _markup │ │ │ └── .gitkeep │ │ ├── single.html │ │ ├── index.json │ │ ├── index.js │ │ ├── list.html │ │ ├── baseof.html │ │ └── section.sitemap.xml │ ├── partials │ │ ├── head │ │ │ ├── script-header.html │ │ │ ├── resource-hints.html │ │ │ ├── favicons.html │ │ │ ├── head.html │ │ │ ├── stylesheet.html │ │ │ ├── twitter_cards.html │ │ │ ├── seo.html │ │ │ ├── opengraph.html │ │ │ └── structured-data.html │ │ ├── footer │ │ │ ├── alert.html │ │ │ ├── footer.html │ │ │ └── script-footer.html │ │ ├── main │ │ │ ├── headline-hash.html │ │ │ ├── blog-meta.html │ │ │ ├── edit-page.html │ │ │ └── docs-navigation.html │ │ ├── sidebar │ │ │ ├── docs-toc.html │ │ │ └── docs-menu.html │ │ └── header │ │ │ └── header.html │ ├── shortcodes │ │ ├── btn-copy.html │ │ ├── alert.html │ │ ├── img-simple.html │ │ ├── email.html │ │ └── img.html │ ├── robots.txt │ ├── index.redirects │ ├── 404.html │ ├── blog │ │ ├── single.html │ │ └── list.html │ ├── index.headers │ ├── docs │ │ ├── list.html │ │ └── single.html │ ├── contributors │ │ └── list.html │ ├── sitemap.xml │ ├── rss.xml │ └── index.html ├── .markdownlintignore ├── .eslintignore ├── .stylelintignore ├── archetypes │ ├── default.md │ ├── blog.md │ └── docs.md ├── content │ ├── docs │ │ ├── _index.md │ │ ├── codec │ │ │ └── dynosaur │ │ │ │ └── index.md │ │ ├── introduction │ │ │ ├── runtests │ │ │ │ └── index.md │ │ │ └── getstarted │ │ │ │ └── index.md │ │ └── api │ │ │ ├── expression │ │ │ └── index.md │ │ │ ├── highlevel │ │ │ └── index.md │ │ │ └── tableactions │ │ │ └── index.md │ └── _index.md ├── .markdownlint.json ├── .eslintrc.json ├── .stylelintrc.json └── package.json ├── project ├── build.properties └── plugins.sbt ├── .scala-steward.conf ├── awssdk └── src │ ├── it │ └── scala │ │ ├── api │ │ └── hi │ │ │ ├── RoundTripResult.scala │ │ │ └── SimpleIndexSpec.scala │ │ ├── ITSpec.scala │ │ ├── ScanOpsSpec.scala │ │ ├── DeleteOpsSpec.scala │ │ ├── GetOpsSpec.scala │ │ ├── PutOpsSpec.scala │ │ └── UpdateOpsSpec.scala │ ├── test │ └── scala │ │ ├── Arbitraries.scala │ │ ├── api │ │ └── DedupOpsSpec.scala │ │ ├── models.scala │ │ └── CodecSpec.scala │ └── main │ └── scala │ ├── implicits.scala │ ├── codec │ └── Codec.scala │ ├── api │ ├── DedupOps.scala │ ├── DeleteOps.scala │ └── PutOps.scala │ └── errors.scala ├── .mergify.yml ├── docker-compose.yml ├── README.md ├── .scalafmt.conf ├── dynosaur └── src │ ├── test │ └── scala │ │ ├── ConversionsUsageSpec.scala │ │ └── ConversionsSpec.scala │ └── main │ └── scala │ └── conversions.scala ├── LICENSE ├── .github └── workflows │ ├── build-branches.yml │ ├── build-website.yml │ └── build-main.yml └── .gitignore /website/data/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/assets/images/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/assets/js/vendor/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/assets/lambda/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/config/staging/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/assets/scss/vendor/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/config/production/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/static/fonts/vendor/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/layouts/_default/_markup/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/layouts/partials/head/script-header.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.10.0 2 | -------------------------------------------------------------------------------- /website/.markdownlintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | CHANGELOG.md 3 | README.md -------------------------------------------------------------------------------- /website/.eslintignore: -------------------------------------------------------------------------------- 1 | assets/js/index.js 2 | assets/js/vendor 3 | node_modules -------------------------------------------------------------------------------- /website/.stylelintignore: -------------------------------------------------------------------------------- 1 | assets/scss/components/_syntax.scss 2 | assets/scss/vendor 3 | node_modules -------------------------------------------------------------------------------- /.scala-steward.conf: -------------------------------------------------------------------------------- 1 | pullRequests.frequency = "0 0 ? * 3" # every thursday on midnight 2 | updatePullRequests = "always" -------------------------------------------------------------------------------- /website/static/api/lib/ownerbg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/ownerbg.gif -------------------------------------------------------------------------------- /website/static/api/lib/ownerbg2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/ownerbg2.gif -------------------------------------------------------------------------------- /website/static/api/lib/ownderbg2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/ownderbg2.gif -------------------------------------------------------------------------------- /website/static/api/lib/class_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/class_diagram.png -------------------------------------------------------------------------------- /website/static/api/lib/trait_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/trait_diagram.png -------------------------------------------------------------------------------- /website/static/api/lib/type_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/type_diagram.png -------------------------------------------------------------------------------- /website/static/api/lib/object_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/object_diagram.png -------------------------------------------------------------------------------- /website/static/api/lib/lato-v11-latin-100.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/lato-v11-latin-100.eot -------------------------------------------------------------------------------- /website/static/api/lib/lato-v11-latin-100.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/lato-v11-latin-100.ttf -------------------------------------------------------------------------------- /website/static/api/lib/lato-v11-latin-100.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/lato-v11-latin-100.woff -------------------------------------------------------------------------------- /website/static/api/lib/MaterialIcons-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/MaterialIcons-Regular.eot -------------------------------------------------------------------------------- /website/static/api/lib/MaterialIcons-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/MaterialIcons-Regular.ttf -------------------------------------------------------------------------------- /website/static/api/lib/MaterialIcons-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/MaterialIcons-Regular.woff -------------------------------------------------------------------------------- /website/static/api/lib/lato-v11-latin-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/lato-v11-latin-regular.eot -------------------------------------------------------------------------------- /website/static/api/lib/lato-v11-latin-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/lato-v11-latin-regular.ttf -------------------------------------------------------------------------------- /website/static/api/lib/lato-v11-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/lato-v11-latin-regular.woff -------------------------------------------------------------------------------- /website/static/api/lib/open-sans-v13-latin-700.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/open-sans-v13-latin-700.eot -------------------------------------------------------------------------------- /website/static/api/lib/open-sans-v13-latin-700.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/open-sans-v13-latin-700.ttf -------------------------------------------------------------------------------- /website/static/api/lib/open-sans-v13-latin-400i.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/open-sans-v13-latin-400i.eot -------------------------------------------------------------------------------- /website/static/api/lib/open-sans-v13-latin-400i.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/open-sans-v13-latin-400i.ttf -------------------------------------------------------------------------------- /website/static/api/lib/open-sans-v13-latin-400i.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/open-sans-v13-latin-400i.woff -------------------------------------------------------------------------------- /website/static/api/lib/open-sans-v13-latin-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/open-sans-v13-latin-700.woff -------------------------------------------------------------------------------- /website/static/api/lib/open-sans-v13-latin-700i.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/open-sans-v13-latin-700i.eot -------------------------------------------------------------------------------- /website/static/api/lib/open-sans-v13-latin-700i.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/open-sans-v13-latin-700i.ttf -------------------------------------------------------------------------------- /website/static/api/lib/open-sans-v13-latin-700i.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/open-sans-v13-latin-700i.woff -------------------------------------------------------------------------------- /website/static/api/lib/open-sans-v13-latin-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/open-sans-v13-latin-regular.eot -------------------------------------------------------------------------------- /website/static/api/lib/open-sans-v13-latin-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/open-sans-v13-latin-regular.ttf -------------------------------------------------------------------------------- /website/static/api/lib/open-sans-v13-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/open-sans-v13-latin-regular.woff -------------------------------------------------------------------------------- /website/static/api/lib/source-code-pro-v6-latin-700.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/source-code-pro-v6-latin-700.eot -------------------------------------------------------------------------------- /website/static/api/lib/source-code-pro-v6-latin-700.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/source-code-pro-v6-latin-700.ttf -------------------------------------------------------------------------------- /website/static/fonts/vendor/jost/jost-v4-latin-500.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/fonts/vendor/jost/jost-v4-latin-500.woff -------------------------------------------------------------------------------- /website/static/fonts/vendor/jost/jost-v4-latin-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/fonts/vendor/jost/jost-v4-latin-700.woff -------------------------------------------------------------------------------- /website/static/api/lib/source-code-pro-v6-latin-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/source-code-pro-v6-latin-700.woff -------------------------------------------------------------------------------- /website/static/fonts/vendor/jost/jost-v4-latin-500.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/fonts/vendor/jost/jost-v4-latin-500.woff2 -------------------------------------------------------------------------------- /website/static/fonts/vendor/jost/jost-v4-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/fonts/vendor/jost/jost-v4-latin-700.woff2 -------------------------------------------------------------------------------- /website/static/api/lib/source-code-pro-v6-latin-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/source-code-pro-v6-latin-regular.eot -------------------------------------------------------------------------------- /website/static/api/lib/source-code-pro-v6-latin-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/source-code-pro-v6-latin-regular.ttf -------------------------------------------------------------------------------- /website/static/api/lib/source-code-pro-v6-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/api/lib/source-code-pro-v6-latin-regular.woff -------------------------------------------------------------------------------- /website/static/fonts/vendor/jost/jost-v4-latin-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/fonts/vendor/jost/jost-v4-latin-italic.woff -------------------------------------------------------------------------------- /website/static/fonts/vendor/jost/jost-v4-latin-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/fonts/vendor/jost/jost-v4-latin-italic.woff2 -------------------------------------------------------------------------------- /website/static/fonts/vendor/jost/jost-v4-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/fonts/vendor/jost/jost-v4-latin-regular.woff -------------------------------------------------------------------------------- /website/static/fonts/vendor/jost/jost-v4-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/fonts/vendor/jost/jost-v4-latin-regular.woff2 -------------------------------------------------------------------------------- /website/layouts/partials/footer/alert.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/static/fonts/vendor/jost/jost-v4-latin-500italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/fonts/vendor/jost/jost-v4-latin-500italic.woff -------------------------------------------------------------------------------- /website/static/fonts/vendor/jost/jost-v4-latin-500italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/fonts/vendor/jost/jost-v4-latin-500italic.woff2 -------------------------------------------------------------------------------- /website/static/fonts/vendor/jost/jost-v4-latin-700italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/fonts/vendor/jost/jost-v4-latin-700italic.woff -------------------------------------------------------------------------------- /website/static/fonts/vendor/jost/jost-v4-latin-700italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2a4u/meteor/HEAD/website/static/fonts/vendor/jost/jost-v4-latin-700italic.woff2 -------------------------------------------------------------------------------- /website/layouts/partials/main/headline-hash.html: -------------------------------------------------------------------------------- 1 | {{ . | replaceRE "()" `${1} ${3}` | safeHTML }} -------------------------------------------------------------------------------- /website/layouts/shortcodes/btn-copy.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /awssdk/src/it/scala/api/hi/RoundTripResult.scala: -------------------------------------------------------------------------------- 1 | package meteor 2 | package api.hi 3 | 4 | private[meteor] case class RoundTripResult[T]( 5 | wrote: T, 6 | read: Option[TestData] 7 | ) 8 | -------------------------------------------------------------------------------- /website/archetypes/default.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "{{ replace .Name "-" " " | title }}" 3 | description: "" 4 | date: {{ .Date }} 5 | lastmod: {{ .Date }} 6 | draft: true 7 | images: [] 8 | --- 9 | -------------------------------------------------------------------------------- /website/layouts/partials/sidebar/docs-toc.html: -------------------------------------------------------------------------------- 1 | {{ if ne .Params.toc false -}} 2 | 6 | {{ end -}} -------------------------------------------------------------------------------- /website/layouts/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | {{ if eq (hugo.Environment) "production" -}} 3 | Allow: / 4 | {{ else -}} 5 | Disallow: / 6 | {{ end }} 7 | Sitemap: {{ "sitemap.xml" | absURL -}} 8 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") 2 | addSbtPlugin("ch.epfl.scala" % "sbt-release-early" % "2.1.1") 3 | addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.1.22") 4 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: merge scala-steward's PRs 3 | conditions: 4 | - author=scala-steward 5 | - status-success=build-tests 6 | actions: 7 | merge: 8 | method: squash -------------------------------------------------------------------------------- /website/layouts/index.redirects: -------------------------------------------------------------------------------- 1 | # redirects for Netlify - https://www.netlify.com/docs/redirects/ 2 | {{- range $p := .Site.Pages -}} 3 | {{- range .Aliases }} 4 | {{ . }} {{ $p.RelPermalink -}} 5 | {{- end }} 6 | {{- end -}} -------------------------------------------------------------------------------- /website/content/docs/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title : "Docs" 3 | description: "meteor documentation" 4 | lead: "" 5 | date: 2021-01-25T23:25:00+00:00 6 | lastmod: 2021-01-25T23:25:00+00:00 7 | draft: false 8 | images: [] 9 | --- 10 | 11 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | dynamodb-local: 5 | image: amazon/dynamodb-local:1.21.0 6 | container_name: dynamodb-local 7 | ports: 8 | - "8000:8000" 9 | command: -jar DynamoDBLocal.jar -sharedDb -inMemory -------------------------------------------------------------------------------- /website/.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "Hyas rules", 3 | 4 | "default": true, 5 | "line_length": false, 6 | "no-inline-html": false, 7 | "no-trailing-punctuation": false, 8 | "no-duplicate-heading": false, 9 | "no-bare-urls": false 10 | } -------------------------------------------------------------------------------- /website/layouts/shortcodes/alert.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/layouts/_default/single.html: -------------------------------------------------------------------------------- 1 | {{ define "main" }} 2 |
3 |
4 |
5 |

{{ .Title }}

6 | {{ .Content }} 7 |
8 |
9 |
10 | {{ end }} 11 | -------------------------------------------------------------------------------- /website/layouts/_default/index.json: -------------------------------------------------------------------------------- 1 | {{- $.Scratch.Add "index" slice -}} 2 | {{- range .Site.RegularPages -}} 3 | {{- $.Scratch.Add "index" (dict "title" .Title "description" .Params.description "contents" .Plain "permalink" .Permalink) -}} 4 | {{- end -}} 5 | {{- $.Scratch.Get "index" | jsonify -}} -------------------------------------------------------------------------------- /website/layouts/_default/index.js: -------------------------------------------------------------------------------- 1 | var docs = [ 2 | {{ range $index, $page := (where .Site.Pages "Section" "docs") -}} 3 | { 4 | id: {{ $index }}, 5 | title: "{{ .Title }}", 6 | description: "{{ .Params.description }}", 7 | href: "{{ .URL | absURL }}" 8 | }, 9 | {{ end -}} 10 | ]; -------------------------------------------------------------------------------- /website/layouts/partials/head/resource-hints.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /website/assets/lambda/hi-from-lambda.js: -------------------------------------------------------------------------------- 1 | exports.handler = (event, context, callback) => { 2 | callback (null, { 3 | statusCode: 200, 4 | headers: { 5 | 'Content-Type': 'application/json', 6 | }, 7 | body: JSON.stringify({ 8 | message: 'Hi from Lambda.', 9 | }), 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /awssdk/src/test/scala/Arbitraries.scala: -------------------------------------------------------------------------------- 1 | package meteor 2 | 3 | import java.time.Instant 4 | 5 | import org.scalacheck.{Arbitrary, Gen} 6 | 7 | object Arbitraries { 8 | implicit val genInstant: Gen[Instant] = Gen.calendar.map(_.toInstant) 9 | implicit val arbInstant: Arbitrary[Instant] = Arbitrary(genInstant) 10 | } 11 | -------------------------------------------------------------------------------- /website/static/api/lib/print.css: -------------------------------------------------------------------------------- 1 | @media print { 2 | * { 3 | text-decoration: none; 4 | font-family: "Lato", Arial, sans-serif; 5 | border-width: 0px; 6 | margin: 0px; 7 | } 8 | #textfilter, #package, #subpackage-spacer, #memberfilter, #filterby, div#definition .big-circle { 9 | display: none !important; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /website/content/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title : "DynamoDB client library" 3 | description: "A Scala wrapper for AWS SDK 2 DynamoDB library using cats-effect and fs2." 4 | lead: "A Scala wrapper for AWS SDK 2 DynamoDB library using cats-effect and fs2." 5 | date: 2021-01-25T23:25:00+00:00 6 | lastmod: 2021-01-25T23:25:00+00:00 7 | draft: false 8 | images: [] 9 | --- 10 | -------------------------------------------------------------------------------- /website/layouts/404.html: -------------------------------------------------------------------------------- 1 | {{ define "main" }} 2 |
3 |
4 |
5 |

Page not found :(

6 |

The page you are looking for doesn't exist or has been moved.

7 |
8 |
9 |
10 | {{ end }} -------------------------------------------------------------------------------- /website/layouts/shortcodes/img-simple.html: -------------------------------------------------------------------------------- 1 | {{ $image := .Page.Resources.GetMatch (printf "*%s*" (.Get "src")) -}} 2 | {{ $lqip := $image.Resize $.Site.Params.lqipWidth -}} 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # meteor 2 | 3 | All documentation is available on the [microsite](https://d2a4u.github.io/meteor/) 4 | 5 | ## Credit 6 | 7 | The project is inspired by [comms-deduplication](https://github.com/ovotech/comms-deduplication) 8 | project. Thanks [@filosganga](https://github.com/filosganga) for his contribution and permission to 9 | use his code in this project. 10 | -------------------------------------------------------------------------------- /website/assets/scss/layouts/_footer.scss: -------------------------------------------------------------------------------- 1 | .footer { 2 | padding-top: 1.125rem; 3 | padding-bottom: 1.125rem; 4 | } 5 | 6 | .footer ul { 7 | margin-bottom: 0; 8 | } 9 | 10 | .footer li { 11 | font-size: $font-size-sm; 12 | margin-bottom: 0; 13 | } 14 | 15 | @include media-breakpoint-up(md) { 16 | .footer li { 17 | font-size: $font-size-base; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /website/assets/scss/components/_forms.scss: -------------------------------------------------------------------------------- 1 | /** Search form */ 2 | .search-form { 3 | @extend .form-inline; 4 | } 5 | 6 | .search-form label { 7 | @extend .form-group; 8 | 9 | font-weight: normal; 10 | } 11 | 12 | .search-form .search-field { 13 | @extend .form-control; 14 | } 15 | 16 | .search-form .search-submit { 17 | @extend .btn; 18 | @extend .btn-secondary; 19 | } 20 | -------------------------------------------------------------------------------- /website/layouts/shortcodes/email.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version=2.7.5 2 | style = Intellij 3 | maxColumn = 80 4 | align.preset = some 5 | align.multiline = false 6 | align.openParenDefnSite = false 7 | align.openParenCallSite = false 8 | align.arrowEnumeratorGenerator = false 9 | project.git = true 10 | project.excludeFilters = ["target/"] 11 | newlines.afterCurlyLambda = preserve 12 | newlines.source=keep 13 | newlines.implicitParamListModifierPrefer = before -------------------------------------------------------------------------------- /website/archetypes/blog.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "{{ replace .Name "-" " " | title }}" 3 | description: "" 4 | lead: "" 5 | date: {{ .Date }} 6 | lastmod: {{ .Date }} 7 | draft: true 8 | weight: 50 9 | images: ["{{ .Name | urlize }}.jpg"] 10 | contributors: [] 11 | --- 12 | 13 | {{< img src="{{ .Name | urlize }}.jpg" alt="{{ replace .Name "-" " " | title }}" caption="{{ replace .Name "-" " " | title }}" class="wide" >}} 14 | -------------------------------------------------------------------------------- /website/archetypes/docs.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "{{ replace .Name "-" " " | title }}" 3 | description: "" 4 | lead: "" 5 | date: {{ .Date }} 6 | lastmod: {{ .Date }} 7 | draft: true 8 | images: [] 9 | menu: 10 | docs: 11 | parent: "" 12 | weight: 999 13 | toc: true 14 | --- 15 | 16 | {{< img src="{{ .Name | urlize }}.jpg" alt="{{ replace .Name "-" " " | title }}" caption="{{ replace .Name "-" " " | title }}" >}} 17 | -------------------------------------------------------------------------------- /website/layouts/_default/list.html: -------------------------------------------------------------------------------- 1 | {{ define "main" }} 2 |
3 |
4 | {{ range .Paginator.Pages }} 5 | 9 | {{ end }} 10 | {{ template "_internal/pagination.html" . }} 11 |
12 |
13 | {{ end }} -------------------------------------------------------------------------------- /website/layouts/partials/main/blog-meta.html: -------------------------------------------------------------------------------- 1 |

Posted {{ .PublishDate.Format "January 2, 2006" }} by {{ if .Params.contributors -}}{{ range $index, $contributor := .Params.contributors }}{{ if gt $index 0 }} and {{ end }}{{ . }}{{ end -}}{{ end -}} ‐ {{ .ReadingTime -}} min read

-------------------------------------------------------------------------------- /website/layouts/blog/single.html: -------------------------------------------------------------------------------- 1 | {{ define "main" }} 2 |

3 |
4 |
5 |
6 |

{{ .Title }}

7 | {{ partial "main/blog-meta.html" . }} 8 |
9 |

{{ .Params.lead | safeHTML }}

10 | {{ .Content }} 11 |
12 |
13 |
14 | {{ end }} -------------------------------------------------------------------------------- /website/layouts/partials/head/favicons.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /website/layouts/partials/main/edit-page.html: -------------------------------------------------------------------------------- 1 |

Edit this page on GitHub

2 | -------------------------------------------------------------------------------- /website/layouts/index.headers: -------------------------------------------------------------------------------- 1 | /* 2 | Strict-Transport-Security: max-age=31536000; includeSubDomains; preload 3 | X-Content-Type-Options: nosniff 4 | X-XSS-Protection: 1; mode=block 5 | Content-Security-Policy: default-src 'self'; frame-ancestors https://jamstackthemes.dev; manifest-src 'self'; connect-src 'self'; font-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 6 | X-Frame-Options: SAMEORIGIN 7 | Referrer-Policy: strict-origin 8 | Feature-Policy: geolocation 'self' 9 | Cache-Control: public, max-age=31536000 -------------------------------------------------------------------------------- /website/layouts/partials/sidebar/docs-menu.html: -------------------------------------------------------------------------------- 1 | {{ $currentPage := . -}} 2 | {{ range .Site.Menus.docs -}} 3 |

{{ .Name }}

4 | {{ if .HasChildren -}} 5 | 12 | {{ end -}} 13 | {{ end -}} -------------------------------------------------------------------------------- /website/assets/scss/layouts/_posts.scss: -------------------------------------------------------------------------------- 1 | .home .card, 2 | .contributors.list .card, 3 | .blog.list .card { 4 | margin-top: 2rem; 5 | margin-bottom: 2rem; 6 | transition: transform 0.3s; 7 | } 8 | 9 | .home .card:hover, 10 | .contributors.list .card:hover, 11 | .blog.list .card:hover { 12 | transform: scale(1.025); 13 | } 14 | 15 | .home .card-body, 16 | .contributors.list .card-body, 17 | .blog.list .card-body { 18 | padding: 0 2rem 1rem; 19 | } 20 | 21 | .blog-header { 22 | text-align: center; 23 | margin-bottom: 2rem; 24 | } 25 | 26 | .blog-footer { 27 | text-align: center; 28 | } 29 | -------------------------------------------------------------------------------- /website/layouts/partials/head/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ block "head/resource-hints" . }}{{ partial "head/resource-hints.html" . }}{{ end }} 6 | {{ block "head/stylesheet" . }}{{ partial "head/stylesheet.html" . }}{{ end }} 7 | {{ block "head/seo" . }}{{ partial "head/seo.html" . }}{{ end }} 8 | {{ block "head/favicons" . }}{{ partial "head/favicons.html" . }}{{ end }} 9 | {{ block "head/script-header" . }}{{ partial "head/script-header.html" . }}{{ end }} 10 | -------------------------------------------------------------------------------- /website/assets/scss/components/_comments.scss: -------------------------------------------------------------------------------- 1 | .comment-list { 2 | @extend .list-unstyled; 3 | } 4 | 5 | .comment-list ol { 6 | list-style: none; 7 | } 8 | 9 | .comment-form p { 10 | @extend .form-group; 11 | } 12 | 13 | .comment-form input[type="text"], 14 | .comment-form input[type="email"], 15 | .comment-form input[type="url"], 16 | .comment-form textarea { 17 | @extend .form-control; 18 | } 19 | 20 | .comment-form input[type="submit"] { 21 | @extend .btn; 22 | @extend .btn-secondary; 23 | } 24 | 25 | blockquote { 26 | margin-bottom: 1rem; 27 | font-size: 1.25rem; 28 | border-left: 3px solid $gray-300; 29 | padding-left: 1rem; 30 | } 31 | -------------------------------------------------------------------------------- /awssdk/src/it/scala/ITSpec.scala: -------------------------------------------------------------------------------- 1 | package meteor 2 | 3 | import cats.effect.unsafe.IORuntime 4 | import org.scalatest.concurrent.ScalaFutures 5 | import org.scalatest.flatspec.AnyFlatSpec 6 | import org.scalatest.matchers.should.Matchers 7 | import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks 8 | 9 | import scala.concurrent.duration._ 10 | 11 | trait ITSpec 12 | extends AnyFlatSpec 13 | with Matchers 14 | with ScalaFutures 15 | with ScalaCheckDrivenPropertyChecks { 16 | implicit val pc: PatienceConfig = 17 | PatienceConfig(scaled(5.minutes), 500.millis) 18 | 19 | implicit val ioRuntime: IORuntime = IORuntime.global 20 | } 21 | -------------------------------------------------------------------------------- /website/layouts/partials/footer/footer.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true 7 | }, 8 | "extends": "eslint:recommended", 9 | "globals": { 10 | "Atomics": "readonly", 11 | "SharedArrayBuffer": "readonly" 12 | }, 13 | "parserOptions": { 14 | "ecmaVersion": 2018 15 | }, 16 | "rules": { 17 | "no-console": 0, 18 | "quotes": ["error", "single"], 19 | "comma-dangle": [ 20 | "error", 21 | { 22 | "arrays": "always-multiline", 23 | "objects": "always-multiline", 24 | "imports": "always-multiline", 25 | "exports": "always-multiline", 26 | "functions": "ignore" 27 | } 28 | ] 29 | } 30 | } -------------------------------------------------------------------------------- /website/layouts/blog/list.html: -------------------------------------------------------------------------------- 1 | {{ define "main" }} 2 |
3 |
4 |
5 |

{{ .Title }}

6 |
{{ .Content }}
7 |
8 | {{ range .Data.Pages -}} 9 |
10 |
11 |

{{ .Params.title }}

12 |

{{ .Params.lead | safeHTML }}

13 | {{ partial "main/blog-meta.html" . -}} 14 |
15 |
16 | {{ end -}} 17 |
18 |
19 |
20 |
21 | {{ end }} -------------------------------------------------------------------------------- /website/config/postcss.config.js: -------------------------------------------------------------------------------- 1 | const autoprefixer = require('autoprefixer'); 2 | const purgecss = require('@fullhuman/postcss-purgecss'); 3 | const whitelister = require('purgecss-whitelister'); 4 | 5 | module.exports = { 6 | plugins: [ 7 | autoprefixer(), 8 | purgecss({ 9 | content: [ 10 | './layouts/**/*.html', 11 | './content/**/*.md', 12 | ], 13 | safelist: [ 14 | 'lazyloaded', 15 | ...whitelister([ 16 | './assets/scss/components/_code.scss', 17 | './assets/scss/components/_search.scss', 18 | './assets/scss/common/_dark.scss', 19 | './assets/scss/components/_syntax.scss', 20 | ]), 21 | ], 22 | }), 23 | ], 24 | } 25 | -------------------------------------------------------------------------------- /website/config/_default/params.toml: -------------------------------------------------------------------------------- 1 | # Meta Data for SEO 2 | 3 | ## Homepage 4 | title = "meteor" 5 | titleSeparator = "-" 6 | titleAddition = "A Scala DynamoDB client library" 7 | description = "A Scala wrapper for AWS SDK 2 DynamoDB library using cats effect and fs2." 8 | 9 | ## Sitelinks Search Box 10 | siteLinksSearchBox = false 11 | 12 | ## Chrome Browser 13 | themeColor = "#fff" 14 | 15 | # Images 16 | quality = 85 17 | bgColor = "#fff" 18 | landscapePhotoWidths = [900, 800, 700, 600, 500] 19 | portraitPhotoWidths = [800, 700, 600, 500] 20 | lqipWidth = "20x" 21 | 22 | # Footer 23 | footer = "" 24 | 25 | # Alert 26 | alert = false 27 | alertText = "" 28 | 29 | # Edit Page 30 | docsRepo = "https://github.com/d2a4u/meteor" 31 | editPage = false 32 | -------------------------------------------------------------------------------- /dynosaur/src/test/scala/ConversionsUsageSpec.scala: -------------------------------------------------------------------------------- 1 | package meteor.dynosaur 2 | package formats 3 | 4 | import cats.syntax.all._ 5 | import dynosaur.Schema 6 | import org.scalatest.flatspec.AnyFlatSpec 7 | import org.scalatest.matchers.should.Matchers 8 | import meteor.codec.Codec 9 | 10 | case class Book(title: String, author: String) 11 | 12 | class ConversionsUsageSpec extends AnyFlatSpec with Matchers { 13 | it should "support implicit usage" in { 14 | implicit val bookSchema: Schema[Book] = Schema.record { field => 15 | ( 16 | field("title", _.title), 17 | field("author", _.author) 18 | ).mapN(Book.apply) 19 | } 20 | 21 | import conversions._ 22 | 23 | implicitly[Codec[Book]] should not be null 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /website/layouts/partials/main/docs-navigation.html: -------------------------------------------------------------------------------- 1 | {{ if or .Prev .Next -}} 2 |
3 | 4 | {{ $pages := where site.RegularPages "Section" .Section -}} 5 | {{ with $pages.Next . -}} 6 | 7 |
8 |
9 | ← {{ .Title }} 10 |
11 |
12 |
13 | {{ end -}} 14 | {{ with $pages.Prev . -}} 15 | 16 |
17 |
18 | {{ .Title }} → 19 |
20 |
21 |
22 | {{ end -}} 23 |
24 | {{ end -}} -------------------------------------------------------------------------------- /website/.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "rules": { 4 | "no-empty-source": null, 5 | "string-quotes": "double", 6 | "at-rule-no-unknown": [ 7 | true, 8 | { 9 | "ignoreAtRules": [ 10 | "extend", 11 | "at-root", 12 | "debug", 13 | "warn", 14 | "error", 15 | "if", 16 | "else", 17 | "for", 18 | "each", 19 | "while", 20 | "mixin", 21 | "include", 22 | "content", 23 | "return", 24 | "function", 25 | "tailwind", 26 | "apply", 27 | "responsive", 28 | "variants", 29 | "screen" 30 | ] 31 | } 32 | ] 33 | } 34 | } -------------------------------------------------------------------------------- /website/assets/scss/layouts/_pages.scss: -------------------------------------------------------------------------------- 1 | .docs-content > h2[id]::before, 2 | .docs-content > h3[id]::before, 3 | .docs-content > h4[id]::before { 4 | display: block; 5 | height: 6rem; 6 | margin-top: -6rem; 7 | content: ""; 8 | } 9 | 10 | .anchor { 11 | visibility: hidden; 12 | padding-left: 0.5rem; 13 | } 14 | 15 | h1:hover a, 16 | h2:hover a, 17 | h3:hover a, 18 | h4:hover a { 19 | visibility: visible; 20 | text-decoration: none; 21 | } 22 | 23 | .card-list { 24 | margin-top: 2.25rem; 25 | } 26 | 27 | .edit-page { 28 | margin-top: 3rem; 29 | font-size: $font-size-base; 30 | } 31 | 32 | .edit-page svg { 33 | margin-right: 0.5rem; 34 | margin-bottom: 0.25rem; 35 | } 36 | 37 | p.meta { 38 | margin-top: 0.5rem; 39 | font-size: $font-size-base; 40 | } 41 | -------------------------------------------------------------------------------- /website/assets/scss/app.scss: -------------------------------------------------------------------------------- 1 | /** Import Bootstrap functions */ 2 | @import "bootstrap/scss/functions"; 3 | 4 | /** Import theme variables */ 5 | @import "common/variables"; 6 | 7 | /** Import Bootstrap */ 8 | @import "bootstrap/scss/bootstrap"; 9 | 10 | /** Import theme styles */ 11 | @import "common/fonts"; 12 | @import "common/global"; 13 | @import "common/dark"; 14 | @import "components/alerts"; 15 | @import "components/buttons"; 16 | @import "components/code"; 17 | @import "components/syntax"; 18 | @import "components/comments"; 19 | @import "components/forms"; 20 | @import "components/images"; 21 | @import "components/search"; 22 | @import "layouts/footer"; 23 | @import "layouts/header"; 24 | @import "layouts/pages"; 25 | @import "layouts/posts"; 26 | @import "layouts/sidebar"; 27 | -------------------------------------------------------------------------------- /website/content/docs/codec/dynosaur/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Dynosaur Schema" 3 | description: "" 4 | lead: "" 5 | date: 2021-01-26T22:19:06Z 6 | lastmod: 2021-01-26T22:19:06Z 7 | draft: false 8 | images: [] 9 | menu: 10 | docs: 11 | parent: "codec" 12 | weight: 1001 13 | toc: true 14 | --- 15 | 16 | The `meteor-dynosaur` module provides integration with [Dynosaur library](https://systemfw.org/dynosaur/#/). 17 | It provides implicit construction from `Dynosaur`'s schema to `meteor`'s codec. This is an experiment 18 | feature, hence, it is subjected to change. 19 | 20 | ```scala 21 | import meteor.dynosaur.formats.conversions._ 22 | import meteor.codec.Codec 23 | 24 | implicit val bookSchema: Schema[Book] = ... 25 | val bookCodec: Codec[Book] = implicitly[Codec[Book]] 26 | ``` 27 | -------------------------------------------------------------------------------- /website/assets/scss/components/_code.scss: -------------------------------------------------------------------------------- 1 | pre, 2 | code, 3 | kbd, 4 | samp { 5 | font-family: $font-family-monospace; 6 | font-size: $font-size-sm; 7 | border-radius: $border-radius; 8 | } 9 | 10 | pre { 11 | background: $beige; 12 | color: $black; 13 | line-height: $line-height-lg; 14 | margin: 2rem 0; 15 | overflow: auto; 16 | padding: 1.25rem 1.5rem; 17 | tab-size: 4; 18 | } 19 | 20 | code { 21 | background: $beige; 22 | color: $black; 23 | padding: 0.25rem 0.5rem; 24 | } 25 | 26 | pre code { 27 | background: none; 28 | font-size: inherit; 29 | padding: 0; 30 | } 31 | 32 | @include media-breakpoint-down(xs) { 33 | pre { 34 | margin: 2rem -1.5rem; 35 | } 36 | 37 | pre, 38 | code, 39 | kbd, 40 | samp { 41 | border-radius: 0; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /website/layouts/docs/list.html: -------------------------------------------------------------------------------- 1 | {{ define "main" }} 2 |
3 |
4 |
5 |

{{ .Title }}

6 |
{{ .Content }}
7 |
8 | {{ $currentSection := .CurrentSection }} 9 | {{ range where .Site.RegularPages.ByTitle "Section" .Section }} 10 | {{ if in (.Permalink | string) $currentSection.RelPermalink }} 11 |
12 | 15 |
16 | {{ end }} 17 | {{ end }} 18 |
19 |
20 |
21 |
22 | {{ end }} -------------------------------------------------------------------------------- /website/layouts/contributors/list.html: -------------------------------------------------------------------------------- 1 | {{ define "main" }} 2 |
3 |
4 |
5 |

{{ .Title }}

6 |
{{ .Content }}
7 |
8 | {{ range .Data.Pages -}} 9 |
10 |
11 |

{{ .Params.title }}

12 | {{ if eq .Section "blog" -}} 13 |

{{ .Params.lead | safeHTML }}

14 | {{ partial "main/blog-meta.html" . -}} 15 | {{ end -}} 16 |
17 |
18 | {{ end -}} 19 |
20 |
21 |
22 |
23 | {{ end }} -------------------------------------------------------------------------------- /website/layouts/partials/head/stylesheet.html: -------------------------------------------------------------------------------- 1 | {{ if eq (hugo.Environment) "development" -}} 2 | {{ $options := (dict "targetPath" "main.css" "enableSourceMap" true "includePaths" (slice "node_modules")) -}} 3 | {{ $css := resources.Get "scss/app.scss" | toCSS $options -}} 4 | 5 | {{ else -}} 6 | {{ $options := (dict "targetPath" "main.css" "outputStyle" "compressed" "includePaths" (slice "node_modules")) -}} 7 | {{ $css := resources.Get "scss/app.scss" | toCSS $options | postCSS (dict "config" "config/postcss.config.js") -}} 8 | {{ $secureCSS := $css | resources.Fingerprint "sha512" -}} 9 | 10 | {{ end -}} 11 | -------------------------------------------------------------------------------- /website/assets/js/app.js: -------------------------------------------------------------------------------- 1 | document.getElementById('mode').addEventListener('click', () => { 2 | 3 | document.body.classList.toggle('dark'); 4 | localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light'); 5 | 6 | }); 7 | 8 | if (localStorage.getItem('theme') === 'dark') { 9 | 10 | document.body.classList.add('dark'); 11 | 12 | } 13 | 14 | /* eslint-disable */ 15 | var clipboard = new ClipboardJS('.btn-clipboard'); 16 | 17 | clipboard.on('success', function(e) { 18 | /* 19 | console.info('Action:', e.action); 20 | console.info('Text:', e.text); 21 | console.info('Trigger:', e.trigger); 22 | */ 23 | 24 | e.clearSelection(); 25 | }); 26 | 27 | clipboard.on('error', function(e) { 28 | console.error('Action:', e.action); 29 | console.error('Trigger:', e.trigger); 30 | }); 31 | /* eslint-enable */ 32 | -------------------------------------------------------------------------------- /dynosaur/src/main/scala/conversions.scala: -------------------------------------------------------------------------------- 1 | package meteor.dynosaur 2 | package formats 3 | 4 | import cats.implicits._ 5 | import dynosaur.{DynamoValue, Schema} 6 | import meteor.codec.Codec 7 | import meteor.errors.DecoderError 8 | import software.amazon.awssdk.services.dynamodb.model.AttributeValue 9 | 10 | object conversions { 11 | implicit def schemaToCodec[A](implicit schema: Schema[A]): Codec[A] = { 12 | new Codec[A] { 13 | override def write(a: A): AttributeValue = { 14 | schema.write(a).fold( 15 | e => throw e, 16 | _.value 17 | ) 18 | } 19 | 20 | override def read(av: AttributeValue): Either[DecoderError, A] = { 21 | val dv = DynamoValue(av) 22 | schema.read(dv).leftMap { err => 23 | DecoderError(err.getMessage, err.getCause.some) 24 | } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /website/assets/scss/components/_images.scss: -------------------------------------------------------------------------------- 1 | figure { 2 | margin: 2rem 0; 3 | } 4 | 5 | .figure-caption { 6 | margin: 0.25rem 0 0.75rem; 7 | } 8 | 9 | figure.wide { 10 | margin: 2rem -1.5rem; 11 | } 12 | 13 | figure.wide .figure-caption { 14 | margin: 0.25rem 1.5rem 0.75rem; 15 | } 16 | 17 | @include media-breakpoint-up(md) { 18 | figure.wide { 19 | margin: 2rem -2.5rem; 20 | } 21 | 22 | figure.wide .figure-caption { 23 | margin: 0.25rem 2.5rem 0.75rem; 24 | } 25 | } 26 | 27 | @include media-breakpoint-up(lg) { 28 | figure.wide { 29 | margin: 2rem -5rem; 30 | } 31 | 32 | figure.wide .figure-caption { 33 | margin: 0.25rem 5rem 0.75rem; 34 | } 35 | } 36 | 37 | .blur-up { 38 | filter: blur(5px); 39 | } 40 | 41 | .blur-up.lazyloaded { 42 | filter: unset; 43 | } 44 | 45 | .img-simple { 46 | margin-top: 0.375rem; 47 | margin-bottom: 1.25rem; 48 | } 49 | -------------------------------------------------------------------------------- /awssdk/src/main/scala/implicits.scala: -------------------------------------------------------------------------------- 1 | package meteor 2 | 3 | import cats.effect.Async 4 | import cats.implicits._ 5 | import meteor.errors.DecoderError 6 | 7 | import java.util.concurrent.CompletableFuture 8 | import java.util.{HashMap => jHashMap, Map => jMap} 9 | 10 | private[meteor] object implicits extends syntax { 11 | type FailureOr[U] = Either[DecoderError, U] 12 | 13 | def liftFuture[F[_], A]( 14 | thunk: => CompletableFuture[A] 15 | )(implicit F: Async[F]): F[A] = 16 | F.fromCompletableFuture(F.delay(thunk)) 17 | 18 | implicit class JavaMap[K, V](m1: jMap[K, V]) { 19 | def ++(m2: jMap[K, V]): jMap[K, V] = { 20 | val m3 = new jHashMap[K, V](m1) 21 | 22 | m2.forEach { 23 | case (key, value) => m3.merge( 24 | key, 25 | value, 26 | (_: V, r: V) => r 27 | ) 28 | } 29 | m3 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /website/layouts/docs/single.html: -------------------------------------------------------------------------------- 1 | {{ define "main" }} 2 |
3 |
4 | 7 |
8 | {{ if ne .Params.toc false -}} 9 | 12 | {{ end -}} 13 | {{ if .Params.toc -}} 14 |
15 | {{ else -}} 16 |
17 | {{ end -}} 18 |

{{ .Title }}

19 |

{{ .Params.lead | safeHTML }}

20 | {{ partial "main/headline-hash.html" .Content }} 21 | {{ if .Site.Params.editPage -}} 22 | {{ partial "main/edit-page.html" . }} 23 | {{ end -}} 24 | {{ partial "main/docs-navigation.html" . }} 25 |
26 |
27 | {{ end }} -------------------------------------------------------------------------------- /website/layouts/sitemap.xml: -------------------------------------------------------------------------------- 1 | {{ printf "" | safeHTML }} 2 | 4 | {{ range .Data.Pages }}{{ if ne .Params.sitemap_exclude true }} 5 | 6 | {{ .Permalink }}{{ if not .Lastmod.IsZero }} 7 | {{ safeHTML ( .Lastmod.Format "2006-01-02T15:04:05-07:00" ) }}{{ end }}{{ with .Sitemap.ChangeFreq }} 8 | {{ . }}{{ end }}{{ if ge .Sitemap.Priority 0.0 }} 9 | {{ .Sitemap.Priority }}{{ end }}{{ if .IsTranslated }}{{ range .Translations }} 10 | {{ end }} 15 | {{ end }} 20 | 21 | {{ end }}{{ end }} 22 | 23 | -------------------------------------------------------------------------------- /website/layouts/_default/baseof.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ partial "head/head.html" . }} 4 | {{ if eq .Kind "home" -}} 5 | {{ .Scratch.Set "class" "home" -}} 6 | {{ else if eq .Kind "404" -}} 7 | {{ .Scratch.Set "class" "error404" -}} 8 | {{ else if eq .Kind "page" -}} 9 | {{ .Scratch.Set "class" .Type -}} 10 | {{ .Scratch.Add "class" " single" -}} 11 | {{ else -}} 12 | {{ .Scratch.Set "class" .Type -}} 13 | {{ .Scratch.Add "class" " list" -}} 14 | {{ end -}} 15 | 16 | {{ partial "header/header.html" . }} 17 |
18 |
19 | {{ block "main" . }}{{ end }} 20 |
21 |
22 | {{ block "sidebar-prefooter" . }}{{ end }} 23 | {{ block "sidebar-footer" . }}{{ end }} 24 | {{ partial "footer/footer.html" . }} 25 | {{ if and .IsHome .Site.Params.alert }} 26 | {{ partial "footer/alert.html" . }} 27 | {{ end }} 28 | {{ partial "footer/script-footer.html" . }} 29 | 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 d2a4u 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /website/config/_default/menus.toml: -------------------------------------------------------------------------------- 1 | [[main]] 2 | name = "Docs" 3 | url = "/docs/introduction/getstarted/" 4 | weight = 10 5 | 6 | [[docs]] 7 | name = "Introduction" 8 | weight = 11 9 | identifier = "introduction" 10 | url = "/docs/introduction/" 11 | 12 | [[docs]] 13 | name = "Codec" 14 | weight = 12 15 | identifier = "codec" 16 | url = "/docs/codec/" 17 | 18 | [[docs]] 19 | name = "API" 20 | weight = 13 21 | identifier = "api" 22 | url = "/docs/api/" 23 | 24 | [[social]] 25 | name = "GitHub" 26 | pre = "" 27 | url = "https://github.com/d2a4u/meteor" 28 | post = "v0.1.0" 29 | weight = 20 30 | 31 | -------------------------------------------------------------------------------- /website/assets/scss/components/_alerts.scss: -------------------------------------------------------------------------------- 1 | .alert { 2 | font-family: $font-family-monospace; 3 | font-size: $font-size-sm; 4 | } 5 | 6 | .alert-icon { 7 | margin-right: 0.75rem; 8 | } 9 | 10 | .docs .alert { 11 | margin: 2rem -1.5rem; 12 | } 13 | 14 | .alert .alert-link { 15 | text-decoration: underline; 16 | } 17 | 18 | .alert-dark { 19 | color: $white; 20 | background-color: $black; 21 | } 22 | 23 | .alert-dark .alert-link { 24 | color: $white; 25 | } 26 | 27 | .alert-light { 28 | color: $black; 29 | } 30 | 31 | .alert-warning { 32 | background: $beige; 33 | color: $black; 34 | } 35 | 36 | /* 37 | .alert-light { 38 | color: #215888; 39 | background: linear-gradient(-45deg, rgb(212, 245, 255), rgb(234, 250, 255), rgb(234, 250, 255), #d3f6ef); 40 | } 41 | 42 | .alert-light .alert-link { 43 | color: #215888; 44 | } 45 | */ 46 | 47 | .alert-white { 48 | background-color: rgba(255, 255, 255, 0.95); 49 | } 50 | 51 | .alert-primary { 52 | color: $white; 53 | background-color: $primary; 54 | } 55 | 56 | .alert-primary .alert-link { 57 | color: $white; 58 | } 59 | 60 | .alert .alert-link:hover, 61 | .alert .alert-link:focus { 62 | text-decoration: none; 63 | } 64 | -------------------------------------------------------------------------------- /awssdk/src/it/scala/api/hi/SimpleIndexSpec.scala: -------------------------------------------------------------------------------- 1 | package meteor 2 | package api.hi 3 | 4 | import cats.effect.IO 5 | import cats.implicits._ 6 | import meteor.Util._ 7 | import meteor.codec.Encoder 8 | 9 | class SimpleIndexSpec extends ITSpec { 10 | 11 | val data = sample[TestData] 12 | 13 | "SimpleTable" should "filter results by given filter expression" in { 14 | def retrieval(index: SimpleIndex[IO, Id], cond: Boolean) = 15 | index.retrieve[TestData]( 16 | Query( 17 | data.id, 18 | Expression( 19 | "#b = :bool", 20 | Map("#b" -> "bool"), 21 | Map( 22 | ":bool" -> Encoder[Boolean].write(cond) 23 | ) 24 | ) 25 | ), 26 | consistentRead = false 27 | ) 28 | 29 | simpleTable[IO].use { table => 30 | val read = for { 31 | some <- retrieval(table, data.bool) 32 | none <- retrieval(table, !data.bool) 33 | } yield (some, none) 34 | table.put[TestData](data) >> read 35 | }.unsafeToFuture().futureValue match { 36 | case (Some(d), None) => 37 | d shouldEqual data 38 | 39 | case _ => 40 | fail() 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /website/layouts/shortcodes/img.html: -------------------------------------------------------------------------------- 1 | {{ $image := .Page.Resources.GetMatch (printf "*%s*" (.Get "src")) -}} 2 | {{ $lqip := $image.Resize $.Site.Params.lqipWidth -}} 3 | 4 | {{ $imgSrc := "" -}} 5 | {{ $imgSrcSet := slice -}} 6 | 7 | {{ $widths := $.Site.Params.landscapePhotoWidths -}} 8 | {{ if gt $image.Height $image.Width -}} 9 | {{ $widths = $.Site.Params.portraitPhotoWidths -}} 10 | {{ end -}} 11 | 12 | {{ range $widths -}} 13 | {{ $srcUrl := (printf "%dx" . | $image.Resize).Permalink -}} 14 | {{ if eq $imgSrc "" -}}{{ $imgSrc = $srcUrl -}}{{ end -}} 15 | {{ $imgSrcSet = $imgSrcSet | append (printf "%s %dw" $srcUrl .) -}} 16 | {{ end -}} 17 | {{ $imgSrcSet = (delimit $imgSrcSet ",") -}} 18 | 19 | 20 | 21 | 22 | {{ with .Get "caption" }}
{{ . | safeHTML }}
{{ end }} 23 | 24 | -------------------------------------------------------------------------------- /awssdk/src/it/scala/ScanOpsSpec.scala: -------------------------------------------------------------------------------- 1 | package meteor 2 | 3 | import cats.implicits._ 4 | import cats.effect.{IO, Ref} 5 | import meteor.Util._ 6 | import org.scalacheck.Arbitrary 7 | 8 | import scala.concurrent.duration._ 9 | 10 | class ScanOpsSpec extends ITSpec { 11 | 12 | behavior.of("scan operation") 13 | 14 | it should "return the whole table" in { 15 | val size = 200 16 | val ref = Ref.of[IO, Int](0) 17 | 18 | val testData = sample[TestData] 19 | val input = fs2.Stream.range(0, size).map { i => 20 | testData.copy(id = Id(i.toString)) 21 | }.covary[IO] 22 | 23 | def updated(ref: Ref[IO, Int]) = 24 | compositeKeysTable[IO].use { 25 | case (client, table) => 26 | client.batchPut[TestData]( 27 | table, 28 | 100.millis, 29 | Client.BackoffStrategy.default 30 | ).apply( 31 | input 32 | ).compile.drain >> 33 | client.scan[TestData]( 34 | table.tableName, 35 | consistentRead = false, 36 | 1 37 | ).evalMap { _ => 38 | ref.update(_ + 1) 39 | }.compile.drain 40 | } 41 | 42 | val result = 43 | for { 44 | r <- ref 45 | _ <- updated(r) 46 | i <- r.get 47 | } yield i 48 | result.unsafeRunSync() shouldEqual size 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /website/assets/scss/components/_buttons.scss: -------------------------------------------------------------------------------- 1 | .navbar .btn-link { 2 | color: $navbar-light-color; 3 | padding: 0.4375rem 0; 4 | } 5 | 6 | #mode { 7 | margin-right: 1.25rem; 8 | } 9 | 10 | .btn-link:focus { 11 | outline: 0; 12 | box-shadow: none; 13 | } 14 | 15 | #navigation { 16 | margin-left: 1.25rem; 17 | } 18 | 19 | @include media-breakpoint-up(md) { 20 | #mode { 21 | margin-right: 0.5rem; 22 | } 23 | 24 | .navbar .btn-link { 25 | padding: 0.5625em 0.25rem 0.5rem 0.125rem; 26 | } 27 | } 28 | 29 | .navbar .btn-link:hover { 30 | color: $navbar-light-hover-color; 31 | } 32 | 33 | .navbar .btn-link:active { 34 | color: $navbar-light-active-color; 35 | } 36 | 37 | body .toggle-dark { 38 | display: block; 39 | } 40 | 41 | body .toggle-light { 42 | display: none; 43 | } 44 | 45 | body.dark .toggle-light { 46 | display: block; 47 | } 48 | 49 | body.dark .toggle-dark { 50 | display: none; 51 | } 52 | 53 | .btn-clipboard { 54 | display: none; 55 | } 56 | 57 | @include media-breakpoint-up(md) { 58 | .btn-clipboard { 59 | display: block; 60 | margin: 2.0625rem 0.25rem -4rem auto; 61 | } 62 | } 63 | 64 | .copy-status::after, 65 | .copy-status:hover::after { 66 | content: "Copy"; 67 | display: block; 68 | } 69 | 70 | .copy-status:focus::after, 71 | .copy-status:active::after { 72 | content: "Copied"; 73 | display: block; 74 | } 75 | -------------------------------------------------------------------------------- /.github/workflows/build-branches.yml: -------------------------------------------------------------------------------- 1 | name: build-branches 2 | 3 | on: 4 | push: 5 | branches: 6 | - '!main' 7 | paths-ignore: 8 | - '**.md' 9 | - 'website/**' 10 | pull_request: 11 | branches: 12 | - 'main' 13 | 14 | jobs: 15 | build-tests: 16 | runs-on: ubuntu-20.04 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Setup Scala 20 | uses: olafurpg/setup-scala@v10 21 | with: 22 | java-version: adopt@1.11 23 | - name: Gen cache keys 24 | run: | 25 | md5sum project/build.properties project/plugins.sbt build.sbt > $GITHUB_WORKSPACE/.sbt_cache_key 26 | cat $GITHUB_WORKSPACE/.sbt_cache_key 27 | - name: Check cache 28 | uses: actions/cache@v2.1.1 29 | with: 30 | key: sbt-${{ hashFiles('**/.sbt_cache_key') }} 31 | path: | 32 | ~/.ivy2 33 | ~/.sbt 34 | ~/.cache/coursier 35 | - name: Spin up Dynamo local 36 | run: docker-compose up -d 37 | - name: Test Dynamo connection 38 | run: | 39 | docker ps 40 | docker run --network container:dynamodb-local curlimages/curl:7.73.0 -v --retry 20 --retry-all-errors http://localhost:8000/ 41 | - name: Check format, build and tests 42 | run: 43 | sbt ";scalafmtCheckAll;+compile;+test;+it:test" 44 | -------------------------------------------------------------------------------- /awssdk/src/main/scala/codec/Codec.scala: -------------------------------------------------------------------------------- 1 | package meteor 2 | package codec 3 | 4 | import meteor.errors.DecoderError 5 | import software.amazon.awssdk.services.dynamodb.model.AttributeValue 6 | 7 | /** Provides an encoding and a decoding functions for a given type. 8 | * 9 | * @tparam A 10 | */ 11 | trait Codec[A] extends Decoder[A] with Encoder[A] 12 | 13 | object Codec { 14 | def apply[A](implicit codec: Codec[A]): Codec[A] = codec 15 | 16 | /** Returns a new [[Codec]] for the specified type given an [[Encoder]] and a [[Decoder]] in scope 17 | * for the type. 18 | */ 19 | implicit def dynamoCodecFromEncoderAndDecoder[A]( 20 | implicit encoder: Encoder[A], 21 | decoder: Decoder[A] 22 | ): Codec[A] = 23 | new Codec[A] { 24 | override def write(a: A): AttributeValue = encoder.write(a) 25 | 26 | override def read(av: AttributeValue): Either[DecoderError, A] = 27 | decoder.read(av) 28 | } 29 | 30 | /** Returns a new [[Codec]] of type B given isomorphic functions of A to B and B to A 31 | */ 32 | def iso[A: Codec, B](fa: A => B)(fb: B => A): Codec[B] = 33 | new Codec[B] { 34 | override def read(av: AttributeValue): Either[DecoderError, B] = { 35 | Codec[A].read(av).map(fa) 36 | } 37 | 38 | override def write(b: B): AttributeValue = { 39 | Codec[A].write(fb(b)) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /website/layouts/partials/footer/script-footer.html: -------------------------------------------------------------------------------- 1 | {{ $indexTemplate := resources.Get "js/index.js" -}} 2 | {{ $index := $indexTemplate | resources.ExecuteAsTemplate "index.js" . -}} 3 | {{ $lazysizes := resources.Get "js/vendor/lazysizes/lazysizes.min.js" -}} 4 | {{ $flexsearch := resources.Get "js/vendor/flexsearch/dist/flexsearch.min.js" -}} 5 | {{ $clipboard := resources.Get "js/vendor/clipboard/dist/clipboard.min.js" -}} 6 | {{ if eq (hugo.Environment) "development" -}} 7 | {{ $app := resources.Get "js/app.js" -}} 8 | {{ $js := slice $lazysizes $clipboard $flexsearch $app | resources.Concat "main.js" -}} 9 | 10 | 11 | {{ else -}} 12 | {{ $instantPage := resources.Get "js/vendor/instant.page/instantpage.js" | minify -}} 13 | {{ $app := resources.Get "js/app.js" | minify -}} 14 | {{ $js := slice $lazysizes $clipboard $flexsearch $instantPage $app | resources.Concat "main.js" -}} 15 | {{ $jsProd := $js | resources.Fingerprint "sha512" -}} 16 | {{ $indexProd := $index | resources.Minify | resources.Fingerprint "sha512" -}} 17 | 18 | 19 | {{ end -}} -------------------------------------------------------------------------------- /website/layouts/partials/head/twitter_cards.html: -------------------------------------------------------------------------------- 1 | {{ with $.Params.images -}} 2 | 3 | 4 | {{ else -}} 5 | {{ $images := $.Resources.ByType "image" -}} 6 | {{ $featured := $images.GetMatch "*feature*" -}} 7 | {{ if not $featured -}} 8 | {{ $featured = $images.GetMatch "{*cover*,*thumbnail*}" -}} 9 | {{ end -}} 10 | {{ with $featured -}} 11 | 12 | 13 | {{ else -}} 14 | {{ with $.Site.Params.images -}} 15 | 16 | 17 | {{ else -}} 18 | 19 | {{ end -}} 20 | {{ end -}} 21 | {{ end -}} 22 | 23 | 24 | 25 | {{ with .Site.Social.twitter -}} 26 | 27 | {{ end -}} 28 | 29 | {{ range .Site.Authors -}} 30 | {{ with .twitter -}} 31 | 32 | {{ end -}} 33 | {{ end -}} -------------------------------------------------------------------------------- /awssdk/src/test/scala/api/DedupOpsSpec.scala: -------------------------------------------------------------------------------- 1 | package meteor 2 | package api 3 | 4 | import cats.implicits._ 5 | import fs2.Chunk 6 | import org.scalatest.flatspec.AnyFlatSpecLike 7 | import org.scalatest.matchers.should.Matchers 8 | import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks 9 | 10 | import scala.util.Try 11 | 12 | class DedupOpsSpec 13 | extends AnyFlatSpecLike 14 | with Matchers 15 | with ScalaCheckDrivenPropertyChecks { 16 | 17 | "deduplication" should "remove duplicated items" in forAll { 18 | (input: List[Int]) => 19 | val expect = input.distinct 20 | val dedupped = 21 | DedupOps.dedupInOrdered[Try, Int, Int, Int]( 22 | Chunk(input ++ input: _*) 23 | )(Try(_))(Try(_)) 24 | dedupped.get.length shouldEqual expect.length 25 | } 26 | 27 | it should "not remove none duplicated items" in forAll { 28 | (input: List[Int]) => 29 | val expect = input.distinct 30 | val dedupped = 31 | BatchGetOps.dedupInOrdered[Try, Int, Int, Int](Chunk(input: _*))( 32 | Try(_) 33 | )(Try(_)) 34 | dedupped.get should contain theSameElementsAs expect 35 | } 36 | 37 | it should "preserve ordering" in { 38 | val input = 0 until 100 39 | val dedupped = 40 | DedupOps.dedupInOrdered[Try, Int, Int, Int](Chunk.iterable(input))( 41 | Try(_) 42 | )(Try(_)) 43 | dedupped.get should contain theSameElementsInOrderAs input 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /website/assets/scss/components/_search.scss: -------------------------------------------------------------------------------- 1 | .navbar-form { 2 | position: relative; 3 | } 4 | 5 | #suggestions { 6 | position: absolute; 7 | right: 0; 8 | margin-top: 0.5rem; 9 | width: calc(100vw - 3rem); 10 | } 11 | 12 | #suggestions a { 13 | display: block; 14 | text-decoration: none; 15 | padding: 0.75rem; 16 | margin: 0 0.5rem; 17 | } 18 | 19 | #suggestions a:focus { 20 | background: $gray-100; 21 | outline: 0; 22 | } 23 | 24 | #suggestions div:not(:first-child) { 25 | border-top: 1px dashed $gray-200; 26 | } 27 | 28 | #suggestions div:first-child { 29 | margin-top: 0.5rem; 30 | } 31 | 32 | #suggestions div:last-child { 33 | margin-bottom: 0.5rem; 34 | } 35 | 36 | #suggestions a:hover { 37 | background: $gray-100; 38 | } 39 | 40 | #suggestions span { 41 | display: flex; 42 | font-size: $font-size-base; 43 | } 44 | 45 | #suggestions span:first-child { 46 | font-weight: $headings-font-weight; 47 | color: $black; 48 | } 49 | 50 | #suggestions span:nth-child(2) { 51 | color: $gray-700; 52 | } 53 | 54 | @include media-breakpoint-up(sm) { 55 | #suggestions { 56 | width: 30rem; 57 | } 58 | 59 | #suggestions a { 60 | display: flex; 61 | } 62 | 63 | #suggestions span:first-child { 64 | width: 9rem; 65 | padding-right: 1rem; 66 | border-right: 1px solid $gray-200; 67 | display: inline-block; 68 | text-align: right; 69 | } 70 | 71 | #suggestions span:nth-child(2) { 72 | width: 19rem; 73 | padding-left: 1rem; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /website/static/api/lib/ref-index.css: -------------------------------------------------------------------------------- 1 | /* fonts */ 2 | @font-face { 3 | font-family: 'Source Code Pro'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: url('source-code-pro-v6-latin-regular.eot'); 7 | src: local('Source Code Pro'), local('SourceCodePro-Regular'), 8 | url('source-code-pro-v6-latin-regular.eot?#iefix') format('embedded-opentype'), 9 | url('source-code-pro-v6-latin-regular.woff') format('woff'), 10 | url('source-code-pro-v6-latin-regular.ttf') format('truetype'); 11 | } 12 | @font-face { 13 | font-family: 'Source Code Pro'; 14 | font-style: normal; 15 | font-weight: 700; 16 | src: url('source-code-pro-v6-latin-700.eot'); 17 | src: local('Source Code Pro Bold'), local('SourceCodePro-Bold'), 18 | url('source-code-pro-v6-latin-700.eot?#iefix') format('embedded-opentype'), 19 | url('source-code-pro-v6-latin-700.woff') format('woff'), 20 | url('source-code-pro-v6-latin-700.ttf') format('truetype'); 21 | } 22 | 23 | body { 24 | font-size: 10pt; 25 | font-family: Arial, sans-serif; 26 | } 27 | 28 | a { 29 | color:#315479; 30 | } 31 | 32 | .letters { 33 | width:100%; 34 | text-align:center; 35 | margin:0.6em; 36 | padding:0.1em; 37 | border-bottom:1px solid gray; 38 | } 39 | 40 | div.entry { 41 | padding: 0.5em; 42 | background-color: #e1e7ed; 43 | border-radius: 0.2em; 44 | color: #103a51; 45 | margin: 0.5em 0; 46 | } 47 | 48 | .name { 49 | font-family: "Source Code Pro"; 50 | font-size: 1.1em; 51 | } 52 | 53 | .occurrences { 54 | margin-left: 1em; 55 | margin-top: 5px; 56 | } 57 | -------------------------------------------------------------------------------- /awssdk/src/it/scala/DeleteOpsSpec.scala: -------------------------------------------------------------------------------- 1 | package meteor 2 | 3 | import cats.effect.IO 4 | import cats.implicits._ 5 | import meteor.Util._ 6 | 7 | class DeleteOpsSpec extends ITSpec { 8 | 9 | behavior.of("delete operation") 10 | 11 | it should "delete an item when using both keys" in forAll { 12 | (test: TestData) => 13 | compositeKeysTable[IO].use { 14 | case (client, table) => 15 | val put = client.put[TestData](table.tableName, test) 16 | val delete = client.delete(table, test.id, test.range) 17 | val get = client.get[Id, Range, TestData]( 18 | table, 19 | test.id, 20 | test.range, 21 | consistentRead = false 22 | ) 23 | put >> Util.retryOf(get)(_.isDefined) >> 24 | delete >> Util.retryOf(get)(_.isEmpty) 25 | }.unsafeToFuture().futureValue shouldEqual None 26 | } 27 | 28 | it should "delete an item when using partition key only (table doesn't have range key)" in forAll { 29 | (test: TestDataSimple) => 30 | partitionKeyTable[IO].use { 31 | case (client, table) => 32 | val put = client.put[TestDataSimple](table.tableName, test) 33 | val delete = client.delete(table, test.id) 34 | val get = client.get[Id, TestDataSimple]( 35 | table, 36 | test.id, 37 | consistentRead = false 38 | ) 39 | put >> Util.retryOf(get)(_.isDefined) >> 40 | delete >> Util.retryOf(get)(_.isEmpty) 41 | }.unsafeToFuture().futureValue shouldEqual None 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /website/content/docs/introduction/runtests/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Build and Tests" 3 | description: "" 4 | lead: "" 5 | date: 2021-01-27T00:39:02Z 6 | lastmod: 2021-01-27T00:39:02Z 7 | draft: false 8 | images: [] 9 | menu: 10 | docs: 11 | parent: "introduction" 12 | weight: 902 13 | toc: true 14 | --- 15 | 16 | ## Build 17 | 18 | The project requires Scala 2.12.12 or 2.13.3. To build the project locally: 19 | 20 | ```bash 21 | git@github.com:d2a4u/meteor.git 22 | cd ./meteor 23 | sbt compile 24 | ``` 25 | 26 | ## Tests 27 | 28 | #### Local 29 | 30 | Unit tests can be run locally by `sbt test`. Integration tests run against docker container of 31 | [DynamoDB Local](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.DownloadingAndRunning.html). 32 | Hence, you will need docker installed and then to run integration tests: 33 | 34 | ```bash 35 | docker-compose up -d # make sure that the service is ready before running tests 36 | sbt it:test 37 | ``` 38 | 39 | A `docker-compose.yml` is provided at the root of the project. 40 | 41 | #### Github Actions 42 | 43 | All PRs and branches are built and tests using Github Actions using the same mechanism with 44 | `docker-compose`. The project is built and tests are run against both Scala 2.12 and 2.13. 45 | 46 | ## Release 47 | 48 | The project uses [sbt-release-early plugin](https://github.com/jvican/sbt-release-early) to release 49 | artifacts to Sonatype repositories. Versioning is via git tags. Release process is automated via 50 | Github actions. Minor version is automatically incremented on PR merge. To release other version 51 | bump, add `#major` or `#patch` to commit message. 52 | -------------------------------------------------------------------------------- /website/layouts/rss.xml: -------------------------------------------------------------------------------- 1 | {{ printf "" | safeHTML }} 2 | 3 | 4 | {{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}{{ end }} 5 | {{ .Permalink }} 6 | Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }} 7 | Hugo -- gohugo.io{{ with .Site.LanguageCode }} 8 | {{.}}{{end}}{{ with .Site.Author.email }} 9 | {{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}{{end}}{{ with .Site.Author.email }} 10 | {{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}{{end}}{{ with .Site.Copyright }} 11 | {{.}}{{end}}{{ if not .Date.IsZero }} 12 | {{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}{{ end }} 13 | {{ with .OutputFormats.Get "RSS" }} 14 | {{ printf "" .Permalink .MediaType | safeHTML }} 15 | {{ end }} 16 | {{ range .Pages }}{{ if ne .Params.feed_exclude true }} 17 | 18 | {{ .Title }} 19 | {{ .Permalink }} 20 | {{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }} 21 | {{ with .Site.Author.email }}{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}{{end}} 22 | {{ .Permalink }} 23 | {{ .Summary | html }} 24 | 25 | {{ end }}{{ end }} 26 | 27 | 28 | -------------------------------------------------------------------------------- /awssdk/src/main/scala/api/DedupOps.scala: -------------------------------------------------------------------------------- 1 | package meteor 2 | package api 3 | 4 | import cats.MonadThrow 5 | import cats.implicits._ 6 | import fs2.Chunk 7 | 8 | private[meteor] trait DedupOps { 9 | private[meteor] def dedupInOrdered[F[_]: MonadThrow, T, U, V]( 10 | input: Chunk[T] 11 | )(mkKey: T => F[U])(transform: T => F[V]): F[List[V]] = { 12 | val iterator = input.reverseIterator 13 | def dedupInternal(exists: Set[U])(soFar: List[V]): F[List[V]] = { 14 | if (iterator.hasNext) { 15 | val t = iterator.next() 16 | for { 17 | u <- mkKey(t) 18 | v <- transform(t) 19 | o <- 20 | if (exists.contains(u)) { 21 | dedupInternal(exists)(soFar) 22 | } else { 23 | dedupInternal(exists + u)(v +: soFar) 24 | } 25 | } yield o 26 | } else { 27 | soFar.pure[F] 28 | } 29 | } 30 | 31 | dedupInternal(Set.empty)(List.empty) 32 | } 33 | 34 | private[meteor] def dedupInOrdered[F[_]: MonadThrow, T, U]( 35 | input: Chunk[T] 36 | )(mkKey: T => F[U]): F[List[U]] = { 37 | val iterator = input.reverseIterator 38 | def dedupInternal(exists: Set[U])(soFar: List[U]): F[List[U]] = { 39 | if (iterator.hasNext) { 40 | val t = iterator.next() 41 | for { 42 | u <- mkKey(t) 43 | o <- 44 | if (exists.contains(u)) { 45 | dedupInternal(exists)(soFar) 46 | } else { 47 | dedupInternal(exists + u)(u +: soFar) 48 | } 49 | } yield o 50 | } else { 51 | soFar.pure[F] 52 | } 53 | } 54 | 55 | dedupInternal(Set.empty)(List.empty) 56 | } 57 | } 58 | 59 | private[meteor] object DedupOps extends DedupOps 60 | -------------------------------------------------------------------------------- /.github/workflows/build-website.yml: -------------------------------------------------------------------------------- 1 | name: build-website 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - 'website/**' 9 | 10 | jobs: 11 | build-deploy-website: 12 | runs-on: ubuntu-20.04 13 | steps: 14 | - uses: actions/checkout@v2 15 | with: 16 | submodules: false 17 | fetch-depth: 0 18 | 19 | - name: Setup Scala 20 | uses: olafurpg/setup-scala@v10 21 | with: 22 | java-version: adopt@1.11 23 | 24 | - name: Setup Hugo 25 | uses: peaceiris/actions-hugo@v2 26 | with: 27 | hugo-version: '0.80.0' 28 | extended: true 29 | 30 | - name: Setup Node 31 | uses: actions/setup-node@v2 32 | with: 33 | node-version: '12' 34 | 35 | - name: Build API Docs 36 | run: | 37 | sbt awssdk/doc 38 | cp -rf ./awssdk/target/scala-3.*/api ./website/static 39 | 40 | - name: Build 41 | run: | 42 | cd website 43 | npm install 44 | mkdir -p ./static/css 45 | hugo gen chromastyles --style=monokai >> assets/scss/components/_syntax.scss 46 | hugo 47 | 48 | - name: Deploy 49 | uses: peaceiris/actions-gh-pages@v3 50 | with: 51 | github_token: ${{ secrets.GITHUB_TOKEN }} 52 | publish_dir: ./website/public 53 | 54 | - name: Push changes to main 55 | run: | 56 | git config user.name "GitHub Actions Bot" 57 | git config user.email "<>" 58 | git status 59 | git add ./website/static/* 60 | git commit -m "Update website [ci skip]" || echo "No changes to commit" 61 | git push 62 | env: 63 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 64 | 65 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "doks", 3 | "version": "0.1.5", 4 | "private": true, 5 | "hugo-bin": { 6 | "buildTags": "extended" 7 | }, 8 | "browserslist": [ 9 | "defaults" 10 | ], 11 | "scripts": { 12 | "init": "rimraf .git && git init -b main", 13 | "create": "hugo new", 14 | "prestart": "npm run clean", 15 | "start": "hugo server --disableFastRender", 16 | "prebuild": "npm run clean", 17 | "build": "hugo --gc --minify && npm run build:functions", 18 | "build:functions": "netlify-lambda build assets/lambda", 19 | "build:preview": "npm run build -D -F", 20 | "clean": "rimraf public resources functions", 21 | "lint": "npm run -s lint:scripts && npm run -s lint:styles && npm run -s lint:markdown", 22 | "lint:scripts": "eslint assets/js assets/lambda config", 23 | "lint:styles": "stylelint \"assets/scss/**/*.{css,sass,scss,sss,less}\"", 24 | "lint:markdown": "markdownlint *.md content/**/*.md", 25 | "release": "standard-version", 26 | "server": "hugo server", 27 | "test": "npm run -s lint", 28 | "env": "env", 29 | "precheck": "npm version", 30 | "check": "hugo version" 31 | }, 32 | "devDependencies": { 33 | "@babel/cli": "^7.12", 34 | "@babel/core": "^7.12", 35 | "@babel/preset-env": "^7.12", 36 | "@fullhuman/postcss-purgecss": "^4.0", 37 | "autoprefixer": "^10.2", 38 | "bootstrap": "^4.6", 39 | "clipboard": "^2.0", 40 | "eslint": "^7.18", 41 | "flexsearch": "^0.6", 42 | "hugo-bin": "^0.68.0", 43 | "instant.page": "^5.1", 44 | "lazysizes": "^5.3", 45 | "markdownlint-cli": "^0.26", 46 | "netlify-lambda": "^2.0", 47 | "postcss": "^8.2", 48 | "postcss-cli": "^8.3", 49 | "purgecss-whitelister": "^2.4", 50 | "rimraf": "^3.0", 51 | "standard-version": "^9.1", 52 | "stylelint": "^13.9", 53 | "stylelint-config-standard": "^20.0" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /website/layouts/index.html: -------------------------------------------------------------------------------- 1 | {{ define "main" }} 2 |
3 |
4 |
5 |

{{ .Title }}

6 |
7 |
8 |

{{ .Params.Lead | safeHTML }}

9 | API Docs 10 | Documentation 11 |

12 | build 13 | Download 14 | Join the chat at https://gitter.im/dynamodb-meteor/community 15 |

16 |
17 |
18 |
19 | {{ end }} 20 | 21 | {{ define "sidebar-prefooter" }} 22 |
23 |
24 |
25 |
26 | {{ end }} 27 | 28 | {{ define "sidebar-footer" }} 29 |
30 |
31 |
32 | {{- .Content -}} 33 |
34 |
35 |
36 | {{ end }} 37 | -------------------------------------------------------------------------------- /awssdk/src/it/scala/GetOpsSpec.scala: -------------------------------------------------------------------------------- 1 | package meteor 2 | 3 | import cats.implicits._ 4 | import cats.effect.IO 5 | import meteor.Util._ 6 | 7 | class GetOpsSpec extends ITSpec { 8 | 9 | behavior.of("get operation") 10 | 11 | it should "return inserted item using partition key and range key" in forAll { 12 | (test: TestData) => 13 | compositeKeysTable[IO].use[Option[TestData]] { 14 | case (client, table) => 15 | client.put[TestData](table.tableName, test) >> 16 | client.get[Id, Range, TestData]( 17 | table, 18 | test.id, 19 | test.range, 20 | consistentRead = false 21 | ) 22 | }.unsafeToFuture().futureValue shouldEqual Some(test) 23 | } 24 | 25 | it should "return inserted item using partition key only (table doesn't have range key)" in forAll { 26 | (test: TestDataSimple) => 27 | partitionKeyTable[IO].use[Option[TestDataSimple]] { 28 | case (client, table) => 29 | client.put[TestDataSimple](table.tableName, test) >> 30 | client.get[Id, TestDataSimple]( 31 | table, 32 | test.id, 33 | consistentRead = false 34 | ) 35 | }.unsafeToFuture().futureValue shouldEqual Some(test) 36 | } 37 | 38 | it should "return None if both keys does not exist" in { 39 | val result = compositeKeysTable[IO].use { 40 | case (client, table) => 41 | client.get[Id, Range, TestData]( 42 | table, 43 | Id("doesnt-exists"), 44 | Range("doesnt-exists"), 45 | consistentRead = false 46 | ) 47 | }.unsafeToFuture().futureValue 48 | result shouldEqual None 49 | } 50 | 51 | it should "return None if partition key does not exist, range key is not used" in { 52 | val result = partitionKeyTable[IO].use { 53 | case (client, table) => 54 | client.get[Id, TestData]( 55 | table, 56 | Id("doesnt-exists"), 57 | consistentRead = false 58 | ) 59 | }.unsafeToFuture().futureValue 60 | result shouldEqual None 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /website/content/docs/api/expression/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Expression" 3 | description: "" 4 | lead: "" 5 | date: 2021-01-28T10:14:17Z 6 | lastmod: 2021-01-28T10:14:17Z 7 | draft: false 8 | images: [] 9 | menu: 10 | docs: 11 | parent: "api" 12 | weight: 996 13 | toc: true 14 | --- 15 | 16 | An `Expression` case class represents various expressions in DynamoDB actions including: 17 | 18 | - [FilterExpression](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-FilterExpression) - Scan API 19 | - [UpdateExpression](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html#DDB-UpdateItem-request-UpdateExpression) - UpdateItem API 20 | - [ConditionExpression](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html#DDB-UpdateItem-request-ConditionExpression) - UpdateItem API 21 | - [ConditionExpression](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html#DDB-PutItem-request-ConditionExpression) - PutItem API 22 | - [ProjectionExpression](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ProjectionExpressions.html) - GetItem, Query, or Scan API 23 | 24 | These expressions in DynamoDB share the same structure, hence, in `meteor` they are abstracted as: 25 | 26 | ```scala 27 | case class Expression( 28 | expression: String, 29 | attributeNames: Map[String, String], 30 | attributeValues: Map[String, AttributeValue] 31 | ) 32 | ``` 33 | 34 | For example: 35 | 36 | ```scala 37 | Expression( 38 | expression = "#pAt BETWEEN :from AND :to", 39 | attributeNames = Map( 40 | "#pAt" -> "publishedAt" 41 | ), 42 | attributeValues = Map( 43 | ":from" -> 1870.asAttributeValue, 44 | ":to" -> 1880.asAttributeValue 45 | ) 46 | ) 47 | ``` 48 | 49 | where `expression: String` is DynamoDB expression syntax which mirrors Java AWS SDK. `#` and `:` 50 | prefixes of `pAt`, `from` and `to` are part of the syntax, to avoid crashes with internal DynamoDB 51 | reserved words. The actual attribute's name and value are replaced by providing the `attributeNames` 52 | and `attributeValues` map respectively. 53 | -------------------------------------------------------------------------------- /website/layouts/_default/section.sitemap.xml: -------------------------------------------------------------------------------- 1 | {{ printf "" | safeHTML -}} 2 | 4 | {{ range $i, $e := .Data.Pages -}} 5 | {{ if ne .Params.sitemap_exclude true }} 6 | 7 | {{ .Permalink }}{{ if not .Lastmod.IsZero }} 8 | {{ safeHTML ( .Lastmod.Format "2006-01-02T15:04:05-07:00" ) }}{{ end }}{{ with .Sitemap.ChangeFreq }} 9 | {{ . }}{{ end }}{{ if ge .Sitemap.Priority 0.0 }} 10 | {{ .Sitemap.Priority }}{{ end }}{{ if .IsTranslated }}{{ range .Translations }} 11 | {{ end }} 16 | {{ end }} 21 | 22 | {{ end -}} 23 | {{ end -}} 24 | {{ range .Sections -}} 25 | {{ range $i, $e := .Data.Pages -}} 26 | {{ if ne .Params.sitemap_exclude true -}} 27 | 28 | {{ .Permalink }}{{ if not .Lastmod.IsZero }} 29 | {{ safeHTML ( .Lastmod.Format "2006-01-02T15:04:05-07:00" ) }}{{ end }}{{ with .Sitemap.ChangeFreq }} 30 | {{ . }}{{ end }}{{ if ge .Sitemap.Priority 0.0 }} 31 | {{ .Sitemap.Priority }}{{ end }}{{ if .IsTranslated }}{{ range .Translations }} 32 | {{ end }} 37 | {{ end }} 42 | 43 | {{ end -}} 44 | {{ end -}} 45 | {{ end -}} 46 | -------------------------------------------------------------------------------- /website/layouts/partials/head/seo.html: -------------------------------------------------------------------------------- 1 | {{ if eq .Kind "404" -}} 2 | 3 | {{ else -}} 4 | {{ with .Params.robots -}} 5 | 6 | {{ else -}} 7 | 8 | 9 | 10 | {{ end -}} 11 | {{ end -}} 12 | 13 | {{ if .IsHome -}} 14 | {{ .Site.Params.title }} {{ .Site.Params.titleSeparator }} {{ .Site.Params.titleAddition }} 15 | {{ else -}} 16 | {{ .Title }} {{ .Site.Params.titleSeparator }} {{ .Site.Params.title }} 17 | {{ end -}} 18 | 19 | {{ with .Description -}} 20 | 21 | {{ else -}} 22 | 23 | {{ end -}} 24 | 25 | {{ if $.Scratch.Get "paginator" }} 26 | 27 | {{ if .Paginator.HasPrev -}} 28 | 29 | {{ end -}} 30 | {{ if .Paginator.HasNext -}} 31 | 32 | {{ end -}} 33 | {{ else -}} 34 | 35 | {{ end -}} 36 | 37 | {{ partial "head/twitter_cards.html" . }} 38 | 39 | 40 | 41 | {{ partial "head/opengraph.html" . }} 42 | 43 | 44 | 45 | 46 | {{ range .AlternativeOutputFormats -}} 47 | 48 | {{ end -}} 49 | 50 | {{ partial "head/structured-data.html" . }} 51 | -------------------------------------------------------------------------------- /awssdk/src/main/scala/api/DeleteOps.scala: -------------------------------------------------------------------------------- 1 | package meteor 2 | package api 3 | 4 | import cats.effect.Async 5 | import cats.implicits._ 6 | import meteor.codec.{Decoder, Encoder} 7 | import meteor.implicits._ 8 | import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient 9 | import software.amazon.awssdk.services.dynamodb.model._ 10 | 11 | private[meteor] trait DeleteOps 12 | extends PartitionKeyDeleteOps 13 | with CompositeKeysDeleteOps {} 14 | 15 | private[meteor] trait CompositeKeysDeleteOps { 16 | private[meteor] def deleteOp[F[_]: Async, P: Encoder, S: Encoder, U: Decoder]( 17 | table: CompositeKeysTable[P, S], 18 | partitionKey: P, 19 | sortKey: S, 20 | returnValue: ReturnValue 21 | )(jClient: DynamoDbAsyncClient): F[Option[U]] = { 22 | table.mkKey[F](partitionKey, sortKey).flatMap { key => 23 | val req = 24 | DeleteItemRequest.builder() 25 | .tableName(table.tableName) 26 | .key(key) 27 | .returnValues(returnValue) 28 | .build() 29 | liftFuture(jClient.deleteItem(req)).flatMap { resp => 30 | if (resp.hasAttributes) { 31 | Async[F].fromEither( 32 | resp.attributes().asAttributeValue.as[U] 33 | ).map(_.some) 34 | } else { 35 | none[U].pure[F] 36 | } 37 | } 38 | } 39 | } 40 | } 41 | 42 | private[meteor] trait PartitionKeyDeleteOps { 43 | private[meteor] def deleteOp[F[_]: Async, P: Encoder, U: Decoder]( 44 | table: PartitionKeyTable[P], 45 | partitionKey: P, 46 | returnValue: ReturnValue 47 | )(jClient: DynamoDbAsyncClient): F[Option[U]] = { 48 | table.mkKey[F](partitionKey).flatMap { key => 49 | val req = 50 | DeleteItemRequest.builder() 51 | .tableName(table.tableName) 52 | .key(key) 53 | .returnValues(returnValue) 54 | .build() 55 | liftFuture(jClient.deleteItem(req)).flatMap { resp => 56 | if (resp.hasAttributes) { 57 | Async[F].fromEither( 58 | resp.attributes().asAttributeValue.as[U] 59 | ).map(_.some) 60 | } else { 61 | none[U].pure[F] 62 | } 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /awssdk/src/main/scala/errors.scala: -------------------------------------------------------------------------------- 1 | package meteor 2 | 3 | import cats.implicits._ 4 | 5 | import software.amazon.awssdk.services.dynamodb.model.TableStatus 6 | 7 | object errors { 8 | sealed abstract class DynamoError extends Exception 9 | 10 | case class DecoderError(message: String, cause: Option[Throwable] = None) 11 | extends DynamoError { 12 | override def getMessage: String = message 13 | } 14 | 15 | case class EncoderError(message: String) extends DynamoError { 16 | override def getMessage: String = message 17 | } 18 | 19 | object DecoderError { 20 | def invalidTypeFailure(t: DynamoDbType): DecoderError = 21 | DecoderError(s"The AttributeValue must be of type ${t.show}") 22 | 23 | def missingKeyFailure(key: String): DecoderError = 24 | DecoderError(s"The AttributeValue is a map but $key key doesn't exists") 25 | } 26 | 27 | object EncoderError { 28 | def invalidTypeFailure(t: DynamoDbType): EncoderError = 29 | EncoderError(s"The AttributeValue must be of type ${t.show}") 30 | 31 | val invalidKeyTypeFailure: EncoderError = 32 | EncoderError(s"The AttributeValue for key must be of type B, S or N") 33 | 34 | def missingKeyFailure: EncoderError = 35 | EncoderError( 36 | s"The AttributeValue is a map but does not contain index key attribute(s)" 37 | ) 38 | } 39 | 40 | case object InvalidExpression extends DynamoError { 41 | override def getMessage: String = 42 | "The expression is invalid" 43 | } 44 | 45 | case class InvalidKeyType[K](k: K) extends DynamoError { 46 | override def getMessage: String = 47 | s"Key of type ${k.getClass.getTypeName} is invalid, expect either N, S or B" 48 | } 49 | 50 | case class UnexpectedTableStatus( 51 | tableName: String, 52 | status: TableStatus, 53 | expects: Set[TableStatus] 54 | ) extends DynamoError { 55 | override def getMessage: String = 56 | s"$tableName table's status is ${status.toString}, expect ${expects.mkString(", ")}" 57 | } 58 | 59 | case class ConditionalCheckFailed(msg: String) extends DynamoError { 60 | override def getMessage: String = msg 61 | } 62 | 63 | case class UnsupportedArgument(msg: String) extends DynamoError { 64 | override def getMessage: String = msg 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /website/assets/scss/common/_fonts.scss: -------------------------------------------------------------------------------- 1 | /* jost-regular - latin */ 2 | @font-face { 3 | font-family: "Jost"; 4 | font-style: normal; 5 | font-weight: 400; 6 | font-display: swap; 7 | src: 8 | local("Jost"), 9 | url("/fonts/vendor/jost/jost-v4-latin-regular.woff2") format("woff2"), 10 | url("/fonts/vendor/jost/jost-v4-latin-regular.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 11 | } 12 | 13 | /* jost-500 - latin */ 14 | @font-face { 15 | font-family: "Jost"; 16 | font-style: normal; 17 | font-weight: 500; 18 | font-display: swap; 19 | src: 20 | local("Jost"), 21 | url("/fonts/vendor/jost/jost-v4-latin-500.woff2") format("woff2"), 22 | url("/fonts/vendor/jost/jost-v4-latin-500.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 23 | } 24 | 25 | /* jost-700 - latin */ 26 | @font-face { 27 | font-family: "Jost"; 28 | font-style: normal; 29 | font-weight: 700; 30 | font-display: swap; 31 | src: 32 | local("Jost"), 33 | url("/fonts/vendor/jost/jost-v4-latin-700.woff2") format("woff2"), 34 | url("/fonts/vendor/jost/jost-v4-latin-700.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 35 | } 36 | 37 | /* jost-italic - latin */ 38 | @font-face { 39 | font-family: "Jost"; 40 | font-style: italic; 41 | font-weight: 400; 42 | font-display: swap; 43 | src: 44 | local("Jost"), 45 | url("/fonts/vendor/jost/jost-v4-latin-italic.woff2") format("woff2"), 46 | url("/fonts/vendor/jost/jost-v4-latin-italic.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 47 | } 48 | 49 | /* jost-500italic - latin */ 50 | @font-face { 51 | font-family: "Jost"; 52 | font-style: italic; 53 | font-weight: 500; 54 | font-display: swap; 55 | src: 56 | local("Jost"), 57 | url("/fonts/vendor/jost/jost-v4-latin-500italic.woff2") format("woff2"), 58 | url("/fonts/vendor/jost/jost-v4-latin-500italic.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 59 | } 60 | 61 | /* jost-700italic - latin */ 62 | @font-face { 63 | font-family: "Jost"; 64 | font-style: italic; 65 | font-weight: 700; 66 | font-display: swap; 67 | src: 68 | local("Jost"), 69 | url("/fonts/vendor/jost/jost-v4-latin-700italic.woff2") format("woff2"), 70 | url("/fonts/vendor/jost/jost-v4-latin-700italic.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 71 | } 72 | -------------------------------------------------------------------------------- /website/content/docs/api/highlevel/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "High Level API" 3 | description: "" 4 | lead: "" 5 | date: 2021-03-06T07:43:39Z 6 | lastmod: 2021-03-06T07:43:39Z 7 | draft: false 8 | images: [] 9 | menu: 10 | docs: 11 | parent: "api" 12 | weight: 997 13 | toc: true 14 | --- 15 | 16 | The actions API is considered low level because it is a 1-2-1 mapping to the Java AWS SDK calls. 17 | In contrast, high level API abstracts over DynamoDB tables which makes it simpler to work with. 18 | For example: 19 | 20 | **Low level API action:** 21 | 22 | ```scala 23 | import meteor._ 24 | import cats.effect.IO 25 | 26 | val client: Client[IO] = ??? 27 | val booksTable = PartitionKeyTable[Int]("books-table", KeyDef[Int]("id", DynamoDbType.N)) 28 | val lotr = Book(1, "The Lord of the Rings") 29 | 30 | val get = 31 | client.get[Int, Book]( 32 | booksTable, 33 | 1, 34 | consistentRead = false 35 | ) 36 | ``` 37 | 38 | **High level API table:** 39 | 40 | ```scala 41 | import meteor._ 42 | import meteor.api.hi._ 43 | import cats.effect.IO 44 | 45 | val jClient: DynamoDbAsyncClient = ??? 46 | val booksTable = SimpleTable[IO, Int]("books-table", KeyDef[Int]("id", DynamoDbType.N), jClient) 47 | val lotr = Book(1, "The Lord of the Rings") 48 | 49 | val put = booksTable.get[Book](1, consistentRead = false) 50 | ``` 51 | 52 | The differences might not look like much, but there are several benefits that high level API 53 | provides. 54 | 55 | When using low level API actions, it can get confusing sometimes when there are several 56 | overloaded methods for the same actions to cater use cases for a table with a partition key or for 57 | a table with composite keys. The high level API removes this confusion by only provide the methods 58 | that are actionable on the table. It also bounds the key's type(s) to table's definition to reduce 59 | the number of type parameters required. Context bound of `F[_]` is now also on the method's level. 60 | 61 | ## Supports 62 | 63 | - `SimpleTable` represent a DynamoDB table which only has partition key index. 64 | - `CompositeTable` represent a DynamoDB table which has both partition key and sort key indexes. 65 | - `SimpleIndex` represent a secondary index on a partition key. 66 | - `SecondaryCompositeIndex` represent a secondary index on composite keys. 67 | 68 | ## Limitations 69 | 70 | Table actions and scan actions are not supported in high level API to keep them flexible. 71 | -------------------------------------------------------------------------------- /website/config/_default/config.toml: -------------------------------------------------------------------------------- 1 | baseurl = "https://d2a4u.github.io/meteor/" 2 | disableAliases = true 3 | disableHugoGeneratorInject = true 4 | enableEmoji = true 5 | enableGitInfo = false 6 | enableRobotsTXT = true 7 | languageCode = "en-US" 8 | paginate = 7 9 | rssLimit = 10 10 | pygmentsCodeFences = true 11 | pygmentsUseClasses = true 12 | 13 | # add redirects/headers 14 | [outputs] 15 | home = ["HTML", "RSS", "REDIRECTS", "HEADERS"] 16 | section = ["HTML", "RSS", "SITEMAP"] 17 | 18 | # remove .{ext} from text/netlify 19 | [mediaTypes."text/netlify"] 20 | suffixes = [""] 21 | delimiter = "" 22 | 23 | # add output format for netlify _redirects 24 | [outputFormats.REDIRECTS] 25 | mediaType = "text/netlify" 26 | baseName = "_redirects" 27 | isPlainText = true 28 | notAlternative = true 29 | 30 | # add output format for netlify _headers 31 | [outputFormats.HEADERS] 32 | mediaType = "text/netlify" 33 | baseName = "_headers" 34 | isPlainText = true 35 | notAlternative = true 36 | 37 | # add output format for section sitemap.xml 38 | [outputFormats.SITEMAP] 39 | mediaType = "application/xml" 40 | baseName = "sitemap" 41 | isHTML = false 42 | isPlainText = true 43 | noUgly = true 44 | rel = "sitemap" 45 | 46 | [markup] 47 | [markup.goldmark] 48 | [markup.goldmark.extensions] 49 | linkify = false 50 | [markup.goldmark.renderer] 51 | unsafe = true 52 | [markup.highlight] 53 | codeFences = true 54 | guessSyntax = true 55 | hl_Lines = "" 56 | lineNoStart = 1 57 | lineNos = false 58 | lineNumbersInTable = true 59 | noClasses = false 60 | style = "solarized-light" 61 | tabWidth = 2 62 | 63 | [sitemap] 64 | changefreq = "weekly" 65 | filename = "sitemap.xml" 66 | priority = 0.5 67 | 68 | [taxonomies] 69 | contributor = "contributors" 70 | 71 | [permalinks] 72 | blog = "/blog/:title/" 73 | 74 | [module] 75 | [[module.mounts]] 76 | source = "assets" 77 | target = "assets" 78 | [[module.mounts]] 79 | source = "static" 80 | target = "static" 81 | [[module.mounts]] 82 | source = "node_modules/lazysizes" 83 | target = "assets/js/vendor/lazysizes" 84 | [[module.mounts]] 85 | source = "node_modules/instant.page" 86 | target = "assets/js/vendor/instant.page" 87 | [[module.mounts]] 88 | source = "node_modules/lazysizes" 89 | target = "assets/js/vendor/lazysizes" 90 | [[module.mounts]] 91 | source = "node_modules/flexsearch" 92 | target = "assets/js/vendor/flexsearch" 93 | [[module.mounts]] 94 | source = "node_modules/clipboard" 95 | target = "assets/js/vendor/clipboard" 96 | -------------------------------------------------------------------------------- /website/assets/scss/layouts/_sidebar.scss: -------------------------------------------------------------------------------- 1 | .docs-links, 2 | .docs-toc { 3 | scrollbar-width: thin; 4 | scrollbar-color: $white $white; 5 | } 6 | 7 | .docs-links::-webkit-scrollbar, 8 | .docs-toc::-webkit-scrollbar { 9 | width: 5px; 10 | } 11 | 12 | .docs-links::-webkit-scrollbar-track, 13 | .docs-toc::-webkit-scrollbar-track { 14 | background: $white; 15 | } 16 | 17 | .docs-links::-webkit-scrollbar-thumb, 18 | .docs-toc::-webkit-scrollbar-thumb { 19 | background: $white; 20 | } 21 | 22 | .docs-links:hover, 23 | .docs-toc:hover { 24 | scrollbar-width: thin; 25 | scrollbar-color: $gray-200 $white; 26 | } 27 | 28 | .docs-links:hover::-webkit-scrollbar-thumb, 29 | .docs-toc:hover::-webkit-scrollbar-thumb { 30 | background: $gray-200; 31 | } 32 | 33 | .docs-links::-webkit-scrollbar-thumb:hover, 34 | .docs-toc::-webkit-scrollbar-thumb:hover { 35 | background: $gray-200; 36 | } 37 | 38 | .docs-links h3, 39 | .page-links h3 { 40 | text-transform: uppercase; 41 | font-size: $font-size-base; 42 | margin: 1.25rem 0 0.5rem 0; 43 | padding: 1.5rem 0 0 0; 44 | } 45 | 46 | @include media-breakpoint-up(lg) { 47 | .docs-links h3, 48 | .page-links h3 { 49 | margin: 1.125rem 1.5rem 0.75rem 0; 50 | padding: 1.375rem 0 0 0; 51 | } 52 | } 53 | 54 | .docs-links h3:not(:first-child) { 55 | border-top: 1px solid $gray-200; 56 | } 57 | 58 | a.docs-link { 59 | color: $body-color; 60 | display: block; 61 | padding: 0.125rem 0; 62 | font-size: $font-size-base; 63 | } 64 | 65 | .page-links li { 66 | margin-top: 0.375rem; 67 | padding-top: 0.375rem; 68 | } 69 | 70 | .page-links li ul li { 71 | border-top: none; 72 | padding-left: 1rem; 73 | margin-top: 0.125rem; 74 | padding-top: 0.125rem; 75 | } 76 | 77 | .page-links li:not(:first-child) { 78 | border-top: 1px dashed $gray-200; 79 | } 80 | 81 | .page-links a { 82 | color: $body-color; 83 | display: block; 84 | padding: 0.125rem 0; 85 | font-size: $font-size-base * 0.9375; 86 | } 87 | 88 | .docs-link:hover, 89 | .docs-link.active, 90 | .page-links a:hover { 91 | text-decoration: none; 92 | color: $link-color; 93 | } 94 | 95 | .docs-links h3.sidebar-link, 96 | .page-links h3.sidebar-link { 97 | text-transform: none; 98 | font-size: $font-size-md; 99 | font-weight: normal; 100 | } 101 | 102 | .docs-links h3.sidebar-link a, 103 | .page-links h3.sidebar-link a { 104 | color: $body-color; 105 | } 106 | 107 | .docs-links h3.sidebar-link a:hover, 108 | .page-links h3.sidebar-link a:hover { 109 | text-decoration: underline; 110 | } 111 | -------------------------------------------------------------------------------- /website/content/docs/api/tableactions/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Table Actions" 3 | description: "" 4 | lead: "" 5 | date: 2021-01-27T00:17:07Z 6 | lastmod: 2021-01-27T00:17:07Z 7 | draft: false 8 | images: [] 9 | menu: 10 | docs: 11 | parent: "api" 12 | weight: 999 13 | toc: true 14 | --- 15 | 16 | ## Create 17 | 18 | Table creation returns `F[Unit]` where `F[_]` is semantically blocked (no actual JVM thread being 19 | blocked) until the table has been created and its status is `available`. 20 | 21 | ```scala 22 | import meteor._ 23 | 24 | val creation: F[Unit] = client.createPartitionKeyTable( 25 | "books-table", 26 | KeyDef[Int]("id", DynamoDbType.N), 27 | BillingMode.PAY_PER_REQUEST 28 | ) 29 | ``` 30 | 31 | `attributeDefinition` is required only when there are secondary indexes on those attributes. For 32 | example, given the following secondary index: 33 | 34 | ```scala 35 | import software.amazon.awssdk.services.dynamodb.model._ 36 | 37 | val global2ndIndex = 38 | GlobalSecondaryIndex 39 | .builder() 40 | .indexName("books-by-author") 41 | .keySchema( 42 | KeySchemaElement.builder().attributeName("author").keyType(KeyType.HASH).build(), 43 | KeySchemaElement.builder().attributeName("title").keyType(KeyType.RANGE).build() 44 | ) 45 | .projection(Projection.builder().projectionType(ProjectionType.ALL).build()) 46 | .build() 47 | ``` 48 | 49 | Since the index keys are on `"author"` and `"title"` attributes, when creating the table, they need 50 | to be defined: 51 | 52 | ```scala 53 | import meteor.DynamoDbType 54 | 55 | val creation: F[Unit] = client.createPartitionKeyTable[String]( 56 | tableName = "books_table", 57 | partitionKeyDef = KeyDef[String]("author", DynamoDbType.S), 58 | billingMode = BillingMode.PAY_PER_REQUEST, 59 | attributeDefinition = Map( 60 | "author" -> DynamoDbType.S, 61 | "title" -> DynamoDbType.S 62 | ), 63 | globalSecondaryIndexes = Set(global2ndIndex), 64 | localSecondaryIndexes = Set.empty 65 | ) 66 | ``` 67 | 68 | ## Delete 69 | 70 | Table deletion also returns `F[Unit]` but it is fire and forget. It returns `Unit` once the 71 | underline `DeleteTable` request is responded successfully. 72 | 73 | ```scala 74 | val deletion: F[Unit] = client.deleteTable(table.tableName) 75 | ``` 76 | 77 | ## Scan 78 | 79 | Scanning a DynamoDB table returns a `fs2.Stream`. It also abstracts away the complexity around 80 | `LastEvaluatedKey` and `Segment`. A filter expression can also be provided. 81 | 82 | ```scala 83 | val books: Stream[F, Book] = client.scan[Book]( 84 | tableName = table.tableName, 85 | consistentRead = false, 86 | parallelism = 32 87 | ) 88 | ``` 89 | 90 | -------------------------------------------------------------------------------- /website/content/docs/introduction/getstarted/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Get Started" 3 | description: "" 4 | lead: "" 5 | date: 2021-01-25T23:54:06Z 6 | lastmod: 2021-01-25T23:54:06Z 7 | draft: false 8 | images: [] 9 | menu: 10 | docs: 11 | parent: "introduction" 12 | weight: 900 13 | toc: true 14 | --- 15 | 16 | `meteor` is a wrapper around Java AWS SDK v2 library, provides higher level API over standard 17 | DynamoDB's actions. 18 | 19 | ## Features 20 | 21 | * wraps AWS Java SDK V2 CRUD actions 22 | * stream batch actions or scan table as `fs2` Stream 23 | * auto processes left over items 24 | * auto remove duplication in batch actions 25 | * support DynamoDB's single table design 26 | * provides codec as a simple abstraction and syntax on top of `AttributeValue` 27 | * support `Dynosaur` codec library 28 | 29 | ## Installation 30 | 31 | Add the following to your `build.sbt`, see the badge on homepage for latest version. Supports Scala 32 | 2.12 and 2.13. 33 | 34 | ```scala 35 | libraryDependencies += "io.github.d2a4u" %% "meteor-awssdk" % "LATEST_VERSION" 36 | ``` 37 | 38 | ### Modules 39 | 40 | #### [Dynosaur Codecs](https://systemfw.org/dynosaur) 41 | 42 | ```scala 43 | libraryDependencies += "io.github.d2a4u" %% "meteor-dynosaur" % "LATEST_VERSION" 44 | ``` 45 | 46 | ## Optimisation 47 | 48 | `meteor` uses AWS SDK v2 `DynamoDbAsyncClient` under the hood to make calls to the underline Java 49 | API. The `DynamoDbAsyncClient` internally create a `NettyNioAsyncHttpClient` with a default value 50 | for maximum connections of 50. This can be increased depending on the hardware/virtual machine 51 | configuration. There is also an alternative [AWS CRT HTTP client](https://aws.amazon.com/about-aws/whats-new/2020/09/aws-crt-http-client-in-aws-sdk-for-java-2x/) 52 | that can be used and tuned similarly. The official documentation can be found [here](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/http-configuration-netty.html). 53 | 54 | ```scala 55 | import cats.effect._ 56 | import meteor.Client 57 | import io.netty.channel.ChannelOption 58 | import software.amazon.awssdk.http.SdkHttpClient 59 | import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient 60 | import software.amazon.awssdk.services.dynamodb.DynamoDbClient 61 | 62 | val httpClientSrc = Resource.fromAutoCloseable( 63 | IO { 64 | NettyNioAsyncHttpClient 65 | .builder() 66 | .putChannelOption(ChannelOption.SO_KEEPALIVE, true) 67 | .maxConcurrency(100) 68 | .maxPendingConnectionAcquires(10000).build() 69 | } 70 | ) 71 | 72 | def sdkDynamoClientSrc(http: SdkHttpClient) = 73 | Resource.fromAutoCloseable( 74 | IO { 75 | DynamoDbClient.builder().httpClient(http).build() 76 | } 77 | ) 78 | 79 | val clientSrc = 80 | for { 81 | http <- httpClientSrc 82 | sdkClient <-sdkDynamoClientSrc(http) 83 | } yield Client[IO](sdkClient) 84 | ``` 85 | -------------------------------------------------------------------------------- /.github/workflows/build-main.yml: -------------------------------------------------------------------------------- 1 | name: build-main 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | paths-ignore: 8 | - '**.md' 9 | - 'website/**' 10 | - 'build-website.yml' 11 | - 'build-branches.yml' 12 | 13 | jobs: 14 | build-tests: 15 | runs-on: ubuntu-20.04 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Setup Scala 19 | uses: olafurpg/setup-scala@v10 20 | with: 21 | java-version: adopt@1.11 22 | - name: Gen cache keys 23 | run: | 24 | md5sum project/build.properties project/plugins.sbt build.sbt > $GITHUB_WORKSPACE/.sbt_cache_key 25 | cat $GITHUB_WORKSPACE/.sbt_cache_key 26 | - name: Check cache 27 | uses: actions/cache@v2.1.1 28 | with: 29 | key: sbt-${{ hashFiles('**/.sbt_cache_key') }} 30 | path: | 31 | ~/.ivy2 32 | ~/.sbt 33 | ~/.cache/coursier 34 | - name: Spin up Dynamo local 35 | run: docker-compose up -d 36 | - name: Test Dynamo connection 37 | run: | 38 | docker ps 39 | docker run --network container:dynamodb-local curlimages/curl:7.73.0 -v --retry 20 --retry-all-errors http://localhost:8000/ 40 | - name: Run build and tests 41 | run: 42 | sbt ";scalafmtCheckAll;+compile;+test;+it:test" 43 | 44 | tag-release: 45 | concurrency: ci-${{ github.ref }} 46 | needs: build-tests 47 | runs-on: ubuntu-20.04 48 | steps: 49 | - uses: actions/checkout@v2 50 | with: 51 | fetch-depth: 0 # fetch the whole repo history 52 | - name: Setup Scala 53 | uses: olafurpg/setup-scala@v10 54 | with: 55 | java-version: adopt@1.11 56 | - name: Check cache 57 | uses: actions/cache@v2.1.1 58 | with: 59 | key: sbt-${{ hashFiles('**/.sbt_cache_key') }} 60 | path: | 61 | ~/.ivy2 62 | ~/.sbt 63 | ~/.cache/coursier 64 | - name: Bump version and push tag 65 | uses: anothrNick/github-tag-action@1.67.0 66 | env: 67 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 68 | WITH_V: true 69 | RELEASE_BRANCHES: main 70 | DEFAULT_BUMP: patch 71 | - name: Import keys 72 | run: | 73 | touch /tmp/local.pubring.asc 74 | touch /tmp/local.secring.asc 75 | echo $PGP_PUB_RING > /tmp/local.pubring.asc 76 | echo $PGP_SEC_RING > /tmp/local.secring.asc 77 | env: 78 | PGP_PUB_RING: ${{ secrets.PGP_PUB_RING }} 79 | PGP_SEC_RING: ${{ secrets.PGP_SEC_RING }} 80 | - name: Publish 81 | run: | 82 | sbt +releaseEarly -J-Xms3G -J-Xmx6G -J-Xss4m 83 | env: 84 | SONATYPE_USER: ${{ secrets.SONATYPE_USER }} 85 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 86 | PGP_PASSWORD: ${{ secrets.PGP_PASSWORD }} 87 | - name: Clean up 88 | run: | 89 | rm /tmp/local.pubring.asc 90 | rm /tmp/local.secring.asc 91 | -------------------------------------------------------------------------------- /awssdk/src/main/scala/api/PutOps.scala: -------------------------------------------------------------------------------- 1 | package meteor 2 | package api 3 | 4 | import cats.effect.Async 5 | import cats.implicits._ 6 | import meteor.codec.{Decoder, Encoder} 7 | import meteor.errors.ConditionalCheckFailed 8 | import meteor.implicits._ 9 | import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient 10 | import software.amazon.awssdk.services.dynamodb.model._ 11 | 12 | import scala.jdk.CollectionConverters._ 13 | 14 | private[meteor] trait PutOps { 15 | private[meteor] def putOp[F[_]: Async, T: Encoder]( 16 | tableName: String, 17 | t: T, 18 | condition: Expression 19 | )(jClient: DynamoDbAsyncClient): F[Unit] = { 20 | val builder0 = 21 | PutItemRequest.builder() 22 | .tableName(tableName) 23 | .item(Encoder[T].write(t).m()) 24 | .returnValues(ReturnValue.NONE) 25 | val builder = 26 | if (condition.isEmpty) { 27 | builder0 28 | } else { 29 | val builder1 = builder0 30 | .conditionExpression(condition.expression) 31 | val builder2 = 32 | if (condition.attributeValues.nonEmpty) { 33 | builder0 34 | .expressionAttributeValues(condition.attributeValues.asJava) 35 | } else { 36 | builder1 37 | } 38 | if (condition.attributeNames.nonEmpty) { 39 | builder2.expressionAttributeNames(condition.attributeNames.asJava) 40 | } else { 41 | builder2 42 | } 43 | } 44 | val req = builder.build() 45 | liftFuture(jClient.putItem(req)).void.adaptError { 46 | case err: ConditionalCheckFailedException => 47 | ConditionalCheckFailed(err.getMessage) 48 | } 49 | } 50 | 51 | private[meteor] def putOp[F[_]: Async, T: Encoder, U: Decoder]( 52 | tableName: String, 53 | t: T, 54 | condition: Expression 55 | )(jClient: DynamoDbAsyncClient): F[Option[U]] = { 56 | val builder0 = 57 | PutItemRequest.builder() 58 | .tableName(tableName) 59 | .item(Encoder[T].write(t).m()) 60 | .returnValues(ReturnValue.ALL_OLD) 61 | val builder = 62 | if (condition.isEmpty) { 63 | builder0 64 | } else { 65 | val builder1 = builder0 66 | .conditionExpression(condition.expression) 67 | val builder2 = 68 | if (condition.attributeValues.nonEmpty) { 69 | builder0 70 | .expressionAttributeValues(condition.attributeValues.asJava) 71 | } else { 72 | builder1 73 | } 74 | if (condition.attributeNames.nonEmpty) { 75 | builder2.expressionAttributeNames(condition.attributeNames.asJava) 76 | } else { 77 | builder2 78 | } 79 | } 80 | val req = builder.build() 81 | liftFuture(jClient.putItem(req)).flatMap { resp => 82 | if (resp.hasAttributes) { 83 | Async[F].fromEither(resp.attributes().asAttributeValue.as[U]).map( 84 | _.some 85 | ) 86 | } else { 87 | none[U].pure[F] 88 | } 89 | }.adaptError { 90 | case err: ConditionalCheckFailedException => 91 | ConditionalCheckFailed(err.getMessage) 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /website/layouts/partials/header/header.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /awssdk/src/test/scala/models.scala: -------------------------------------------------------------------------------- 1 | package meteor 2 | 3 | import meteor.syntax._ 4 | import meteor.codec.{Codec, Decoder, Encoder} 5 | import org.scalacheck.{Arbitrary, Gen} 6 | import software.amazon.awssdk.services.dynamodb.model.AttributeValue 7 | 8 | import scala.jdk.CollectionConverters._ 9 | 10 | case class Id(value: String) extends AnyVal 11 | object Id { 12 | implicit val codecId: Codec[Id] = Codec.iso[String, Id](Id.apply)(_.value) 13 | 14 | implicit val genId: Gen[Id] = 15 | Gen.nonEmptyListOf(Gen.alphaNumChar).map(chars => Id(chars.mkString)) 16 | implicit val arbId: Arbitrary[Id] = Arbitrary(genId) 17 | } 18 | 19 | case class Range(value: String) extends AnyVal 20 | object Range { 21 | implicit val codecRange: Codec[Range] = 22 | Codec.iso[String, Range](Range.apply)(_.value) 23 | 24 | implicit val genRange: Gen[Range] = 25 | Gen.nonEmptyListOf(Gen.alphaNumChar).map(chars => Range(chars.mkString)) 26 | implicit val arbRange: Arbitrary[Range] = Arbitrary(genRange) 27 | } 28 | 29 | case class TestData( 30 | id: Id, 31 | range: Range, 32 | str: String, 33 | int: Int, 34 | bool: Boolean 35 | ) 36 | object TestData { 37 | implicit val decoder: Decoder[TestData] = Decoder.instance { av => 38 | for { 39 | id <- av.getAs[String]("id") 40 | range <- av.getAs[String]("range") 41 | str <- av.getAs[String]("str") 42 | int <- av.getAs[Int]("int") 43 | bool <- av.getAs[Boolean]("bool") 44 | } yield TestData(Id(id), Range(range), str, int, bool) 45 | } 46 | 47 | implicit val encoder: Encoder[TestData] = Encoder.instance { t => 48 | Map( 49 | "id" -> t.id.value.asAttributeValue, 50 | "range" -> t.range.value.asAttributeValue, 51 | "str" -> t.str.asAttributeValue, 52 | "int" -> t.int.asAttributeValue, 53 | "bool" -> t.bool.asAttributeValue 54 | ).asAttributeValue 55 | } 56 | 57 | implicit val genTestData: Gen[TestData] = 58 | for { 59 | id <- implicitly[Gen[Id]] 60 | range <- implicitly[Gen[Range]] 61 | str <- Gen.nonEmptyListOf(Gen.asciiPrintableChar) 62 | int <- Gen.chooseNum(Int.MinValue, Int.MaxValue) 63 | bool <- Gen.oneOf(Seq(true, false)) 64 | } yield TestData(id, range, str.mkString, int, bool) 65 | 66 | implicit val arbTestData: Arbitrary[TestData] = Arbitrary(genTestData) 67 | } 68 | 69 | case class TestDataSimple( 70 | id: Id, 71 | data: String 72 | ) 73 | object TestDataSimple { 74 | implicit val decoder: Decoder[TestDataSimple] = Decoder.instance { av => 75 | val obj = av.m() 76 | for { 77 | id <- Decoder[String].read(obj.get("id")) 78 | data <- Decoder[String].read(obj.get("data")) 79 | } yield TestDataSimple(Id(id), data) 80 | } 81 | 82 | implicit def encoder: Encoder[TestDataSimple] = 83 | Encoder.instance { t => 84 | val jMap = Map( 85 | "id" -> Encoder[String].write(t.id.value), 86 | "data" -> Encoder[String].write(t.data) 87 | ).asJava 88 | AttributeValue.builder().m(jMap).build() 89 | } 90 | 91 | implicit val genTestDataSimple: Gen[TestDataSimple] = 92 | TestData.genTestData.map(data => TestDataSimple(data.id, data.str)) 93 | 94 | implicit val arbTestDataSimple: Arbitrary[TestDataSimple] = 95 | Arbitrary(genTestDataSimple) 96 | } 97 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Scala 2 | *.class 3 | *.log 4 | apidoc/redoc-static.html 5 | 6 | # sbt specific 7 | .cache/ 8 | .history/ 9 | .lib/ 10 | dist/* 11 | target/ 12 | lib_managed/ 13 | src_managed/ 14 | project/boot/ 15 | project/plugins/project/ 16 | 17 | #sbt-plugins specific 18 | 19 | # Scala-IDE specific 20 | .scala_dependencies 21 | .worksheet 22 | .idea 23 | project/target 24 | *.env 25 | 26 | # IntelliJ 27 | /out/ 28 | *.iml 29 | 30 | # vim 31 | *.swp 32 | .tags 33 | 34 | # Mac 35 | .DS_Store 36 | 37 | .envrc 38 | 39 | logs/ 40 | 41 | ### Node ### 42 | # Logs 43 | logs 44 | *.log 45 | npm-debug.log* 46 | yarn-debug.log* 47 | yarn-error.log* 48 | lerna-debug.log* 49 | 50 | # Diagnostic reports (https://nodejs.org/api/report.html) 51 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 52 | 53 | # Runtime data 54 | pids 55 | *.pid 56 | *.seed 57 | *.pid.lock 58 | 59 | # Directory for instrumented libs generated by jscoverage/JSCover 60 | lib-cov 61 | 62 | # Coverage directory used by tools like istanbul 63 | coverage 64 | *.lcov 65 | 66 | # nyc test coverage 67 | .nyc_output 68 | 69 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 70 | .grunt 71 | 72 | # Bower dependency directory (https://bower.io/) 73 | bower_components 74 | 75 | # node-waf configuration 76 | .lock-wscript 77 | 78 | # Compiled binary addons (https://nodejs.org/api/addons.html) 79 | build/Release 80 | 81 | # Dependency directories 82 | node_modules/ 83 | jspm_packages/ 84 | 85 | # TypeScript v1 declaration files 86 | typings/ 87 | 88 | # TypeScript cache 89 | *.tsbuildinfo 90 | 91 | # Optional npm cache directory 92 | .npm 93 | 94 | # Optional eslint cache 95 | .eslintcache 96 | 97 | # Optional REPL history 98 | .node_repl_history 99 | 100 | # Output of 'npm pack' 101 | *.tgz 102 | 103 | # Yarn Integrity file 104 | .yarn-integrity 105 | 106 | # dotenv environment variables file 107 | .env 108 | .env.test 109 | 110 | # parcel-bundler cache (https://parceljs.org/) 111 | .cache 112 | 113 | # next.js build output 114 | .next 115 | 116 | # nuxt.js build output 117 | .nuxt 118 | 119 | # rollup.js default build output 120 | dist/ 121 | 122 | # Uncomment the public line if your project uses Gatsby 123 | # https://nextjs.org/blog/next-9-1#public-directory-support 124 | # https://create-react-app.dev/docs/using-the-public-folder/#docsNav 125 | # public 126 | 127 | # Storybook build outputs 128 | .out 129 | .storybook-out 130 | 131 | # vuepress build output 132 | .vuepress/dist 133 | 134 | # Serverless directories 135 | .serverless/ 136 | 137 | # FuseBox cache 138 | .fusebox/ 139 | 140 | # DynamoDB Local files 141 | .dynamodb/ 142 | 143 | # Temporary folders 144 | tmp/ 145 | temp/ 146 | 147 | website/build/ 148 | 149 | Main.scala 150 | 151 | # VSCode/Metals 152 | .bloop/ 153 | .metals/ 154 | .vscode/ 155 | .bsp/ 156 | project/**/metals.sbt 157 | 158 | website/.github/ 159 | website/.gitignore 160 | website/.npmrc 161 | website/.versionrc.json 162 | website/CHANGELOG.md 163 | website/CODE_OF_CONDUCT.md 164 | website/LICENSE 165 | website/README.md 166 | website/babel.config.js 167 | website/netlify.toml 168 | website/theme.toml 169 | website/.editorconfig 170 | website/resources/ 171 | website/public/ 172 | entrypoint.sh 173 | -------------------------------------------------------------------------------- /awssdk/src/test/scala/CodecSpec.scala: -------------------------------------------------------------------------------- 1 | package meteor 2 | 3 | import cats._ 4 | import cats.implicits._ 5 | import meteor.Arbitraries._ 6 | import meteor.codec.{Codec, Decoder, Encoder} 7 | import meteor.syntax._ 8 | import meteor.errors.DecoderError 9 | import org.scalatest.flatspec.AnyFlatSpec 10 | import org.scalatest.matchers.should.Matchers 11 | import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks 12 | 13 | import java.time.Instant 14 | import java.util.UUID 15 | import scala.collection.immutable 16 | 17 | class CodecSpec 18 | extends AnyFlatSpec 19 | with Matchers 20 | with ScalaCheckDrivenPropertyChecks { 21 | behavior.of("Encoder and Decoder") 22 | 23 | it should "successful round trip for Int" in forAll { (int: Int) => 24 | roundTrip(int) shouldEqual Right(int) 25 | } 26 | 27 | it should "successful round trip for String" in forAll { (str: String) => 28 | roundTrip(str) shouldEqual Right(str) 29 | } 30 | 31 | it should "successful round trip for UUID" in forAll { (uuid: UUID) => 32 | roundTrip(uuid) shouldEqual Right(uuid) 33 | } 34 | 35 | it should "successful round trip for Boolean" in forAll { (bool: Boolean) => 36 | roundTrip(bool) shouldEqual Right(bool) 37 | } 38 | 39 | it should "successful round trip for Long" in forAll { (long: Long) => 40 | roundTrip(long) shouldEqual Right(long) 41 | } 42 | 43 | it should "successful round trip for Instant" in forAll { 44 | (instant: Instant) => 45 | roundTrip(instant) shouldEqual Right(instant) 46 | } 47 | 48 | it should "successful round trip for Seq[String]" in forAll { 49 | ( 50 | str: immutable.Seq[String] 51 | ) => 52 | roundTrip(str) shouldEqual Right(str) 53 | } 54 | 55 | it should "successful round trip for List[String]" in forAll { 56 | ( 57 | str: List[String] 58 | ) => 59 | roundTrip(str) shouldEqual Right(str) 60 | } 61 | 62 | it should "successful round trip for Option[String]" in forAll { 63 | ( 64 | str: Option[String] 65 | ) => 66 | roundTripOpt(str) shouldEqual Right(str) 67 | } 68 | 69 | it should "successful round trip for Some of empty String" in { 70 | roundTripOpt(Some("")) shouldEqual Right(Some("")) 71 | } 72 | 73 | it should "successful round trip for Map[String, String]" in forAll { 74 | ( 75 | str: Map[String, String] 76 | ) => 77 | roundTrip(str) shouldEqual Right(str) 78 | } 79 | 80 | it should "successful round trip for Array[Byte]" in forAll { 81 | (bytes: Array[Byte]) => 82 | roundTrip[Array[Byte]](bytes) match { 83 | case Right(b) => b should contain theSameElementsAs bytes 84 | case _ => fail() 85 | } 86 | } 87 | 88 | it should "successful round trip for Seq[Array[Byte]]" in forAll { 89 | (bytes: List[Array[Byte]]) => 90 | roundTrip[immutable.List[Array[Byte]]](bytes) match { 91 | case Right(b) => b should contain theSameElementsAs bytes 92 | case Left(err) => fail(err) 93 | } 94 | } 95 | 96 | def roundTrip[T: Codec](t: T): Either[DecoderError, T] = { 97 | Codec[T].read( 98 | Codec[T].write(t) 99 | ) 100 | } 101 | 102 | def roundTripOpt[T: Decoder](t: Option[T])(implicit 103 | enc: Encoder[Option[T]]): Either[DecoderError, Option[T]] = { 104 | enc.write(t).asOpt[T] 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /website/static/api/lib/class.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | C 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /website/static/api/lib/object.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | O 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /website/static/api/lib/package.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | p 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /website/static/api/lib/trait.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | t 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /website/static/api/lib/annotation.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | @ 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /website/static/api/lib/abstract_type.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | a 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /dynosaur/src/test/scala/ConversionsSpec.scala: -------------------------------------------------------------------------------- 1 | package meteor.dynosaur 2 | package formats 3 | 4 | import dynosaur.Schema 5 | import meteor.codec.Codec.dynamoCodecFromEncoderAndDecoder 6 | import meteor.codec.{Codec, Decoder, Encoder} 7 | import meteor.syntax._ 8 | import org.scalacheck.{Arbitrary, Gen} 9 | import org.scalatest.flatspec.AnyFlatSpec 10 | import org.scalatest.matchers.should.Matchers 11 | import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks 12 | 13 | import scala.collection.immutable 14 | 15 | class ConversionsSpec 16 | extends AnyFlatSpec 17 | with Matchers 18 | with ScalaCheckDrivenPropertyChecks { 19 | behavior.of("Schema conversion") 20 | 21 | implicit val genNonEmptyString: Gen[String] = 22 | Gen.nonEmptyListOf(Gen.alphaNumChar).map(_.mkString("")) 23 | 24 | implicit val arbNonEmptyString: Arbitrary[String] = 25 | Arbitrary(genNonEmptyString) 26 | 27 | def roundTrip[T: Codec: Schema](t: T): Boolean = { 28 | val convertedCodec = conversions.schemaToCodec(Schema[T]) 29 | val round1 = Codec[T].read(convertedCodec.write(t)) 30 | val round2 = convertedCodec.read(Codec[T].write(t)) 31 | round1.isRight && round1 == round2 32 | } 33 | 34 | def roundTripOpt[T: Decoder]( 35 | encoder: Encoder[Option[T]], 36 | schema: Schema[Option[T]], 37 | t: Option[T] 38 | ): Boolean = { 39 | val convertedCodec = conversions.schemaToCodec(schema) 40 | val round1 = convertedCodec.write(t).asOpt[T] 41 | val round2 = convertedCodec.read(encoder.write(t)) 42 | round1.isRight && round1 == round2 43 | } 44 | 45 | //TODO: test for Array[Byte] 46 | it should "cross read/write from Schema to Codec for Int" in forAll { 47 | (int: Int) => 48 | roundTrip(int) shouldBe true 49 | } 50 | 51 | it should "cross read/write from Schema to Codec for non empty String" in forAll { 52 | (str: String) => 53 | roundTrip(str) shouldBe true 54 | } 55 | 56 | it should "cross read/write from Schema to Codec for Boolean" in forAll { 57 | (bool: Boolean) => 58 | roundTrip(bool) shouldBe true 59 | } 60 | 61 | it should "cross read/write from Schema to Codec for Long" in forAll { 62 | (long: Long) => 63 | roundTrip(long) shouldBe true 64 | } 65 | 66 | it should "cross read/write from Schema to Codec for Float" in forAll { 67 | (float: Float) => 68 | roundTrip(float) shouldBe true 69 | } 70 | 71 | it should "cross read/write from Schema to Codec for Double" in forAll { 72 | (double: Double) => 73 | roundTrip(double) shouldBe true 74 | } 75 | 76 | it should "cross read/write from Schema to Codec for Short" in forAll { 77 | (short: Short) => 78 | roundTrip(short) shouldBe true 79 | } 80 | 81 | it should "cross read/write from Schema to Codec for Option[Int]" in forAll { 82 | (opt: Option[Int]) => 83 | roundTripOpt[Int]( 84 | Encoder[Option[Int]], 85 | Schema.nullable[Int], 86 | opt 87 | ) shouldBe true 88 | } 89 | 90 | it should "cross read/write from Schema to Codec for List[Int]" in forAll { 91 | (list: List[Int]) => 92 | roundTrip(list) shouldBe true 93 | } 94 | 95 | it should "cross read/write from Schema to Codec for Seq[Int]" in forAll { 96 | (seq: immutable.Seq[Int]) => 97 | roundTrip(seq) shouldBe true 98 | } 99 | 100 | it should "cross read/write from Schema to Codec for Map[String, Int]" in forAll { 101 | (map: Map[String, Int]) => 102 | roundTrip(map) shouldBe true 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /website/layouts/partials/head/opengraph.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ if $.Scratch.Get "paginator" -}} 5 | {{ $paginator := .Paginate (where .Site.RegularPages.ByDate.Reverse "Section" "blog" ) -}} 6 | 7 | {{ else -}} 8 | 9 | {{ end -}} 10 | 11 | {{ with $.Params.images -}} 12 | {{ range first 6 . -}} 13 | 14 | {{ end -}} 15 | {{ else -}} 16 | {{ $images := $.Resources.ByType "image" -}} 17 | {{ $featured := $images.GetMatch "*feature*" -}} 18 | {{ if not $featured -}} 19 | {{ $featured = $images.GetMatch "{*cover*,*thumbnail*}" }} 20 | {{ end -}} 21 | {{ with $featured -}} 22 | 23 | {{ else -}} 24 | {{ with $.Site.Params.images -}} 25 | 26 | {{ end -}} 27 | {{ end -}} 28 | {{ end -}} 29 | 30 | {{ $iso8601 := "2006-01-02T15:04:05-07:00" -}} 31 | {{ if .IsPage -}} 32 | {{ if not .PublishDate.IsZero -}} 33 | 34 | {{ else if not .Date.IsZero -}} 35 | 36 | {{ end -}} 37 | {{ if not .Lastmod.IsZero -}} 38 | 39 | {{ end -}} 40 | {{ else -}} 41 | {{ if not .Date.IsZero -}} 42 | 43 | {{ end -}} 44 | {{ end -}} 45 | 46 | {{ with .Params.audio -}} 47 | 48 | {{ end -}} 49 | {{ with .Params.locale -}} 50 | 51 | {{ end -}} 52 | {{ with .Site.Params.title -}} 53 | 54 | {{ end -}} 55 | {{ with .Params.videos -}} 56 | {{ range . -}} 57 | 58 | {{ end -}} 59 | {{ end -}} 60 | 61 | {{ $permalink := .Permalink -}} 62 | {{ $siteSeries := .Site.Taxonomies.series -}} 63 | {{ with .Params.series -}} 64 | {{ range $name := . -}} 65 | {{ $series := index $siteSeries $name -}} 66 | {{ range $page := first 6 $series.Pages -}} 67 | {{ if ne $page.Permalink $permalink -}} 68 | 69 | {{ end -}} 70 | {{ end -}} 71 | {{ end -}} 72 | {{ end -}} 73 | 74 | {{ if .IsPage -}} 75 | {{ range .Site.Authors -}} 76 | {{ with .Social.facebook -}} 77 | 78 | {{ end -}} 79 | {{ with .Site.Social.facebook -}} 80 | 81 | {{ end -}} 82 | 83 | {{ with .Params.tags -}} 84 | {{ range first 6 . -}} 85 | 86 | {{ end -}} 87 | {{ end -}} 88 | {{ end -}} 89 | {{ end -}} 90 | 91 | {{ with .Site.Social.facebook_admin -}} 92 | 93 | {{ end -}} -------------------------------------------------------------------------------- /awssdk/src/it/scala/PutOpsSpec.scala: -------------------------------------------------------------------------------- 1 | package meteor 2 | 3 | import cats.effect.IO 4 | import cats.implicits._ 5 | import meteor.Util._ 6 | import meteor.errors.ConditionalCheckFailed 7 | 8 | class PutOpsSpec extends ITSpec { 9 | 10 | behavior.of("put operation") 11 | 12 | it should "success inserting item with both keys" in forAll { 13 | (test: TestData) => 14 | compositeKeysTable[IO].use { 15 | case (client, table) => 16 | client.put[TestData](table.tableName, test) 17 | }.unsafeToFuture().futureValue shouldBe an[Unit] 18 | } 19 | 20 | it should "return old value after successfully inserting item with both keys" in forAll { 21 | (old: TestData) => 22 | val updated = old.copy(str = old.str + "-updated") 23 | compositeKeysTable[IO].use { 24 | case (client, table) => 25 | client.put[TestData](table.tableName, old) >> client.put[ 26 | TestData, 27 | TestData 28 | ]( 29 | table.tableName, 30 | updated 31 | ) 32 | }.unsafeToFuture().futureValue shouldEqual Some(old) 33 | } 34 | 35 | it should "return none if there isn't a previous record with the same keys" in forAll { 36 | (test: TestData) => 37 | compositeKeysTable[IO].use { 38 | case (client, table) => 39 | client.put[TestData, TestData](table.tableName, test) 40 | }.unsafeToFuture().futureValue shouldEqual None 41 | } 42 | 43 | it should "success inserting item without sort key" in forAll { 44 | (test: TestDataSimple) => 45 | val result = partitionKeyTable[IO].use { 46 | case (client, table) => 47 | client.put[TestDataSimple](table.tableName, test) 48 | } 49 | result.unsafeToFuture().futureValue shouldBe an[Unit] 50 | } 51 | 52 | it should "return old value after successfully inserting item without sort key" in forAll { 53 | (old: TestDataSimple) => 54 | val updated = old.copy(data = old.data + "-updated") 55 | partitionKeyTable[IO].use { 56 | case (client, table) => 57 | client.put[TestDataSimple](table.tableName, old) >> client.put[ 58 | TestDataSimple, 59 | TestDataSimple 60 | ](table.tableName, updated) 61 | }.unsafeToFuture().futureValue shouldEqual Some(old) 62 | } 63 | 64 | it should "success inserting item if key(s) doesn't exist by using condition expression" in forAll { 65 | (test: TestData) => 66 | compositeKeysTable[IO].use { 67 | case (client, table) => 68 | client.put[TestData]( 69 | table.tableName, 70 | test, 71 | Expression( 72 | "attribute_not_exists(#id)", 73 | Map("#id" -> "id"), 74 | Map.empty 75 | ) 76 | ) 77 | }.unsafeToFuture().futureValue shouldBe an[Unit] 78 | } 79 | 80 | it should "fail inserting item if key(s) exists by using condition expression" in forAll { 81 | (test: TestData) => 82 | val result = compositeKeysTable[IO].use { 83 | case (client, table) => 84 | client.put[TestData]( 85 | table.tableName, 86 | test 87 | ) >> 88 | client.put[TestData]( 89 | table.tableName, 90 | test, 91 | Expression( 92 | "attribute_not_exists(#id)", 93 | Map("#id" -> "id"), 94 | Map.empty 95 | ) 96 | ) 97 | } 98 | result.attempt.unsafeToFuture().futureValue.swap.getOrElse( 99 | throw new Exception("testing failure") 100 | ) shouldBe a[ 101 | ConditionalCheckFailed 102 | ] 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /website/static/api/lib/class_comp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | C 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /website/static/api/lib/object_comp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | O 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /website/static/api/lib/trait_comp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | t 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /website/static/api/lib/annotation_comp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | @ 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /website/static/api/lib/object_comp_trait.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | O 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /awssdk/src/it/scala/UpdateOpsSpec.scala: -------------------------------------------------------------------------------- 1 | package meteor 2 | 3 | import cats.implicits._ 4 | import cats.effect.IO 5 | import meteor.Util._ 6 | import meteor.codec.Encoder 7 | import meteor.errors.ConditionalCheckFailed 8 | import software.amazon.awssdk.services.dynamodb.model.ReturnValue 9 | 10 | class UpdateOpsSpec extends ITSpec { 11 | 12 | behavior.of("update operation") 13 | 14 | it should "return new value when condition is met" in forAll { 15 | (test: TestData) => 16 | // condition is int > 0 17 | val newInt = 2 18 | val input = test.copy(int = 1) 19 | val expected = test.copy(int = newInt) 20 | val result = compositeKeysTable[IO].use { 21 | case (client, table) => 22 | client.put[TestData](table.tableName, input) >> 23 | client.update[Id, Range, TestData]( 24 | table, 25 | input.id, 26 | input.range, 27 | Expression( 28 | "SET #int_u = :int_u_value", 29 | Map("#int_u" -> "int"), 30 | Map(":int_u_value" -> Encoder[Int].write(newInt)) 31 | ), 32 | Expression( 33 | s"#int_c > :int_c_value", 34 | Map("#int_c" -> "int"), 35 | Map(":int_c_value" -> Encoder[Int].write(0)) 36 | ), 37 | ReturnValue.ALL_NEW 38 | ) 39 | } 40 | result.unsafeToFuture().futureValue shouldEqual Some(expected) 41 | } 42 | 43 | it should "return old value when condition is met" in forAll { 44 | (test: TestData) => 45 | // condition is int > 0 46 | val newInt = 2 47 | val input = test.copy(int = 1) 48 | val result = compositeKeysTable[IO].use { 49 | case (client, table) => 50 | client.put[TestData](table.tableName, input) >> 51 | client.update[Id, Range, TestData]( 52 | table, 53 | input.id, 54 | input.range, 55 | Expression( 56 | "SET #int_u = :int_u_value", 57 | Map("#int_u" -> "int"), 58 | Map(":int_u_value" -> Encoder[Int].write(newInt)) 59 | ), 60 | Expression( 61 | s"#int_c > :int_c_value", 62 | Map("#int_c" -> "int"), 63 | Map(":int_c_value" -> Encoder[Int].write(0)) 64 | ), 65 | ReturnValue.ALL_OLD 66 | ) 67 | } 68 | result.unsafeToFuture().futureValue shouldEqual Some(input) 69 | } 70 | 71 | it should "raise error when condition is not met" in forAll { 72 | (test: TestData) => 73 | // condition is int > 0 74 | val newInt = 2 75 | val input = test.copy(int = -1) 76 | val result = compositeKeysTable[IO].use { 77 | case (client, table) => 78 | client.put[TestData](table.tableName, input) >> 79 | client.update[Id, Range, TestData]( 80 | table, 81 | input.id, 82 | input.range, 83 | Expression( 84 | "SET #int_u = :int_u_value", 85 | Map("#int_u" -> "int"), 86 | Map(":int_u_value" -> Encoder[Int].write(newInt)) 87 | ), 88 | Expression( 89 | s"#int_c > :int_c_value", 90 | Map("#int_c" -> "int"), 91 | Map(":int_c_value" -> Encoder[Int].write(0)) 92 | ), 93 | ReturnValue.ALL_OLD 94 | ) 95 | } 96 | val expect = result.attempt.unsafeToFuture().futureValue.swap.getOrElse( 97 | throw new Exception("testing failure") 98 | ) 99 | expect shouldBe a[ConditionalCheckFailed] 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /website/static/api/lib/diagrams.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Material Icons'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: url(MaterialIcons-Regular.eot); 6 | src: local('Material Icons'), 7 | local('MaterialIcons-Regular'), 8 | url(MaterialIcons-Regular.woff) format('woff'), 9 | url(MaterialIcons-Regular.ttf) format('truetype'); 10 | } 11 | 12 | .material-icons { 13 | font-family: 'Material Icons'; 14 | font-weight: normal; 15 | font-style: normal; 16 | font-size: 24px; 17 | display: inline-block; 18 | width: 1em; 19 | height: 1em; 20 | line-height: 1; 21 | text-transform: none; 22 | letter-spacing: normal; 23 | word-wrap: normal; 24 | white-space: nowrap; 25 | direction: ltr; 26 | -webkit-font-smoothing: antialiased; 27 | text-rendering: optimizeLegibility; 28 | -moz-osx-font-smoothing: grayscale; 29 | font-feature-settings: 'liga'; 30 | } 31 | 32 | .diagram-container { 33 | display: block; 34 | } 35 | 36 | .diagram-container > span.toggle { 37 | z-index: 9; 38 | } 39 | 40 | .diagram { 41 | overflow: hidden; 42 | display: none; 43 | padding-top:15px; 44 | } 45 | 46 | .diagram svg { 47 | display: block; 48 | position: static; 49 | visibility: visible; 50 | z-index: auto; 51 | margin: auto; 52 | } 53 | 54 | .diagram-help { 55 | float:right; 56 | display:none; 57 | } 58 | 59 | .magnifying { 60 | cursor: -webkit-zoom-in ! important; 61 | cursor: -moz-zoom-in ! important; 62 | cursor: pointer; 63 | } 64 | 65 | #close-link { 66 | position: absolute; 67 | z-index: 100; 68 | font-family: Arial, sans-serif; 69 | font-size: 10pt; 70 | text-decoration: underline; 71 | color: #315479; 72 | } 73 | 74 | #close:hover { 75 | text-decoration: none; 76 | } 77 | 78 | #inheritance-diagram { 79 | padding-bottom: 20px; 80 | } 81 | 82 | 83 | #inheritance-diagram-container > span.toggle { 84 | z-index: 2; 85 | } 86 | 87 | .diagram-container.full-screen { 88 | position: fixed !important; 89 | margin: 0; 90 | border-radius: 0; 91 | top: 0em; 92 | bottom: 3em; 93 | left: 0; 94 | width: 100%; 95 | height: 100%; 96 | z-index: 10000; 97 | } 98 | 99 | .diagram-container.full-screen > span.toggle { 100 | display: none; 101 | } 102 | 103 | .diagram-container.full-screen > div.diagram { 104 | position: absolute; 105 | top: 0; right: 0; bottom: 0; left: 0; 106 | margin: auto; 107 | } 108 | 109 | #diagram-controls { 110 | z-index: 2; 111 | position: absolute; 112 | bottom: 1em; 113 | right: 1em; 114 | } 115 | 116 | #diagram-controls > button.diagram-btn { 117 | border-radius: 1.25em; 118 | height: 2.5em; 119 | width: 2.5em; 120 | background-color: #c2c2c2; 121 | color: #fff; 122 | border: 0; 123 | float: left; 124 | margin: 0 0.1em; 125 | cursor: pointer; 126 | line-height: 0.9; 127 | outline: none; 128 | } 129 | 130 | #diagram-controls > button.diagram-btn:hover { 131 | background-color: #e2e2e2; 132 | } 133 | 134 | #diagram-controls > button.diagram-btn > i.material-icons { 135 | font-size: 1.5em; 136 | } 137 | 138 | svg a { 139 | cursor:pointer; 140 | } 141 | 142 | svg text { 143 | font-size: 8.5px; 144 | } 145 | 146 | 147 | svg { 148 | border: 1px solid #999; 149 | overflow: hidden; 150 | } 151 | 152 | svg .node { 153 | white-space: nowrap; 154 | } 155 | 156 | svg .node rect, 157 | svg .node circle, 158 | svg .node ellipse { 159 | stroke: #333; 160 | fill: #fff; 161 | stroke-width: 1.5px; 162 | } 163 | 164 | svg .cluster rect { 165 | stroke: #333; 166 | fill: #000; 167 | fill-opacity: 0.1; 168 | stroke-width: 1.5px; 169 | } 170 | 171 | svg .edgePath path.path { 172 | stroke: #333; 173 | stroke-width: 1.5px; 174 | fill: none; 175 | } 176 | 177 | 178 | -------------------------------------------------------------------------------- /website/static/api/lib/object_comp_annotation.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | O 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /website/assets/scss/components/_syntax.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * GitHub Light v0.5.0 3 | * Copyright (c) 2012 - 2017 GitHub, Inc. 4 | * Licensed under MIT (https://github.com/primer/github-syntax-theme-generator/blob/master/LICENSE) 5 | */ 6 | 7 | .c1, 8 | .c /* comment, punctuation.definition.comment, string.comment */ { 9 | color: #6a737d; 10 | } 11 | 12 | .v /* variable */, 13 | .smw /* sublimelinter.mark.warning */ { 14 | color: #e36209; 15 | } 16 | 17 | // .c1 /* constant, entity.name.constant, variable.other.constant, variable.language, support, meta.property-name, support.constant, support.variable, meta.module-reference, markup.raw, meta.diff.header, meta.output */, 18 | .s .v /* string variable */ { 19 | color: #005cc5; 20 | } 21 | 22 | .e /* entity */, 23 | .en /* entity.name */ { 24 | color: #6f42c1; 25 | } 26 | 27 | .smi /* variable.parameter.function, storage.modifier.package, storage.modifier.import, storage.type.java, variable.other */, 28 | .s .s1 /* string source */ { 29 | color: #24292e; 30 | } 31 | 32 | .ent /* entity.name.tag, markup.quote */ { 33 | color: #22863a; 34 | } 35 | 36 | .k /* keyword, storage, storage.type */ { 37 | color: #d73a49; 38 | } 39 | 40 | .s /* string */, 41 | .pds /* punctuation.definition.string, source.regexp, string.regexp.character-class */, 42 | .s .pse .s1 /* string punctuation.section.embedded source */, 43 | .sr /* string.regexp */, 44 | .sr .cce /* string.regexp constant.character.escape */, 45 | .sr .sre /* string.regexp source.ruby.embedded */, 46 | .sr .sra /* string.regexp string.regexp.arbitrary-repitition */ { 47 | color: #032f62; 48 | } 49 | 50 | .bu /* invalid.broken, invalid.deprecated, invalid.unimplemented, message.error, brackethighlighter.unmatched, sublimelinter.mark.error */ { 51 | color: #b31d28; 52 | } 53 | 54 | .ii /* invalid.illegal */ { 55 | color: #fafbfc; 56 | background-color: #b31d28; 57 | } 58 | 59 | .c2 /* carriage-return */ { 60 | color: #fafbfc; 61 | background-color: #d73a49; 62 | } 63 | 64 | .c2::before /* carriage-return */ { 65 | content: "^M"; 66 | } 67 | 68 | .sr .cce /* string.regexp constant.character.escape */ { 69 | font-weight: bold; 70 | color: #22863a; 71 | } 72 | 73 | .ml /* markup.list */ { 74 | color: #735c0f; 75 | } 76 | 77 | .mh /* markup.heading */, 78 | .mh .en /* markup.heading entity.name */, 79 | .ms /* meta.separator */ { 80 | font-weight: bold; 81 | color: #005cc5; 82 | } 83 | 84 | .mi /* markup.italic */ { 85 | font-style: italic; 86 | color: #24292e; 87 | } 88 | 89 | .mb /* markup.bold */ { 90 | font-weight: bold; 91 | color: #24292e; 92 | } 93 | 94 | .md /* markup.deleted, meta.diff.header.from-file, punctuation.definition.deleted */ { 95 | color: #b31d28; 96 | background-color: #ffeef0; 97 | } 98 | 99 | .mi1 /* markup.inserted, meta.diff.header.to-file, punctuation.definition.inserted */ { 100 | color: #22863a; 101 | background-color: #f0fff4; 102 | } 103 | 104 | .mc /* markup.changed, punctuation.definition.changed */ { 105 | color: #e36209; 106 | background-color: #ffebda; 107 | } 108 | 109 | .mi2 /* markup.ignored, markup.untracked */ { 110 | color: #f6f8fa; 111 | background-color: #005cc5; 112 | } 113 | 114 | .mdr /* meta.diff.range */ { 115 | font-weight: bold; 116 | color: #6f42c1; 117 | } 118 | 119 | .ba /* brackethighlighter.tag, brackethighlighter.curly, brackethighlighter.round, brackethighlighter.square, brackethighlighter.angle, brackethighlighter.quote */ { 120 | color: #586069; 121 | } 122 | 123 | .sg /* sublimelinter.gutter-mark */ { 124 | color: #959da5; 125 | } 126 | 127 | .corl /* constant.other.reference.link, string.other.link */ { 128 | text-decoration: underline; 129 | color: #032f62; 130 | } 131 | -------------------------------------------------------------------------------- /website/assets/js/index.js: -------------------------------------------------------------------------------- 1 | var suggestions = document.getElementById('suggestions'); 2 | var userinput = document.getElementById('userinput'); 3 | 4 | document.addEventListener('keydown', inputFocus); 5 | 6 | function inputFocus(e) { 7 | 8 | if (e.keyCode === 191 ) { 9 | e.preventDefault(); 10 | userinput.focus(); 11 | } 12 | 13 | if (e.keyCode === 27 ) { 14 | userinput.blur(); 15 | suggestions.classList.add('d-none'); 16 | } 17 | 18 | } 19 | 20 | document.addEventListener('click', function(event) { 21 | 22 | var isClickInsideElement = suggestions.contains(event.target); 23 | 24 | if (!isClickInsideElement) { 25 | suggestions.classList.add('d-none'); 26 | } 27 | 28 | }); 29 | 30 | /* 31 | Source: 32 | - https://dev.to/shubhamprakash/trap-focus-using-javascript-6a3 33 | */ 34 | 35 | document.addEventListener('keydown',suggestionFocus); 36 | 37 | function suggestionFocus(e){ 38 | 39 | const focusableSuggestions= suggestions.querySelectorAll('a'); 40 | const focusable= [...focusableSuggestions]; 41 | const index = focusable.indexOf(document.activeElement); 42 | 43 | let nextIndex = 0; 44 | 45 | if (e.keyCode === 38) { 46 | e.preventDefault(); 47 | nextIndex= index > 0 ? index-1 : 0; 48 | focusableSuggestions[nextIndex].focus(); 49 | } 50 | else if (e.keyCode === 40) { 51 | e.preventDefault(); 52 | nextIndex= index+1 < focusable.length ? index+1 : index; 53 | focusableSuggestions[nextIndex].focus(); 54 | } 55 | 56 | } 57 | 58 | 59 | /* 60 | Source: 61 | - https://github.com/nextapps-de/flexsearch#index-documents-field-search 62 | - https://raw.githack.com/nextapps-de/flexsearch/master/demo/autocomplete.html 63 | */ 64 | 65 | (function(){ 66 | 67 | var index = new FlexSearch({ 68 | preset: 'score', 69 | cache: true, 70 | doc: { 71 | id: 'id', 72 | field: [ 73 | 'title', 74 | 'description', 75 | 'content', 76 | ], 77 | store: [ 78 | 'href', 79 | 'title', 80 | 'description', 81 | ], 82 | }, 83 | }); 84 | 85 | var docs = [ 86 | {{ range $index, $page := (where .Site.Pages "Section" "docs") -}} 87 | { 88 | id: {{ $index }}, 89 | href: "{{ .Permalink | absURL }}", 90 | title: {{ .Title | jsonify }}, 91 | description: {{ .Params.description | jsonify }}, 92 | content: {{ .Content | jsonify }} 93 | }, 94 | {{ end -}} 95 | ]; 96 | 97 | index.add(docs); 98 | 99 | userinput.addEventListener('input', show_results, true); 100 | suggestions.addEventListener('click', accept_suggestion, true); 101 | 102 | function show_results(){ 103 | 104 | var value = this.value; 105 | var results = index.search(value, 5); 106 | var entry, childs = suggestions.childNodes; 107 | var i = 0, len = results.length; 108 | 109 | suggestions.classList.remove('d-none'); 110 | 111 | results.forEach(function(page) { 112 | 113 | entry = document.createElement('div'); 114 | 115 | entry.innerHTML = ''; 116 | 117 | a = entry.querySelector('a'), 118 | t = entry.querySelector('span:first-child'), 119 | d = entry.querySelector('span:nth-child(2)'); 120 | 121 | a.href = page.href; 122 | t.textContent = page.title; 123 | d.textContent = page.description; 124 | 125 | suggestions.appendChild(entry); 126 | 127 | }); 128 | 129 | while(childs.length > len){ 130 | 131 | suggestions.removeChild(childs[i]) 132 | } 133 | 134 | } 135 | 136 | function accept_suggestion(){ 137 | 138 | while(suggestions.lastChild){ 139 | 140 | suggestions.removeChild(suggestions.lastChild); 141 | } 142 | 143 | return false; 144 | } 145 | 146 | }()); 147 | -------------------------------------------------------------------------------- /website/static/api/lib/scheduler.js: -------------------------------------------------------------------------------- 1 | // © 2010 EPFL/LAMP 2 | // code by Gilles Dubochet, Felix Mulder 3 | 4 | function Scheduler() { 5 | var scheduler = this; 6 | var resolution = 0; 7 | this.timeout = undefined; 8 | this.queues = new Array(0); // an array of work packages indexed by index in the labels table. 9 | this.labels = new Array(0); // an indexed array of labels indexed by priority. This should be short. 10 | 11 | this.label = function(name, priority) { 12 | this.name = name; 13 | this.priority = priority; 14 | } 15 | 16 | this.work = function(fn, self, args) { 17 | this.fn = fn; 18 | this.self = self; 19 | this.args = args; 20 | } 21 | 22 | this.addLabel = function(name, priority) { 23 | var idx = 0; 24 | while (idx < scheduler.queues.length && scheduler.labels[idx].priority <= priority) { idx = idx + 1; } 25 | scheduler.labels.splice(idx, 0, new scheduler.label(name, priority)); 26 | scheduler.queues.splice(idx, 0, new Array(0)); 27 | } 28 | 29 | this.clearLabel = function(name) { 30 | var idx = scheduler.indexOf(name); 31 | if (idx != -1) { 32 | scheduler.labels.splice(idx, 1); 33 | scheduler.queues.splice(idx, 1); 34 | } 35 | } 36 | 37 | this.nextWork = function() { 38 | var fn = undefined; 39 | var idx = 0; 40 | while (idx < scheduler.queues.length && scheduler.queues[idx].length == 0) { idx = idx + 1; } 41 | 42 | if (idx < scheduler.queues.length && scheduler.queues[idx].length > 0) 43 | var fn = scheduler.queues[idx].shift(); 44 | 45 | return fn; 46 | } 47 | 48 | this.add = function(labelName, fn, self, args) { 49 | var doWork = function() { 50 | scheduler.timeout = setTimeout(function() { 51 | var work = scheduler.nextWork(); 52 | if (work != undefined) { 53 | if (work.args == undefined) { work.args = new Array(0); } 54 | work.fn.apply(work.self, work.args); 55 | doWork(); 56 | } 57 | else { 58 | scheduler.timeout = undefined; 59 | } 60 | }, resolution); 61 | } 62 | 63 | var idx = scheduler.indexOf(labelName) 64 | if (idx != -1) { 65 | scheduler.queues[idx].push(new scheduler.work(fn, self, args)); 66 | if (scheduler.timeout == undefined) doWork(); 67 | } else { 68 | throw("queue for add is non-existent"); 69 | } 70 | } 71 | 72 | this.clear = function(labelName) { 73 | scheduler.queues[scheduler.indexOf(labelName)] = new Array(); 74 | } 75 | 76 | this.indexOf = function(label) { 77 | var idx = 0; 78 | while (idx < scheduler.labels.length && scheduler.labels[idx].name != label) 79 | idx++; 80 | 81 | return idx < scheduler.queues.length && scheduler.labels[idx].name == label ? idx : -1; 82 | } 83 | 84 | this.queueEmpty = function(label) { 85 | var idx = scheduler.indexOf(label); 86 | if (idx != -1) 87 | return scheduler.queues[idx].length == 0; 88 | else 89 | throw("queue for label '" + label + "' is non-existent"); 90 | } 91 | 92 | this.scheduleLast = function(label, fn) { 93 | if (scheduler.queueEmpty(label)) { 94 | fn(); 95 | } else { 96 | scheduler.add(label, function() { 97 | scheduler.scheduleLast(label, fn); 98 | }); 99 | } 100 | } 101 | 102 | this.numberOfJobs = function(label) { 103 | var index = scheduler.indexOf(label); 104 | if (index == -1) throw("queue for label '" + label + "' non-existent"); 105 | 106 | return scheduler.queues[index].length; 107 | } 108 | }; 109 | -------------------------------------------------------------------------------- /website/assets/scss/common/_variables.scss: -------------------------------------------------------------------------------- 1 | // Color system 2 | 3 | $white: #fff; 4 | $gray-100: #f8f9fa; 5 | $gray-200: #e9ecef; 6 | $gray-300: #dee2e6; 7 | $gray-400: #ced4da; 8 | $gray-500: #adb5bd; 9 | $gray-600: #6c757d; 10 | $gray-700: #495057; 11 | $gray-800: #343a40; 12 | $gray-900: #212529; 13 | $black: #000; 14 | 15 | $yellow: #ffe000; 16 | $black: #1d2d35; 17 | $beige: #fbf7f0; 18 | $red: #e55235; 19 | $purple: #5d2f86; 20 | $brown: #aa9c84; 21 | 22 | $blue-300: #8ed6fb; 23 | $pink-500: #d32e9d; 24 | 25 | $primary: $purple; 26 | 27 | /** Bootstrap navbar fix (https://git.io/fADqW) */ 28 | $navbar-dark-toggler-icon-bg: none; 29 | $navbar-light-toggler-icon-bg: none; 30 | 31 | // Options 32 | // 33 | // Quickly modify global styling by enabling or disabling optional features. 34 | 35 | $enable-responsive-font-sizes: true; 36 | 37 | // Body 38 | // 39 | // Settings for the `` element. 40 | 41 | $body-bg: $white; 42 | $body-color: $black; 43 | 44 | // Grid containers 45 | // 46 | // Define the maximum width of `.container` for different screen sizes. 47 | 48 | $container-max-widths: ( 49 | sm: 540px, 50 | md: 720px, 51 | lg: 960px, 52 | xl: 1240px 53 | ); 54 | 55 | @include _assert-ascending($container-max-widths, "$container-max-widths"); 56 | 57 | // Grid columns 58 | // 59 | // Set the number of columns and specify the width of the gutters. 60 | 61 | $grid-columns: 16; 62 | $grid-gutter-width: 48px; 63 | $grid-row-columns: 6; 64 | 65 | // Components 66 | // 67 | // Define common padding and border radius sizes and more. 68 | 69 | $border-color: $gray-200; 70 | 71 | // Typography 72 | // 73 | // Font, line-height, and color for body text, headings, and more. 74 | 75 | // stylelint-disable value-keyword-case 76 | $font-family-sans-serif: "Jost", -apple-system, blinkmacsystemfont, "Segoe UI", roboto, "Helvetica Neue", arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 77 | $font-family-monospace: sfmono-regular, menlo, monaco, consolas, "Liberation Mono", "Courier New", monospace; 78 | $font-family-base: $font-family-sans-serif; 79 | // stylelint-enable value-keyword-case 80 | 81 | $font-size-base: 1rem; // Assumes the browser default, typically `16px` 82 | $font-size-xl: $font-size-base * 1.375; 83 | $font-size-lg: $font-size-base * 1.25; 84 | $font-size-md: $font-size-base * 1.125; 85 | $font-size-sm: $font-size-base * 0.875; 86 | 87 | $line-height-base: 1.5; 88 | 89 | $headings-font-family: null; 90 | $headings-font-weight: 700; 91 | 92 | $lead-font-weight: 400; 93 | 94 | // Spacing 95 | // 96 | // Control the default styling of most Bootstrap elements by modifying these 97 | // variables. Mostly focused on spacing. 98 | // You can add more entries to the $spacers map, should you need more variation. 99 | 100 | $spacer: 1rem; 101 | 102 | // Navbar 103 | 104 | $navbar-padding-y: $spacer / 2; 105 | $navbar-padding-x: 0; 106 | 107 | $navbar-nav-link-padding-x: 0.5rem; 108 | 109 | $navbar-light-color: $black; 110 | $navbar-light-hover-color: $primary; 111 | $navbar-light-active-color: $primary; 112 | 113 | // Cards 114 | 115 | $card-border-color: $gray-200; 116 | 117 | // Alerts 118 | // 119 | // Define alert colors, border radius, and padding. 120 | 121 | $alert-padding-y: 1rem; 122 | $alert-padding-x: 1.5rem; 123 | $alert-margin-bottom: 0; 124 | $alert-border-radius: 0; 125 | $alert-link-font-weight: $headings-font-weight; 126 | $alert-border-width: 0; 127 | 128 | $alert-bg-level: 0; 129 | $alert-border-level: 0; 130 | $alert-color-level: 0; 131 | -------------------------------------------------------------------------------- /website/layouts/partials/head/structured-data.html: -------------------------------------------------------------------------------- 1 | {{ if .IsHome -}} 2 | {{ if eq .Site.Params.schemaType "Organization" -}} 3 | 17 | {{ end -}} 18 | {{ if eq .Site.Params.schemaType "Person" -}} 19 | 32 | {{ end -}} 33 | {{ if .Site.Params.siteLinksSearchBox -}} 34 | 46 | {{ end -}} 47 | {{ end -}} 48 | {{ if .IsPage -}} 49 | {{ if eq .Section .Site.Params.schemaSection -}} 50 | 79 | {{ end -}} 80 | {{ end -}} 81 | 82 | {{ $dot := . -}} 83 | {{ $dot.Scratch.Set "path" "" -}} 84 | {{ $dot.Scratch.Set "breadcrumb" slice -}} 85 | 86 | {{ $url := replace .Permalink ( printf "%s" .Site.BaseURL) "" -}} 87 | {{ $.Scratch.Add "path" .Site.BaseURL -}} 88 | 89 | {{ $.Scratch.Add "breadcrumb" (slice (dict "url" .Site.BaseURL "name" "home" "position" 1 )) -}} 90 | {{ range $index, $element := split $url "/" -}} 91 | {{ $dot.Scratch.Add "path" $element -}} 92 | {{ $.Scratch.Add "path" "/" -}} 93 | {{ if ne $element "" -}} 94 | {{ $.Scratch.Add "breadcrumb" (slice (dict "url" ($.Scratch.Get "path") "name" . "position" (add $index 2))) -}} 95 | {{ end -}} 96 | {{ end -}} 97 | 98 | -------------------------------------------------------------------------------- /website/static/api/index.html: -------------------------------------------------------------------------------- 1 |

Packages

p

root package

package root

Package Members

  1. package meteor

Ungrouped

3 | --------------------------------------------------------------------------------