{"id":16965030,"url":"https://github.com/propensive/polyvinyl","last_synced_at":"2025-09-03T05:44:11.257Z","repository":{"id":39258518,"uuid":"438372062","full_name":"propensive/polyvinyl","owner":"propensive","description":"Typesafe record types for Scala","archived":false,"fork":false,"pushed_at":"2025-01-26T12:15:29.000Z","size":2415,"stargazers_count":21,"open_issues_count":3,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-09-03T05:44:10.614Z","etag":null,"topics":["record-types","scala","schemas","type-providers","typesafe-schemas"],"latest_commit_sha":null,"homepage":"https://propensive.com/polyvinyl/","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/propensive.png","metadata":{"files":{"readme":".github/readme.md","changelog":null,"contributing":".github/contributing.md","funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-12-14T19:15:50.000Z","updated_at":"2025-03-24T11:42:59.000Z","dependencies_parsed_at":"2024-02-19T20:35:46.816Z","dependency_job_id":"8ceb9f14-57fc-47ee-8d72-882095663fb8","html_url":"https://github.com/propensive/polyvinyl","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/propensive/polyvinyl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fpolyvinyl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fpolyvinyl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fpolyvinyl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fpolyvinyl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/propensive","download_url":"https://codeload.github.com/propensive/polyvinyl/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fpolyvinyl/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273397843,"owners_count":25098234,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-03T02:00:09.631Z","response_time":76,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["record-types","scala","schemas","type-providers","typesafe-schemas"],"created_at":"2024-10-13T23:44:51.889Z","updated_at":"2025-09-03T05:44:11.235Z","avatar_url":"https://github.com/propensive.png","language":"Scala","readme":"[\u003cimg alt=\"GitHub Workflow\" src=\"https://img.shields.io/github/actions/workflow/status/propensive/polyvinyl/main.yml?style=for-the-badge\" height=\"24\"\u003e](https://github.com/propensive/polyvinyl/actions)\n[\u003cimg src=\"https://img.shields.io/discord/633198088311537684?color=8899f7\u0026label=DISCORD\u0026style=for-the-badge\" height=\"24\"\u003e](https://discord.com/invite/MBUrkTgMnA)\n\u003cimg src=\"/doc/images/github.png\" valign=\"middle\"\u003e\n\n# Polyvinyl\n\n__Typesafe record types for Scala__\n\n_Polyvinyl_ makes it easy to define schemas for and use record types, originating from a\nvariety of possible sources. These could originate from a database, a file or some other\nsource, loaded at compiletime, and utilized in a later phase of compilation.\n\n## Features\n\n- provides support for record types in Scala\n- allows the implementation of F#-style type providers\n- enforces namespace-safety on field access\n- record schemas may be defined programmatically, without writing explicit case classes\n- schemas can be defined dynamically, taking strings as field names\n- schemas may be defined in the same module, provided they are not used in the same file\n- uses Scala 3's `Selectable` trait internally\n\n\n## Availability\n\n\n\n\n\n\n\n## Getting Started\n\n### Untyped Schemas\n\nA schema may be implemented as a singleton object extending `SimpleSchema[T]`, where `T` is the\nfixed type that each field will return. Additionally, the object should also define the method\n`fields`, returning a `List[String]` of the valid field names for record instances of that schema.\nWhichever string values this method returns when the code to construct a record is invoked (at\ncompiletime) will be considered valid field names for that record.\n\nAccordingly, different schemas must be implemented as different singletons.\n\nFurthermore, an `record` method should be implemented as a macro on the singleton object, exactly\nas follows:\n```scala\ntransparent inline def record(inline fn: String =\u003e T): SimpleRecord[T] = ${build('fn)}\n```\n\nInvoking this method will construct a new record, an instance of `SimpleRecord[T]`, whose field\nvalues will be obtained by calling the `fn` function with the field's name, as a `String`.\n\nHere is a full, albeit uninteresting, example:\n```scala\nobject Numbers extends SimpleSchema[Int]:\n  def fields = List(\"one\", \"two\", \"three\", \"four\")\n  transparent inline def record(inline fn: String =\u003e Int): SimpleRecord[Int] =\n    ${build('fn)}\n```\n\nThe field names are defined in the source code, but they could be obtained from anywhere\n(provided the code will produce the desired output when it is invoked inside the compiler at\ncompiletime).\n\nIn a different file, this `Numbers` schema object may be used to construct new `SimpleRecord[Int]`\nobjects. These instances must be backed by some means of obtaining the field values, given a\nfield name; this is just a lambda, so the implementation is up to the programmer.\n\nHere, we implement a record using a `Map[String, Int]`:\n```scala\nval numberMap = Map(\n  \"one\"   -\u003e 1,\n  \"two\"   -\u003e 2,\n  \"three\" -\u003e 3,\n  \"four\"  -\u003e 4,\n  \"five\"  -\u003e 5\n)\n\nval rec = Numbers.record(numberMap(_))\n```\n\nGiven the value, `rec`, any fields defined in the `fields` method may be invoked on it, and will\nreturn appropriate values from the map.\n\nFor example, `rec.one` will return the `Int`, `1`. But `rec.six` will be a compile error,\nas will `rec.five`: even though the runtime `numberMap` object contains a value for the\n`String` `\"five\"`, the schema does not define it.\n\n### Typed Schemas\n\nA schema whose fields may return different types requires a bit more work to implement. Its records\nwill be instances of `Record` (rather than `SimpleRecord[T]`) and its schemas will have the type,\n`Schema[E]`, where `E` must be an enumeration type (a subtype of `reflect.Enum`), consisting only\nof unparameterized values. Instead of the value, `fields`, a `Schema[E]` has a `Map[String, E]`\ncalled `types`.\n\nThe purpose of `E` is to facilitate a correspondence between a set of runtime values, and a set of\ncompiletime types. This is necessary because some _runtime_ representation of a field's return type\nis needed to inform the compiler about the structure of records.\n\nSo first, an `enum` should be written with cases for each possible return type, for example:\n```scala\nenum FieldType:\n  case Number, Bool, Varchar\n```\n\nEach enumeration case has a corresponding singleton type, and the schema—in this example,\n`Schema[FieldType]`—needs a _match type_ to specify how the singleton type of each `FieldType` case\nmaps to a return type. This type constructor, and member of `Schema[E]`, is called `Result` and\ncould be implemented as follows:\n```scala\ntype Result[T \u003c: FieldType] = T match\n  case FieldType.Number.type  =\u003e Double\n  case FieldType.Bool.type    =\u003e Boolean\n  case FieldType.Varchar.type =\u003e String\n```\n\nThen, the schema requires an implementation of `types`, which should map from a `String` key to\nthe enum type representing its return type. For example,\n```scala\nlazy val types: Map[String, FieldType] = Map(\n  \"age\"      -\u003e FieldType.Number,\n  \"name\"     -\u003e FieldType.Varchar,\n  \"employed\" -\u003e FieldType.Bool\n)\n```\n\nA more typical implementation of `types` may have much more complex logic, reading an external\nschema definition, and marshalling it into the `Map[String, E]` instance.\n\nAs with `SimpleSchema`, a `Schema` must be a singleton object, and requires an implementation of\n`record`. Collecting these parts together, we have:\n```scala\n\nenum FieldType:\n  case Number, Bool, Varchar\n\nobject MySchema extends Schema[FieldType]:\n  import FieldType.*\n  \n  lazy val types: Map[String, FieldType] =\n    Map(\"age\" -\u003e Number, \"name\" -\u003e Varchar, \"employed\" -\u003e Bool)\n  \n  type Result[T \u003c: FieldType] = T match\n    case Number  =\u003e Double\n    case Bool    =\u003e Boolean\n    case Varchar =\u003e String\n  \n  transparent inline def record(inline fn: String =\u003e Any): Record =\n    ${build('fn)}\n```\n\nAs before, new `Record` instances may be constructed by calling `MySchema.record(fn)` with an\nappropriate function, `fn`. This should be implemented carefully: `fn`'s type is\n`String =\u003e Any`, but its return value will be cast to the the declared type whenever the field is\naccessed. So the runtime type returned by this method _must_ be consistent with the field types\ndeclared in `types`.\n\nCommonly, this method would refer to `types` to work out the type of the value to return, for\nexample, backed by a `Map[String, String]`, called `data`:\n```scala\nMySchema.record { key =\u003e\n  types(key) match\n    case FieldType.Number =\u003e data(key)\n    case FieldType.Bool   =\u003e data(key) == \"true\"\n    case FieldType.Int    =\u003e data(key).toDouble\n}\n```\n\nThis logic may be inconvenient to include at every record creation site, so it would be convenient\nto include in the `MySchema` object itself, for example, as an `apply` method which takes the\n`Map[String, String]` directly, for example,\n```scala\ntransparent inline def apply(value: Map[String, String]): Record =\n  record { key =\u003e ... }\n```\nremembering to make it transparent and inline to ensure that the precise type of the returned\n`Record` is preserved—after all, that is the whole point!\n\n### Structural Types\n\nPolyvinyl uses Scala 3 macros to return structural refinements of `Record` and `SimpleRecord` types.\n\nFor example, given the definition of `MySchema`, above, the return type of the method, `record`,\nwould be:\n```scala\nRecord { def age: Double; def name: String; def employed: Boolean }\n```\n\nThis means that, not only is the return value a `Record`, but it additionally has fields called,\n`age`, `name` and `employed` returning a `Double`, a `String` and a `Boolean` respectively.\n\n### Compilation Order\n\nIt is crucial that the schema singleton is defined in a different file from the invocation. This\nis to guarantee that the object (and hence the fields it defines) is available as a runtime object\nduring later compilations which use it. Scala is able to compile a macro definition and its usage\nin the correct order so long as they are defined in separate files (and no cyclic dependencies\nexist between those files).\n\n### Limitations\n\n`SimpleSchema` and `Schema` do not currently make their precise record types available except as the\nreturn type of calling `record`. However the type is statically known, and could potentially be made\navailable as a type member.\n\n\n\n\n\n## Status\n\nPolyvinyl is classified as __fledgling__. For reference, Soundness projects are\ncategorized into one of the following five stability levels:\n\n- _embryonic_: for experimental or demonstrative purposes only, without any guarantees of longevity\n- _fledgling_: of proven utility, seeking contributions, but liable to significant redesigns\n- _maturescent_: major design decisions broady settled, seeking probatory adoption and refinement\n- _dependable_: production-ready, subject to controlled ongoing maintenance and enhancement; tagged as version `1.0.0` or later\n- _adamantine_: proven, reliable and production-ready, with no further breaking changes ever anticipated\n\nProjects at any stability level, even _embryonic_ projects, can still be used,\nas long as caution is taken to avoid a mismatch between the project's stability\nlevel and the required stability and maintainability of your own project.\n\nPolyvinyl is designed to be _small_. Its entire source code currently consists\nof 105 lines of code.\n\n## Building\n\nPolyvinyl will ultimately be built by Fury, when it is published. In the\nmeantime, two possibilities are offered, however they are acknowledged to be\nfragile, inadequately tested, and unsuitable for anything more than\nexperimentation. They are provided only for the necessity of providing _some_\nanswer to the question, \"how can I try Polyvinyl?\".\n\n1. *Copy the sources into your own project*\n   \n   Read the `fury` file in the repository root to understand Polyvinyl's build\n   structure, dependencies and source location; the file format should be short\n   and quite intuitive. Copy the sources into a source directory in your own\n   project, then repeat (recursively) for each of the dependencies.\n\n   The sources are compiled against the latest nightly release of Scala 3.\n   There should be no problem to compile the project together with all of its\n   dependencies in a single compilation.\n\n2. *Build with [Wrath](https://github.com/propensive/wrath/)*\n\n   Wrath is a bootstrapping script for building Polyvinyl and other projects in\n   the absence of a fully-featured build tool. It is designed to read the `fury`\n   file in the project directory, and produce a collection of JAR files which can\n   be added to a classpath, by compiling the project and all of its dependencies,\n   including the Scala compiler itself.\n   \n   Download the latest version of\n   [`wrath`](https://github.com/propensive/wrath/releases/latest), make it\n   executable, and add it to your path, for example by copying it to\n   `/usr/local/bin/`.\n\n   Clone this repository inside an empty directory, so that the build can\n   safely make clones of repositories it depends on as _peers_ of `polyvinyl`.\n   Run `wrath -F` in the repository root. This will download and compile the\n   latest version of Scala, as well as all of Polyvinyl's dependencies.\n\n   If the build was successful, the compiled JAR files can be found in the\n   `.wrath/dist` directory.\n\n## Contributing\n\nContributors to Polyvinyl are welcome and encouraged. New contributors may like\nto look for issues marked\n[beginner](https://github.com/propensive/polyvinyl/labels/beginner).\n\nWe suggest that all contributors read the [Contributing\nGuide](/contributing.md) to make the process of contributing to Polyvinyl\neasier.\n\nPlease __do not__ contact project maintainers privately with questions unless\nthere is a good reason to keep them private. While it can be tempting to\nrepsond to such questions, private answers cannot be shared with a wider\naudience, and it can result in duplication of effort.\n\n## Author\n\nPolyvinyl was designed and developed by Jon Pretty, and commercial support and\ntraining on all aspects of Scala 3 is available from [Propensive\nO\u0026Uuml;](https://propensive.com/).\n\n\n\n## Name\n\nPolyvinyl is the substance from which records (LPs) are made; the purpose of this library is to produce record types.\n\nIn general, Soundness project names are always chosen with some rationale,\nhowever it is usually frivolous. Each name is chosen for more for its\n_uniqueness_ and _intrigue_ than its concision or catchiness, and there is no\nbias towards names with positive or \"nice\" meanings—since many of the libraries\nperform some quite unpleasant tasks.\n\nNames should be English words, though many are obscure or archaic, and it\nshould be noted how willingly English adopts foreign words. Names are generally\nof Greek or Latin origin, and have often arrived in English via a romance\nlanguage.\n\n## Logo\n\nThe logo is a record, or LP, common as a distribution medium for music until the late 20th Century, and made of polyvinyl chloride and polyvinyl acetate.\n\n## License\n\nPolyvinyl is copyright \u0026copy; 2025 Jon Pretty \u0026 Propensive O\u0026Uuml;, and\nis made available under the [Apache 2.0 License](/license.md).\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpropensive%2Fpolyvinyl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpropensive%2Fpolyvinyl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpropensive%2Fpolyvinyl/lists"}