Full Code of alexeyraspopov/dataclass for AI

master 754b8b3cf9da cached
37 files
75.0 KB
20.2k tokens
73 symbols
1 requests
Download .txt
Repository: alexeyraspopov/dataclass
Branch: master
Commit: 754b8b3cf9da
Files: 37
Total size: 75.0 KB

Directory structure:
gitextract_5xq9i1un/

├── .github/
│   └── workflows/
│       ├── docs.yaml
│       └── testing.yaml
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── docs/
│   ├── .vitepress/
│   │   ├── .gitignore
│   │   └── config.js
│   ├── guide/
│   │   ├── caveats.md
│   │   ├── contributing.md
│   │   ├── getting-started.md
│   │   ├── index.md
│   │   ├── installation.md
│   │   ├── migrating.md
│   │   ├── objects-equality.md
│   │   └── serialization-deserialization.md
│   ├── index.md
│   ├── package.json
│   └── reference/
│       └── index.md
├── integration/
│   ├── Data.test.ts
│   ├── integration.js
│   ├── legacy.json
│   ├── modern.json
│   ├── runtime.test.ts
│   └── spec.json
├── modules/
│   ├── Data.js
│   └── runtime.js
├── package.json
├── rollup.config.mjs
├── tsconfig.json
├── typings/
│   ├── dataclass.d.ts
│   ├── dataclass.js.flow
│   ├── runtime.d.ts
│   └── runtime.js.flow
└── vitest.config.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/docs.yaml
================================================
name: Docs

on:
  push:
    branches: [master]
  workflow_dispatch:

jobs:
  deploy:
    name: Deploy Docs
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: 18
      - name: Install docs-related dependencies
        run: cd docs && npm install && npm run build && cd ..
      - name: Deploy to GitHub Pages
        uses: crazy-max/ghaction-github-pages@v2
        with:
          target_branch: gh-pages
          build_dir: docs/.vitepress/dist
          fqdn: dataclass.js.org
          verbose: true
        env:
          GITHUB_TOKEN: ${{ secrets.DOCS_GITHUB_TOKEN }}


================================================
FILE: .github/workflows/testing.yaml
================================================
name: Testing

on:
  push:
    branches: [master]
  pull_request:
    branches: [master]

jobs:
  integration:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: 20
      - run: npm install
      - run: npm run build
      - run: TARGET=modern npm run integration
      - run: TARGET=spec npm run integration
      - run: TARGET=legacy npm run integration
      - run: npm run test


================================================
FILE: .gitignore
================================================
node_modules
coverage
build


================================================
FILE: CHANGELOG.md
================================================
# Changelog

## [`v2.1.1`](https://github.com/alexeyraspopov/dataclass/releases/tag/v2.1.1)

- TypeScript typings fix: omit `Data` base class keys in `create()` and `copy()` signatures. Not a
  breaking change since attempt to override these keys would lead to runtime error already. Mainly
  affects autocomplete function of your editor, only showing the keys that can be updated.

## [`v2.1.0`](https://github.com/alexeyraspopov/dataclass/releases/tag/v2.1.0)

- Data instances are now sealed. Adding extra keys via `create()` or `copy()` will result in runtime
  error. If type system is properly utilized, this should not create any issues to existing code.
- Fully rewritten instantiation and copy algorithms with backward compatibility. New implementation
  consumes less memory and uses faster approach in copying objects.
- Fixed dynamic defaults being re-generated after `copy()`
- `copy()` methods now both can omit the argument, creating a referential copy of the instance.
- `equals()` now compares all keys (previously it was checking only the ones overriding defaults).
  The assumed optimizaiton in time didn't pay out and only caused unnecessary complication to
  copying mechanism and higher memory consumtion.

## [`v2.0.0`](https://github.com/alexeyraspopov/dataclass/releases/tag/v2.0.0)

- Dataclass is now licensed under [ISC License](https://en.wikipedia.org/wiki/ISC_license)  
  https://github.com/alexeyraspopov/dataclass/blob/master/LICENSE
- **Breaking:** the utility class has been renamed from `Record` to `Data`
  - "Record" now means a lot of other things in the ecosystem
- **Breaking:** use `.create()` static method instead of `new` operator
  - This fixes the issue with existing browser implementation of class properties
  - The use of `new` operator now throws a runtime error
- **Breaking:** TypeScript classes no longer need to be generic
- **Breaking:** an attempt to mutate properties now throws runtime errors
- **Breaking:** use named import instead of default `import { Data } from "dataclass"`
  - This should fix possible CJS/ESM compatibility issues and allow future API extensions
- **Breaking:** explicit `toJSON()` implementation has been removed, _but the behavior is preserved_
- **Breaking:** library code is no longer transpiled to ES5
  - Unless you support evergreen browsers, you still need to transpile TypeScript or class
    properties so the build step is inevitable. Thus, make sure `dataclass` is transpiled if
    necessary
- Fixed `equals()` algorithm to ensure proper custom values comparison
- Fixed `equals()` algorithm to avoid runtime errors for nullable properties
- Added `sideEffects: false` to `package.json`

## [`v1.2.0`](https://github.com/alexeyraspopov/dataclass/releases/tag/v1.2.0)

- Fixed the ability to subclass records
- Added support for `toJSON()` of nested records
- Ensure `equals()` works for nested records
- Use `valueOf()` as a part of `equals()` algorithm to support complex structures

## [`v1.1.0`](https://github.com/alexeyraspopov/dataclass/releases/tag/v1.1.0)

- Implement default `toJSON()` serialization behavior

## [`v1.0.4`](https://github.com/alexeyraspopov/dataclass/releases/tag/v1.0.4)

- Added support for custom getters

## [`v1.0.3`](https://github.com/alexeyraspopov/dataclass/releases/tag/v1.0.3)

- Fixed Flow typings

## [`v1.0.2`](https://github.com/alexeyraspopov/dataclass/releases/tag/v1.0.2)

- Fixed `copy()` method for TypeScript

## [`v1.0.0`](https://github.com/alexeyraspopov/dataclass/releases/tag/v1.0.0)

- Initial public version


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as contributors and maintainers
pledge to making participation in our project and our community a harassment-free experience for
everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level
of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment include:

- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit
  permission
- Other conduct which could reasonably be considered inappropriate in a professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable behavior and are
expected to take appropriate and fair corrective action in response to any instances of unacceptable
behavior.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits,
code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or
to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces when an individual is
representing the project or its community. Examples of representing a project or community include
using an official project e-mail address, posting via an official social media account, or acting as
an appointed representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting
the project team at oleksii.raspopov@gmail.com. The project team will review and investigate all
complaints, and will respond in a way that it deems appropriate to the circumstances. The project
team is obligated to maintain confidentiality with regard to the reporter of an incident. Further
details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good faith may face
temporary or permanent repercussions as determined by other members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at
[http://contributor-covenant.org/version/1/4][version]

[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing Guide

Greetings! I'm glad that you are interested in contributing to this project.

Before submitting your contribution though, please take a moment and read through the following
guidelines.

## Issue Reporting Guidelines

- You are free to open an issue with any question you have. This helps us to improve the docs and
  make the project more developers-friendly.
- Make sure you question has not been answered before in other issues or in the docs.
- Please provide an environment or list of steps to reproduce the bug you've found. You can attach a
  link to a repo or gist that has all the sources needed for reproducing.

## Pull Request Guidelines

- Feel free to open pull requests against `master` branch.
- Provide descriptive explanation of the things you want to fix, improve, or change.
- Create new automated tests for bug fixes, to ensure the effect of introduced changes and ability
  to avoid regressions.
- Keep git history clear and readable. No "ugh linter again" commits.


================================================
FILE: LICENSE
================================================
ISC License

Copyright (c) 2017-2024 Oleksii Raspopov

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.


================================================
FILE: README.md
================================================
# dataclass

    npm install dataclass

Syntax sugar that leverages the power of available type systems in TypeScript and JavaScript to
provide an effortless way for defining value objects that are immutable and persistent.

Read full docs [on the website](https://dataclass.js.org).

```ts
import { Data } from "dataclass";

class User extends Data {
  name: string = "Anon";
  age: number = 25;
}

let user = User.create({ name: "Liza", age: 23 });
// > User { name: "Liza", age: 23 }

let updated = user.copy({ name: "Ann" });
// > User { name: "Ann", age: 23 }

let isEqual = user.equals(updated);
// > false
```

## Prior Art

The implemented concept is heavily inspired by Scala and Kotlin. Both languages have the
implementation of data classes as a part of their syntax and share similar APIs.

See [Data Classes](https://kotlinlang.org/docs/reference/data-classes.html) in Kotlin (also
[Case Classes](https://docs.scala-lang.org/tour/case-classes.html) in Scala):

```kotlin
data class User(val name: String = "Anonymous", val age: Int = 0)

val user = User(name = "Liza", age = 23)
val updated = user.copy(name = "Ann")

user.equals(updated)
```

And [Data Classes](https://docs.python.org/3/library/dataclasses.html) in Python:

```python
from dataclasses import dataclass, replace

@dataclass
class User:
  name: str = "Anonymous"
  age: int = 0

user = User(name="Liza", age=23)
updated = replace(user, name="Ann")

user == updated
```

## Contributing

The project is opened for any contributions (features, updates, fixes, etc). If you're interested,
please check
[the contributing guidelines](https://github.com/alexeyraspopov/dataclass/blob/master/CONTRIBUTING.md).

The project is licensed under the
[ISC](https://github.com/alexeyraspopov/dataclass/blob/master/LICENSE) license.


================================================
FILE: docs/.vitepress/.gitignore
================================================
cache
dist


================================================
FILE: docs/.vitepress/config.js
================================================
export default {
  title: "dataclass",
  description: "Data Classes for TypeScript & JavaScript",

  lastUpdated: true,

  themeConfig: {
    nav: [
      { text: "Guide", link: "/guide/" },
      { text: "Reference", link: "/reference/" },
    ],
    sidebar: [
      {
        text: "Guide",
        items: [
          { text: "Introduction", link: "/guide/" },
          { text: "Installation", link: "/guide/installation" },
          { text: "Getting Started", link: "/guide/getting-started" },
          { text: "Objects Equality", link: "/guide/objects-equality" },
          { text: "Serialization & Deserialization", link: "/guide/serialization-deserialization" },
          { text: "Caveats", link: "/guide/caveats" },
          { text: "Migrating", link: "/guide/migrating" },
          { text: "Contributing", link: "/guide/contributing" },
        ],
      },
      {
        text: "Reference",
        items: [{ text: "API Reference", link: "/reference/index" }],
      },
    ],

    outline: "deep",

    search: {
      provider: "local",
    },

    editLink: {
      pattern: "https://github.com/alexeyraspopov/dataclass/edit/master/docs/:path",
    },

    socialLinks: [{ icon: "github", link: "https://github.com/alexeyraspopov/dataclass" }],
    externalLinkIcon: true,

    footer: {
      message: `Made by <a href="alexeyraspopov.com" rel="noopener noreferrer" target="_blank">Oleksii Raspopov</a> with ❤️`,
      copyright: "ISC License &copy; Oleksii Raspopov",
    },
  },
};


================================================
FILE: docs/guide/caveats.md
================================================
# Caveats

In the world of always changing tooling and evolving specs, it is hard to avoid weird edge cases
that can lead to hours of wasted time. On this page you may find some known caveats and potential
issues when using data classes, and possible ways to resolve or avoid them.

## Optional keys and code compilation

Defining a data class you may find yourself in a situation where a property doesn't have a
reasonable default value. Actual value may be provided during instantiation or the property's value
can be treated as missing.

The most universal approach (i.e. works in any environment for both TypeScript and Flowtype) going
to be the use of `null` as default value:

```ts
class Entity extends Data {
  optionalProp: string | null = null;
}

let entity = Entity.create();
// > Entity { optionalProp: null }

let payload = JSON.stringify(entity);
// > { "optionalProp": null }
```

When using TypeScript, you may want to use optional parameters instead of nullable types, as a less
noisy syntax:

```ts
class Entity extends Data {
  optionalProp?: string;
}

let entity = Entity.create();
// > Entity { optionalProp: undefined }
```

When doing so, you have to consider ensure following: you're using `tsc` version 4.3.2 or higher and
either your compile `target` is `es2022` or
[`useDefineForClassFields` is set to `true`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#the-usedefineforclassfields-flag-and-the-declare-property-modifier).
You may find this flag being set to `false` if some dependencies in your system rely on the old
behavior of class fields initialization. In any other case, optional properties are going to be
missing completely and you won't be able to define them using `create()` method.

**Warning**: During serialization, undefined values do not appear in resulting JSON string:

```ts{8-9}
class Entity extends Data {
  optionalProp?: string;
}

let entity = Entity.create();
// > Entity { optionalProp: undefined }

let payload = JSON.stringify(entity);
// > {}
```

When using Flowtype, it is preferable to stick to nullable types with `null` explicitly set as
default value. Basic Flowtype plugins for Babel going to strip out typings including properties
themselves.

```js
class Entity extends Data {
  optionalProp: ?string = null;
}

let entity = Entity.create();
// > Entity { optionalProp: null }

let payload = JSON.stringify(entity);
// > { "optionalProp": null }
```

## Explicit `this` signature in methods

When using TypeScript, whenever a data class contains a method, that uses `this.copy()` or
`this.equals()`, you may see a typing error. But you have done nothing wrong. There are some
limitations in TypeScript or this library's typings, but that's something you can easily fix:

```ts{4-5}
class Entity extends Data {
  counter: number = 13;

  // set explicit this type to a method that uses other data class methods
  increment(this: Entity) {
    return this.copy({ counter: this.counter + 1 })
  }
}

let entity = Entity.create({ counter: 10 });
// > Entity { counter: 10 }

let updated = entity.increment();
// > Entity { counter: 11 }
```


================================================
FILE: docs/guide/contributing.md
================================================
# Contributing

The project is opened for any contributions (features, updates, fixes, etc) and is
[located](https://github.com/alexeyraspopov/dataclass) on GitHub. If you're interested, please check
[the contributing guidelines](https://github.com/alexeyraspopov/dataclass/blob/master/CONTRIBUTING.md).

The project is licensed under the
[ISC](https://github.com/alexeyraspopov/dataclass/blob/master/LICENSE) license.


================================================
FILE: docs/guide/getting-started.md
================================================
# Getting Started

## Defining data classes

This library provides an abstract class `Data`:

```ts:no-line-numbers
import { Data } from "dataclass";
```

Which allows to define custom data classes with their set of fields. Assuming, the user is aware of
type systems and have one enabled for their project, this library does not do any type checks in
runtime. This means less overhead for the things, that have to be preserved in compile time or by a
safety net of tests.

The peak of developer experience can be achieved by using TypeScript or JavaScript that is extended
by [class properties](https://github.com/tc39/proposal-class-fields) and
[flowtype](https://flow.org). This allows to write a class with a set of fields following by their
types and default values:

```ts
class User extends Data {
  name: string = "Anonymous";
  age: number = 0;
}
```

Providing a set of fields defines the class' API.

## Creating data objects and accessing properties

New entity is created by using static method `create()` provided by `Data`:

```ts
let userWithCustomValues = User.create({ name: "Liza", age: 23 });
// > User { name: "Liza", age: 23 }

let userWithDefaultValue = User.create({ name: "Ann" });
// > User { name: "Ann", age: 0 }
```

**Warning**: the ability to use `new` operator is prohibited since `Data` needs access to all
properties.

Created entity has all the fields' getters that return either custom or default value:

```ts
// custom value provided to constructor
userWithCustomValues.name === "Liza";

// default value used from the model definition
userWithDefaultValue.age === 0;
```

## Making changes in data objects

Whenever a change should be made, there is `copy()` method that has the same signature as
constructor, based on a fields definition:

```ts
let user = User.create({ name: "Ann" });
// > User { name: "Ann", age: 0 }

let updated = user.copy({ age: 28 });
// > User { name: "Ann", age: 28 }
```

This method returns a new entity built upon previous set of values. The target of `copy()` calls is
not changed, by the definition of persistence.

## Comparing data objects by value

Since all the entities of one class are unique by their object reference, comparison operator will
always give `false` as a result. To compare the actual properties of the same class' entities,
`equals()` method should be used:

```ts
let userA = User.create({ name: "Ann" });
let userB = User.create({ name: "Ann" });

userA === userB;
// > false

userA.equals(userB);
// > true
```

All the API is fully compatible, so the code looks the same in JavaScript and TypeScript.

## Going beyond properties

Often, models may have a set of additional getters that represent computed values based on raw data.
They can be easily described as plain class' methods:

```ts{6-8,10-12}
class User extends Data {
  firstName: string = "John";
  lastName: string = "Doe";
  age: number = 0;

  isAdult() {
    return this.age >= 18;
  }

  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}
```

Getters may receive arguments, however it is recommended to keep them primitive, so a model
[won't know](https://en.wikipedia.org/wiki/Law_of_Demeter) about some others' internals.

## Nested data objects

When you're modeling complex domains, you may find the need to have one value object as a part of
another value object. This library supports it seamlessly:

```ts{7}
class Url extends Data {
  protocol: string = "https";
  hostname: string;
}

class Server extends Data {
  location: Url;
}
```

## Dynamic values as defaults

Default values of data class properties must be useful. JavaScript provides an ability to use any
expression as a value of class property, and so `dataclass` allows you to leverage this for good.

```ts
import { v4 as uuidv4 } from "uuid";

class Entity extends Data {
  id: string = uuidv4();
}

let entityA = Entity.create();
// > Entity { id: '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d' }

let entityB = Entity.create();
// > Entity { id: '1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed' }
```

**Note**: make sure to use `dataclass` version 2.1.0 or higher to avoid some old bugs related to
dynamic defaults.


================================================
FILE: docs/guide/index.md
================================================
# Introduction

    npm install dataclass

Syntax sugar that leverages the power of available type systems in TypeScript and JavaScript to
provide an effortless way for defining value objects that are immutable and persistent.

Dataclass can be used in browsers, Node.js, and Deno.

```ts:no-line-numbers{3,9,13,19}
import { Data } from "dataclass";

// 1. easily describe your data classes using just language features
class User extends Data {
  name: string = "Anon";
  age: number = 0;
}

// 2. instantiate classes while type systems ensure correctness
let user = User.create({ name: "Liza", age: 23 });
// > User { name: "Liza", age: 23 }

// 3. make changes while benefiting from immutable values
let updated = user.copy({ name: "Ann" });
// > User { name: "Ann", age: 23 }
updated = updated.copy({ name: "Liza" });
// > User { name: "Liza", age: 23 }

// 4. compare objects by their value, not reference
console.log(user === updated, user.equals(updated));
// > false, true
```

## Quick Start

1. **Define data class** — use language features to define the data schema and default values

```ts:no-line-numbers
class Task extends Data {
  contents: string = "";
  completed: boolean = false;
  priority: number = 1;
}
```

2. **Create data objects** — create immutable instance with custom values, fallback to defaults if
   necessary

```ts:no-line-numbers
let taskA = Task.create({
  contents: "Upgrade dependencies",
});
// > Task { contents: "Upgrade dependencies", completed: false, priority: 1 }

let taskB = Task.create({
  contents: "Provide work summary",
  priority: 2,
});
// > Task { contents: "Provide work summary", completed: false, priority: 2 }
```

3. **Read values** — data objects behave just like any other objects, read the properties you
   defined before

```ts:no-line-numbers
let tasks: Array<Task> = [
  /* some Task data objects */
];
tasks.sort((a, b) => (a.priority > b.priority ? -1 : 1));
```

4. **Make copies** — whenever a change is needed, make a copy data object with the new values

```ts:no-line-numbers
function markCompleted(task: Task) {
  return task.copy({ completed: true });
}
```

5. **Compare by values** — compare data objects by their value instead of immutable reference

```ts:no-line-numbers
function isTicketChanged(prev: Task, next: Task) {
  return !prev.equals(next);
}
```

6. Read more in [Getting Started](./getting-started.md) guide, or see deep dive explanation of
   different aspects: [value object equality](./objects-equality.md),
   [serialization/deserialization](./serialization-deserialization.md).

## Prior Art

The implemented concept is heavily inspired by Scala and Kotlin. Both languages have the
implementation of data classes as a part of their syntax and share similar APIs.

See [Data Classes](https://kotlinlang.org/docs/reference/data-classes.html) in Kotlin (also
[Case Classes](https://docs.scala-lang.org/tour/case-classes.html) in Scala):

```kotlin:no-line-numbers
data class User(val name: String = "Anonymous", val age: Int = 0)

val user = User(name = "Liza", age = 23)
val updated = user.copy(name = "Ann")

user.equals(updated)
```

And [Data Classes](https://docs.python.org/3/library/dataclasses.html) in Python:

```python:no-line-numbers
from dataclasses import dataclass, replace

@dataclass
class User:
  name: str = "Anonymous"
  age: int = 0

user = User(name="Liza", age=23)
updated = replace(user, name="Ann")

user == updated
```


================================================
FILE: docs/guide/installation.md
================================================
# Installation

### Installing via NPM

The library is available [in NPM registry](https://www.npmjs.com/package/dataclass) and can be
installed via NPM or similar package manager:

```sh:no-line-numbers
npm install dataclass
```

### Installing via CDNs

The library can be imported via [UNPKG](https://unpkg.com/). It is recommended to use `?module`
parameter to import ES Module version of the code:

```js:no-line-numbers
import { Data } from "https://unpkg.com/dataclass@2?module";
```

_Note: the library does not support [UMD](https://github.com/umdjs/umd) format._

In similar way, the library can be imported via [esm.sh](http://esm.sh/). This can be useful for
[Deno](https://deno.land/) since this CDN also serves `.d.ts` files.

```ts:no-line-numbers
import { Data } from "https://esm.sh/dataclass@2";
```

_Note: it is preferable to put explicit version range in the URL._

## Troubleshooting

The library is shipped with CommonJS and ES Module support. The source code is written using ES2015
features. Given [the global reach](https://caniuse.com/es6-class) of ES2015 Classes it is very
likely you won't need to compile this type of things. If the environments you are targetting support
these features or you know for sure that a node module will be properly pre-compiled if necessary,
you can skip the rest of this guide.

If older standards support required, the bundler of choice needs to be configured to transpile
`dataclass` dependency as well. Assuming you would like to use `dataclass` for its typings benefits,
you already have the build step in your environment.

### Using with Parcel or Vite

Parcel is capable of properly transpiling `node_modules` and relies on Browserslist to figure stuff.
Make sure you have `browserslist` defined. Read more
[in Parcel docs](https://parceljs.org/getting-started/webapp/#declaring-browser-targets).

Vite has a special way to handle dependencies transpiling. Read more
[in the related guide](https://vitejs.dev/guide/dep-pre-bundling.html).

### Using with Create React App

Create React App transpiles `node_modules` as a part of the build pipeline and relies on
Browserslist to figure what to transpile. Make sure you have `browserslist` properly configured.
Read more
[in CRA docs](https://create-react-app.dev/docs/supported-browsers-features/#configuring-supported-browsers).

### Using with Webpack & Babel

It is very likely, that your webpack config excludes `node_modules` from running through
`babel-loader` for the sake of faster builds. If any of your targeted environments require code
transpiling to ES5 (e.g. Internet Explorer 11), the config will require some changes to make it work
with `dataclass` (and possibly other dependencies that are published as a modern JS code).

#### Explicit targets including approach

The easiest way to extend your existing webpack config to transpile certain node_modules using
`babel-loader` is to explicitly mention them in `include` property corresponding rule.

_Note: this change can be applied to production config only._

#### Basic dependencies transpiling approach

While the previous approach easily works for `dataclass` and in no way affects the build time, there
is another approach you may consider, that will potentially help you with other dependecies.

_Note: this change can be applied to production config only._


================================================
FILE: docs/guide/migrating.md
================================================
# Migrating from v1 to v2

## The class name and import type has been changed

The library was created in 2017, long ago before
[Records & Tuples proposal](https://github.com/tc39/proposal-record-tuple) was created. The fact
this proposal is moving towards being a part of the language means "Record" as a term gains very
particular meaning for the ecosystem. Besides,
[`Immutable.Record`](https://immutable-js.com/docs/v4.0.0/Record/) and
[TypeScript's Record](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)
could potentially create confusion as well. Thus, the abstract class `Record` has been renamed to
`Data`.

Dataclass v1 exposed a single default export which seemed to work just fine for most of the cases.
However, it can create additional burden for CommonJS code and require some unnecessary tricks from
the bundlers. Thus, Dataclass v2 uses named export.

```diff:no-line-numbers
-import Record from "dataclass";
+import { Data } from "dataclass";
```

## Drop TypeScript generic from class definitions

Dataclass v1 required TypeScript classes to be generic due to
[polymorphic `this` for static members issue](https://github.com/Microsoft/TypeScript/issues/5863).
The issue has not been resolved but in Dataclass v2 there was a change in typings that helped
avoiding the issue in the first place. Now, the user's classes don't need to be generic.

```diff:no-line-numbers
-class User extends Record<User> {
+class User extends Data {
  name: string = "Anon";
}
```

## Use static method `create()` instead of `new` operator

Dataclass v2 uses new implementation for class instantiation due to some browser incompatibilities.

```diff:no-line-numbers
-let user = new User({ name: "Ann" });
+let user = User.create({ name: "Ann" });
```

Moving to dataclass v2 will make use of `new` operator throwing runtime errors, suggesting to use
static `create()` method instead.

## Ensure no mutations happening in the code

While instance of data classes treated as immutable, the implementation still uses some safety
precautions to ensure no mutations (accidental or intentional) can be made. In v1, when a prop is
mutated, nothing happens, the value remains the same. The operation is basically ignored.

```ts:no-line-numbers{3}
let user = new User({ age: 18 });

user.age = 100;

console.log(user.age);
// > 18
```

In v2, however, some additional precautions were made, to ensure that developers can spot bad code
and mistakes. Mutating a property will now throw an error:

```ts:no-line-numbers{3}
let user = new User({ age: 18 });

user.age = 100;
// Uncaught TypeError: "age" is read-only
```

This error comes from the use of
[`Object.freeze()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze)
which throws an error when there was an attempt to mutate an existing property and when the user
tries to add new property to the object.

## Make sure the dependency is transpiled, if necessary

See [Installation Guide & Troubleshooting](./installation.md#troubleshooting) for more details.


================================================
FILE: docs/guide/objects-equality.md
================================================
# Objects Equality

The biggest part of dataclasses flexibility is the fact they can be compared by the value they
contain, rather than by the instances reference.

Let's consider an example:

```ts
import { Data } from "dataclass";

class Circle extends Data {
  x: number = 0;
  y: number = 0;
  radius: number = 0;
}

let circleA = Circle.create({ x: 0, y: 10, radius: 2 });
// let's assume the new value is coming from an outside source
let circleB = circleA.copy({ radius: getCircleRadius() });
// …and now we need to check if the value actually changed
let isEqual = circleA.equals(circleB);
```

This guide describes what happens when `target.equals(other)` is being called.

1. The runtime does not check `other` for being the same data class as `target`. This is what
   supposed to be checked by the typing system (TypeScript or Flowtype) even before the code is
   executed.
2. The `equals()` method iterates over the properties of the `target` class and compares the values
   to the same keys in `other` instance.
3. If two values are not strictly equal (via `===` comparison), and both of the values are not
   nullish (i.e. neither `undefined` nor `null`), the method checks whether these values are data
   classes that also have `equals()` method. If so, the rest of comparison for these two values is
   delegated to their `equals()` method.
4. If the values are not data classes, `.valueOf()` method is used for both values to extract
   possible primitive representation. The resulting values are compared using `===` operator. If
   result is `false`, `equals()` method returns `false` and skip comparing the rest of the
   properties.
5. If none of changed properties are different in both `target` and `other`, `equals()` method
   returns `true`.

The idea behind this algorithm attempts to find `equals()` of a dataclass properties is that you can
create a data class that will be using another data class as a property.

The reason for using `valueOf()` for other types of properties is the fact that there are some data
types in JavaScript that are actually value objects and should be compared by their value while
having different reference. The prime example of it is `Date`. Instead of directly checking for
values to be instanceof of `Date`, `equals()` method relies on the mechanism of `valueOf()` itself,
allowing you to define custom `valueOf()` methods for any special data types that can be a part of
data classes.


================================================
FILE: docs/guide/serialization-deserialization.md
================================================
# Serialization & Deserialization

## Serialization

By default, when using
[`JSON.stringify()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify),
an instance will be serialized to a plain object with all the fields as is. This works without any
additional effort

```ts{5-8}
class User extends Data {
  name: string = "Anonymous";
  age: number = 0;

  // implicit behavior, no need to implement
  toJSON(): Object {
    return { name: this.name, age: this.age };
  }
}

let user = User.create({ name: "Liza", age: 23 });
// > User { name: "Liza", age: 23 }

JSON.stringify(user);
// > { "name": "Liza", "age": 23 }
```

## Deserialization

In cases where the input data cannot be determined (API requests) or there should be some additional
data preparation done, it is recommended to provide custom and agnostic static methods:

```ts{5-10}
class User extends Data {
  name: string = "Anonymous";
  age: number = 0;

  // custom method with arbitrary interface
  static from(data: Object): User {
    let name: string = data.name;
    let age: number = parseInt(data.age, 10);
    return User.create({ name, age });
  }
}

let user = User.from({ name: "Liza", age: "18", someUnusedFlag: true });
```

That's how native things handle these cases: see
[`Array.from()`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/from).


================================================
FILE: docs/index.md
================================================
---
layout: home
hero:
  name: dataclass
  # text: lorem ipsum
  tagline: Data Classes for TypeScript & JavaScript
  actions:
    - theme: brand
      text: Get Started
      link: /guide/
    - theme: alt
      text: GitHub
      link: https://github.com/alexeyraspopov/dataclass

home: true
tagline: Data Classes for TypeScript & JavaScript
actions:
  - text: Get Started
    link: /guide/
    type: primary
  - text: GitHub
    link: https://github.com/alexeyraspopov/dataclass
    type: secondary
features:
  - icon: 🪶
    title: Lightweight
    details:
      The library takes less than 500B in your bundle (min+gzip) while still providing a lot of
      flexibility and typings
  - icon: 🧱
    title: Immutable
    details: The power of value objects is based on a simple convention that objects never mutate
  - icon: ✨
    title: Delightful
    details:
      The project is built with developer experience in mind. Coding should be easy and dataclass is
      here to help
---

<style>
:root {
  --vp-home-hero-name-color: transparent;
  --vp-home-hero-name-background: linear-gradient(-30deg, hsl(200deg 100% 65%), var(--vp-c-brand-dark));
}
</style>


================================================
FILE: docs/package.json
================================================
{
  "scripts": {
    "start": "vitepress dev .",
    "build": "vitepress build ."
  },
  "dependencies": {
    "vitepress": "^1.0.0-rc.4"
  }
}


================================================
FILE: docs/reference/index.md
================================================
# API Reference

### class `Data`

Base class for domain models. Should be extended with a set of class fields that describe the shape
of desired model.

#### Example

```ts
import { Data } from "dataclass";

class Project extends Data {
  id: string = "";
  name: string = "Untitled Project";
  createdBy: string = "";
  createdAt: Date | null = null;
}
```

### static `create(values)`

Once extended, data class can be instantiated with a new data. That's the way to get a unique
immutable persistent model.

#### Arguments

1.  `values` (_Object_): POJO which shape satisfy the contract described during class extension. If
    you use [Flow](https://flow.org), it will warn you about the mistakes.

#### Returns

(_Data_): an instance of your data class with all the defined fields accessible as in the plain
object. Properties are read only.

#### Example

```ts
class Vehicle extends Data {
  model: string = "Unspecified";
  manufacturer: string = "Unknown";
}

let vehicle = Vehicle.create({ manufacturer: "Tesla", model: "S" });
// > Vehicle { manufacturer: 'Tesla', model: 'S' }

vehicle.manufacturer;
// > 'Tesla'
```

### method `copy(values)`

Create new immutable instance based on an existent one. Since properties are read only, that's the
way to provide an updated model's fields to a consumer keeping the rest unchanged.

#### Arguments

1.  `values` (_Data_): POJO that includes new values that you want to change. Properties should
    satisfy the contract described by the class.

#### Returns

(_Data_): new instance of the same type and with new values.

#### Example

```ts
class User extends Data {
  name: string = "Anonymous";
  email: string | null = null;
}

let user = User.create({ name: "Liza" });
// > User { name: 'Liza', email: null }

let updated = user.copy({ email: "liza@example.com" });
// > User { name: 'Liza', email: 'liza@example.com' }
```

### method `equals(other)`

Since immutable instances always have not equal references, there should be a way to compare the
actual values.

#### Arguments

1.  `other` (_Object_): a data object of the same class as a target one.

#### Returns

(_Boolean_): `false` if some field value is not
[strictly equal](https://www.ecma-international.org/ecma-262/5.1/#sec-11.9.6) in both instances.
`true` otherwise.

#### Example

```ts
class Box extends Data {
  size: number = 16;
  color: string = "red";
}

let first = Box.create({ color: "green" });
let second = Box.create({ color: "blue" });
let third = first.copy({ color: "blue" });

first === second;
// > false

first === third;
// > false

first.equals(second);
// > false

second.equals(third);
// > true
```


================================================
FILE: integration/Data.test.ts
================================================
import { test } from "vitest";
import { deepEqual, equal, throws } from "node:assert/strict";

import { Data } from "dataclass";

class Entity extends Data {
  someString: string = "default string";
  someNum: number = 0.134;
  someBool: boolean = true;
  someNullable: string | null = null;

  get exclamation() {
    return this.someString + "!";
  }
}

function plain(target: object) {
  return Object.fromEntries(Object.entries(target));
}

test("should create an entity with default values", () => {
  let entity = Entity.create();

  deepEqual(plain(entity), {
    someString: "default string",
    someNum: 0.134,
    someBool: true,
    someNullable: null,
  });
});

test("should override defaults with custom values", () => {
  let entity = Entity.create({ someNullable: "1", someString: "hello" });

  deepEqual(plain(entity), {
    someString: "hello",
    someNum: 0.134,
    someBool: true,
    someNullable: "1",
  });
});

test("should satisfy composition law", () => {
  let entity = Entity.create();
  let left = entity.copy({ someNum: 13, someBool: false });
  let right = entity.copy({ someNum: 13 }).copy({ someBool: false });

  deepEqual(left, right);
  equal(left.equals(right), true);
});

test("should support subclassing", () => {
  class SubEntity extends Entity {
    someNewThing: string = "default";
  }

  let entityA = SubEntity.create();
  let entityB = SubEntity.create({ someString: "test", someNewThing: "blah" });

  deepEqual(plain(entityA), {
    someString: "default string",
    someNum: 0.134,
    someBool: true,
    someNullable: null,
    someNewThing: "default",
  });

  deepEqual(plain(entityB), {
    someString: "test",
    someNum: 0.134,
    someBool: true,
    someNullable: null,
    someNewThing: "blah",
  });
});

test("should support polymorphism", () => {
  class Base extends Data {
    format: string = "AAA";

    transform(value: string) {
      return this.format.replace(/A/g, value);
    }
  }

  class Child extends Base {
    transform(value: string) {
      return "-" + this.format.replace(/A/g, value);
    }
  }

  let baseEntity = Base.create({ format: "AAAAA" });
  let childEntity = Child.create();

  equal(baseEntity.transform("1"), "11111");
  equal(childEntity.transform("1"), "-111");
});

test("should create new entity based on existent", () => {
  let entity = Entity.create({ someBool: false });
  let updated = entity.copy({ someNum: 14 });

  deepEqual(plain(entity), {
    someString: "default string",
    someNum: 0.134,
    someBool: false,
    someNullable: null,
  });

  deepEqual(plain(updated), {
    someString: "default string",
    someNum: 14,
    someBool: false,
    someNullable: null,
  });
});

test("should compare custom values for two entities of the same type", () => {
  let entityA = Entity.create({ someBool: false, someNullable: null });
  let equalE = Entity.create({ someBool: false, someNum: 0.134 });
  let unequal = Entity.create({ someBool: false, someNullable: undefined });
  let entityB = Entity.create({ someNullable: "1" });
  let entityC = Entity.create({ someNullable: null });
  let extended = entityB.copy({ someBool: true });
  let updated = entityA.copy({ someNum: 14 });

  equal(entityA.equals(updated), false);
  equal(entityA.equals(equalE), true);
  equal(unequal.equals(equalE), false);
  equal(entityB.equals(extended), true);
  equal(entityB.equals(entityA), false);
  equal(entityB.equals(entityC), false);
});

class Embedded extends Data {
  name: string = "name";
  age: number = 1;
  entity: Entity | null = Entity.create();
  date: Date = new Date();
  obj: Object | null = { foo: "bar" };
}

test("should be serializable with embedded dataclass", () => {
  let dummyDate = new Date("1996-12-17T03:24:00");
  let embedded = Embedded.create({ date: dummyDate });
  let raw = {
    name: "name",
    age: 1,
    entity: {
      someString: "default string",
      someNum: 0.134,
      someBool: true,
      someNullable: null,
    },
    date: dummyDate.toISOString(),
    obj: {
      foo: "bar",
    },
  };
  equal(JSON.stringify(embedded), JSON.stringify(raw));
});

test("should compare dataclass with nested value objects", () => {
  let embeddedA = Embedded.create({
    date: new Date("1996-12-17T03:24:00"),
    entity: Entity.create({ someBool: false }),
    obj: null,
  });
  let embeddedB = Embedded.create({
    date: new Date("1996-12-17T03:24:00"),
    entity: Entity.create({ someBool: false }),
    obj: null,
  });
  let embeddedC = Embedded.create({
    date: new Date("1996-12-17T03:24:00"),
    entity: Entity.create({ someBool: true }),
  });
  let embeddedD = Embedded.create({
    date: new Date("2001-12-17T03:24:00"),
    entity: Entity.create({ someBool: true }),
  });
  let embeddedE = Embedded.create({
    date: new Date("2001-12-17T03:24:00"),
    entity: null,
  });
  equal(embeddedA.equals(embeddedB), true);
  equal(embeddedB.equals(embeddedC), false);
  equal(embeddedC.equals(embeddedD), false);
  equal(embeddedD.equals(embeddedE), false);
});

test("should satisfy symmetry law", () => {
  let a = Entity.create({ someString: "1" });
  let b = Entity.create({ someString: "1" });
  let c = Entity.create({ someString: "2" });

  equal(a.equals(b), true);
  equal(b.equals(a), true);
  equal(a.equals(c), false);
  equal(c.equals(a), false);
});

test("should satisfy transitivity law", () => {
  let a = Entity.create({ someString: "hello" });
  let b = Entity.create({ someString: "hello" });
  let c = Entity.create({ someString: "hello" });

  equal(a.equals(b), true);
  equal(b.equals(c), true);
  equal(a.equals(c), true);
});

test("should support iterables", () => {
  let entity = Entity.create({ someBool: false });

  deepEqual(Object.entries(entity), [
    ["someString", "default string"],
    ["someNum", 0.134],
    ["someBool", false],
    ["someNullable", null],
  ]);

  deepEqual(Object.keys(entity), ["someString", "someNum", "someBool", "someNullable"]);

  deepEqual(Object.values(entity), ["default string", 0.134, false, null]);
});

test("should not allow assignment", () => {
  let entity = Entity.create({ someBool: false });

  equal(Object.isFrozen(entity), true);

  throws(() => {
    entity.someBool = true;
  }, /Cannot assign/);

  throws(() => {
    // @ts-ignore intentional addition of inexistent property to assert runtime error
    entity.somethingElse = null;
  }, /Cannot add property/);
});

test("should prohibit new properties", () => {
  let entity = Entity.create({ someBool: false });

  equal(Object.isSealed(entity), true);

  throws(() => {
    // @ts-ignore intentional addition of inexistent property to assert runtime error
    Entity.create({ thisShouldNotBeHere: 1 });
  }, /object is not extensible/);

  throws(() => {
    // @ts-ignore intentional addition of inexistent property to assert runtime error
    Entity.create().copy({ thisShouldNotBeHere: 1 });
  }, /object is not extensible/);
});

test("should support predefined getters", () => {
  let entity = Entity.create({ someString: "abcde" });

  equal(entity.exclamation, "abcde!");
});

test("should disallow use of constructor", () => {
  throws(() => {
    new Entity();
  }, /Use Entity.create/);
});

test("should allow dynamic defaults per instance", () => {
  class Ent extends Data {
    id: string = Math.random().toString(16).slice(2, 8);
  }
  let a1 = Ent.create();
  let a2 = a1.copy();
  let b = Ent.create();
  equal(a1.equals(a2), true);
  equal(b.equals(a1), false);
  equal(b.equals(a2), false);
});


================================================
FILE: integration/integration.js
================================================
import { test } from "node:test";
import { deepEqual, equal, throws } from "node:assert/strict";

import { Data } from "dataclass";

class Entity extends Data {
  someString = "default string";
  someNum = 0.134;
  someBool = true;
  someNullable = null;

  get exclamation() {
    return this.someString + "!";
  }
}

test("should create an entity with default values", () => {
  let entity = Entity.create();

  matches(entity, {
    someString: "default string",
    someNum: 0.134,
    someBool: true,
    someNullable: null,
  });
});

test("should override defaults with custom values", () => {
  let entity = Entity.create({ someNullable: "1", someString: "hello" });

  matches(entity, {
    someString: "hello",
    someNum: 0.134,
    someBool: true,
    someNullable: "1",
  });
});

test("should satisfy composition law", () => {
  let entity = Entity.create();
  let left = entity.copy({ someNum: 13, someBool: false });
  let right = entity.copy({ someNum: 13 }).copy({ someBool: false });

  deepEqual(left, right);
  equal(left.equals(right), true);
});

test("should support subclassing", () => {
  class SubEntity extends Entity {
    someNewThing = "default";
  }

  let entityA = SubEntity.create();
  let entityB = SubEntity.create({ someString: "test", someNewThing: "blah" });

  matches(entityA, {
    someString: "default string",
    someNum: 0.134,
    someBool: true,
    someNullable: null,
    someNewThing: "default",
  });

  matches(entityB, {
    someString: "test",
    someNum: 0.134,
    someBool: true,
    someNullable: null,
    someNewThing: "blah",
  });
});

test("should support polymorphism", () => {
  class Base extends Data {
    format = "AAA";

    transform(value) {
      return this.format.replace(/A/g, value);
    }
  }

  class Child extends Base {
    transform(value) {
      return "-" + this.format.replace(/A/g, value);
    }
  }

  let baseEntity = Base.create({ format: "AAAAA" });
  let childEntity = Child.create();

  equal(baseEntity.transform("1"), "11111");
  equal(childEntity.transform("1"), "-111");
});

test("should create new entity based on existent", () => {
  let entity = Entity.create({ someBool: false });
  let updated = entity.copy({ someNum: 14 });

  matches(entity, {
    someString: "default string",
    someNum: 0.134,
    someBool: false,
    someNullable: null,
  });

  matches(updated, {
    someString: "default string",
    someNum: 14,
    someBool: false,
    someNullable: null,
  });
});

test("should compare custom values for two entities of the same type", () => {
  let entityA = Entity.create({ someBool: false, someNullable: null });
  let equalE = Entity.create({ someBool: false, someNum: 0.134 });
  let unequal = Entity.create({ someBool: false, someNullable: undefined });
  let entityB = Entity.create({ someNullable: "1" });
  let entityC = Entity.create({ someNullable: null });
  let extended = entityB.copy({ someBool: true });
  let updated = entityA.copy({ someNum: 14 });

  equal(entityA.equals(updated), false);
  equal(entityA.equals(equalE), true);
  equal(unequal.equals(equalE), false);
  equal(entityB.equals(extended), true);
  equal(entityB.equals(entityA), false);
  equal(entityB.equals(entityC), false);
});

class Embedded extends Data {
  name = "name";
  age = 1;
  entity = Entity.create();
  date = new Date();
  obj = { foo: "bar" };
}

test("should be serializable with embedded dataclass", () => {
  let dummyDate = new Date("1996-12-17T03:24:00");
  let embedded = Embedded.create({ date: dummyDate });
  let raw = {
    name: "name",
    age: 1,
    entity: {
      someString: "default string",
      someNum: 0.134,
      someBool: true,
      someNullable: null,
    },
    date: dummyDate.toISOString(),
    obj: {
      foo: "bar",
    },
  };
  equal(JSON.stringify(embedded), JSON.stringify(raw));
});

test("should compare dataclass with nested value objects", () => {
  let embeddedA = Embedded.create({
    date: new Date("1996-12-17T03:24:00"),
    entity: Entity.create({ someBool: false }),
    obj: null,
  });
  let embeddedB = Embedded.create({
    date: new Date("1996-12-17T03:24:00"),
    entity: Entity.create({ someBool: false }),
    obj: null,
  });
  let embeddedC = Embedded.create({
    date: new Date("1996-12-17T03:24:00"),
    entity: Entity.create({ someBool: true }),
  });
  let embeddedD = Embedded.create({
    date: new Date("2001-12-17T03:24:00"),
    entity: Entity.create({ someBool: true }),
  });
  let embeddedE = Embedded.create({
    date: new Date("2001-12-17T03:24:00"),
    entity: null,
  });
  equal(embeddedA.equals(embeddedB), true);
  equal(embeddedB.equals(embeddedC), false);
  equal(embeddedC.equals(embeddedD), false);
  equal(embeddedD.equals(embeddedE), false);
});

test("should satisfy symmetry law", () => {
  let a = Entity.create({ someString: "1" });
  let b = Entity.create({ someString: "1" });
  let c = Entity.create({ someString: "2" });

  equal(a.equals(b), true);
  equal(b.equals(a), true);
  equal(a.equals(c), false);
  equal(c.equals(a), false);
});

test("should satisfy transitivity law", () => {
  let a = Entity.create({ someString: "hello" });
  let b = Entity.create({ someString: "hello" });
  let c = Entity.create({ someString: "hello" });

  equal(a.equals(b), true);
  equal(b.equals(c), true);
  equal(a.equals(c), true);
});

test("should support iterables", () => {
  let entity = Entity.create({ someBool: false });

  deepEqual(Object.entries(entity), [
    ["someString", "default string"],
    ["someNum", 0.134],
    ["someBool", false],
    ["someNullable", null],
  ]);

  deepEqual(Object.keys(entity), ["someString", "someNum", "someBool", "someNullable"]);

  deepEqual(Object.values(entity), ["default string", 0.134, false, null]);
});

test("should not allow assignment", () => {
  let entity = Entity.create({ someBool: false });

  equal(Object.isFrozen(entity), true);

  throws(() => {
    entity.someBool = true;
  }, /Cannot assign/);

  throws(() => {
    // @ts-ignore intentional addition of inexistent property to assert runtime error
    entity.somethingElse = null;
  }, /Cannot add property/);
});

test("should prohibit new properties", () => {
  let entity = Entity.create({ someBool: false });

  equal(Object.isSealed(entity), true);

  throws(() => {
    // @ts-ignore intentional addition of inexistent property to assert runtime error
    Entity.create({ thisShouldNotBeHere: 1 });
  }, /object is not extensible/);

  throws(() => {
    // @ts-ignore intentional addition of inexistent property to assert runtime error
    Entity.create().copy({ thisShouldNotBeHere: 1 });
  }, /object is not extensible/);
});

test("should support predefined getters", () => {
  let entity = Entity.create({ someString: "abcde" });

  equal(entity.exclamation, "abcde!");
});

test("should disallow use of constructor", () => {
  throws(() => {
    new Entity();
  }, /Use Entity.create/);
});

test("should allow dynamic defaults per instance", () => {
  class Ent extends Data {
    id = Math.random().toString(16).slice(2, 8);
  }
  let a1 = Ent.create();
  let a2 = a1.copy();
  let b = Ent.create();
  equal(a1.equals(a2), true);
  equal(b.equals(a1), false);
  equal(b.equals(a2), false);
});

function matches(entity, object, message) {
  deepEqual(plain(entity), object, message);
}

function plain(target) {
  return Object.fromEntries(Object.entries(target));
}


================================================
FILE: integration/legacy.json
================================================
{
  "compilerOptions": {
    "target": "es2017",
    "module": "commonjs",
    "useDefineForClassFields": false,
    "esModuleInterop": true,
    "strict": true,
    "noImplicitAny": true,
    "skipLibCheck": true,
    "allowJs": true
  }
}


================================================
FILE: integration/modern.json
================================================
{
  "compilerOptions": {
    "target": "es2022",
    "module": "commonjs",
    "useDefineForClassFields": true,
    "esModuleInterop": true,
    "strict": true,
    "noImplicitAny": true,
    "skipLibCheck": true,
    "allowJs": true
  }
}


================================================
FILE: integration/runtime.test.ts
================================================
import { test, expectTypeOf } from "vitest";
import { deepEqual, throws } from "node:assert/strict";

import { Data } from "dataclass";
import { runtime, data } from "dataclass/runtime";

test("throws", () => {
	@runtime
	class Entity extends Data {
		name = data.string("", data.required);
		things = data.union([data.string(), data.number()], 1);
	}

	// @ts-expect-error
	throws(() => Entity.create(), /is required but value was not provided/);
	// @ts-expect-error
	throws(() => Entity.create({ name: 123 }), /expected to be of type/);
	matches(Entity.create({ name: "Liza" }), { name: "Liza", things: 1 });
});

test("defaults", () => {
	@runtime
	class Entity extends Data {
		prop = data.number(13);
	}

	// @ts-expect-error
	throws(() => Entity.create({ prop: "boo" }), /expected to be of type/);
	matches(Entity.create(), { prop: 13 });

	let entity = Entity.create();
	expectTypeOf(entity.prop).toBeNumber();
});

test("inherited", () => {
	@runtime
	class Base extends Data {
		name = data.string();
		// blab = data.instance<Promise<number>>(Promise);
	}

	class Entity extends Base {
		age: number | null = null;
	}

	// @ts-expect-error
	throws(() => Entity.create({}), /is required but value was not provided/);
});

function matches(entity: Data, object: object, message?: string) {
	deepEqual(plain(entity), object, message);
}

function plain(target: object) {
	return Object.fromEntries(Object.entries(target));
}


================================================
FILE: integration/spec.json
================================================
{
  "compilerOptions": {
    "target": "es2020",
    "module": "commonjs",
    "useDefineForClassFields": true,
    "esModuleInterop": true,
    "strict": true,
    "noImplicitAny": true,
    "skipLibCheck": true,
    "allowJs": true
  }
}


================================================
FILE: modules/Data.js
================================================
let O = Object;

let produce = (proto, base, values) =>
  O.freeze(O.assign(O.seal(O.assign(O.create(proto), base)), values));

export class Data {
  static create(values) {
    return produce(this.prototype, new this(Data), values);
  }

  constructor(values) {
    if (values !== Data) {
      throw new Error(`Use ${this.constructor.name}.create(...) instead of new operator`);
    }
  }

  copy(values) {
    return produce(O.getPrototypeOf(this), this, values);
  }

  equals(other) {
    for (let key in this) {
      let a = this[key];
      let b = other[key];
      if (
        !Object.is(a, b) &&
        (a == null ||
          b == null ||
          (a instanceof Data && b instanceof Data
            ? !a.equals(b)
            : !Object.is(a.valueOf(), b.valueOf())))
      )
        return false;
    }

    return true;
  }
}


================================================
FILE: modules/runtime.js
================================================
import { Data } from "./Data.js";

export function runtime(Class) {
  if (!Data.prototype.isPrototypeOf(Class.prototype)) {
    throw new Error("Provided class is not a subclass of Data");
  }

  let self = new Class(Data);

  Object.defineProperty(Class, "create", {
    value(values = {}) {
      // this will not let me check for Fields in inherited properties
      // is it though? those are still own properties — needs a test
      for (let key in self) {
        if (self[key] instanceof Field) {
          let field = self[key];
          if (field.required && !(key in values)) {
            throw new Error(`${key} is required but value was not provided.`);
          }

          if (key in values && !field.validate(values[key])) {
            let value = JSON.stringify(values[key]);
            throw new Error(
              `${key} expected to be of type ${field.label}, but received value ${value}.`,
            );
          }

          if (!(key in values)) {
            values[key] = field.defaults;
          }
        }
      }
      return Object.getPrototypeOf(this).create.call(Class, values);
    },
  });

  Object.defineProperty(Class.prototype, "copy", {
    value(values) {
      if (values != null) {
        for (let key in self) {
          if (self[key] instanceof Field) {
            let field = self[key];
            if (key in values && !field.validate(values[key])) {
              let value = JSON.stringify(values[key]);
              throw new Error(
                `${key} expected to be of type ${field.label}, but received value ${value}.`,
              );
            }
          }
        }
      }
      return Object.getPrototypeOf(this).copy.call(this, values);
    },
  });

  return Class;
}

class Field {
  constructor(label, defaults, options) {
    this.label = label;
    this.defaults = defaults;
    this.required =
      options != null && "required" in options ? options.required : typeof defaults === "undefined";
    this.nullable = false;
  }

  validate(value) {
    return this.nullable && value === null;
  }
}

class PrimitiveField extends Field {
  validate(value) {
    return super.validate(value) || typeof value === this.label;
  }
}

class InstanceField extends Field {
  constructor(Class, defaults) {
    super("instance", defaults);
    this.Class = Class;
  }

  validate(value) {
    return super.validate(value) || value instanceof Class;
  }
}

class LiteralField extends Field {
  validate(value) {
    return super.validate(value) || Object.is(value, this.defaults);
  }
}

class UnionField extends Field {
  constructor(fields, defaults) {
    super(`union(${fields.map((field) => field.label).join(", ")})`, defaults);
    this.fields = fields;
  }

  validate(value) {
    return super.validate(value) || this.fields.some((field) => field.validate(value));
  }
}

class UnknownField extends Field {
  validate() {
    return true;
  }
}

class ObjectField extends Field {
  constructor(schema, defaults, options) {
    super("object", defaults, options);
    this.schema = schema;
  }

  validate(value) {
    if (super.validate(value)) return true;
    if (value == null || typeof value != "object") return false;
    for (let key in this.schema) {
      if (!(key in value) && !this.schema[key].validate(value[key])) return false;
    }
    for (let key in value) {
      if (!this.schema[key].validate(value[key])) return false;
    }
  }
}

class ArrayField extends Field {
  constructor(schema, defaults, options) {
    super("array", defaults, options);
    this.schema = schema;
  }

  validate(value) {
    if (super.validate(value)) return true;
    if (value == null || !Array.isArray(value)) return false;
    for (let item of value) {
      if (!this.schema.some((field) => field.validate(item))) {
        return false;
      }
    }
  }
}

export let data = {
  string: (defaults, options) => new PrimitiveField("string", defaults, options),
  number: (defaults, options) => new PrimitiveField("number", defaults, options),
  boolean: (defaults, options) => new PrimitiveField("boolean", defaults, options),
  bigint: (defaults, options) => new PrimitiveField("bigint", defaults, options),
  symbol: (defaults, options) => new PrimitiveField("symbol", defaults, options),

  literal: (defaults) => new LiteralField(typeof defaults, defaults),
  undefined: () => data.literal(void 0),
  null: () => data.literal(null),

  instance: (Class, defaults) => new InstanceField(Class, defaults),
  regexp: (defaults) => data.instance(RegExp, defaults),
  date: (defaults) => data.instance(Date, defaults),

  union: (fields, defaults) => new UnionField(fields, defaults),
  unknown: (defaults) => new UnknownField("unknown", defaults),

  required: { required: true },
  optional: { optional: true },
  nullable: { nullable: true },

  object: (schema, defaults, options) => new ObjectField(schema, defaults, options),
  array: (schema, defaults, options) => new ArrayField(schema, defaults, options),

  // map, set
  // record, tuple?
};


================================================
FILE: package.json
================================================
{
  "name": "dataclass",
  "version": "3.0.0-beta.1",
  "description": "Data classes for TypeScript & JavaScript",
  "author": "Oleksii Raspopov",
  "license": "ISC",
  "homepage": "https://dataclass.js.org",
  "repository": "alexeyraspopov/dataclass",
  "main": "./dataclass.js",
  "module": "./dataclass.module.js",
  "types": "./dataclass.d.ts",
  "sideEffects": false,
  "files": [
    "dataclass.*"
  ],
  "keywords": [
    "dataclass",
    "immutable",
    "value-objects",
    "data-structures",
    "typings"
  ],
  "scripts": {
    "test": "vitest",
    "integration": "tsx --tsconfig integration/$TARGET.json --experimental-test-coverage integration/integration.js",
    "build": "rollup --config rollup.config.mjs"
  },
  "devDependencies": {
    "@vitest/coverage-istanbul": "^1.5.0",
    "flow-bin": "^0.234.0",
    "prettier": "^2.4.1",
    "rollup": "^4.15.0",
    "rollup-plugin-copy": "^3.4.0",
    "tsx": "^4.7.2",
    "typescript": "^5.4.5",
    "vitest": "^1.5.0"
  },
  "prettier": {
    "printWidth": 100,
    "trailingComma": "all",
    "proseWrap": "always"
  }
}


================================================
FILE: rollup.config.mjs
================================================
import { defineConfig } from "rollup";
import copy from "rollup-plugin-copy";
import { extname } from "node:path";

let destination = "node_modules/dataclass";

export default defineConfig({
  input: ["modules/Data.js", "modules/runtime.js"],
  output: [
    {
      dir: destination,
      format: "cjs",
      entryFileNames: ({ name }) => (name === "Data" ? "dataclass.js" : "[name].js"),
    },
    {
      dir: destination,
      format: "esm",
      entryFileNames: ({ name }) => (name === "Data" ? "dataclass.module.js" : "[name].module.js"),
    },
  ],
  plugins: [
    copy({
      targets: [
        { src: ["typings/*", "LICENSE"], dest: destination },
        {
          src: "typings/*",
          dest: destination,
          rename: (name, extension) =>
            name.endsWith(".d") || name.endsWith(".js")
              ? `${name.slice(0, name.lastIndexOf("."))}.module${extname(name)}.${extension}`
              : `${name}.module.${extension}`,
        },
        { src: "README.md", dest: destination, transform: generateReadme },
        { src: "package.json", dest: destination, transform: generatePkg },
      ],
    }),
  ],
});

function generatePkg(contents) {
  let pkg = JSON.parse(contents.toString());
  return JSON.stringify(
    {
      name: pkg.name,
      version: pkg.version,
      description: pkg.description,
      author: pkg.author,
      license: pkg.license,
      homepage: pkg.homepage,
      repository: pkg.repository,
      main: pkg.main,
      module: pkg.module,
      exports: {
        ".": "./dataclass.module.js",
        "./runtime": "./runtime.module.js",
      },
      types: pkg.types,
      sideEffects: pkg.sideEffects,
      files: pkg.files,
      keywords: pkg.keywords,
    },
    null,
    2,
  );
}

function generateReadme() {
  return `
The library brings flexibility and usefulness of data classes from Kotlin, Scala, or Python to TypeScript and JavaScript.

Read full docs [on the homepage](https://dataclass.js.org).

\`\`\`javascript
import { Data } from "dataclass";

// 1. easily describe your data classes using just language features
class User extends Data {
  name: string = "Anon";
  age: number = 0;
}

// 2. instantiate classes while type systems ensure correctness
let user = User.create({ name: "Liza", age: 23 });
// > User { name: "Liza", age: 23 }

// 3. make changes while benefiting from immutable values
let updated = user.copy({ name: "Ann" });
// > User { name: "Ann", age: 23 }
updated = updated.copy({ name: "Liza" });
// > User { name: "Liza", age: 23 }

// 4. compare objects by their value, not reference
console.log(user === updated, user.equals(updated));
// > false, true
\`\`\`
`.trimStart();
}


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "target": "es2022",
    "module": "commonjs",
    "moduleResolution": "bundler",
    "useDefineForClassFields": true,
    "esModuleInterop": true,
    "strict": true,
    "noImplicitAny": true,
    "skipLibCheck": true,
    "allowJs": true,
    "experimentalDecorators": true
  }
}


================================================
FILE: typings/dataclass.d.ts
================================================
interface S {
  /** If you see this, the property is coming from a dataclass instance and was required */
  valueOf(): 2147483647;
}

/**
 * Special generic type to mark data class field as required to be initialized
 * at creation time. Can be used along with ! (exclamation mark operator) to
 * define a field without a default value, but explicit type and make `create()`
 * method require this field to be provided.
 */
export type Enforced<T> = T & S;

type Unwrap<T> = T extends Enforced<infer V> ? V : T;
type OwnKey<K> = K extends keyof Data ? never : K;

type ANY<T> = never;

type EnforcedFields<T> = Required<{
  [P in keyof T as T[P] extends Enforced<T[P]>
    ? T[P] extends ANY<infer V>
      ? unknown
      : OwnKey<P>
    : never]: Unwrap<T[P]>;
}>;

type ExplicitFields<T> = Partial<{
  [P in keyof T as T[P] extends Enforced<T[P]>
    ? T[P] extends ANY<infer V>
      ? OwnKey<P>
      : never
    : OwnKey<P>]: T[P];
}>;
type Init<T> = EnforcedFields<T> & ExplicitFields<T>;

/**
 * Abstract class that allows defining custom data classes. Should be extended
 * with a set of class fields that define the shape of desired model.
 *
 * ```ts
 * import { Data, type Enforced } from "dataclass";
 *
 * class Project extends Data {
 *   // this property is required when creating an instance
 *   id: Enforced<string>;
 *   // these properties have defaults but can be overwritten
 *   name: string = "Untitled";
 *   createdBy: string = "Anon";
 *   // this property may contain null and won't be required
 *   createdAt: Date | null = null;
 * }
 *
 * let project = Project.create({
 *   id: 'abc123',
 *   createdBy: 'Oleksii',
 * });
 * // > Project { id: 'abc123', name: 'Untitled', createdBy: 'Oleksii', createdAt: null }
 * ```
 *
 * @link https://dataclass.js.org
 */
export class Data {
  /**
   * Instantiate the data class. Provide custom values that should override
   * defaults. If the class has optional properties, create() method will
   * require to explicitly define them.
   *
   * ```ts
   * class User extends Data {
   *   name: string = "Anon";
   *   age: number | null = null;
   * }
   * ```
   *
   * @link https://dataclass.js.org/guide/getting-started.html
   */
  static create<Type extends Data>(
    this: { new (): Type },
    ...values: EnforcedFields<Type> extends Record<any, never> ? [Init<Type>?] : [Init<Type>]
  ): Type;
  /**
   * Create new immutable instance based on existing one, with some properties changed.
   *
   * ```ts
   * class User extends Data {
   *   name: string = "Anon";
   *   age: number | null = null;
   * }
   *
   * let initial = User.create({ name: "Liza" });
   *
   * // creates an immutable copy with previously defined
   * // `name: "Liza"` and additionaly defined `age: 28`
   * let updated = initial.copy({ age: 28 });
   * ```
   */
  copy(values?: Partial<Omit<this, keyof Data>>): this;
  /**
   * Compare the instance to another instance of the same data class.
   *
   * @link https://dataclass.js.org/guide/objects-equality.html
   */
  equals(other: this): boolean;
}


================================================
FILE: typings/dataclass.js.flow
================================================
/* @flow */

/**
 * Special generic type to mark data class field as required to be initialized
 * at creation time. Can be used along with ! (exclamation mark operator) to
 * define a field without a default value, but explicit type and make `create()`
 * method require this field to be provided.
 */
declare export opaque type Enforced<T> : T;

declare type OptKeys<T> = $Values<{ [P in $Keys<T>]: T[P] extends Enforced<any> ? P : empty}>;
declare type EnforcedFields<T> = Pick<{ [P in $Keys<T>]: T[P] extends Enforced<infer V> ? V : T[P] }, OptKeys<T>>;
declare type ExplicitFields<T> = Omit<{ [P in $Keys<T>]: T[P] extends Enforced<infer V> ? V : T[P] }, OptKeys<T>>;
// do I still need Required for Optional part?
declare type Values<T> = { ...EnforcedFields<T>, ...Partial<ExplicitFields<T>> };
declare type Init<T> = EnforcedFields<T> extends Record<any, empty> ? Values<T> | void : Values<T>;
declare type Shape<T> = Partial<Values<T>>;

/**
 * Abstract class that allows defining custom data classes. Should be extended
 * with a set of class fields that define the shape of desired model.
 *
 * ```js
 * // @flow
 * import { Data, type Enforced } from "dataclass";
 *
 * class Project extends Data {
 *   // this property is required when creating an instance
 *   id: Enforced<string>;
 *   // these properties have defaults but can be overwritten
 *   name: string = "Untitled";
 *   createdBy: string = "Anon";
 *   // this property may contain null and won't be required
 *   createdAt: Date | null = null;
 * }
 *
 * let project = Project.create({
 *   id: 'abc123',
 *   createdBy: 'Oleksii',
 * });
 * // > Project { id: 'abc123', name: 'Untitled', createdBy: 'Oleksii', createdAt: null }
 * ```
 *
 * @link https://dataclass.js.org
 */
declare export class Data {
  /**
   * Instantiate the data class. Provide custom values that should override
   * defaults. If the class has optional properties, create() method will
   * require to explicitly define them.
   *
   * ```js
   * // @flow
   * 
   * class User extends Data {
   *   name: string = "Anon";
   *   age: number | null = null;
   * }
   * ```
   *
   * @link https://dataclass.js.org/guide/getting-started.html
   */
  static create(values: Init<this>): this;
  /**
   * Create new immutable instance based on existing one, with some properties changed.
   *
   * ```js
   * // @flow
   * 
   * class User extends Data {
   *   name: string = "Anon";
   *   age: number | null = null;
   * }
   *
   * let initial = User.create({ name: "Liza" });
   *
   * // creates an immutable copy with previously defined
   * // `name: "Liza"` and additionaly defined `age: 28`
   * let updated = initial.copy({ age: 28 });
   * ```
   */
  copy(values?: Shape<this>): this;
  /**
   * Compare the instance to another instance of the same data class.
   *
   * @link https://dataclass.js.org/guide/objects-equality.html
   */
  equals(other: this): boolean;
}


================================================
FILE: typings/runtime.d.ts
================================================
import { Enforced } from "./dataclass";

export function runtime<T>(C: T, ...args: any): T;

interface TypedField<Type> {
  (): Enforced<Type>;
  (defaults: Type): Type;
  (defaults?: Type, options: { required: true }): Enforced<Type>;
  (defaults?: Type, options: { optional: true }): Type | undefined;
  (defaults?: Type, options: { nullable: true }): Type | null;
}

interface LiteralField {
  <Type>(defaults: Type): Type;
}

interface ConstField<Value> {
  (): Value;
  (options: { required: true }): Enforced<Value>;
}

interface InstanceField {
  <Class>(Ctor: { new (...args: any): Class }, defaults?: Class): Class;
}

// how do I make it possible to use null|undefined
interface UnionField {
  <T extends any[]>(fields: T): Enforced<
    T extends Array<infer V> ? (V extends Enforced<infer VV> ? VV : V) : T
  >;

  <T extends any[]>(
    fields: T,
    defaults: T extends Array<infer V> ? (V extends Enforced<infer VV> ? VV : V) : T,
  ): T extends Array<infer V> ? (V extends Enforced<infer VV> ? VV : V) : T;

  <T extends any[]>(
    fields: T,
    defaults?: T extends Array<infer V> ? (V extends Enforced<infer VV> ? VV : V) : T,
    options: { required: true },
  ): Enforced<T extends Array<infer V> ? (V extends Enforced<infer VV> ? VV : V) : T>;

  <T extends any[]>(
    fields: T,
    defaults?: T extends Array<infer V> ? (V extends Enforced<infer VV> ? VV : V) : T,
    options: { optional: true },
  ): (T extends Array<infer V> ? (V extends Enforced<infer VV> ? VV : V) : T) | undefined;

  <T extends any[]>(
    fields: T,
    defaults?: T extends Array<infer V> ? (V extends Enforced<infer VV> ? VV : V) : T,
    options: { nullable: true },
  ): (T extends Array<infer V> ? (V extends Enforced<infer VV> ? VV : V) : T) | null;
}

interface ObjectField {
  <Type extends Record<string, any>>(schema: Type): Enforced<{
    [P in keyof Type]: Type[P] extends Enforced<infer V> ? V : Type[P];
  }>;

  <Type extends Record<string, any>>(
    schema: Type,
    defaults: NoInfer<{ [P in keyof Type]: Type[P] extends Enforced<infer V> ? V : Type[P] }>,
  ): { [P in keyof Type]: Type[P] extends Enforced<infer V> ? V : Type[P] };

  <Type extends Record<string, any>>(
    schema: Type,
    defaults?: NoInfer<{ [P in keyof Type]: Type[P] extends Enforced<infer V> ? V : Type[P] }>,
    options: { required: true },
  ): Enforced<{ [P in keyof Type]: Type[P] extends Enforced<infer V> ? V : Type[P] }>;

  <Type extends Record<string, any>>(
    schema: Type,
    defaults?: NoInfer<{ [P in keyof Type]: Type[P] extends Enforced<infer V> ? V : Type[P] }>,
    options: { optional: true },
  ): { [P in keyof Type]: Type[P] extends Enforced<infer V> ? V : Type[P] } | undefined;

  <Type extends Record<string, any>>(
    schema: Type,
    defaults?: NoInfer<{ [P in keyof Type]: Type[P] extends Enforced<infer V> ? V : Type[P] }>,
    options: { nullable: true },
  ): { [P in keyof Type]: Type[P] extends Enforced<infer V> ? V : Type[P] } | null;
}

interface ArrayField {
  <Type extends any[]>(schema: Type): Enforced<
    (Type extends Array<infer V> ? (V extends Enforced<infer VV> ? VV : V) : Type)[]
  >;

  <Type extends any[]>(
    schema: Type,
    defaults: NoInfer<
      (Type extends Array<infer V> ? (V extends Enforced<infer VV> ? VV : V) : Type)[]
    >,
  ): (Type extends Array<infer V> ? (V extends Enforced<infer VV> ? VV : V) : Type)[];

  <Type extends any[]>(
    schema: Type,
    defaults?: NoInfer<
      (Type extends Array<infer V> ? (V extends Enforced<infer VV> ? VV : V) : Type)[]
    >,
    options: { required: true },
  ): Enforced<(Type extends Array<infer V> ? (V extends Enforced<infer VV> ? VV : V) : Type)[]>;

  <Type extends any[]>(
    schema: Type,
    defaults?: NoInfer<
      (Type extends Array<infer V> ? (V extends Enforced<infer VV> ? VV : V) : Type)[]
    >,
    options: { optional: true },
  ): (Type extends Array<infer V> ? (V extends Enforced<infer VV> ? VV : V) : Type)[] | undefined;

  <Type extends any[]>(
    schema: Type,
    defaults?: NoInfer<
      (Type extends Array<infer V> ? (V extends Enforced<infer VV> ? VV : V) : Type)[]
    >,
    options: { nullable: true },
  ): (Type extends Array<infer V> ? (V extends Enforced<infer VV> ? VV : V) : Type)[] | null;
}

interface DataFields {
  string: TypedField<string>;
  number: TypedField<number>;
  boolean: TypedField<boolean>;
  bigint: TypedField<bigint>;
  symbol: TypedField<symbol>;

  literal: LiteralField;
  null: ConstField<null>;
  undefined: ConstField<undefined>;

  // can i use return type to get result of applying Class to TypedField?
  instance: InstanceField;
  regexp: TypedField<RegExp>;
  date: TypedField<Date>;

  union: UnionField;
  unknown: ConstField<unknown>; // should have required option

  required: { required: true };
  optional: { optional: true };
  nullable: { nullable: true };

  object: ObjectField;
  array: ArrayField;
}

export let data: DataFields;


================================================
FILE: typings/runtime.js.flow
================================================
/* @flow */

declare export function runtime(options?: {}): <T>(C: T) => T;

declare export let data: Record<string, any> = {};


================================================
FILE: vitest.config.ts
================================================
import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    coverage: {
      enabled: true,
      provider: "istanbul",
    },
  },
});
Download .txt
gitextract_5xq9i1un/

├── .github/
│   └── workflows/
│       ├── docs.yaml
│       └── testing.yaml
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── docs/
│   ├── .vitepress/
│   │   ├── .gitignore
│   │   └── config.js
│   ├── guide/
│   │   ├── caveats.md
│   │   ├── contributing.md
│   │   ├── getting-started.md
│   │   ├── index.md
│   │   ├── installation.md
│   │   ├── migrating.md
│   │   ├── objects-equality.md
│   │   └── serialization-deserialization.md
│   ├── index.md
│   ├── package.json
│   └── reference/
│       └── index.md
├── integration/
│   ├── Data.test.ts
│   ├── integration.js
│   ├── legacy.json
│   ├── modern.json
│   ├── runtime.test.ts
│   └── spec.json
├── modules/
│   ├── Data.js
│   └── runtime.js
├── package.json
├── rollup.config.mjs
├── tsconfig.json
├── typings/
│   ├── dataclass.d.ts
│   ├── dataclass.js.flow
│   ├── runtime.d.ts
│   └── runtime.js.flow
└── vitest.config.ts
Download .txt
SYMBOL INDEX (73 symbols across 8 files)

FILE: integration/Data.test.ts
  class Entity (line 6) | class Entity extends Data {
    method exclamation (line 12) | get exclamation() {
  function plain (line 17) | function plain(target: object) {
  class SubEntity (line 53) | class SubEntity extends Entity {
  class Base (line 78) | class Base extends Data {
    method transform (line 81) | transform(value: string) {
  class Child (line 86) | class Child extends Base {
    method transform (line 87) | transform(value: string) {
  class Embedded (line 135) | class Embedded extends Data {
  class Ent (line 272) | class Ent extends Data {

FILE: integration/integration.js
  class Entity (line 6) | class Entity extends Data {
    method exclamation (line 12) | get exclamation() {
  class SubEntity (line 49) | class SubEntity extends Entity {
  class Base (line 74) | class Base extends Data {
    method transform (line 77) | transform(value) {
  class Child (line 82) | class Child extends Base {
    method transform (line 83) | transform(value) {
  class Embedded (line 131) | class Embedded extends Data {
  class Ent (line 268) | class Ent extends Data {
  function matches (line 279) | function matches(entity, object, message) {
  function plain (line 283) | function plain(target) {

FILE: integration/runtime.test.ts
  class Entity (line 8) | @runtime
  class Entity (line 22) | @runtime
  class Base (line 36) | @runtime
  class Entity (line 42) | class Entity extends Base {
  function matches (line 50) | function matches(entity: Data, object: object, message?: string) {
  function plain (line 54) | function plain(target: object) {

FILE: modules/Data.js
  class Data (line 6) | class Data {
    method create (line 7) | static create(values) {
    method constructor (line 11) | constructor(values) {
    method copy (line 17) | copy(values) {
    method equals (line 21) | equals(other) {

FILE: modules/runtime.js
  function runtime (line 3) | function runtime(Class) {
  class Field (line 59) | class Field {
    method constructor (line 60) | constructor(label, defaults, options) {
    method validate (line 68) | validate(value) {
  class PrimitiveField (line 73) | class PrimitiveField extends Field {
    method validate (line 74) | validate(value) {
  class InstanceField (line 79) | class InstanceField extends Field {
    method constructor (line 80) | constructor(Class, defaults) {
    method validate (line 85) | validate(value) {
  class LiteralField (line 90) | class LiteralField extends Field {
    method validate (line 91) | validate(value) {
  class UnionField (line 96) | class UnionField extends Field {
    method constructor (line 97) | constructor(fields, defaults) {
    method validate (line 102) | validate(value) {
  class UnknownField (line 107) | class UnknownField extends Field {
    method validate (line 108) | validate() {
  class ObjectField (line 113) | class ObjectField extends Field {
    method constructor (line 114) | constructor(schema, defaults, options) {
    method validate (line 119) | validate(value) {
  class ArrayField (line 131) | class ArrayField extends Field {
    method constructor (line 132) | constructor(schema, defaults, options) {
    method validate (line 137) | validate(value) {

FILE: rollup.config.mjs
  function generatePkg (line 40) | function generatePkg(contents) {
  function generateReadme (line 67) | function generateReadme() {

FILE: typings/dataclass.d.ts
  type S (line 1) | interface S {
  type Enforced (line 12) | type Enforced<T> = T & S;
  type Unwrap (line 14) | type Unwrap<T> = T extends Enforced<infer V> ? V : T;
  type OwnKey (line 15) | type OwnKey<K> = K extends keyof Data ? never : K;
  type ANY (line 17) | type ANY<T> = never;
  type EnforcedFields (line 19) | type EnforcedFields<T> = Required<{
  type ExplicitFields (line 27) | type ExplicitFields<T> = Partial<{
  type Init (line 34) | type Init<T> = EnforcedFields<T> & ExplicitFields<T>;
  class Data (line 62) | class Data {

FILE: typings/runtime.d.ts
  type TypedField (line 5) | interface TypedField<Type> {
  type LiteralField (line 13) | interface LiteralField {
  type ConstField (line 17) | interface ConstField<Value> {
  type InstanceField (line 22) | interface InstanceField {
  type UnionField (line 27) | interface UnionField {
  type ObjectField (line 56) | interface ObjectField {
  type ArrayField (line 85) | interface ArrayField {
  type DataFields (line 122) | interface DataFields {
Condensed preview — 37 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (82K chars).
[
  {
    "path": ".github/workflows/docs.yaml",
    "chars": 669,
    "preview": "name: Docs\n\non:\n  push:\n    branches: [master]\n  workflow_dispatch:\n\njobs:\n  deploy:\n    name: Deploy Docs\n    runs-on: "
  },
  {
    "path": ".github/workflows/testing.yaml",
    "chars": 477,
    "preview": "name: Testing\n\non:\n  push:\n    branches: [master]\n  pull_request:\n    branches: [master]\n\njobs:\n  integration:\n    runs-"
  },
  {
    "path": ".gitignore",
    "chars": 28,
    "preview": "node_modules\ncoverage\nbuild\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 3561,
    "preview": "# Changelog\n\n## [`v2.1.1`](https://github.com/alexeyraspopov/dataclass/releases/tag/v2.1.1)\n\n- TypeScript typings fix: o"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 3225,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1010,
    "preview": "# Contributing Guide\n\nGreetings! I'm glad that you are interested in contributing to this project.\n\nBefore submitting yo"
  },
  {
    "path": "LICENSE",
    "chars": 752,
    "preview": "ISC License\n\nCopyright (c) 2017-2024 Oleksii Raspopov\n\nPermission to use, copy, modify, and/or distribute this software "
  },
  {
    "path": "README.md",
    "chars": 1798,
    "preview": "# dataclass\n\n    npm install dataclass\n\nSyntax sugar that leverages the power of available type systems in TypeScript an"
  },
  {
    "path": "docs/.vitepress/.gitignore",
    "chars": 11,
    "preview": "cache\ndist\n"
  },
  {
    "path": "docs/.vitepress/config.js",
    "chars": 1505,
    "preview": "export default {\n  title: \"dataclass\",\n  description: \"Data Classes for TypeScript & JavaScript\",\n\n  lastUpdated: true,\n"
  },
  {
    "path": "docs/guide/caveats.md",
    "chars": 3141,
    "preview": "# Caveats\n\nIn the world of always changing tooling and evolving specs, it is hard to avoid weird edge cases\nthat can lea"
  },
  {
    "path": "docs/guide/contributing.md",
    "chars": 419,
    "preview": "# Contributing\n\nThe project is opened for any contributions (features, updates, fixes, etc) and is\n[located](https://git"
  },
  {
    "path": "docs/guide/getting-started.md",
    "chars": 4161,
    "preview": "# Getting Started\n\n## Defining data classes\n\nThis library provides an abstract class `Data`:\n\n```ts:no-line-numbers\nimpo"
  },
  {
    "path": "docs/guide/index.md",
    "chars": 3441,
    "preview": "# Introduction\n\n    npm install dataclass\n\nSyntax sugar that leverages the power of available type systems in TypeScript"
  },
  {
    "path": "docs/guide/installation.md",
    "chars": 3349,
    "preview": "# Installation\n\n### Installing via NPM\n\nThe library is available [in NPM registry](https://www.npmjs.com/package/datacla"
  },
  {
    "path": "docs/guide/migrating.md",
    "chars": 3083,
    "preview": "# Migrating from v1 to v2\n\n## The class name and import type has been changed\n\nThe library was created in 2017, long ago"
  },
  {
    "path": "docs/guide/objects-equality.md",
    "chars": 2452,
    "preview": "# Objects Equality\n\nThe biggest part of dataclasses flexibility is the fact they can be compared by the value they\nconta"
  },
  {
    "path": "docs/guide/serialization-deserialization.md",
    "chars": 1409,
    "preview": "# Serialization & Deserialization\n\n## Serialization\n\nBy default, when using\n[`JSON.stringify()`](https://developer.mozil"
  },
  {
    "path": "docs/index.md",
    "chars": 1162,
    "preview": "---\nlayout: home\nhero:\n  name: dataclass\n  # text: lorem ipsum\n  tagline: Data Classes for TypeScript & JavaScript\n  act"
  },
  {
    "path": "docs/package.json",
    "chars": 144,
    "preview": "{\n  \"scripts\": {\n    \"start\": \"vitepress dev .\",\n    \"build\": \"vitepress build .\"\n  },\n  \"dependencies\": {\n    \"vitepres"
  },
  {
    "path": "docs/reference/index.md",
    "chars": 2651,
    "preview": "# API Reference\n\n### class `Data`\n\nBase class for domain models. Should be extended with a set of class fields that desc"
  },
  {
    "path": "integration/Data.test.ts",
    "chars": 7519,
    "preview": "import { test } from \"vitest\";\nimport { deepEqual, equal, throws } from \"node:assert/strict\";\n\nimport { Data } from \"dat"
  },
  {
    "path": "integration/integration.js",
    "chars": 7420,
    "preview": "import { test } from \"node:test\";\nimport { deepEqual, equal, throws } from \"node:assert/strict\";\n\nimport { Data } from \""
  },
  {
    "path": "integration/legacy.json",
    "chars": 241,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"es2017\",\n    \"module\": \"commonjs\",\n    \"useDefineForClassFields\": false,\n    \"es"
  },
  {
    "path": "integration/modern.json",
    "chars": 240,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"es2022\",\n    \"module\": \"commonjs\",\n    \"useDefineForClassFields\": true,\n    \"esM"
  },
  {
    "path": "integration/runtime.test.ts",
    "chars": 1433,
    "preview": "import { test, expectTypeOf } from \"vitest\";\nimport { deepEqual, throws } from \"node:assert/strict\";\n\nimport { Data } fr"
  },
  {
    "path": "integration/spec.json",
    "chars": 240,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"module\": \"commonjs\",\n    \"useDefineForClassFields\": true,\n    \"esM"
  },
  {
    "path": "modules/Data.js",
    "chars": 843,
    "preview": "let O = Object;\n\nlet produce = (proto, base, values) =>\n  O.freeze(O.assign(O.seal(O.assign(O.create(proto), base)), val"
  },
  {
    "path": "modules/runtime.js",
    "chars": 5038,
    "preview": "import { Data } from \"./Data.js\";\n\nexport function runtime(Class) {\n  if (!Data.prototype.isPrototypeOf(Class.prototype)"
  },
  {
    "path": "package.json",
    "chars": 1088,
    "preview": "{\n  \"name\": \"dataclass\",\n  \"version\": \"3.0.0-beta.1\",\n  \"description\": \"Data classes for TypeScript & JavaScript\",\n  \"au"
  },
  {
    "path": "rollup.config.mjs",
    "chars": 2702,
    "preview": "import { defineConfig } from \"rollup\";\nimport copy from \"rollup-plugin-copy\";\nimport { extname } from \"node:path\";\n\nlet "
  },
  {
    "path": "tsconfig.json",
    "chars": 311,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"es2022\",\n    \"module\": \"commonjs\",\n    \"moduleResolution\": \"bundler\",\n    \"useDe"
  },
  {
    "path": "typings/dataclass.d.ts",
    "chars": 3068,
    "preview": "interface S {\n  /** If you see this, the property is coming from a dataclass instance and was required */\n  valueOf(): 2"
  },
  {
    "path": "typings/dataclass.js.flow",
    "chars": 2932,
    "preview": "/* @flow */\n\n/**\n * Special generic type to mark data class field as required to be initialized\n * at creation time. Can"
  },
  {
    "path": "typings/runtime.d.ts",
    "chars": 4940,
    "preview": "import { Enforced } from \"./dataclass\";\n\nexport function runtime<T>(C: T, ...args: any): T;\n\ninterface TypedField<Type> "
  },
  {
    "path": "typings/runtime.js.flow",
    "chars": 128,
    "preview": "/* @flow */\n\ndeclare export function runtime(options?: {}): <T>(C: T) => T;\n\ndeclare export let data: Record<string, any"
  },
  {
    "path": "vitest.config.ts",
    "chars": 168,
    "preview": "import { defineConfig } from \"vitest/config\";\n\nexport default defineConfig({\n  test: {\n    coverage: {\n      enabled: tr"
  }
]

About this extraction

This page contains the full source code of the alexeyraspopov/dataclass GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 37 files (75.0 KB), approximately 20.2k tokens, and a symbol index with 73 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!